diff options
author | Lorry <lorry@roadtrain.codethink.co.uk> | 2012-08-22 14:29:52 +0100 |
---|---|---|
committer | Lorry <lorry@roadtrain.codethink.co.uk> | 2012-08-22 14:29:52 +0100 |
commit | f1bdf13786f0752c0846cf36f0d91e4fc6747929 (patch) | |
tree | 4223b2035bf2240d681a53822808b3c7f687b905 /subversion/svn | |
download | subversion-tarball-f1bdf13786f0752c0846cf36f0d91e4fc6747929.tar.gz |
Tarball conversion
Diffstat (limited to 'subversion/svn')
55 files changed, 15842 insertions, 0 deletions
diff --git a/subversion/svn/add-cmd.c b/subversion/svn/add-cmd.c new file mode 100644 index 0000000..7b79ee1 --- /dev/null +++ b/subversion/svn/add-cmd.c @@ -0,0 +1,112 @@ +/* + * add-cmd.c -- Subversion add/unadd commands + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + +/* ==================================================================== */ + + + +/*** Includes. ***/ +#define APR_WANT_STDIO +#include <apr_want.h> + +#include "svn_path.h" +#include "svn_client.h" +#include "svn_error.h" +#include "svn_pools.h" +#include "cl.h" + +#include "svn_private_config.h" + + +/*** Code. ***/ + +/* This implements the `svn_opt_subcommand_t' interface. */ +svn_error_t * +svn_cl__add(apr_getopt_t *os, + void *baton, + apr_pool_t *pool) +{ + svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state; + svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx; + apr_array_header_t *targets; + int i; + apr_pool_t *iterpool; + apr_array_header_t *errors = apr_array_make(pool, 0, sizeof(apr_status_t)); + + SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os, + opt_state->targets, + ctx, FALSE, pool)); + + if (! targets->nelts) + return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0, NULL); + + if (opt_state->depth == svn_depth_unknown) + opt_state->depth = svn_depth_infinity; + + SVN_ERR(svn_cl__eat_peg_revisions(&targets, targets, pool)); + + SVN_ERR(svn_cl__check_targets_are_local_paths(targets)); + + iterpool = svn_pool_create(pool); + for (i = 0; i < targets->nelts; i++) + { + const char *target = APR_ARRAY_IDX(targets, i, const char *); + + svn_pool_clear(iterpool); + SVN_ERR(svn_cl__check_cancel(ctx->cancel_baton)); + SVN_ERR(svn_cl__try + (svn_client_add4(target, + opt_state->depth, + opt_state->force, opt_state->no_ignore, + opt_state->parents, ctx, iterpool), + errors, opt_state->quiet, + SVN_ERR_ENTRY_EXISTS, + SVN_ERR_WC_PATH_NOT_FOUND, + SVN_NO_ERROR)); + } + + svn_pool_destroy(iterpool); + + if (errors->nelts > 0) + { + svn_error_t *err; + + err = svn_error_create(SVN_ERR_ILLEGAL_TARGET, NULL, NULL); + for (i = 0; i < errors->nelts; i++) + { + apr_status_t status = APR_ARRAY_IDX(errors, i, apr_status_t); + if (status == SVN_ERR_WC_PATH_NOT_FOUND) + err = svn_error_quick_wrap(err, + _("Could not add all targets because " + "some targets don't exist")); + else if (status == SVN_ERR_ENTRY_EXISTS) + err = svn_error_quick_wrap(err, + _("Could not add all targets because " + "some targets are already versioned")); + } + + return svn_error_trace(err); + } + + return SVN_NO_ERROR; +} diff --git a/subversion/svn/blame-cmd.c b/subversion/svn/blame-cmd.c new file mode 100644 index 0000000..e198178 --- /dev/null +++ b/subversion/svn/blame-cmd.c @@ -0,0 +1,416 @@ +/* + * blame-cmd.c -- Display blame information + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + + +/*** Includes. ***/ + +#include "svn_client.h" +#include "svn_error.h" +#include "svn_dirent_uri.h" +#include "svn_path.h" +#include "svn_pools.h" +#include "svn_props.h" +#include "svn_cmdline.h" +#include "svn_xml.h" +#include "svn_time.h" +#include "cl.h" + +#include "svn_private_config.h" + +typedef struct blame_baton_t +{ + svn_cl__opt_state_t *opt_state; + svn_stream_t *out; + svn_stringbuf_t *sbuf; +} blame_baton_t; + + +/*** Code. ***/ + +/* This implements the svn_client_blame_receiver3_t interface, printing + XML to stdout. */ +static svn_error_t * +blame_receiver_xml(void *baton, + svn_revnum_t start_revnum, + svn_revnum_t end_revnum, + apr_int64_t line_no, + svn_revnum_t revision, + apr_hash_t *rev_props, + svn_revnum_t merged_revision, + apr_hash_t *merged_rev_props, + const char *merged_path, + const char *line, + svn_boolean_t local_change, + apr_pool_t *pool) +{ + svn_cl__opt_state_t *opt_state = + ((blame_baton_t *) baton)->opt_state; + svn_stringbuf_t *sb = ((blame_baton_t *) baton)->sbuf; + + /* "<entry ...>" */ + /* line_no is 0-based, but the rest of the world is probably Pascal + programmers, so we make them happy and output 1-based line numbers. */ + svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "entry", + "line-number", + apr_psprintf(pool, "%" APR_INT64_T_FMT, + line_no + 1), + NULL); + + if (SVN_IS_VALID_REVNUM(revision)) + svn_cl__print_xml_commit(&sb, revision, + svn_prop_get_value(rev_props, + SVN_PROP_REVISION_AUTHOR), + svn_prop_get_value(rev_props, + SVN_PROP_REVISION_DATE), + pool); + + if (opt_state->use_merge_history && SVN_IS_VALID_REVNUM(merged_revision)) + { + /* "<merged>" */ + svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "merged", + "path", merged_path, NULL); + + svn_cl__print_xml_commit(&sb, merged_revision, + svn_prop_get_value(merged_rev_props, + SVN_PROP_REVISION_AUTHOR), + svn_prop_get_value(merged_rev_props, + SVN_PROP_REVISION_DATE), + pool); + + /* "</merged>" */ + svn_xml_make_close_tag(&sb, pool, "merged"); + + } + + /* "</entry>" */ + svn_xml_make_close_tag(&sb, pool, "entry"); + + SVN_ERR(svn_cl__error_checked_fputs(sb->data, stdout)); + svn_stringbuf_setempty(sb); + + return SVN_NO_ERROR; +} + + +static svn_error_t * +print_line_info(svn_stream_t *out, + svn_revnum_t revision, + const char *author, + const char *date, + const char *path, + svn_boolean_t verbose, + svn_revnum_t end_revnum, + apr_pool_t *pool) +{ + const char *time_utf8; + const char *time_stdout; + const char *rev_str; + int rev_maxlength; + + /* The standard column width for the revision number is 6 characters. + If the revision number can potentially be larger (i.e. if the end_revnum + is larger than 1000000), we increase the column width as needed. */ + rev_maxlength = 6; + while (end_revnum >= 1000000) + { + rev_maxlength++; + end_revnum = end_revnum / 10; + } + rev_str = SVN_IS_VALID_REVNUM(revision) + ? apr_psprintf(pool, "%*ld", rev_maxlength, revision) + : apr_psprintf(pool, "%*s", rev_maxlength, "-"); + + if (verbose) + { + if (date) + { + SVN_ERR(svn_cl__time_cstring_to_human_cstring(&time_utf8, + date, pool)); + SVN_ERR(svn_cmdline_cstring_from_utf8(&time_stdout, time_utf8, + pool)); + } + else + { + /* ### This is a 44 characters long string. It assumes the current + format of svn_time_to_human_cstring and also 3 letter + abbreviations for the month and weekday names. Else, the + line contents will be misaligned. */ + time_stdout = " -"; + } + + SVN_ERR(svn_stream_printf(out, pool, "%s %10s %s ", rev_str, + author ? author : " -", + time_stdout)); + + if (path) + SVN_ERR(svn_stream_printf(out, pool, "%-14s ", path)); + } + else + { + return svn_stream_printf(out, pool, "%s %10.10s ", rev_str, + author ? author : " -"); + } + + return SVN_NO_ERROR; +} + +/* This implements the svn_client_blame_receiver3_t interface. */ +static svn_error_t * +blame_receiver(void *baton, + svn_revnum_t start_revnum, + svn_revnum_t end_revnum, + apr_int64_t line_no, + svn_revnum_t revision, + apr_hash_t *rev_props, + svn_revnum_t merged_revision, + apr_hash_t *merged_rev_props, + const char *merged_path, + const char *line, + svn_boolean_t local_change, + apr_pool_t *pool) +{ + svn_cl__opt_state_t *opt_state = + ((blame_baton_t *) baton)->opt_state; + svn_stream_t *out = ((blame_baton_t *)baton)->out; + svn_boolean_t use_merged = FALSE; + + if (opt_state->use_merge_history) + { + /* Choose which revision to use. If they aren't equal, prefer the + earliest revision. Since we do a forward blame, we want to the first + revision which put the line in its current state, so we use the + earliest revision. If we ever switch to a backward blame algorithm, + we may need to adjust this. */ + if (merged_revision < revision) + { + SVN_ERR(svn_stream_printf(out, pool, "G ")); + use_merged = TRUE; + } + else + SVN_ERR(svn_stream_printf(out, pool, " ")); + } + + if (use_merged) + SVN_ERR(print_line_info(out, merged_revision, + svn_prop_get_value(merged_rev_props, + SVN_PROP_REVISION_AUTHOR), + svn_prop_get_value(merged_rev_props, + SVN_PROP_REVISION_DATE), + merged_path, opt_state->verbose, end_revnum, + pool)); + else + SVN_ERR(print_line_info(out, revision, + svn_prop_get_value(rev_props, + SVN_PROP_REVISION_AUTHOR), + svn_prop_get_value(rev_props, + SVN_PROP_REVISION_DATE), + NULL, opt_state->verbose, end_revnum, + pool)); + + return svn_stream_printf(out, pool, "%s%s", line, APR_EOL_STR); +} + + +/* This implements the `svn_opt_subcommand_t' interface. */ +svn_error_t * +svn_cl__blame(apr_getopt_t *os, + void *baton, + apr_pool_t *pool) +{ + svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state; + svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx; + apr_pool_t *subpool; + apr_array_header_t *targets; + blame_baton_t bl; + int i; + svn_boolean_t end_revision_unspecified = FALSE; + svn_diff_file_options_t *diff_options = svn_diff_file_options_create(pool); + svn_boolean_t seen_nonexistent_target = FALSE; + + SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os, + opt_state->targets, + ctx, FALSE, pool)); + + /* Blame needs a file on which to operate. */ + if (! targets->nelts) + return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0, NULL); + + if (opt_state->end_revision.kind == svn_opt_revision_unspecified) + { + if (opt_state->start_revision.kind != svn_opt_revision_unspecified) + { + /* In the case that -rX was specified, we actually want to set the + range to be -r1:X. */ + + opt_state->end_revision = opt_state->start_revision; + opt_state->start_revision.kind = svn_opt_revision_number; + opt_state->start_revision.value.number = 1; + } + else + end_revision_unspecified = TRUE; + } + + if (opt_state->start_revision.kind == svn_opt_revision_unspecified) + { + opt_state->start_revision.kind = svn_opt_revision_number; + opt_state->start_revision.value.number = 1; + } + + /* The final conclusion from issue #2431 is that blame info + is client output (unlike 'svn cat' which plainly cats the file), + so the EOL style should be the platform local one. + */ + if (! opt_state->xml) + SVN_ERR(svn_stream_for_stdout(&bl.out, pool)); + else + bl.sbuf = svn_stringbuf_create("", pool); + + bl.opt_state = opt_state; + + subpool = svn_pool_create(pool); + + if (opt_state->extensions) + { + apr_array_header_t *opts; + opts = svn_cstring_split(opt_state->extensions, " \t\n\r", TRUE, pool); + SVN_ERR(svn_diff_file_options_parse(diff_options, opts, pool)); + } + + if (opt_state->xml) + { + if (opt_state->verbose) + return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, + _("'verbose' option invalid in XML mode")); + + /* If output is not incremental, output the XML header and wrap + everything in a top-level element. This makes the output in + its entirety a well-formed XML document. */ + if (! opt_state->incremental) + SVN_ERR(svn_cl__xml_print_header("blame", pool)); + } + else + { + if (opt_state->incremental) + return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, + _("'incremental' option only valid in XML " + "mode")); + } + + for (i = 0; i < targets->nelts; i++) + { + svn_error_t *err; + const char *target = APR_ARRAY_IDX(targets, i, const char *); + const char *truepath; + svn_opt_revision_t peg_revision; + svn_client_blame_receiver3_t receiver; + + svn_pool_clear(subpool); + SVN_ERR(svn_cl__check_cancel(ctx->cancel_baton)); + + /* Check for a peg revision. */ + SVN_ERR(svn_opt_parse_path(&peg_revision, &truepath, target, + subpool)); + + if (end_revision_unspecified) + { + if (peg_revision.kind != svn_opt_revision_unspecified) + opt_state->end_revision = peg_revision; + else if (svn_path_is_url(target)) + opt_state->end_revision.kind = svn_opt_revision_head; + else + opt_state->end_revision.kind = svn_opt_revision_working; + } + + if (opt_state->xml) + { + /* "<target ...>" */ + /* We don't output this tag immediately, which avoids creating + a target element if this path is skipped. */ + const char *outpath = truepath; + if (! svn_path_is_url(target)) + outpath = svn_dirent_local_style(truepath, subpool); + svn_xml_make_open_tag(&bl.sbuf, pool, svn_xml_normal, "target", + "path", outpath, NULL); + + receiver = blame_receiver_xml; + } + else + receiver = blame_receiver; + + err = svn_client_blame5(truepath, + &peg_revision, + &opt_state->start_revision, + &opt_state->end_revision, + diff_options, + opt_state->force, + opt_state->use_merge_history, + receiver, + &bl, + ctx, + subpool); + + if (err) + { + if (err->apr_err == SVN_ERR_CLIENT_IS_BINARY_FILE) + { + svn_error_clear(err); + SVN_ERR(svn_cmdline_fprintf(stderr, subpool, + _("Skipping binary file: '%s'\n"), + target)); + } + else if (err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND || + err->apr_err == SVN_ERR_FS_NOT_FILE || + err->apr_err == SVN_ERR_FS_NOT_FOUND) + { + svn_handle_warning2(stderr, err, "svn: "); + svn_error_clear(err); + err = NULL; + seen_nonexistent_target = TRUE; + } + else + { + return svn_error_trace(err); + } + } + else if (opt_state->xml) + { + /* "</target>" */ + svn_xml_make_close_tag(&(bl.sbuf), pool, "target"); + SVN_ERR(svn_cl__error_checked_fputs(bl.sbuf->data, stdout)); + } + + if (opt_state->xml) + svn_stringbuf_setempty(bl.sbuf); + } + svn_pool_destroy(subpool); + if (opt_state->xml && ! opt_state->incremental) + SVN_ERR(svn_cl__xml_print_footer("blame", pool)); + + if (seen_nonexistent_target) + return svn_error_create( + SVN_ERR_ILLEGAL_TARGET, NULL, + _("Could not perform blame on all targets because some " + "targets don't exist")); + else + return SVN_NO_ERROR; +} diff --git a/subversion/svn/cat-cmd.c b/subversion/svn/cat-cmd.c new file mode 100644 index 0000000..7e28a81 --- /dev/null +++ b/subversion/svn/cat-cmd.c @@ -0,0 +1,118 @@ +/* + * cat-cmd.c -- Print the content of a file or URL. + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + +/* ==================================================================== */ + + + +/*** Includes. ***/ + +#include "svn_pools.h" +#include "svn_client.h" +#include "svn_error.h" +#include "svn_opt.h" +#include "cl.h" + +#include "svn_private_config.h" + + +/*** Code. ***/ + +/* This implements the `svn_opt_subcommand_t' interface. */ +svn_error_t * +svn_cl__cat(apr_getopt_t *os, + void *baton, + apr_pool_t *pool) +{ + svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state; + svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx; + apr_array_header_t *targets; + int i; + svn_stream_t *out; + apr_pool_t *subpool = svn_pool_create(pool); + apr_array_header_t *errors = apr_array_make(pool, 0, sizeof(apr_status_t)); + svn_error_t *err; + + SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os, + opt_state->targets, + ctx, FALSE, pool)); + + /* Cat cannot operate on an implicit '.' so a filename is required */ + if (! targets->nelts) + return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0, NULL); + + SVN_ERR(svn_stream_for_stdout(&out, pool)); + + for (i = 0; i < targets->nelts; i++) + { + const char *target = APR_ARRAY_IDX(targets, i, const char *); + const char *truepath; + svn_opt_revision_t peg_revision; + + svn_pool_clear(subpool); + SVN_ERR(svn_cl__check_cancel(ctx->cancel_baton)); + + /* Get peg revisions. */ + SVN_ERR(svn_opt_parse_path(&peg_revision, &truepath, target, + subpool)); + + SVN_ERR(svn_cl__try(svn_client_cat2(out, truepath, &peg_revision, + &(opt_state->start_revision), + ctx, subpool), + errors, opt_state->quiet, + SVN_ERR_UNVERSIONED_RESOURCE, + SVN_ERR_ENTRY_NOT_FOUND, + SVN_ERR_CLIENT_IS_DIRECTORY, + SVN_ERR_FS_NOT_FOUND, + SVN_NO_ERROR)); + } + svn_pool_destroy(subpool); + + if (errors->nelts > 0) + { + err = svn_error_create(SVN_ERR_ILLEGAL_TARGET, NULL, NULL); + + for (i = 0; i < errors->nelts; i++) + { + apr_status_t status = APR_ARRAY_IDX(errors, i, apr_status_t); + + if (status == SVN_ERR_ENTRY_NOT_FOUND || + status == SVN_ERR_FS_NOT_FOUND) + err = svn_error_quick_wrap(err, + _("Could not cat all targets because " + "some targets don't exist")); + else if (status == SVN_ERR_UNVERSIONED_RESOURCE) + err = svn_error_quick_wrap(err, + _("Could not cat all targets because " + "some targets are not versioned")); + else if (status == SVN_ERR_CLIENT_IS_DIRECTORY) + err = svn_error_quick_wrap(err, + _("Could not cat all targets because " + "some targets are directories")); + } + + return svn_error_trace(err); + } + + return SVN_NO_ERROR; +} diff --git a/subversion/svn/changelist-cmd.c b/subversion/svn/changelist-cmd.c new file mode 100644 index 0000000..46347b6 --- /dev/null +++ b/subversion/svn/changelist-cmd.c @@ -0,0 +1,149 @@ +/* + * changelist-cmd.c -- Associate (or deassociate) a wc path with a changelist. + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + +#include "svn_client.h" +#include "svn_error_codes.h" +#include "svn_error.h" +#include "svn_path.h" +#include "svn_utf.h" + +#include "cl.h" + +#include "svn_private_config.h" + + + + +/* This implements the `svn_opt_subcommand_t' interface. */ +svn_error_t * +svn_cl__changelist(apr_getopt_t *os, + void *baton, + apr_pool_t *pool) +{ + const char *changelist_name = NULL; + svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state; + svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx; + apr_array_header_t *targets; + svn_depth_t depth = opt_state->depth; + apr_array_header_t *errors = apr_array_make(pool, 0, sizeof(apr_status_t)); + + /* If we're not removing changelists, then our first argument should + be the name of a changelist. */ + + if (! opt_state->remove) + { + apr_array_header_t *args; + SVN_ERR(svn_opt_parse_num_args(&args, os, 1, pool)); + changelist_name = APR_ARRAY_IDX(args, 0, const char *); + SVN_ERR(svn_utf_cstring_to_utf8(&changelist_name, + changelist_name, pool)); + } + + /* Parse the remaining arguments as paths. */ + SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os, + opt_state->targets, + ctx, FALSE, pool)); + + /* Changelist has no implicit dot-target `.', so don't you put that + code here! */ + if (! targets->nelts) + return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0, NULL); + + SVN_ERR(svn_cl__check_targets_are_local_paths(targets)); + + if (opt_state->quiet) + /* FIXME: This is required because svn_client_create_context() + always initializes ctx->notify_func2 to a wrapper function + which calls ctx->notify_func() if it isn't NULL. In other + words, typically, ctx->notify_func2 is never NULL. This isn't + usually a problem, but the changelist logic generates + svn_error_t's as part of its notification. + + So, svn_wc_set_changelist() checks its notify_func (our + ctx->notify_func2) for NULL-ness, and seeing non-NULL-ness, + generates a notificaton object and svn_error_t to describe some + problem. It passes that off to its notify_func (our + ctx->notify_func2) which drops the notification on the floor + (because it wraps a NULL ctx->notify_func). But svn_error_t's + dropped on the floor cause SEGFAULTs at pool cleanup time -- + they need instead to be cleared. + + SOOOooo... we set our ctx->notify_func2 to NULL so the WC code + doesn't even generate the errors. */ + ctx->notify_func2 = NULL; + + if (depth == svn_depth_unknown) + depth = svn_depth_empty; + + SVN_ERR(svn_cl__eat_peg_revisions(&targets, targets, pool)); + + if (changelist_name) + { + SVN_ERR(svn_cl__try( + svn_client_add_to_changelist(targets, changelist_name, + depth, opt_state->changelists, + ctx, pool), + errors, opt_state->quiet, + SVN_ERR_UNVERSIONED_RESOURCE, + SVN_ERR_WC_PATH_NOT_FOUND, + SVN_NO_ERROR)); + } + else + { + SVN_ERR(svn_cl__try( + svn_client_remove_from_changelists(targets, depth, + opt_state->changelists, + ctx, pool), + errors, opt_state->quiet, + SVN_ERR_UNVERSIONED_RESOURCE, + SVN_ERR_WC_PATH_NOT_FOUND, + SVN_NO_ERROR)); + } + + if (errors->nelts > 0) + { + int i; + svn_error_t *err; + + err = svn_error_create(SVN_ERR_ILLEGAL_TARGET, NULL, NULL); + for (i = 0; i < errors->nelts; i++) + { + apr_status_t status = APR_ARRAY_IDX(errors, i, apr_status_t); + + if (status == SVN_ERR_WC_PATH_NOT_FOUND) + err = svn_error_quick_wrap(err, + _("Could not set changelists on " + "all targets because some targets " + "don't exist")); + else if (status == SVN_ERR_UNVERSIONED_RESOURCE) + err = svn_error_quick_wrap(err, + _("Could not set changelists on " + "all targets because some targets " + "are not versioned")); + } + + return svn_error_trace(err); + } + + return SVN_NO_ERROR; +} diff --git a/subversion/svn/checkout-cmd.c b/subversion/svn/checkout-cmd.c new file mode 100644 index 0000000..6c192a0 --- /dev/null +++ b/subversion/svn/checkout-cmd.c @@ -0,0 +1,173 @@ +/* + * checkout-cmd.c -- Subversion checkout command + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + +/* ==================================================================== */ + + + +/*** Includes. ***/ + +#include "svn_client.h" +#include "svn_dirent_uri.h" +#include "svn_path.h" +#include "svn_error.h" +#include "svn_pools.h" +#include "cl.h" + +#include "svn_private_config.h" + + +/*** Code. ***/ + +/* + This is what it does + + - case 1: one URL + $ svn co http://host/repos/module + checkout into ./module/ + + - case 2: one URL and explicit path + $ svn co http://host/repos/module path + checkout into ./path/ + + - case 3: multiple URLs + $ svn co http://host1/repos1/module1 http://host2/repos2/module2 + checkout into ./module1/ and ./module2/ + + - case 4: multiple URLs and explicit path + $ svn co http://host1/repos1/module1 http://host2/repos2/module2 path + checkout into ./path/module1/ and ./path/module2/ + + Is this the same as CVS? Does it matter if it is not? +*/ + + +/* This implements the `svn_opt_subcommand_t' interface. */ +svn_error_t * +svn_cl__checkout(apr_getopt_t *os, + void *baton, + apr_pool_t *pool) +{ + svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state; + svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx; + apr_pool_t *subpool; + apr_array_header_t *targets; + const char *last_target, *local_dir; + int i; + + SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os, + opt_state->targets, + ctx, FALSE, pool)); + + if (! targets->nelts) + return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, NULL, NULL); + + /* Determine LOCAL_DIR (case 1: URL basename; 2,4: specified; 3: "") + * and leave TARGETS holding just the source URLs. */ + last_target = APR_ARRAY_IDX(targets, targets->nelts - 1, const char *); + if (svn_path_is_url(last_target)) + { + if (targets->nelts == 1) + { + svn_opt_revision_t pegrev; + + /* Use the URL basename, discarding any peg revision. */ + SVN_ERR(svn_opt_parse_path(&pegrev, &local_dir, last_target, pool)); + local_dir = svn_uri_basename(local_dir, pool); + } + else + { + local_dir = ""; + } + } + else + { + if (targets->nelts == 1) + /* What? They gave us one target, and it wasn't a URL. */ + return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, NULL); + + apr_array_pop(targets); + local_dir = last_target; + } + + if (! opt_state->quiet) + SVN_ERR(svn_cl__notifier_mark_checkout(ctx->notify_baton2)); + + subpool = svn_pool_create(pool); + for (i = 0; i < targets->nelts; ++i) + { + const char *repos_url = APR_ARRAY_IDX(targets, i, const char *); + const char *target_dir; + const char *true_url; + svn_opt_revision_t revision = opt_state->start_revision; + svn_opt_revision_t peg_revision; + + svn_pool_clear(subpool); + + SVN_ERR(svn_cl__check_cancel(ctx->cancel_baton)); + + /* Validate the REPOS_URL */ + if (! svn_path_is_url(repos_url)) + return svn_error_createf + (SVN_ERR_BAD_URL, NULL, + _("'%s' does not appear to be a URL"), repos_url); + + /* Get a possible peg revision. */ + SVN_ERR(svn_opt_parse_path(&peg_revision, &true_url, repos_url, + subpool)); + + /* Use sub-directory of destination if checking-out multiple URLs */ + if (targets->nelts == 1) + { + target_dir = local_dir; + } + else + { + target_dir = svn_dirent_join(local_dir, + svn_uri_basename(true_url, subpool), + subpool); + } + + /* Checkout doesn't accept an unspecified revision, so default to + the peg revision, or to HEAD if there wasn't a peg. */ + if (revision.kind == svn_opt_revision_unspecified) + { + if (peg_revision.kind != svn_opt_revision_unspecified) + revision = peg_revision; + else + revision.kind = svn_opt_revision_head; + } + + SVN_ERR(svn_client_checkout3 + (NULL, true_url, target_dir, + &peg_revision, + &revision, + opt_state->depth, + opt_state->ignore_externals, + opt_state->force, + ctx, subpool)); + } + svn_pool_destroy(subpool); + + return SVN_NO_ERROR; +} diff --git a/subversion/svn/cl.h b/subversion/svn/cl.h new file mode 100644 index 0000000..2d4d341 --- /dev/null +++ b/subversion/svn/cl.h @@ -0,0 +1,840 @@ +/* + * cl.h: shared stuff in the command line program + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + +/* ==================================================================== */ + + + +#ifndef SVN_CL_H +#define SVN_CL_H + +/*** Includes. ***/ +#include <apr_tables.h> +#include <apr_getopt.h> + +#include "svn_wc.h" +#include "svn_client.h" +#include "svn_string.h" +#include "svn_opt.h" +#include "svn_auth.h" +#include "svn_cmdline.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + + +/*** Option processing ***/ + +/* --accept actions */ +typedef enum svn_cl__accept_t +{ + /* invalid accept action */ + svn_cl__accept_invalid = -2, + + /* unspecified accept action */ + svn_cl__accept_unspecified = -1, + + /* Leave conflicts alone, for later resolution. */ + svn_cl__accept_postpone, + + /* Resolve the conflict with the pre-conflict base file. */ + svn_cl__accept_base, + + /* Resolve the conflict with the current working file. */ + svn_cl__accept_working, + + /* Resolve the conflicted hunks by choosing the corresponding text + from the pre-conflict working copy file. + + Note: this is a placeholder, not actually implemented in 1.5. */ + svn_cl__accept_mine_conflict, + + /* Resolve the conflicted hunks by choosing the corresponding text + from the post-conflict base copy file. + + Note: this is a placeholder, not actually implemented in 1.5. */ + svn_cl__accept_theirs_conflict, + + /* Resolve the conflict by taking the entire pre-conflict working + copy file. */ + svn_cl__accept_mine_full, + + /* Resolve the conflict by taking the entire post-conflict base file. */ + svn_cl__accept_theirs_full, + + /* Launch user's editor and resolve conflict with edited file. */ + svn_cl__accept_edit, + + /* Launch user's resolver and resolve conflict with edited file. */ + svn_cl__accept_launch + +} svn_cl__accept_t; + +/* --accept action user input words */ +#define SVN_CL__ACCEPT_POSTPONE "postpone" +#define SVN_CL__ACCEPT_BASE "base" +#define SVN_CL__ACCEPT_WORKING "working" +#define SVN_CL__ACCEPT_MINE_CONFLICT "mine-conflict" +#define SVN_CL__ACCEPT_THEIRS_CONFLICT "theirs-conflict" +#define SVN_CL__ACCEPT_MINE_FULL "mine-full" +#define SVN_CL__ACCEPT_THEIRS_FULL "theirs-full" +#define SVN_CL__ACCEPT_EDIT "edit" +#define SVN_CL__ACCEPT_LAUNCH "launch" + +/* Return the svn_cl__accept_t value corresponding to WORD, using exact + * case-sensitive string comparison. Return svn_cl__accept_invalid if WORD + * is empty or is not one of the known values. */ +svn_cl__accept_t +svn_cl__accept_from_word(const char *word); + + +/*** Mergeinfo flavors. ***/ + +/* --show-revs values */ +typedef enum svn_cl__show_revs_t { + svn_cl__show_revs_invalid = -1, + svn_cl__show_revs_merged, + svn_cl__show_revs_eligible +} svn_cl__show_revs_t; + +/* --show-revs user input words */ +#define SVN_CL__SHOW_REVS_MERGED "merged" +#define SVN_CL__SHOW_REVS_ELIGIBLE "eligible" + +/* Return svn_cl__show_revs_t value corresponding to word. */ +svn_cl__show_revs_t +svn_cl__show_revs_from_word(const char *word); + + +/*** Command dispatch. ***/ + +/* Hold results of option processing that are shared by multiple + commands. */ +typedef struct svn_cl__opt_state_t +{ + /* An array of svn_opt_revision_range_t *'s representing revisions + ranges indicated on the command-line via the -r and -c options. + For each range in the list, if only one revision was provided + (-rN), its 'end' member remains 'svn_opt_revision_unspecified'. + This array always has at least one element, even if that is a + null range in which both ends are 'svn_opt_revision_unspecified'. */ + apr_array_header_t *revision_ranges; + + /* These are simply a copy of the range start and end values present + in the first item of the revision_ranges list. */ + svn_opt_revision_t start_revision; + svn_opt_revision_t end_revision; + + /* Flag which is only set if the '-c' option was used. */ + svn_boolean_t used_change_arg; + + /* Flag which is only set if the '-r' option was used. */ + svn_boolean_t used_revision_arg; + + /* Max number of log messages to get back from svn_client_log2. */ + int limit; + + /* After option processing is done, reflects the switch actually + given on the command line, or svn_depth_unknown if none. */ + svn_depth_t depth; + + /* Was --no-unlock specified? */ + svn_boolean_t no_unlock; + + const char *message; /* log message */ + const char *ancestor_path; /* ### todo: who sets this? */ + svn_boolean_t force; /* be more forceful, as in "svn rm -f ..." */ + svn_boolean_t force_log; /* force validity of a suspect log msg file */ + svn_boolean_t incremental; /* yield output suitable for concatenation */ + svn_boolean_t quiet; /* sssh...avoid unnecessary output */ + svn_boolean_t non_interactive; /* do no interactive prompting */ + svn_boolean_t version; /* print version information */ + svn_boolean_t verbose; /* be verbose */ + svn_boolean_t update; /* contact the server for the full story */ + svn_boolean_t strict; /* do strictly what was requested */ + svn_stringbuf_t *filedata; /* contents of file used as option data */ + const char *encoding; /* the locale/encoding of the data*/ + svn_boolean_t help; /* print usage message */ + const char *auth_username; /* auth username */ /* UTF-8! */ + const char *auth_password; /* auth password */ /* UTF-8! */ + const char *extensions; /* subprocess extension args */ /* UTF-8! */ + apr_array_header_t *targets; /* target list from file */ /* UTF-8! */ + svn_boolean_t xml; /* output in xml, e.g., "svn log --xml" */ + svn_boolean_t no_ignore; /* disregard default ignores & svn:ignore's */ + svn_boolean_t no_auth_cache; /* do not cache authentication information */ + svn_boolean_t no_diff_deleted; /* do not show diffs for deleted files */ + svn_boolean_t show_copies_as_adds; /* do not diff copies with their source */ + svn_boolean_t notice_ancestry; /* notice ancestry for diff-y operations */ + svn_boolean_t ignore_ancestry; /* ignore ancestry for merge-y operations */ + svn_boolean_t ignore_externals;/* ignore externals definitions */ + svn_boolean_t stop_on_copy; /* don't cross copies during processing */ + svn_boolean_t dry_run; /* try operation but make no changes */ + svn_boolean_t revprop; /* operate on a revision property */ + const char *diff_cmd; /* the external diff command to use */ + const char *merge_cmd; /* the external merge command to use */ + const char *editor_cmd; /* the external editor command to use */ + svn_boolean_t record_only; /* whether to record mergeinfo */ + const char *old_target; /* diff target */ + const char *new_target; /* diff target */ + svn_boolean_t relocate; /* rewrite urls (svn switch) */ + const char *config_dir; /* over-riding configuration directory */ + apr_array_header_t *config_options; /* over-riding configuration options */ + svn_boolean_t autoprops; /* enable automatic properties */ + svn_boolean_t no_autoprops; /* disable automatic properties */ + const char *native_eol; /* override system standard eol marker */ + svn_boolean_t summarize; /* create a summary of a diff */ + svn_boolean_t remove; /* deassociate a changelist */ + apr_array_header_t *changelists; /* changelist filters */ + const char *changelist; /* operate on this changelist + THIS IS TEMPORARY (LAST OF CHANGELISTS) */ + svn_boolean_t keep_changelists;/* don't remove changelists after commit */ + svn_boolean_t keep_local; /* delete path only from repository */ + svn_boolean_t all_revprops; /* retrieve all revprops */ + svn_boolean_t no_revprops; /* retrieve no revprops */ + apr_hash_t *revprop_table; /* table of revision properties to get/set */ + svn_boolean_t parents; /* create intermediate directories */ + svn_boolean_t use_merge_history; /* use/display extra merge information */ + svn_cl__accept_t accept_which; /* how to handle conflicts */ + svn_cl__show_revs_t show_revs; /* mergeinfo flavor */ + svn_depth_t set_depth; /* new sticky ambient depth value */ + svn_boolean_t reintegrate; /* use "reintegrate" merge-source heuristic */ + svn_boolean_t trust_server_cert; /* trust server SSL certs that would + otherwise be rejected as "untrusted" */ + int strip; /* number of leading path components to strip */ + svn_boolean_t ignore_keywords; /* do not expand keywords */ + svn_boolean_t reverse_diff; /* reverse a diff (e.g. when patching) */ + svn_boolean_t ignore_whitespace; /* don't account for whitespace when + patching */ + svn_boolean_t show_diff; /* produce diff output (maps to --diff) */ + svn_boolean_t internal_diff; /* override diff_cmd in config file */ + svn_boolean_t use_git_diff_format; /* Use git's extended diff format */ + svn_boolean_t allow_mixed_rev; /* Allow operation on mixed-revision WC */ +} svn_cl__opt_state_t; + + +typedef struct svn_cl__cmd_baton_t +{ + svn_cl__opt_state_t *opt_state; + svn_client_ctx_t *ctx; +} svn_cl__cmd_baton_t; + + +/* Declare all the command procedures */ +svn_opt_subcommand_t + svn_cl__add, + svn_cl__blame, + svn_cl__cat, + svn_cl__changelist, + svn_cl__checkout, + svn_cl__cleanup, + svn_cl__commit, + svn_cl__copy, + svn_cl__delete, + svn_cl__diff, + svn_cl__export, + svn_cl__help, + svn_cl__import, + svn_cl__info, + svn_cl__lock, + svn_cl__log, + svn_cl__list, + svn_cl__merge, + svn_cl__mergeinfo, + svn_cl__mkdir, + svn_cl__move, + svn_cl__patch, + svn_cl__propdel, + svn_cl__propedit, + svn_cl__propget, + svn_cl__proplist, + svn_cl__propset, + svn_cl__relocate, + svn_cl__revert, + svn_cl__resolve, + svn_cl__resolved, + svn_cl__status, + svn_cl__switch, + svn_cl__unlock, + svn_cl__update, + svn_cl__upgrade; + + +/* See definition in main.c for documentation. */ +extern const svn_opt_subcommand_desc2_t svn_cl__cmd_table[]; + +/* See definition in main.c for documentation. */ +extern const int svn_cl__global_options[]; + +/* See definition in main.c for documentation. */ +extern const apr_getopt_option_t svn_cl__options[]; + + +/* A helper for the many subcommands that wish to merely warn when + * invoked on an unversioned, nonexistent, or otherwise innocuously + * errorful resource. Meant to be wrapped with SVN_ERR(). + * + * If ERR is null, return SVN_NO_ERROR. + * + * Else if ERR->apr_err is one of the error codes supplied in varargs, + * then handle ERR as a warning (unless QUIET is true), clear ERR, and + * return SVN_NO_ERROR, and push the value of ERR->apr_err into the + * ERRORS_SEEN array, if ERRORS_SEEN is not NULL. + * + * Else return ERR. + * + * Typically, error codes like SVN_ERR_UNVERSIONED_RESOURCE, + * SVN_ERR_ENTRY_NOT_FOUND, etc, are supplied in varargs. Don't + * forget to terminate the argument list with SVN_NO_ERROR. + */ +svn_error_t * +svn_cl__try(svn_error_t *err, + apr_array_header_t *errors_seen, + svn_boolean_t quiet, + ...); + + +/* Our cancellation callback. */ +svn_error_t * +svn_cl__check_cancel(void *baton); + + + +/* Various conflict-resolution callbacks. */ + +typedef struct svn_cl__conflict_baton_t { + svn_cl__accept_t accept_which; + apr_hash_t *config; + const char *editor_cmd; + svn_boolean_t external_failed; + svn_cmdline_prompt_baton_t *pb; +} svn_cl__conflict_baton_t; + +/* Create and return a conflict baton, allocated from POOL, with the values + ACCEPT_WHICH, CONFIG, EDITOR_CMD and PB placed in the same-named fields + of the baton, and its 'external_failed' field initialised to FALSE. */ +svn_cl__conflict_baton_t * +svn_cl__conflict_baton_make(svn_cl__accept_t accept_which, + apr_hash_t *config, + const char *editor_cmd, + svn_cmdline_prompt_baton_t *pb, + apr_pool_t *pool); + +/* A conflict-resolution callback which prompts the user to choose + one of the 3 fulltexts, edit the merged file on the spot, or just + skip the conflict (to be resolved later). + Implements @c svn_wc_conflict_resolver_func_t. */ +svn_error_t * +svn_cl__conflict_handler(svn_wc_conflict_result_t **result, + const svn_wc_conflict_description_t *desc, + void *baton, + apr_pool_t *pool); + + + +/*** Command-line output functions -- printing to the user. ***/ + +/* Print out commit information found in COMMIT_INFO to the console. + * POOL is used for temporay allocations. + * COMMIT_INFO should not be NULL. + * + * This function implements svn_commit_callback2_t. + */ +svn_error_t * +svn_cl__print_commit_info(const svn_commit_info_t *commit_info, + void *baton, + apr_pool_t *pool); + + +/* Convert the date in DATA to a human-readable UTF-8-encoded string + * *HUMAN_CSTRING, or set the latter to "(invalid date)" if DATA is not + * a valid date. DATA should be as expected by svn_time_from_cstring(). + * + * Do all allocations in POOL. + */ +svn_error_t * +svn_cl__time_cstring_to_human_cstring(const char **human_cstring, + const char *data, + apr_pool_t *pool); + + +/* Print STATUS for PATH to stdout for human consumption. Prints in + abbreviated format by default, or DETAILED format if flag is set. + + When DETAILED is set, use SHOW_LAST_COMMITTED to toggle display of + the last-committed-revision and last-committed-author. + + If SKIP_UNRECOGNIZED is TRUE, this function will not print out + unversioned items found in the working copy. + + When DETAILED is set, and REPOS_LOCKS is set, treat missing repository locks + as broken WC locks. + + Increment *TEXT_CONFLICTS, *PROP_CONFLICTS, or *TREE_CONFLICTS if + a conflict was encountered. + */ +svn_error_t * +svn_cl__print_status(const char *path, + const svn_client_status_t *status, + svn_boolean_t detailed, + svn_boolean_t show_last_committed, + svn_boolean_t skip_unrecognized, + svn_boolean_t repos_locks, + unsigned int *text_conflicts, + unsigned int *prop_conflicts, + unsigned int *tree_conflicts, + svn_client_ctx_t *ctx, + apr_pool_t *pool); + + +/* Print STATUS for PATH in XML to stdout. Use POOL for temporary + allocations. */ +svn_error_t * +svn_cl__print_status_xml(const char *path, + const svn_client_status_t *status, + svn_client_ctx_t *ctx, + apr_pool_t *pool); + + +/* Print to stdout a hash that maps property names (char *) to property + values (svn_string_t *). The names are assumed to be in UTF-8 format; + the values are either in UTF-8 (the special Subversion props) or + plain binary values. + + If OUT is not NULL, then write to it rather than stdout. + + If NAMES_ONLY is true, print just names, else print names and + values. */ +svn_error_t * +svn_cl__print_prop_hash(svn_stream_t *out, + apr_hash_t *prop_hash, + svn_boolean_t names_only, + apr_pool_t *pool); + +/* Same as svn_cl__print_prop_hash(), only output xml to *OUTSTR. If *OUTSTR is + NULL, allocate it first from POOL, otherwise append to it. */ +svn_error_t * +svn_cl__print_xml_prop_hash(svn_stringbuf_t **outstr, + apr_hash_t *prop_hash, + svn_boolean_t names_only, + apr_pool_t *pool); + +/* Output a commit xml element to *OUTSTR. If *OUTSTR is NULL, allocate it + first from POOL, otherwise append to it. If AUTHOR or DATE is + NULL, it will be omitted. */ +void +svn_cl__print_xml_commit(svn_stringbuf_t **outstr, + svn_revnum_t revision, + const char *author, + const char *date, + apr_pool_t *pool); + +/* Output an XML "<lock>" element describing LOCK to *OUTSTR. If *OUTSTR is + NULL, allocate it first from POOL, otherwise append to it. */ +void +svn_cl__print_xml_lock(svn_stringbuf_t **outstr, + const svn_lock_t *lock, + apr_pool_t *pool); + +/* Do the following things that are commonly required before accessing revision + properties. Ensure that REVISION is specified explicitly and is not + relative to a working-copy item. Ensure that exactly one target is + specified in TARGETS. Set *URL to the URL of the target. Return an + appropriate error if any of those checks or operations fail. Use CTX for + accessing the working copy + */ +svn_error_t * +svn_cl__revprop_prepare(const svn_opt_revision_t *revision, + const apr_array_header_t *targets, + const char **URL, + svn_client_ctx_t *ctx, + apr_pool_t *pool); + +/* Search for a text editor command in standard environment variables, + and invoke it to edit CONTENTS (using a temporary file created in + directory BASE_DIR). Return the new contents in *EDITED_CONTENTS, + or set *EDITED_CONTENTS to NULL if no edit was performed. + + If EDITOR_CMD is not NULL, it is the name of the external editor + command to use, overriding anything else that might determine the + editor. + + If TMPFILE_LEFT is NULL, the temporary file will be destroyed. + Else, the file will be left on disk, and its path returned in + *TMPFILE_LEFT. + + CONFIG is a hash of svn_config_t * items keyed on a configuration + category (SVN_CONFIG_CATEGORY_CONFIG et al), and may be NULL. + + If AS_TEXT is TRUE, recode CONTENTS and convert to native eol-style before + editing and back again afterwards. In this case, ENCODING determines the + encoding used during editing. If non-NULL, use the named encoding, else + use the system encoding. If AS_TEXT is FALSE, don't do any translation. + In that case, ENCODING is ignored. + + Use POOL for all allocations. Use PREFIX as the prefix for the + temporary file used by the editor. + + If return error, *EDITED_CONTENTS is not touched. */ +svn_error_t * +svn_cl__edit_string_externally(svn_string_t **edited_contents, + const char **tmpfile_left, + const char *editor_cmd, + const char *base_dir, + const svn_string_t *contents, + const char *prefix, + apr_hash_t *config, + svn_boolean_t as_text, + const char *encoding, + apr_pool_t *pool); + + +/* Search for a text editor command in standard environment variables, + and invoke it to edit PATH. Use POOL for all allocations. + + If EDITOR_CMD is not NULL, it is the name of the external editor + command to use, overriding anything else that might determine the + editor. + + CONFIG is a hash of svn_config_t * items keyed on a configuration + category (SVN_CONFIG_CATEGORY_CONFIG et al), and may be NULL. */ +svn_error_t * +svn_cl__edit_file_externally(const char *path, + const char *editor_cmd, + apr_hash_t *config, + apr_pool_t *pool); + +/* Search for a merge tool command in environment variables, + and use it to perform the merge of the four given files. + WC_PATH is the path of the file that is in conflict, relative + to the merge target. + Use POOL for all allocations. + + CONFIG is a hash of svn_config_t * items keyed on a configuration + category (SVN_CONFIG_CATEGORY_CONFIG et al), and may be NULL. + + Upon success, set *REMAINS_IN_CONFLICT to indicate whether the + merge result contains conflict markers. + */ +svn_error_t * +svn_cl__merge_file_externally(const char *base_path, + const char *their_path, + const char *my_path, + const char *merged_path, + const char *wc_path, + apr_hash_t *config, + svn_boolean_t *remains_in_conflict, + apr_pool_t *pool); + + + +/*** Notification functions to display results on the terminal. */ + +/* Set *NOTIFY_FUNC_P and *NOTIFY_BATON_P to a notifier/baton for all + * operations, allocated in POOL. + * + * If don't want a summary line at the end of notifications, set + * SUPPRESS_FINAL_LINE. + */ +svn_error_t * +svn_cl__get_notifier(svn_wc_notify_func2_t *notify_func_p, + void **notify_baton_p, + svn_boolean_t suppress_final_line, + apr_pool_t *pool); + +/* Make the notifier for use with BATON print the appropriate summary + * line at the end of the output. + */ +svn_error_t * +svn_cl__notifier_mark_checkout(void *baton); + +/* Make the notifier for use with BATON print the appropriate summary + * line at the end of the output. + */ +svn_error_t * +svn_cl__notifier_mark_export(void *baton); + +/* Make the notifier for use with BATON print the appropriate notifications + * for a wc to repository copy + */ +svn_error_t * +svn_cl__notifier_mark_wc_to_repos_copy(void *baton); + +/* Baton for use with svn_cl__check_externals_failed_notify_wrapper(). */ +struct svn_cl__check_externals_failed_notify_baton +{ + svn_wc_notify_func2_t wrapped_func; /* The "real" notify_func2. */ + void *wrapped_baton; /* The "real" notify_func2 baton. */ + svn_boolean_t had_externals_error; /* Did something fail in an external? */ +}; + +/* Notification function wrapper (implements `svn_wc_notify_func2_t'). + Use with an svn_cl__check_externals_failed_notify_baton BATON. */ +void +svn_cl__check_externals_failed_notify_wrapper(void *baton, + const svn_wc_notify_t *n, + apr_pool_t *pool); + +/* Print conflict stats accumulated in NOTIFY_BATON. + * Return any error encountered during printing. + * Do all allocations in POOL.*/ +svn_error_t * +svn_cl__print_conflict_stats(void *notify_baton, apr_pool_t *pool); + + +/*** Log message callback stuffs. ***/ + +/* Allocate in POOL a baton for use with svn_cl__get_log_message(). + + OPT_STATE is the set of command-line options given. + + BASE_DIR is a directory in which to create temporary files if an + external editor is used to edit the log message. If BASE_DIR is + NULL, the current working directory (`.') will be used, and + therefore the user must have the proper permissions on that + directory. ### todo: What *should* happen in the NULL case is that + we ask APR to tell us where a suitable tmp directory is (like, /tmp + on Unix and C:\Windows\Temp on Win32 or something), and use it. + But APR doesn't yet have that capability. + + CONFIG is a client configuration hash of svn_config_t * items keyed + on config categories, and may be NULL. + + NOTE: While the baton itself will be allocated from POOL, the items + add to it are added by reference, not duped into POOL!*/ +svn_error_t * +svn_cl__make_log_msg_baton(void **baton, + svn_cl__opt_state_t *opt_state, + const char *base_dir, + apr_hash_t *config, + apr_pool_t *pool); + +/* A function of type svn_client_get_commit_log3_t. */ +svn_error_t * +svn_cl__get_log_message(const char **log_msg, + const char **tmp_file, + const apr_array_header_t *commit_items, + void *baton, + apr_pool_t *pool); + +/* Handle the cleanup of a log message, using the data in the + LOG_MSG_BATON, in the face of COMMIT_ERR. This may mean removing a + temporary file left by an external editor, or it may be a complete + no-op. COMMIT_ERR may be NULL to indicate to indicate that the + function should act as though no commit error occurred. Use POOL + for temporary allocations. + + All error returns from this function are guaranteed to at least + include COMMIT_ERR, and perhaps additional errors attached to the + end of COMMIT_ERR's chain. */ +svn_error_t * +svn_cl__cleanup_log_msg(void *log_msg_baton, + svn_error_t *commit_err, + apr_pool_t *pool); + +/* Add a message about --force if appropriate */ +svn_error_t * +svn_cl__may_need_force(svn_error_t *err); + +/* Write the STRING to the stdio STREAM, returning an error if it fails. + + This function is equal to svn_cmdline_fputs() minus the utf8->local + encoding translation. */ +svn_error_t * +svn_cl__error_checked_fputs(const char *string, FILE* stream); + +/* If STRING is non-null, append it, wrapped in a simple XML CDATA element + named TAGNAME, to the string SB. Use POOL for temporary allocations. */ +void +svn_cl__xml_tagged_cdata(svn_stringbuf_t **sb, + apr_pool_t *pool, + const char *tagname, + const char *string); + +/* Print the XML prolog and document root element start-tag to stdout, using + TAGNAME as the root element name. Use POOL for temporary allocations. */ +svn_error_t * +svn_cl__xml_print_header(const char *tagname, apr_pool_t *pool); + +/* Print the XML document root element end-tag to stdout, using TAGNAME as the + root element name. Use POOL for temporary allocations. */ +svn_error_t * +svn_cl__xml_print_footer(const char *tagname, apr_pool_t *pool); + + +/* For use in XML output, return a non-localised string representation + * of KIND, being "none" or "dir" or "file" or, in any other case, + * the empty string. */ +const char * +svn_cl__node_kind_str_xml(svn_node_kind_t kind); + +/* Return a (possibly localised) string representation of KIND, being "none" or + "dir" or "file" or, in any other case, the empty string. */ +const char * +svn_cl__node_kind_str_human_readable(svn_node_kind_t kind); + + +/** Provides an XML name for a given OPERATION. + * Note: POOL is currently not used. + */ +const char * +svn_cl__operation_str_xml(svn_wc_operation_t operation, apr_pool_t *pool); + +/** Return a possibly localized human readable string for + * a given OPERATION. + * Note: POOL is currently not used. + */ +const char * +svn_cl__operation_str_human_readable(svn_wc_operation_t operation, + apr_pool_t *pool); + + +/* If PROPNAME is one of the svn: properties with a boolean value, and + * PROPVAL looks like an attempt to turn the property off (i.e., it's + * "off", "no", "false", or ""), then print a warning to the user that + * setting the property to this value might not do what they expect. + * Perform temporary allocations in POOL. + */ +void +svn_cl__check_boolean_prop_val(const char *propname, + const char *propval, + apr_pool_t *pool); + +/* De-streamifying wrapper around svn_client_get_changelists(), which + is called for each target in TARGETS to populate *PATHS (a list of + paths assigned to one of the CHANGELISTS. + If all targets are to be included, may set *PATHS to TARGETS without + reallocating. */ +svn_error_t * +svn_cl__changelist_paths(apr_array_header_t **paths, + const apr_array_header_t *changelists, + const apr_array_header_t *targets, + svn_depth_t depth, + svn_client_ctx_t *ctx, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); + +/* Like svn_client_args_to_target_array() but, if the only error is that some + * arguments are reserved file names, then print warning messages for those + * targets, store the rest of the targets in TARGETS_P and return success. */ +svn_error_t * +svn_cl__args_to_target_array_print_reserved(apr_array_header_t **targets_p, + apr_getopt_t *os, + const apr_array_header_t *known_targets, + svn_client_ctx_t *ctx, + svn_boolean_t keep_dest_origpath_on_truepath_collision, + apr_pool_t *pool); + +/* Return a string allocated in POOL that is a copy of STR but with each + * line prefixed with INDENT. A line is all characters up to the first + * CR-LF, LF-CR, CR or LF, or the end of STR if sooner. */ +const char * +svn_cl__indent_string(const char *str, + const char *indent, + apr_pool_t *pool); + + +/* Return a string showing NODE's kind, URL and revision, to the extent that + * that information is available in NODE. If NODE itself is NULL, this prints + * just a 'none' node kind. + * WC_REPOS_ROOT_URL should reflect the target working copy's repository + * root URL. If NODE is from that same URL, the printed URL is abbreviated + * to caret notation (^/). WC_REPOS_ROOT_URL may be NULL, in which case + * this function tries to print the conflicted node's complete URL. */ +const char * +svn_cl__node_description(const svn_wc_conflict_version_t *node, + const char *wc_repos_root_URL, + apr_pool_t *pool); + +/* Return, in @a *true_targets_p, a copy of @a targets with peg revision + * specifiers snipped off the end of each element. + * + * ### JAF TODO: This function is not good because it does not allow the + * ### caller to detect if an invalid peg revision was specified. + * ### + * ### Callers should never have a need to silently *discard* all peg + * ### revisions, even if they are doing this *after* saving any peg + * ### revisions that might be of interest on certain arguments: I don't + * ### think it can ever be correct to silently ignore a peg revision that + * ### is specified, whether it makes semantic sense or not. + * ### + * ### Instead, callers should parse all the arguments and silently + * ### ignore an *empty* peg revision part (just an "@", which can be + * ### used to escape an earlier "@" in the argument) on any argument, + * ### even an argument on which a peg revision does not make sense, + * ### but should not silently ignore a non-empty peg when it does not + * ### make sense. + * ### + * ### Something like: + * ### For each (URL-like?) argument that doesn't accept a peg rev: + * ### Parse into peg-rev and true-path parts; + * ### If (peg rev != unspecified) + * ### Error("This arg doesn't accept a peg rev."). + * ### Use the true-path part. + * + * This function is useful for subcommands for which peg revisions + * do not make any sense. Such subcommands still need to allow peg + * revisions to be specified on the command line so that users of + * the command line client can consistently escape '@' characters + * in filenames by appending an '@' character, regardless of the + * subcommand being used. + * + * If a peg revision is present but cannot be parsed, an error is thrown. + * The user has likely forgotten to escape an '@' character in a filename. + * + * It is safe to pass the address of @a targets as @a true_targets_p. + * + * Do all allocations in @a pool. */ +svn_error_t * +svn_cl__eat_peg_revisions(apr_array_header_t **true_targets_p, + const apr_array_header_t *targets, + apr_pool_t *pool); + +/* Return an error if TARGETS contains a mixture of URLs and paths; otherwise + * return SVN_NO_ERROR. */ +svn_error_t * +svn_cl__assert_homogeneous_target_type(const apr_array_header_t *targets); + +/* Return an error if TARGETS contains a URL; otherwise return SVN_NO_ERROR. */ +svn_error_t * +svn_cl__check_targets_are_local_paths(const apr_array_header_t *targets); + +/* Return an error if TARGET is a URL; otherwise return SVN_NO_ERROR. */ +svn_error_t * +svn_cl__check_target_is_local_path(const char *target); + +/* Return a copy of PATH, converted to the local path style, skipping + * PARENT_PATH if it is non-null and is a parent of or equal to PATH. + * + * This function assumes PARENT_PATH and PATH are both absolute "dirents" + * or both relative "dirents". */ +const char * +svn_cl__local_style_skip_ancestor(const char *parent_path, + const char *path, + apr_pool_t *pool); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* SVN_CL_H */ diff --git a/subversion/svn/cleanup-cmd.c b/subversion/svn/cleanup-cmd.c new file mode 100644 index 0000000..64fa400 --- /dev/null +++ b/subversion/svn/cleanup-cmd.c @@ -0,0 +1,104 @@ +/* + * cleanup-cmd.c -- Subversion cleanup command + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + +/* ==================================================================== */ + + + +/*** Includes. ***/ + +#include "svn_client.h" +#include "svn_path.h" +#include "svn_pools.h" +#include "svn_error.h" +#include "cl.h" + +#include "svn_private_config.h" + + +/*** Code. ***/ + +/* This implements the `svn_opt_subcommand_t' interface. */ +svn_error_t * +svn_cl__cleanup(apr_getopt_t *os, + void *baton, + apr_pool_t *pool) +{ + svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state; + svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx; + apr_array_header_t *targets; + apr_pool_t *subpool; + int i; + + SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os, + opt_state->targets, + ctx, FALSE, pool)); + + /* Add "." if user passed 0 arguments */ + svn_opt_push_implicit_dot_target(targets, pool); + + SVN_ERR(svn_cl__check_targets_are_local_paths(targets)); + + SVN_ERR(svn_cl__eat_peg_revisions(&targets, targets, pool)); + + subpool = svn_pool_create(pool); + for (i = 0; i < targets->nelts; i++) + { + const char *target = APR_ARRAY_IDX(targets, i, const char *); + svn_error_t *err; + + svn_pool_clear(subpool); + SVN_ERR(svn_cl__check_cancel(ctx->cancel_baton)); + err = svn_client_cleanup(target, ctx, subpool); + if (err && err->apr_err == SVN_ERR_WC_LOCKED) + { + const char *target_abspath; + svn_error_t *err2 = svn_dirent_get_absolute(&target_abspath, + target, subpool); + if (err2) + { + err = svn_error_compose_create(err, err2); + } + else + { + const char *wcroot_abspath; + + err2 = svn_client_get_wc_root(&wcroot_abspath, target_abspath, + ctx, subpool, subpool); + if (err2) + err = svn_error_compose_create(err, err2); + else + err = svn_error_createf(SVN_ERR_WC_LOCKED, err, + _("Working copy locked; try running " + "'svn cleanup' on the root of the " + "working copy ('%s') instead."), + svn_dirent_local_style(wcroot_abspath, + subpool)); + } + } + SVN_ERR(err); + } + + svn_pool_destroy(subpool); + return SVN_NO_ERROR; +} diff --git a/subversion/svn/client_errors.h b/subversion/svn/client_errors.h new file mode 100644 index 0000000..5041e25 --- /dev/null +++ b/subversion/svn/client_errors.h @@ -0,0 +1,95 @@ +/* + * client_errors.h: error codes this command line client features + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + +/* ==================================================================== */ + + + +#ifndef SVN_CLIENT_ERRORS_H +#define SVN_CLIENT_ERRORS_H + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/* + * This error defining system is copied from and explained in + * ../../include/svn_error_codes.h + */ + +/* Process this file if we're building an error array, or if we have + not defined the enumerated constants yet. */ +#if defined(SVN_ERROR_BUILD_ARRAY) || !defined(SVN_CMDLINE_ERROR_ENUM_DEFINED) + +#if defined(SVN_ERROR_BUILD_ARRAY) + +#define SVN_ERROR_START \ + static const err_defn error_table[] = { \ + { SVN_ERR_CDMLINE__WARNING, "Warning" }, +#define SVN_ERRDEF(n, s) { n, s }, +#define SVN_ERROR_END { 0, NULL } }; + +#elif !defined(SVN_CMDLINE_ERROR_ENUM_DEFINED) + +#define SVN_ERROR_START \ + typedef enum svn_client_errno_t { \ + SVN_ERR_CDMLINE__WARNING = SVN_ERR_LAST + 1, +#define SVN_ERRDEF(n, s) n, +#define SVN_ERROR_END SVN_ERR_CMDLINE__ERR_LAST } svn_client_errno_t; + +#define SVN_CMDLINE_ERROR_ENUM_DEFINED + +#endif + +/* Define custom command line client error numbers */ + +SVN_ERROR_START + + /* BEGIN Client errors */ + +SVN_ERRDEF(SVN_ERR_CMDLINE__TMPFILE_WRITE, + "Failed writing to temporary file.") + + SVN_ERRDEF(SVN_ERR_CMDLINE__TMPFILE_STAT, + "Failed getting info about temporary file.") + + SVN_ERRDEF(SVN_ERR_CMDLINE__TMPFILE_OPEN, + "Failed opening temporary file.") + + /* END Client errors */ + + +SVN_ERROR_END + +#undef SVN_ERROR_START +#undef SVN_ERRDEF +#undef SVN_ERROR_END + +#endif /* SVN_ERROR_BUILD_ARRAY || !SVN_CMDLINE_ERROR_ENUM_DEFINED */ + + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* SVN_CLIENT_ERRORS_H */ diff --git a/subversion/svn/commit-cmd.c b/subversion/svn/commit-cmd.c new file mode 100644 index 0000000..e227f04 --- /dev/null +++ b/subversion/svn/commit-cmd.c @@ -0,0 +1,184 @@ +/* + * commit-cmd.c -- Check changes into the repository. + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + +/* ==================================================================== */ + + + +/*** Includes. ***/ + +#include <apr_general.h> + +#include "svn_error.h" +#include "svn_error_codes.h" +#include "svn_wc.h" +#include "svn_client.h" +#include "svn_path.h" +#include "svn_dirent_uri.h" +#include "svn_error.h" +#include "svn_config.h" +#include "cl.h" + +#include "svn_private_config.h" + + + +/* Wrapper notify_func2 function and baton for warning about + reduced-depth commits of copied directories. */ +struct copy_warning_notify_baton +{ + svn_wc_notify_func2_t wrapped_func; + void *wrapped_baton; + svn_depth_t depth; + svn_boolean_t warned; +}; + +static void +copy_warning_notify_func(void *baton, + const svn_wc_notify_t *notify, + apr_pool_t *pool) +{ + struct copy_warning_notify_baton *b = baton; + + /* Call the wrapped notification system (if any). */ + if (b->wrapped_func) + b->wrapped_func(b->wrapped_baton, notify, pool); + + /* If we're being notified about a copy of a directory when our + commit depth is less-than-infinite, and we've not already warned + about this situation, then warn about it (and remember that we + now have.) */ + if ((! b->warned) + && (b->depth < svn_depth_infinity) + && (notify->kind == svn_node_dir) + && ((notify->action == svn_wc_notify_commit_copied) || + (notify->action == svn_wc_notify_commit_copied_replaced))) + { + svn_error_t *err; + err = svn_cmdline_printf(pool, + _("svn: The depth of this commit is '%s', " + "but copies are always performed " + "recursively in the repository.\n"), + svn_depth_to_word(b->depth)); + /* ### FIXME: Try to return this error showhow? */ + svn_error_clear(err); + + /* We'll only warn once. */ + b->warned = TRUE; + } +} + + + + +/* This implements the `svn_opt_subcommand_t' interface. */ +svn_error_t * +svn_cl__commit(apr_getopt_t *os, + void *baton, + apr_pool_t *pool) +{ + svn_error_t *err; + svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state; + svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx; + apr_array_header_t *targets; + apr_array_header_t *condensed_targets; + const char *base_dir; + svn_config_t *cfg; + svn_boolean_t no_unlock = FALSE; + struct copy_warning_notify_baton cwnb; + + SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os, + opt_state->targets, + ctx, FALSE, pool)); + + SVN_ERR_W(svn_cl__check_targets_are_local_paths(targets), + _("Commit targets must be local paths")); + + /* Add "." if user passed 0 arguments. */ + svn_opt_push_implicit_dot_target(targets, pool); + + SVN_ERR(svn_cl__eat_peg_revisions(&targets, targets, pool)); + + /* Condense the targets (like commit does)... */ + SVN_ERR(svn_dirent_condense_targets(&base_dir, &condensed_targets, targets, + TRUE, pool, pool)); + + if ((! condensed_targets) || (! condensed_targets->nelts)) + { + const char *parent_dir, *base_name; + + SVN_ERR(svn_wc_get_actual_target2(&parent_dir, &base_name, ctx->wc_ctx, + base_dir, pool, pool)); + if (*base_name) + base_dir = apr_pstrdup(pool, parent_dir); + } + + if (opt_state->depth == svn_depth_unknown) + opt_state->depth = svn_depth_infinity; + + cfg = apr_hash_get(ctx->config, SVN_CONFIG_CATEGORY_CONFIG, + APR_HASH_KEY_STRING); + if (cfg) + SVN_ERR(svn_config_get_bool(cfg, &no_unlock, + SVN_CONFIG_SECTION_MISCELLANY, + SVN_CONFIG_OPTION_NO_UNLOCK, FALSE)); + + /* We're creating a new log message baton because we can use our base_dir + to store the temp file, instead of the current working directory. The + client might not have write access to their working directory, but they + better have write access to the directory they're committing. */ + SVN_ERR(svn_cl__make_log_msg_baton(&(ctx->log_msg_baton3), + opt_state, base_dir, + ctx->config, pool)); + + /* Copies are done server-side, and cheaply, which means they're + effectively always done with infinite depth. This is a potential + cause of confusion for users trying to commit copied subtrees in + part by restricting the commit's depth. See issues #3699 and #3752. */ + if (opt_state->depth < svn_depth_infinity) + { + cwnb.wrapped_func = ctx->notify_func2; + cwnb.wrapped_baton = ctx->notify_baton2; + cwnb.depth = opt_state->depth; + cwnb.warned = FALSE; + ctx->notify_func2 = copy_warning_notify_func; + ctx->notify_baton2 = &cwnb; + } + + /* Commit. */ + err = svn_client_commit5(targets, + opt_state->depth, + no_unlock, + opt_state->keep_changelists, + TRUE /* commit_as_operations */, + opt_state->changelists, + opt_state->revprop_table, + (opt_state->quiet + ? NULL : svn_cl__print_commit_info), + NULL, + ctx, + pool); + SVN_ERR(svn_cl__cleanup_log_msg(ctx->log_msg_baton3, err, pool)); + + return SVN_NO_ERROR; +} diff --git a/subversion/svn/conflict-callbacks.c b/subversion/svn/conflict-callbacks.c new file mode 100644 index 0000000..a158576 --- /dev/null +++ b/subversion/svn/conflict-callbacks.c @@ -0,0 +1,757 @@ +/* + * conflict-callbacks.c: conflict resolution callbacks specific to the + * commandline client. + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + +#include <apr_xlate.h> /* for APR_LOCALE_CHARSET */ + +#define APR_WANT_STDIO +#define APR_WANT_STRFUNC +#include <apr_want.h> + +#include "svn_cmdline.h" +#include "svn_client.h" +#include "svn_types.h" +#include "svn_pools.h" + +#include "cl.h" + +#include "svn_private_config.h" + + + + +svn_cl__conflict_baton_t * +svn_cl__conflict_baton_make(svn_cl__accept_t accept_which, + apr_hash_t *config, + const char *editor_cmd, + svn_cmdline_prompt_baton_t *pb, + apr_pool_t *pool) +{ + svn_cl__conflict_baton_t *b = apr_palloc(pool, sizeof(*b)); + b->accept_which = accept_which; + b->config = config; + b->editor_cmd = editor_cmd; + b->external_failed = FALSE; + b->pb = pb; + return b; +} + +svn_cl__accept_t +svn_cl__accept_from_word(const char *word) +{ + /* Shorthand options are consistent with svn_cl__conflict_handler(). */ + if (strcmp(word, SVN_CL__ACCEPT_POSTPONE) == 0 + || strcmp(word, "p") == 0 || strcmp(word, ":-P") == 0) + return svn_cl__accept_postpone; + if (strcmp(word, SVN_CL__ACCEPT_BASE) == 0) + /* ### shorthand? */ + return svn_cl__accept_base; + if (strcmp(word, SVN_CL__ACCEPT_WORKING) == 0) + /* ### shorthand? */ + return svn_cl__accept_working; + if (strcmp(word, SVN_CL__ACCEPT_MINE_CONFLICT) == 0 + || strcmp(word, "mc") == 0 || strcmp(word, "X-)") == 0) + return svn_cl__accept_mine_conflict; + if (strcmp(word, SVN_CL__ACCEPT_THEIRS_CONFLICT) == 0 + || strcmp(word, "tc") == 0 || strcmp(word, "X-(") == 0) + return svn_cl__accept_theirs_conflict; + if (strcmp(word, SVN_CL__ACCEPT_MINE_FULL) == 0 + || strcmp(word, "mf") == 0 || strcmp(word, ":-)") == 0) + return svn_cl__accept_mine_full; + if (strcmp(word, SVN_CL__ACCEPT_THEIRS_FULL) == 0 + || strcmp(word, "tf") == 0 || strcmp(word, ":-(") == 0) + return svn_cl__accept_theirs_full; + if (strcmp(word, SVN_CL__ACCEPT_EDIT) == 0 + || strcmp(word, "e") == 0 || strcmp(word, ":-E") == 0) + return svn_cl__accept_edit; + if (strcmp(word, SVN_CL__ACCEPT_LAUNCH) == 0 + || strcmp(word, "l") == 0 || strcmp(word, ":-l") == 0) + return svn_cl__accept_launch; + /* word is an invalid action. */ + return svn_cl__accept_invalid; +} + + +/* Print on stdout a diff between the 'base' and 'merged' files, if both of + * those are available, else between 'their' and 'my' files, of DESC. */ +static svn_error_t * +show_diff(const svn_wc_conflict_description_t *desc, + apr_pool_t *pool) +{ + const char *path1, *path2; + svn_diff_t *diff; + svn_stream_t *output; + svn_diff_file_options_t *options; + + if (desc->merged_file && desc->base_file) + { + /* Show the conflict markers to the user */ + path1 = desc->base_file; + path2 = desc->merged_file; + } + else + { + /* There's no base file, but we can show the + difference between mine and theirs. */ + path1 = desc->their_file; + path2 = desc->my_file; + } + + options = svn_diff_file_options_create(pool); + options->ignore_eol_style = TRUE; + SVN_ERR(svn_stream_for_stdout(&output, pool)); + SVN_ERR(svn_diff_file_diff_2(&diff, path1, path2, + options, pool)); + return svn_diff_file_output_unified3(output, diff, + path1, path2, + NULL, NULL, + APR_LOCALE_CHARSET, + NULL, FALSE, + pool); +} + + +/* Print on stdout just the conflict hunks of a diff among the 'base', 'their' + * and 'my' files of DESC. */ +static svn_error_t * +show_conflicts(const svn_wc_conflict_description_t *desc, + apr_pool_t *pool) +{ + svn_diff_t *diff; + svn_stream_t *output; + svn_diff_file_options_t *options; + + options = svn_diff_file_options_create(pool); + options->ignore_eol_style = TRUE; + SVN_ERR(svn_stream_for_stdout(&output, pool)); + SVN_ERR(svn_diff_file_diff3_2(&diff, + desc->base_file, + desc->my_file, + desc->their_file, + options, pool)); + /* ### Consider putting the markers/labels from + ### svn_wc__merge_internal in the conflict description. */ + return svn_diff_file_output_merge2(output, diff, + desc->base_file, + desc->my_file, + desc->their_file, + _("||||||| ORIGINAL"), + _("<<<<<<< MINE (select with 'mc')"), + _(">>>>>>> THEIRS (select with 'tc')"), + "=======", + svn_diff_conflict_display_only_conflicts, + pool); +} + + +/* Run an external editor, passing it the 'merged' file in DESC, or, if the + * 'merged' file is null, return an error. The tool to use is determined by + * B->editor_cmd, B->config and environment variables; see + * svn_cl__edit_file_externally() for details. + * + * If the tool runs, set *PERFORMED_EDIT to true; if a tool is not + * configured or cannot run, do not touch *PERFORMED_EDIT, report the error + * on stderr, and return SVN_NO_ERROR; if any other error is encountered, + * return that error. */ +static svn_error_t * +open_editor(svn_boolean_t *performed_edit, + const svn_wc_conflict_description_t *desc, + svn_cl__conflict_baton_t *b, + apr_pool_t *pool) +{ + svn_error_t *err; + + if (desc->merged_file) + { + err = svn_cl__edit_file_externally(desc->merged_file, b->editor_cmd, + b->config, pool); + if (err && (err->apr_err == SVN_ERR_CL_NO_EXTERNAL_EDITOR)) + { + SVN_ERR(svn_cmdline_fprintf(stderr, pool, "%s\n", + err->message ? err->message : + _("No editor found."))); + svn_error_clear(err); + } + else if (err && (err->apr_err == SVN_ERR_EXTERNAL_PROGRAM)) + { + SVN_ERR(svn_cmdline_fprintf(stderr, pool, "%s\n", + err->message ? err->message : + _("Error running editor."))); + svn_error_clear(err); + } + else if (err) + return svn_error_trace(err); + else + *performed_edit = TRUE; + } + else + SVN_ERR(svn_cmdline_fprintf(stderr, pool, + _("Invalid option; there's no " + "merged version to edit.\n\n"))); + + return SVN_NO_ERROR; +} + + +/* Run an external merge tool, passing it the 'base', 'their', 'my' and + * 'merged' files in DESC. The tool to use is determined by B->config and + * environment variables; see svn_cl__merge_file_externally() for details. + * + * If the tool runs, set *PERFORMED_EDIT to true; if a tool is not + * configured or cannot run, do not touch *PERFORMED_EDIT, report the error + * on stderr, and return SVN_NO_ERROR; if any other error is encountered, + * return that error. */ +static svn_error_t * +launch_resolver(svn_boolean_t *performed_edit, + const svn_wc_conflict_description_t *desc, + svn_cl__conflict_baton_t *b, + apr_pool_t *pool) +{ + svn_error_t *err; + + err = svn_cl__merge_file_externally(desc->base_file, desc->their_file, + desc->my_file, desc->merged_file, + desc->path, b->config, NULL, pool); + if (err && err->apr_err == SVN_ERR_CL_NO_EXTERNAL_MERGE_TOOL) + { + SVN_ERR(svn_cmdline_fprintf(stderr, pool, "%s\n", + err->message ? err->message : + _("No merge tool found.\n"))); + svn_error_clear(err); + } + else if (err && err->apr_err == SVN_ERR_EXTERNAL_PROGRAM) + { + SVN_ERR(svn_cmdline_fprintf(stderr, pool, "%s\n", + err->message ? err->message : + _("Error running merge tool."))); + svn_error_clear(err); + } + else if (err) + return svn_error_trace(err); + else if (performed_edit) + *performed_edit = TRUE; + + return SVN_NO_ERROR; +} + + +/* Implement svn_wc_conflict_resolver_func_t; resolves based on + --accept option if given, else by prompting. */ +svn_error_t * +svn_cl__conflict_handler(svn_wc_conflict_result_t **result, + const svn_wc_conflict_description_t *desc, + void *baton, + apr_pool_t *pool) +{ + svn_cl__conflict_baton_t *b = baton; + svn_error_t *err; + apr_pool_t *subpool; + + /* Start out assuming we're going to postpone the conflict. */ + *result = svn_wc_create_conflict_result(svn_wc_conflict_choose_postpone, + NULL, pool); + + switch (b->accept_which) + { + case svn_cl__accept_invalid: + case svn_cl__accept_unspecified: + /* No (or no valid) --accept option, fall through to prompting. */ + break; + case svn_cl__accept_postpone: + (*result)->choice = svn_wc_conflict_choose_postpone; + return SVN_NO_ERROR; + case svn_cl__accept_base: + (*result)->choice = svn_wc_conflict_choose_base; + return SVN_NO_ERROR; + case svn_cl__accept_working: + (*result)->choice = svn_wc_conflict_choose_merged; + return SVN_NO_ERROR; + case svn_cl__accept_mine_conflict: + (*result)->choice = svn_wc_conflict_choose_mine_conflict; + return SVN_NO_ERROR; + case svn_cl__accept_theirs_conflict: + (*result)->choice = svn_wc_conflict_choose_theirs_conflict; + return SVN_NO_ERROR; + case svn_cl__accept_mine_full: + (*result)->choice = svn_wc_conflict_choose_mine_full; + return SVN_NO_ERROR; + case svn_cl__accept_theirs_full: + (*result)->choice = svn_wc_conflict_choose_theirs_full; + return SVN_NO_ERROR; + case svn_cl__accept_edit: + if (desc->merged_file) + { + if (b->external_failed) + { + (*result)->choice = svn_wc_conflict_choose_postpone; + return SVN_NO_ERROR; + } + + err = svn_cl__edit_file_externally(desc->merged_file, + b->editor_cmd, b->config, pool); + if (err && (err->apr_err == SVN_ERR_CL_NO_EXTERNAL_EDITOR)) + { + SVN_ERR(svn_cmdline_fprintf(stderr, pool, "%s\n", + err->message ? err->message : + _("No editor found;" + " leaving all conflicts."))); + svn_error_clear(err); + b->external_failed = TRUE; + } + else if (err && (err->apr_err == SVN_ERR_EXTERNAL_PROGRAM)) + { + SVN_ERR(svn_cmdline_fprintf(stderr, pool, "%s\n", + err->message ? err->message : + _("Error running editor;" + " leaving all conflicts."))); + svn_error_clear(err); + b->external_failed = TRUE; + } + else if (err) + return svn_error_trace(err); + (*result)->choice = svn_wc_conflict_choose_merged; + return SVN_NO_ERROR; + } + /* else, fall through to prompting. */ + break; + case svn_cl__accept_launch: + if (desc->base_file && desc->their_file + && desc->my_file && desc->merged_file) + { + svn_boolean_t remains_in_conflict; + + if (b->external_failed) + { + (*result)->choice = svn_wc_conflict_choose_postpone; + return SVN_NO_ERROR; + } + + err = svn_cl__merge_file_externally(desc->base_file, + desc->their_file, + desc->my_file, + desc->merged_file, + desc->path, + b->config, + &remains_in_conflict, + pool); + if (err && err->apr_err == SVN_ERR_CL_NO_EXTERNAL_MERGE_TOOL) + { + SVN_ERR(svn_cmdline_fprintf(stderr, pool, "%s\n", + err->message ? err->message : + _("No merge tool found;" + " leaving all conflicts."))); + b->external_failed = TRUE; + return svn_error_trace(err); + } + else if (err && err->apr_err == SVN_ERR_EXTERNAL_PROGRAM) + { + SVN_ERR(svn_cmdline_fprintf(stderr, pool, "%s\n", + err->message ? err->message : + _("Error running merge tool;" + " leaving all conflicts."))); + b->external_failed = TRUE; + return svn_error_trace(err); + } + else if (err) + return svn_error_trace(err); + + if (remains_in_conflict) + (*result)->choice = svn_wc_conflict_choose_postpone; + else + (*result)->choice = svn_wc_conflict_choose_merged; + return SVN_NO_ERROR; + } + /* else, fall through to prompting. */ + break; + } + + /* We're in interactive mode and either the user gave no --accept + option or the option did not apply; let's prompt. */ + subpool = svn_pool_create(pool); + + /* Handle the most common cases, which is either: + + Conflicting edits on a file's text, or + Conflicting edits on a property. + */ + if (((desc->node_kind == svn_node_file) + && (desc->action == svn_wc_conflict_action_edit) + && (desc->reason == svn_wc_conflict_reason_edited)) + || (desc->kind == svn_wc_conflict_kind_property)) + { + const char *answer; + char *prompt; + svn_boolean_t diff_allowed = FALSE; + /* Have they done something that might have affected the merged + file (so that we need to save a .edited copy)? */ + svn_boolean_t performed_edit = FALSE; + /* Have they done *something* (edit, look at diff, etc) to + give them a rational basis for choosing (r)esolved? */ + svn_boolean_t knows_something = FALSE; + + if (desc->kind == svn_wc_conflict_kind_text) + SVN_ERR(svn_cmdline_fprintf(stderr, subpool, + _("Conflict discovered in '%s'.\n"), + desc->path)); + else if (desc->kind == svn_wc_conflict_kind_property) + { + SVN_ERR(svn_cmdline_fprintf(stderr, subpool, + _("Conflict for property '%s' discovered" + " on '%s'.\n"), + desc->property_name, desc->path)); + + if ((!desc->my_file && desc->their_file) + || (desc->my_file && !desc->their_file)) + { + /* One agent wants to change the property, one wants to + delete it. This is not something we can diff, so we + just tell the user. */ + svn_stringbuf_t *myval = NULL, *theirval = NULL; + + if (desc->my_file) + { + SVN_ERR(svn_stringbuf_from_file2(&myval, desc->my_file, + subpool)); + SVN_ERR(svn_cmdline_fprintf(stderr, subpool, + _("They want to delete the property, " + "you want to change the value to '%s'.\n"), + myval->data)); + } + else + { + SVN_ERR(svn_stringbuf_from_file2(&theirval, desc->their_file, + subpool)); + SVN_ERR(svn_cmdline_fprintf(stderr, subpool, + _("They want to change the property value to '%s', " + "you want to delete the property.\n"), + theirval->data)); + } + } + } + else + /* We don't recognize any other sort of conflict yet */ + return SVN_NO_ERROR; + + /* Diffing can happen between base and merged, to show conflict + markers to the user (this is the typical 3-way merge + scenario), or if no base is available, we can show a diff + between mine and theirs. */ + if ((desc->merged_file && desc->base_file) + || (!desc->base_file && desc->my_file && desc->their_file)) + diff_allowed = TRUE; + + while (TRUE) + { + svn_pool_clear(subpool); + + prompt = apr_pstrdup(subpool, _("Select: (p) postpone")); + + if (diff_allowed) + { + prompt = apr_pstrcat(subpool, prompt, + _(", (df) diff-full, (e) edit"), + (char *)NULL); + + if (knows_something) + prompt = apr_pstrcat(subpool, prompt, _(", (r) resolved"), + (char *)NULL); + + if (! desc->is_binary && + desc->kind != svn_wc_conflict_kind_property) + prompt = apr_pstrcat(subpool, prompt, + _(",\n (mc) mine-conflict, " + "(tc) theirs-conflict"), + (char *)NULL); + } + else + { + if (knows_something) + prompt = apr_pstrcat(subpool, prompt, _(", (r) resolved"), + (char *)NULL); + prompt = apr_pstrcat(subpool, prompt, + _(",\n " + "(mf) mine-full, (tf) theirs-full"), + (char *)NULL); + } + + prompt = apr_pstrcat(subpool, prompt, ",\n ", (char *)NULL); + prompt = apr_pstrcat(subpool, prompt, + _("(s) show all options: "), + (char *)NULL); + + SVN_ERR(svn_cmdline_prompt_user2(&answer, prompt, b->pb, subpool)); + + if (strcmp(answer, "s") == 0) + { + /* These are used in svn_cl__accept_from_word(). */ + SVN_ERR(svn_cmdline_fprintf(stderr, subpool, + _("\n" + " (e) edit - change merged file in an editor\n" + " (df) diff-full - show all changes made to merged " + "file\n" + " (r) resolved - accept merged version of file\n" + "\n" + " (dc) display-conflict - show all conflicts " + "(ignoring merged version)\n" + " (mc) mine-conflict - accept my version for all " + "conflicts (same)\n" + " (tc) theirs-conflict - accept their version for all " + "conflicts (same)\n" + "\n" + " (mf) mine-full - accept my version of entire file " + "(even non-conflicts)\n" + " (tf) theirs-full - accept their version of entire " + "file (same)\n" + "\n" + " (p) postpone - mark the conflict to be " + "resolved later\n" + " (l) launch - launch external tool to " + "resolve conflict\n" + " (s) show all - show this list\n\n"))); + } + else if (strcmp(answer, "p") == 0 || strcmp(answer, ":-P") == 0) + { + /* Do nothing, let file be marked conflicted. */ + (*result)->choice = svn_wc_conflict_choose_postpone; + break; + } + else if (strcmp(answer, "mc") == 0 || strcmp(answer, "X-)") == 0) + { + if (desc->is_binary) + { + SVN_ERR(svn_cmdline_fprintf(stderr, subpool, + _("Invalid option; cannot choose " + "based on conflicts in a " + "binary file.\n\n"))); + continue; + } + else if (desc->kind == svn_wc_conflict_kind_property) + { + SVN_ERR(svn_cmdline_fprintf(stderr, subpool, + _("Invalid option; cannot choose " + "based on conflicts for " + "properties.\n\n"))); + continue; + } + + (*result)->choice = svn_wc_conflict_choose_mine_conflict; + if (performed_edit) + (*result)->save_merged = TRUE; + break; + } + else if (strcmp(answer, "tc") == 0 || strcmp(answer, "X-(") == 0) + { + if (desc->is_binary) + { + SVN_ERR(svn_cmdline_fprintf(stderr, subpool, + _("Invalid option; cannot choose " + "based on conflicts in a " + "binary file.\n\n"))); + continue; + } + else if (desc->kind == svn_wc_conflict_kind_property) + { + SVN_ERR(svn_cmdline_fprintf(stderr, subpool, + _("Invalid option; cannot choose " + "based on conflicts for " + "properties.\n\n"))); + continue; + } + (*result)->choice = svn_wc_conflict_choose_theirs_conflict; + if (performed_edit) + (*result)->save_merged = TRUE; + break; + } + else if (strcmp(answer, "mf") == 0 || strcmp(answer, ":-)") == 0) + { + (*result)->choice = svn_wc_conflict_choose_mine_full; + if (performed_edit) + (*result)->save_merged = TRUE; + break; + } + else if (strcmp(answer, "tf") == 0 || strcmp(answer, ":-(") == 0) + { + (*result)->choice = svn_wc_conflict_choose_theirs_full; + if (performed_edit) + (*result)->save_merged = TRUE; + break; + } + else if (strcmp(answer, "dc") == 0) + { + if (desc->is_binary) + { + SVN_ERR(svn_cmdline_fprintf(stderr, subpool, + _("Invalid option; cannot " + "display conflicts for a " + "binary file.\n\n"))); + continue; + } + else if (desc->kind == svn_wc_conflict_kind_property) + { + SVN_ERR(svn_cmdline_fprintf(stderr, subpool, + _("Invalid option; cannot " + "display conflicts for " + "properties.\n\n"))); + continue; + } + else if (! (desc->my_file && desc->base_file && desc->their_file)) + { + SVN_ERR(svn_cmdline_fprintf(stderr, subpool, + _("Invalid option; original " + "files not available.\n\n"))); + continue; + } + SVN_ERR(show_conflicts(desc, subpool)); + knows_something = TRUE; + } + else if (strcmp(answer, "df") == 0) + { + if (! diff_allowed) + { + SVN_ERR(svn_cmdline_fprintf(stderr, subpool, + _("Invalid option; there's no " + "merged version to diff.\n\n"))); + continue; + } + + SVN_ERR(show_diff(desc, subpool)); + knows_something = TRUE; + } + else if (strcmp(answer, "e") == 0 || strcmp(answer, ":-E") == 0) + { + SVN_ERR(open_editor(&performed_edit, desc, b, subpool)); + if (performed_edit) + knows_something = TRUE; + } + else if (strcmp(answer, "l") == 0 || strcmp(answer, ":-l") == 0) + { + if (desc->kind == svn_wc_conflict_kind_property) + { + SVN_ERR(svn_cmdline_fprintf(stderr, subpool, + _("Invalid option; cannot " + "resolve property conflicts " + "with an external merge tool." + "\n\n"))); + continue; + } + if (desc->base_file && desc->their_file && desc->my_file + && desc->merged_file) + { + SVN_ERR(launch_resolver(&performed_edit, desc, b, subpool)); + if (performed_edit) + knows_something = TRUE; + } + else + SVN_ERR(svn_cmdline_fprintf(stderr, subpool, + _("Invalid option.\n\n"))); + } + else if (strcmp(answer, "r") == 0) + { + /* We only allow the user accept the merged version of + the file if they've edited it, or at least looked at + the diff. */ + if (knows_something) + { + (*result)->choice = svn_wc_conflict_choose_merged; + break; + } + else + SVN_ERR(svn_cmdline_fprintf(stderr, subpool, + _("Invalid option.\n\n"))); + } + } + } + /* + Dealing with obstruction of additions can be tricky. The + obstructing item could be unversioned, versioned, or even + schedule-add. Here's a matrix of how the caller should behave, + based on results we return. + + Unversioned Versioned Schedule-Add + + choose_mine skip addition, skip addition skip addition + add existing item + + choose_theirs destroy file, schedule-delete, revert add, + add new item. add new item. rm file, + add new item + + postpone [ bail out ] + + */ + else if ((desc->action == svn_wc_conflict_action_add) + && (desc->reason == svn_wc_conflict_reason_obstructed)) + { + const char *answer; + const char *prompt; + + SVN_ERR(svn_cmdline_fprintf( + stderr, subpool, + _("Conflict discovered when trying to add '%s'.\n" + "An object of the same name already exists.\n"), + desc->path)); + prompt = _("Select: (p) postpone, (mf) mine-full, " + "(tf) theirs-full, (h) help:"); + + while (1) + { + svn_pool_clear(subpool); + + SVN_ERR(svn_cmdline_prompt_user2(&answer, prompt, b->pb, subpool)); + + if (strcmp(answer, "h") == 0 || strcmp(answer, "?") == 0) + { + SVN_ERR(svn_cmdline_fprintf(stderr, subpool, + _(" (p) postpone - resolve the conflict later\n" + " (mf) mine-full - accept pre-existing item " + "(ignore upstream addition)\n" + " (tf) theirs-full - accept incoming item " + "(overwrite pre-existing item)\n" + " (h) help - show this help\n\n"))); + } + if (strcmp(answer, "p") == 0 || strcmp(answer, ":-P") == 0) + { + (*result)->choice = svn_wc_conflict_choose_postpone; + break; + } + if (strcmp(answer, "mf") == 0 || strcmp(answer, ":-)") == 0) + { + (*result)->choice = svn_wc_conflict_choose_mine_full; + break; + } + if (strcmp(answer, "tf") == 0 || strcmp(answer, ":-(") == 0) + { + (*result)->choice = svn_wc_conflict_choose_theirs_full; + break; + } + } + } + + else /* other types of conflicts -- do nothing about them. */ + { + (*result)->choice = svn_wc_conflict_choose_postpone; + } + + svn_pool_destroy(subpool); + return SVN_NO_ERROR; +} diff --git a/subversion/svn/copy-cmd.c b/subversion/svn/copy-cmd.c new file mode 100644 index 0000000..99b8703 --- /dev/null +++ b/subversion/svn/copy-cmd.c @@ -0,0 +1,140 @@ +/* + * copy-cmd.c -- Subversion copy command + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + +/* ==================================================================== */ + + + +/*** Includes. ***/ + +#include "svn_client.h" +#include "svn_path.h" +#include "svn_error.h" +#include "cl.h" + +#include "svn_private_config.h" + + +/*** Code. ***/ + +/* This implements the `svn_opt_subcommand_t' interface. */ +svn_error_t * +svn_cl__copy(apr_getopt_t *os, + void *baton, + apr_pool_t *pool) +{ + svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state; + svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx; + apr_array_header_t *targets, *sources; + const char *src_path, *dst_path; + svn_boolean_t srcs_are_urls, dst_is_url; + svn_error_t *err; + int i; + + SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os, + opt_state->targets, + ctx, FALSE, pool)); + if (targets->nelts < 2) + return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0, NULL); + + /* Get the src list and associated peg revs */ + sources = apr_array_make(pool, targets->nelts - 1, + sizeof(svn_client_copy_source_t *)); + for (i = 0; i < (targets->nelts - 1); i++) + { + const char *target = APR_ARRAY_IDX(targets, i, const char *); + svn_client_copy_source_t *source = apr_palloc(pool, sizeof(*source)); + const char *src; + svn_opt_revision_t *peg_revision = apr_palloc(pool, + sizeof(*peg_revision)); + + SVN_ERR(svn_opt_parse_path(peg_revision, &src, target, pool)); + source->path = src; + source->revision = &(opt_state->start_revision); + source->peg_revision = peg_revision; + + APR_ARRAY_PUSH(sources, svn_client_copy_source_t *) = source; + } + + SVN_ERR(svn_cl__eat_peg_revisions(&targets, targets, pool)); + + /* Figure out which type of notification to use. + (There is no need to check that the src paths are homogeneous; + svn_client_copy6() through its subroutine try_copy() will return an + error if they are not.) */ + src_path = APR_ARRAY_IDX(targets, 0, const char *); + srcs_are_urls = svn_path_is_url(src_path); + dst_path = APR_ARRAY_IDX(targets, targets->nelts - 1, const char *); + apr_array_pop(targets); + dst_is_url = svn_path_is_url(dst_path); + + if ((! srcs_are_urls) && (! dst_is_url)) + { + /* WC->WC */ + } + else if ((! srcs_are_urls) && (dst_is_url)) + { + /* WC->URL : Use notification. */ + if (! opt_state->quiet) + SVN_ERR(svn_cl__notifier_mark_wc_to_repos_copy(ctx->notify_baton2)); + } + else if ((srcs_are_urls) && (! dst_is_url)) + { + /* URL->WC : Use checkout-style notification. */ + if (! opt_state->quiet) + SVN_ERR(svn_cl__notifier_mark_checkout(ctx->notify_baton2)); + } + else + { + /* URL -> URL, meaning that no notification is needed. */ + ctx->notify_func2 = NULL; + } + + if (! dst_is_url) + { + ctx->log_msg_func3 = NULL; + if (opt_state->message || opt_state->filedata || opt_state->revprop_table) + return svn_error_create + (SVN_ERR_CL_UNNECESSARY_LOG_MESSAGE, NULL, + _("Local, non-commit operations do not take a log message " + "or revision properties")); + } + + if (ctx->log_msg_func3) + SVN_ERR(svn_cl__make_log_msg_baton(&(ctx->log_msg_baton3), opt_state, + NULL, ctx->config, pool)); + + err = svn_client_copy6(sources, dst_path, TRUE, + opt_state->parents, opt_state->ignore_externals, + opt_state->revprop_table, + (opt_state->quiet ? NULL : svn_cl__print_commit_info), + NULL, + ctx, pool); + + if (ctx->log_msg_func3) + SVN_ERR(svn_cl__cleanup_log_msg(ctx->log_msg_baton3, err, pool)); + else if (err) + return svn_error_trace(err); + + return SVN_NO_ERROR; +} diff --git a/subversion/svn/delete-cmd.c b/subversion/svn/delete-cmd.c new file mode 100644 index 0000000..e73813b --- /dev/null +++ b/subversion/svn/delete-cmd.c @@ -0,0 +1,95 @@ +/* + * delete-cmd.c -- Delete/undelete commands + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + +/* ==================================================================== */ + + + +/*** Includes. ***/ + +#include "svn_client.h" +#include "svn_error.h" +#include "svn_path.h" +#include "cl.h" + +#include "svn_private_config.h" + + +/*** Code. ***/ + +/* This implements the `svn_opt_subcommand_t' interface. */ +svn_error_t * +svn_cl__delete(apr_getopt_t *os, + void *baton, + apr_pool_t *pool) +{ + svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state; + svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx; + apr_array_header_t *targets; + svn_error_t *err; + svn_boolean_t is_url; + + SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os, + opt_state->targets, + ctx, FALSE, pool)); + + if (! targets->nelts) + return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0, NULL); + + SVN_ERR(svn_cl__assert_homogeneous_target_type(targets)); + is_url = svn_path_is_url(APR_ARRAY_IDX(targets, 0, const char *)); + + if (! is_url) + { + ctx->log_msg_func3 = NULL; + if (opt_state->message || opt_state->filedata || opt_state->revprop_table) + { + return svn_error_create + (SVN_ERR_CL_UNNECESSARY_LOG_MESSAGE, NULL, + _("Local, non-commit operations do not take a log message " + "or revision properties")); + } + } + else + { + SVN_ERR(svn_cl__make_log_msg_baton(&(ctx->log_msg_baton3), opt_state, + NULL, ctx->config, pool)); + } + + SVN_ERR(svn_cl__eat_peg_revisions(&targets, targets, pool)); + + err = svn_client_delete4(targets, opt_state->force, opt_state->keep_local, + opt_state->revprop_table, + (opt_state->quiet + ? NULL : svn_cl__print_commit_info), + NULL, ctx, pool); + if (err) + err = svn_cl__may_need_force(err); + + if (ctx->log_msg_func3) + SVN_ERR(svn_cl__cleanup_log_msg(ctx->log_msg_baton3, err, pool)); + else if (err) + return svn_error_trace(err); + + return SVN_NO_ERROR; +} diff --git a/subversion/svn/diff-cmd.c b/subversion/svn/diff-cmd.c new file mode 100644 index 0000000..3c62523 --- /dev/null +++ b/subversion/svn/diff-cmd.c @@ -0,0 +1,439 @@ +/* + * diff-cmd.c -- Display context diff of a file + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + +/* ==================================================================== */ + + + +/*** Includes. ***/ + +#include "svn_pools.h" +#include "svn_client.h" +#include "svn_string.h" +#include "svn_dirent_uri.h" +#include "svn_path.h" +#include "svn_error_codes.h" +#include "svn_error.h" +#include "svn_types.h" +#include "svn_cmdline.h" +#include "svn_xml.h" +#include "cl.h" + +#include "svn_private_config.h" + + +/*** Code. ***/ + +/* Convert KIND into a single character for display to the user. */ +static char +kind_to_char(svn_client_diff_summarize_kind_t kind) +{ + switch (kind) + { + case svn_client_diff_summarize_kind_modified: + return 'M'; + + case svn_client_diff_summarize_kind_added: + return 'A'; + + case svn_client_diff_summarize_kind_deleted: + return 'D'; + + default: + return ' '; + } +} + +/* Convert KIND into a word describing the kind to the user. */ +static const char * +kind_to_word(svn_client_diff_summarize_kind_t kind) +{ + switch (kind) + { + case svn_client_diff_summarize_kind_modified: return "modified"; + case svn_client_diff_summarize_kind_added: return "added"; + case svn_client_diff_summarize_kind_deleted: return "deleted"; + default: return "none"; + } +} + +/* Print summary information about a given change as XML, implements the + * svn_client_diff_summarize_func_t interface. The @a baton is a 'char *' + * representing the either the path to the working copy root or the url + * the path the working copy root corresponds to. */ +static svn_error_t * +summarize_xml(const svn_client_diff_summarize_t *summary, + void *baton, + apr_pool_t *pool) +{ + /* Full path to the object being diffed. This is created by taking the + * baton, and appending the target's relative path. */ + const char *path = *(const char **)baton; + svn_stringbuf_t *sb = svn_stringbuf_create("", pool); + + /* Tack on the target path, so we can differentiate between different parts + * of the output when we're given multiple targets. */ + if (svn_path_is_url(path)) + { + path = svn_path_url_add_component2(path, summary->path, pool); + } + else + { + path = svn_dirent_join(path, summary->path, pool); + + /* Convert non-urls to local style, so that things like "" + show up as "." */ + path = svn_dirent_local_style(path, pool); + } + + svn_xml_make_open_tag(&sb, pool, svn_xml_protect_pcdata, "path", + "kind", svn_cl__node_kind_str_xml(summary->node_kind), + "item", kind_to_word(summary->summarize_kind), + "props", summary->prop_changed ? "modified" : "none", + NULL); + + svn_xml_escape_cdata_cstring(&sb, path, pool); + svn_xml_make_close_tag(&sb, pool, "path"); + + return svn_cl__error_checked_fputs(sb->data, stdout); +} + +/* Print summary information about a given change, implements the + * svn_client_diff_summarize_func_t interface. */ +static svn_error_t * +summarize_regular(const svn_client_diff_summarize_t *summary, + void *baton, + apr_pool_t *pool) +{ + const char *path = *(const char **)baton; + + /* Tack on the target path, so we can differentiate between different parts + * of the output when we're given multiple targets. */ + if (svn_path_is_url(path)) + { + path = svn_path_url_add_component2(path, summary->path, pool); + } + else + { + path = svn_dirent_join(path, summary->path, pool); + + /* Convert non-urls to local style, so that things like "" + show up as "." */ + path = svn_dirent_local_style(path, pool); + } + + /* Note: This output format tries to look like the output of 'svn status', + * thus the blank spaces where information that is not relevant to + * a diff summary would go. */ + + SVN_ERR(svn_cmdline_printf(pool, + "%c%c %s\n", + kind_to_char(summary->summarize_kind), + summary->prop_changed ? 'M' : ' ', + path)); + + return svn_cmdline_fflush(stdout); +} + +/* An svn_opt_subcommand_t to handle the 'diff' command. + This implements the `svn_opt_subcommand_t' interface. */ +svn_error_t * +svn_cl__diff(apr_getopt_t *os, + void *baton, + apr_pool_t *pool) +{ + svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state; + svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx; + apr_array_header_t *options; + apr_array_header_t *targets; + apr_file_t *outfile, *errfile; + apr_status_t status; + const char *old_target, *new_target; + apr_pool_t *iterpool; + svn_boolean_t pegged_diff = FALSE; + int i; + const svn_client_diff_summarize_func_t summarize_func = + (opt_state->xml ? summarize_xml : summarize_regular); + + if (opt_state->extensions) + options = svn_cstring_split(opt_state->extensions, " \t\n\r", TRUE, pool); + else + options = NULL; + + /* Get an apr_file_t representing stdout and stderr, which is where + we'll have the external 'diff' program print to. */ + if ((status = apr_file_open_stdout(&outfile, pool))) + return svn_error_wrap_apr(status, _("Can't open stdout")); + if ((status = apr_file_open_stderr(&errfile, pool))) + return svn_error_wrap_apr(status, _("Can't open stderr")); + + if (opt_state->xml) + { + svn_stringbuf_t *sb; + + /* Check that the --summarize is passed as well. */ + if (!opt_state->summarize) + return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, + _("'--xml' option only valid with " + "'--summarize' option")); + + SVN_ERR(svn_cl__xml_print_header("diff", pool)); + + sb = svn_stringbuf_create("", pool); + svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "paths", NULL); + SVN_ERR(svn_cl__error_checked_fputs(sb->data, stdout)); + } + + SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os, + opt_state->targets, + ctx, FALSE, pool)); + + if (! opt_state->old_target && ! opt_state->new_target + && (targets->nelts == 2) + && svn_path_is_url(APR_ARRAY_IDX(targets, 0, const char *)) + && svn_path_is_url(APR_ARRAY_IDX(targets, 1, const char *)) + && opt_state->start_revision.kind == svn_opt_revision_unspecified + && opt_state->end_revision.kind == svn_opt_revision_unspecified) + { + /* The 'svn diff OLD_URL[@OLDREV] NEW_URL[@NEWREV]' case matches. */ + + SVN_ERR(svn_opt_parse_path(&opt_state->start_revision, &old_target, + APR_ARRAY_IDX(targets, 0, const char *), + pool)); + SVN_ERR(svn_opt_parse_path(&opt_state->end_revision, &new_target, + APR_ARRAY_IDX(targets, 1, const char *), + pool)); + targets->nelts = 0; + + if (opt_state->start_revision.kind == svn_opt_revision_unspecified) + opt_state->start_revision.kind = svn_opt_revision_head; + if (opt_state->end_revision.kind == svn_opt_revision_unspecified) + opt_state->end_revision.kind = svn_opt_revision_head; + } + else if (opt_state->old_target) + { + apr_array_header_t *tmp, *tmp2; + svn_opt_revision_t old_rev, new_rev; + + /* The 'svn diff --old=OLD[@OLDREV] [--new=NEW[@NEWREV]] + [PATH...]' case matches. */ + + tmp = apr_array_make(pool, 2, sizeof(const char *)); + APR_ARRAY_PUSH(tmp, const char *) = (opt_state->old_target); + APR_ARRAY_PUSH(tmp, const char *) = (opt_state->new_target + ? opt_state->new_target + : APR_ARRAY_IDX(tmp, 0, + const char *)); + + SVN_ERR(svn_cl__args_to_target_array_print_reserved(&tmp2, os, tmp, + ctx, FALSE, pool)); + + /* Check if either or both targets were skipped (e.g. because they + * were .svn directories). */ + if (tmp2->nelts < 2) + return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, NULL, NULL); + + SVN_ERR(svn_opt_parse_path(&old_rev, &old_target, + APR_ARRAY_IDX(tmp2, 0, const char *), + pool)); + if (old_rev.kind != svn_opt_revision_unspecified) + opt_state->start_revision = old_rev; + SVN_ERR(svn_opt_parse_path(&new_rev, &new_target, + APR_ARRAY_IDX(tmp2, 1, const char *), + pool)); + if (new_rev.kind != svn_opt_revision_unspecified) + opt_state->end_revision = new_rev; + + if (opt_state->start_revision.kind == svn_opt_revision_unspecified) + opt_state->start_revision.kind = svn_path_is_url(old_target) + ? svn_opt_revision_head : svn_opt_revision_base; + + if (opt_state->end_revision.kind == svn_opt_revision_unspecified) + opt_state->end_revision.kind = svn_path_is_url(new_target) + ? svn_opt_revision_head : svn_opt_revision_working; + } + else if (opt_state->new_target) + { + return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, + _("'--new' option only valid with " + "'--old' option")); + } + else + { + svn_boolean_t working_copy_present; + + /* The 'svn diff [-r N[:M]] [TARGET[@REV]...]' case matches. */ + + /* Here each target is a pegged object. Find out the starting + and ending paths for each target. */ + + svn_opt_push_implicit_dot_target(targets, pool); + + old_target = ""; + new_target = ""; + + SVN_ERR(svn_cl__assert_homogeneous_target_type(targets)); + + working_copy_present = ! svn_path_is_url(APR_ARRAY_IDX(targets, 0, + const char *)); + + if (opt_state->start_revision.kind == svn_opt_revision_unspecified + && working_copy_present) + opt_state->start_revision.kind = svn_opt_revision_base; + if (opt_state->end_revision.kind == svn_opt_revision_unspecified) + opt_state->end_revision.kind = working_copy_present + ? svn_opt_revision_working : svn_opt_revision_head; + + /* Determine if we need to do pegged diffs. */ + if ((opt_state->start_revision.kind != svn_opt_revision_base + && opt_state->start_revision.kind != svn_opt_revision_working) + || (opt_state->end_revision.kind != svn_opt_revision_base + && opt_state->end_revision.kind != svn_opt_revision_working)) + pegged_diff = TRUE; + + } + + svn_opt_push_implicit_dot_target(targets, pool); + + iterpool = svn_pool_create(pool); + + for (i = 0; i < targets->nelts; ++i) + { + const char *path = APR_ARRAY_IDX(targets, i, const char *); + const char *target1, *target2; + + svn_pool_clear(iterpool); + if (! pegged_diff) + { + /* We can't be tacking URLs onto base paths! */ + if (svn_path_is_url(path)) + return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, + _("Path '%s' not relative to base URLs"), + path); + + if (svn_path_is_url(old_target)) + target1 = svn_path_url_add_component2( + old_target, + svn_relpath_canonicalize(path, iterpool), + iterpool); + else + target1 = svn_dirent_join(old_target, path, iterpool); + + if (svn_path_is_url(new_target)) + target2 = svn_path_url_add_component2( + new_target, + svn_relpath_canonicalize(path, iterpool), + iterpool); + else + target2 = svn_dirent_join(new_target, path, iterpool); + + if (opt_state->summarize) + SVN_ERR(svn_client_diff_summarize2 + (target1, + &opt_state->start_revision, + target2, + &opt_state->end_revision, + opt_state->depth, + ! opt_state->notice_ancestry, + opt_state->changelists, + summarize_func, &target1, + ctx, iterpool)); + else + SVN_ERR(svn_client_diff5 + (options, + target1, + &(opt_state->start_revision), + target2, + &(opt_state->end_revision), + NULL, + opt_state->depth, + ! opt_state->notice_ancestry, + opt_state->no_diff_deleted, + opt_state->show_copies_as_adds, + opt_state->force, + opt_state->use_git_diff_format, + svn_cmdline_output_encoding(pool), + outfile, + errfile, + opt_state->changelists, + ctx, iterpool)); + } + else + { + const char *truepath; + svn_opt_revision_t peg_revision; + + /* First check for a peg revision. */ + SVN_ERR(svn_opt_parse_path(&peg_revision, &truepath, path, + iterpool)); + + /* Set the default peg revision if one was not specified. */ + if (peg_revision.kind == svn_opt_revision_unspecified) + peg_revision.kind = svn_path_is_url(path) + ? svn_opt_revision_head : svn_opt_revision_working; + + if (opt_state->summarize) + SVN_ERR(svn_client_diff_summarize_peg2 + (truepath, + &peg_revision, + &opt_state->start_revision, + &opt_state->end_revision, + opt_state->depth, + ! opt_state->notice_ancestry, + opt_state->changelists, + summarize_func, &truepath, + ctx, iterpool)); + else + SVN_ERR(svn_client_diff_peg5 + (options, + truepath, + &peg_revision, + &opt_state->start_revision, + &opt_state->end_revision, + NULL, + opt_state->depth, + ! opt_state->notice_ancestry, + opt_state->no_diff_deleted, + opt_state->show_copies_as_adds, + opt_state->force, + opt_state->use_git_diff_format, + svn_cmdline_output_encoding(pool), + outfile, + errfile, + opt_state->changelists, + ctx, iterpool)); + } + } + + if (opt_state->xml) + { + svn_stringbuf_t *sb = svn_stringbuf_create("", pool); + svn_xml_make_close_tag(&sb, pool, "paths"); + SVN_ERR(svn_cl__error_checked_fputs(sb->data, stdout)); + SVN_ERR(svn_cl__xml_print_footer("diff", pool)); + } + + svn_pool_destroy(iterpool); + + return SVN_NO_ERROR; +} diff --git a/subversion/svn/export-cmd.c b/subversion/svn/export-cmd.c new file mode 100644 index 0000000..c4c4aa4 --- /dev/null +++ b/subversion/svn/export-cmd.c @@ -0,0 +1,122 @@ +/* + * export-cmd.c -- Subversion export command + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + +/* ==================================================================== */ + + + +/*** Includes. ***/ + +#include "svn_client.h" +#include "svn_error.h" +#include "svn_dirent_uri.h" +#include "svn_path.h" +#include "cl.h" + +#include "svn_private_config.h" +#include "private/svn_opt_private.h" + + +/*** Code. ***/ + +/* This implements the `svn_opt_subcommand_t' interface. */ +svn_error_t * +svn_cl__export(apr_getopt_t *os, + void *baton, + apr_pool_t *pool) +{ + svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state; + svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx; + const char *from, *to; + apr_array_header_t *targets; + svn_error_t *err; + svn_opt_revision_t peg_revision; + const char *truefrom; + struct svn_cl__check_externals_failed_notify_baton nwb; + + SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os, + opt_state->targets, + ctx, FALSE, pool)); + + /* We want exactly 1 or 2 targets for this subcommand. */ + if (targets->nelts < 1) + return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0, NULL); + if (targets->nelts > 2) + return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, 0, NULL); + + /* The first target is the `from' path. */ + from = APR_ARRAY_IDX(targets, 0, const char *); + + /* Get the peg revision if present. */ + SVN_ERR(svn_opt_parse_path(&peg_revision, &truefrom, from, pool)); + + /* If only one target was given, split off the basename to use as + the `to' path. Else, a `to' path was supplied. */ + if (targets->nelts == 1) + { + if (svn_path_is_url(truefrom)) + to = svn_uri_basename(truefrom, pool); + else + to = svn_dirent_basename(truefrom, pool); + } + else + { + to = APR_ARRAY_IDX(targets, 1, const char *); + + if (strcmp("", to) != 0) + /* svn_cl__eat_peg_revisions() but only on one target */ + SVN_ERR(svn_opt__split_arg_at_peg_revision(&to, NULL, to, pool)); + } + + SVN_ERR(svn_cl__check_target_is_local_path(to)); + + if (! opt_state->quiet) + SVN_ERR(svn_cl__notifier_mark_export(ctx->notify_baton2)); + + if (opt_state->depth == svn_depth_unknown) + opt_state->depth = svn_depth_infinity; + + nwb.wrapped_func = ctx->notify_func2; + nwb.wrapped_baton = ctx->notify_baton2; + nwb.had_externals_error = FALSE; + ctx->notify_func2 = svn_cl__check_externals_failed_notify_wrapper; + ctx->notify_baton2 = &nwb; + + /* Do the export. */ + err = svn_client_export5(NULL, truefrom, to, &peg_revision, + &(opt_state->start_revision), + opt_state->force, opt_state->ignore_externals, + opt_state->ignore_keywords, opt_state->depth, + opt_state->native_eol, ctx, pool); + if (err && err->apr_err == SVN_ERR_WC_OBSTRUCTED_UPDATE && !opt_state->force) + SVN_ERR_W(err, + _("Destination directory exists; please remove " + "the directory or use --force to overwrite")); + + if (nwb.had_externals_error) + return svn_error_create(SVN_ERR_CL_ERROR_PROCESSING_EXTERNALS, NULL, + _("Failure occurred processing one or more " + "externals definitions")); + + return svn_error_trace(err); +} diff --git a/subversion/svn/help-cmd.c b/subversion/svn/help-cmd.c new file mode 100644 index 0000000..cd0373a --- /dev/null +++ b/subversion/svn/help-cmd.c @@ -0,0 +1,93 @@ +/* + * help-cmd.c -- Provide help + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + +/* ==================================================================== */ + + + +/*** Includes. ***/ + +#include "svn_string.h" +#include "svn_error.h" +#include "svn_version.h" +#include "cl.h" + +#include "svn_private_config.h" + + +/*** Code. ***/ + +/* This implements the `svn_opt_subcommand_t' interface. */ +svn_error_t * +svn_cl__help(apr_getopt_t *os, + void *baton, + apr_pool_t *pool) +{ + svn_cl__opt_state_t *opt_state; + + /* xgettext: the %s is for SVN_VER_NUMBER. */ + char help_header_template[] = + N_("usage: svn <subcommand> [options] [args]\n" + "Subversion command-line client, version %s.\n" + "Type 'svn help <subcommand>' for help on a specific subcommand.\n" + "Type 'svn --version' to see the program version and RA modules\n" + " or 'svn --version --quiet' to see just the version number.\n" + "\n" + "Most subcommands take file and/or directory arguments, recursing\n" + "on the directories. If no arguments are supplied to such a\n" + "command, it recurses on the current directory (inclusive) by default.\n" + "\n" + "Available subcommands:\n"); + + char help_footer[] = + N_("Subversion is a tool for version control.\n" + "For additional information, see http://subversion.apache.org/\n"); + + char *help_header = + apr_psprintf(pool, _(help_header_template), SVN_VER_NUMBER); + + const char *ra_desc_start + = _("The following repository access (RA) modules are available:\n\n"); + + svn_stringbuf_t *version_footer; + + if (baton) + opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state; + else + opt_state = NULL; + + version_footer = svn_stringbuf_create(ra_desc_start, pool); + SVN_ERR(svn_ra_print_modules(version_footer, pool)); + + return svn_opt_print_help3(os, + "svn", /* ### erm, derive somehow? */ + opt_state ? opt_state->version : FALSE, + opt_state ? opt_state->quiet : FALSE, + version_footer->data, + help_header, /* already gettext()'d */ + svn_cl__cmd_table, + svn_cl__options, + svn_cl__global_options, + _(help_footer), + pool); +} diff --git a/subversion/svn/import-cmd.c b/subversion/svn/import-cmd.c new file mode 100644 index 0000000..f795092 --- /dev/null +++ b/subversion/svn/import-cmd.c @@ -0,0 +1,130 @@ +/* + * import-cmd.c -- Import a file or tree into the repository. + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + +/* ==================================================================== */ + + + +/*** Includes. ***/ + +#include "svn_client.h" +#include "svn_path.h" +#include "svn_error.h" +#include "cl.h" + +#include "svn_private_config.h" + + +/*** Code. ***/ + +/* This implements the `svn_opt_subcommand_t' interface. */ +svn_error_t * +svn_cl__import(apr_getopt_t *os, + void *baton, + apr_pool_t *pool) +{ + svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state; + svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx; + apr_array_header_t *targets; + const char *path; + const char *url; + + /* Import takes two arguments, for example + * + * $ svn import projects/test file:///home/jrandom/repos/trunk + * ^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + * (source) (repository) + * + * or + * + * $ svn import file:///home/jrandom/repos/some/subdir + * + * What is the nicest behavior for import, from the user's point of + * view? This is a subtle question. Seemingly intuitive answers + * can lead to weird situations, such never being able to create + * non-directories in the top-level of the repository. + * + * If 'source' is a file then the basename of 'url' is used as the + * filename in the repository. If 'source' is a directory then the + * import happens directly in the repository target dir, creating + * however many new entries are necessary. If some part of 'url' + * does not exist in the repository then parent directories are created + * as necessary. + * + * In the case where no 'source' is given '.' (the current directory) + * is implied. + * + * ### kff todo: review above behaviors. + */ + + SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os, + opt_state->targets, + ctx, FALSE, pool)); + + if (targets->nelts < 1) + return svn_error_create + (SVN_ERR_CL_INSUFFICIENT_ARGS, NULL, + _("Repository URL required when importing")); + else if (targets->nelts > 2) + return svn_error_create + (SVN_ERR_CL_ARG_PARSING_ERROR, NULL, + _("Too many arguments to import command")); + else if (targets->nelts == 1) + { + url = APR_ARRAY_IDX(targets, 0, const char *); + path = ""; + } + else + { + path = APR_ARRAY_IDX(targets, 0, const char *); + url = APR_ARRAY_IDX(targets, 1, const char *); + } + + SVN_ERR(svn_cl__check_target_is_local_path(path)); + + if (! svn_path_is_url(url)) + return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, + _("Invalid URL '%s'"), url); + + if (opt_state->depth == svn_depth_unknown) + opt_state->depth = svn_depth_infinity; + + SVN_ERR(svn_cl__make_log_msg_baton(&(ctx->log_msg_baton3), opt_state, + NULL, ctx->config, pool)); + + SVN_ERR(svn_cl__cleanup_log_msg + (ctx->log_msg_baton3, + svn_client_import4(path, + url, + opt_state->depth, + opt_state->no_ignore, + opt_state->force, + opt_state->revprop_table, + (opt_state->quiet + ? NULL : svn_cl__print_commit_info), + NULL, + ctx, + pool), pool)); + + return SVN_NO_ERROR; +} diff --git a/subversion/svn/info-cmd.c b/subversion/svn/info-cmd.c new file mode 100644 index 0000000..14d0db6 --- /dev/null +++ b/subversion/svn/info-cmd.c @@ -0,0 +1,635 @@ +/* + * info-cmd.c -- Display information about a resource + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + +/* ==================================================================== */ + + + +/*** Includes. ***/ + +#include "svn_string.h" +#include "svn_cmdline.h" +#include "svn_wc.h" +#include "svn_pools.h" +#include "svn_error_codes.h" +#include "svn_error.h" +#include "svn_dirent_uri.h" +#include "svn_path.h" +#include "svn_time.h" +#include "svn_xml.h" +#include "cl.h" + +#include "svn_private_config.h" +#include "tree-conflicts.h" + + +/*** Code. ***/ + +static svn_error_t * +svn_cl__info_print_time(apr_time_t atime, + const char *desc, + apr_pool_t *pool) +{ + const char *time_utf8; + + time_utf8 = svn_time_to_human_cstring(atime, pool); + return svn_cmdline_printf(pool, "%s: %s\n", desc, time_utf8); +} + + +/* Return string representation of SCHEDULE */ +static const char * +schedule_str(svn_wc_schedule_t schedule) +{ + switch (schedule) + { + case svn_wc_schedule_normal: + return "normal"; + case svn_wc_schedule_add: + return "add"; + case svn_wc_schedule_delete: + return "delete"; + case svn_wc_schedule_replace: + return "replace"; + default: + return "none"; + } +} + + +/* A callback of type svn_client_info_receiver2_t. + Prints svn info in xml mode to standard out */ +static svn_error_t * +print_info_xml(void *baton, + const char *target, + const svn_client_info2_t *info, + apr_pool_t *pool) +{ + svn_stringbuf_t *sb = svn_stringbuf_create("", pool); + const char *rev_str; + const char *path_prefix = baton; + + if (SVN_IS_VALID_REVNUM(info->rev)) + rev_str = apr_psprintf(pool, "%ld", info->rev); + else + rev_str = apr_pstrdup(pool, _("Resource is not under version control.")); + + /* "<entry ...>" */ + svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "entry", + "path", svn_cl__local_style_skip_ancestor( + path_prefix, target, pool), + "kind", svn_cl__node_kind_str_xml(info->kind), + "revision", rev_str, + NULL); + + svn_cl__xml_tagged_cdata(&sb, pool, "url", info->URL); + + if (info->repos_root_URL || info->repos_UUID) + { + /* "<repository>" */ + svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "repository", NULL); + + /* "<root> xx </root>" */ + svn_cl__xml_tagged_cdata(&sb, pool, "root", info->repos_root_URL); + + /* "<uuid> xx </uuid>" */ + svn_cl__xml_tagged_cdata(&sb, pool, "uuid", info->repos_UUID); + + /* "</repository>" */ + svn_xml_make_close_tag(&sb, pool, "repository"); + } + + if (info->wc_info) + { + /* "<wc-info>" */ + svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "wc-info", NULL); + + /* "<wcroot-abspath> xx </wcroot-abspath>" */ + if (info->wc_info->wcroot_abspath) + svn_cl__xml_tagged_cdata(&sb, pool, "wcroot-abspath", + info->wc_info->wcroot_abspath); + + /* "<schedule> xx </schedule>" */ + svn_cl__xml_tagged_cdata(&sb, pool, "schedule", + schedule_str(info->wc_info->schedule)); + + /* "<depth> xx </depth>" */ + { + svn_depth_t depth = info->wc_info->depth; + + /* In the entries world info just passed depth infinity for files */ + if (depth == svn_depth_unknown && info->kind == svn_node_file) + depth = svn_depth_infinity; + + svn_cl__xml_tagged_cdata(&sb, pool, "depth", svn_depth_to_word(depth)); + } + + /* "<copy-from-url> xx </copy-from-url>" */ + svn_cl__xml_tagged_cdata(&sb, pool, "copy-from-url", + info->wc_info->copyfrom_url); + + /* "<copy-from-rev> xx </copy-from-rev>" */ + if (SVN_IS_VALID_REVNUM(info->wc_info->copyfrom_rev)) + svn_cl__xml_tagged_cdata(&sb, pool, "copy-from-rev", + apr_psprintf(pool, "%ld", + info->wc_info->copyfrom_rev)); + + /* "<text-updated> xx </text-updated>" */ + if (info->wc_info->recorded_time) + svn_cl__xml_tagged_cdata(&sb, pool, "text-updated", + svn_time_to_cstring( + info->wc_info->recorded_time, + pool)); + + /* "<checksum> xx </checksum>" */ + /* ### Print the checksum kind. */ + svn_cl__xml_tagged_cdata(&sb, pool, "checksum", + svn_checksum_to_cstring(info->wc_info->checksum, + pool)); + + if (info->wc_info->changelist) + /* "<changelist> xx </changelist>" */ + svn_cl__xml_tagged_cdata(&sb, pool, "changelist", + info->wc_info->changelist); + + /* "</wc-info>" */ + svn_xml_make_close_tag(&sb, pool, "wc-info"); + } + + if (info->last_changed_author + || SVN_IS_VALID_REVNUM(info->last_changed_rev) + || info->last_changed_date) + { + svn_cl__print_xml_commit(&sb, info->last_changed_rev, + info->last_changed_author, + svn_time_to_cstring(info->last_changed_date, + pool), + pool); + } + + if (info->wc_info && info->wc_info->conflicts) + { + int i; + + for (i = 0; i < info->wc_info->conflicts->nelts; i++) + { + const svn_wc_conflict_description2_t *conflict = + APR_ARRAY_IDX(info->wc_info->conflicts, i, + const svn_wc_conflict_description2_t *); + + switch (conflict->kind) + { + case svn_wc_conflict_kind_text: + /* "<conflict>" */ + svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "conflict", + NULL); + + /* "<prev-base-file> xx </prev-base-file>" */ + svn_cl__xml_tagged_cdata(&sb, pool, "prev-base-file", + conflict->base_abspath); + + /* "<prev-wc-file> xx </prev-wc-file>" */ + svn_cl__xml_tagged_cdata(&sb, pool, "prev-wc-file", + conflict->my_abspath); + + /* "<cur-base-file> xx </cur-base-file>" */ + svn_cl__xml_tagged_cdata(&sb, pool, "cur-base-file", + conflict->their_abspath); + + /* "</conflict>" */ + svn_xml_make_close_tag(&sb, pool, "conflict"); + break; + + case svn_wc_conflict_kind_property: + /* "<conflict>" */ + svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "conflict", + NULL); + + /* "<prop-file> xx </prop-file>" */ + svn_cl__xml_tagged_cdata(&sb, pool, "prop-file", + conflict->their_abspath); + + /* "</conflict>" */ + svn_xml_make_close_tag(&sb, pool, "conflict"); + break; + + case svn_wc_conflict_kind_tree: + SVN_ERR(svn_cl__append_tree_conflict_info_xml(sb, conflict, + pool)); + break; + } + } + } + + if (info->lock) + svn_cl__print_xml_lock(&sb, info->lock, pool); + + /* "</entry>" */ + svn_xml_make_close_tag(&sb, pool, "entry"); + + return svn_cl__error_checked_fputs(sb->data, stdout); +} + + +/* A callback of type svn_client_info_receiver2_t. */ +static svn_error_t * +print_info(void *baton, + const char *target, + const svn_client_info2_t *info, + apr_pool_t *pool) +{ + const char *path_prefix = baton; + + SVN_ERR(svn_cmdline_printf(pool, _("Path: %s\n"), + svn_cl__local_style_skip_ancestor( + path_prefix, target, pool))); + + /* ### remove this someday: it's only here for cmdline output + compatibility with svn 1.1 and older. */ + if (info->kind != svn_node_dir) + SVN_ERR(svn_cmdline_printf(pool, _("Name: %s\n"), + svn_dirent_basename(target, pool))); + + if (info->wc_info && info->wc_info->wcroot_abspath) + SVN_ERR(svn_cmdline_printf(pool, _("Working Copy Root Path: %s\n"), + svn_dirent_local_style( + info->wc_info->wcroot_abspath, + pool))); + + if (info->URL) + SVN_ERR(svn_cmdline_printf(pool, _("URL: %s\n"), info->URL)); + + if (info->repos_root_URL) + SVN_ERR(svn_cmdline_printf(pool, _("Repository Root: %s\n"), + info->repos_root_URL)); + + if (info->repos_UUID) + SVN_ERR(svn_cmdline_printf(pool, _("Repository UUID: %s\n"), + info->repos_UUID)); + + if (SVN_IS_VALID_REVNUM(info->rev)) + SVN_ERR(svn_cmdline_printf(pool, _("Revision: %ld\n"), info->rev)); + + switch (info->kind) + { + case svn_node_file: + SVN_ERR(svn_cmdline_printf(pool, _("Node Kind: file\n"))); + break; + + case svn_node_dir: + SVN_ERR(svn_cmdline_printf(pool, _("Node Kind: directory\n"))); + break; + + case svn_node_none: + SVN_ERR(svn_cmdline_printf(pool, _("Node Kind: none\n"))); + break; + + case svn_node_unknown: + default: + SVN_ERR(svn_cmdline_printf(pool, _("Node Kind: unknown\n"))); + break; + } + + if (info->wc_info) + { + switch (info->wc_info->schedule) + { + case svn_wc_schedule_normal: + SVN_ERR(svn_cmdline_printf(pool, _("Schedule: normal\n"))); + break; + + case svn_wc_schedule_add: + SVN_ERR(svn_cmdline_printf(pool, _("Schedule: add\n"))); + break; + + case svn_wc_schedule_delete: + SVN_ERR(svn_cmdline_printf(pool, _("Schedule: delete\n"))); + break; + + case svn_wc_schedule_replace: + SVN_ERR(svn_cmdline_printf(pool, _("Schedule: replace\n"))); + break; + + default: + break; + } + + switch (info->wc_info->depth) + { + case svn_depth_unknown: + /* Unknown depth is the norm for remote directories anyway + (although infinity would be equally appropriate). Let's + not bother to print it. */ + break; + + case svn_depth_empty: + SVN_ERR(svn_cmdline_printf(pool, _("Depth: empty\n"))); + break; + + case svn_depth_files: + SVN_ERR(svn_cmdline_printf(pool, _("Depth: files\n"))); + break; + + case svn_depth_immediates: + SVN_ERR(svn_cmdline_printf(pool, _("Depth: immediates\n"))); + break; + + case svn_depth_exclude: + SVN_ERR(svn_cmdline_printf(pool, _("Depth: exclude\n"))); + break; + + case svn_depth_infinity: + /* Infinity is the default depth for working copy + directories. Let's not print it, it's not special enough + to be worth mentioning. */ + break; + + default: + /* Other depths should never happen here. */ + SVN_ERR(svn_cmdline_printf(pool, _("Depth: INVALID\n"))); + } + + if (info->wc_info->copyfrom_url) + SVN_ERR(svn_cmdline_printf(pool, _("Copied From URL: %s\n"), + info->wc_info->copyfrom_url)); + + if (SVN_IS_VALID_REVNUM(info->wc_info->copyfrom_rev)) + SVN_ERR(svn_cmdline_printf(pool, _("Copied From Rev: %ld\n"), + info->wc_info->copyfrom_rev)); + } + + if (info->last_changed_author) + SVN_ERR(svn_cmdline_printf(pool, _("Last Changed Author: %s\n"), + info->last_changed_author)); + + if (SVN_IS_VALID_REVNUM(info->last_changed_rev)) + SVN_ERR(svn_cmdline_printf(pool, _("Last Changed Rev: %ld\n"), + info->last_changed_rev)); + + if (info->last_changed_date) + SVN_ERR(svn_cl__info_print_time(info->last_changed_date, + _("Last Changed Date"), pool)); + + if (info->wc_info) + { + if (info->wc_info->recorded_time) + SVN_ERR(svn_cl__info_print_time(info->wc_info->recorded_time, + _("Text Last Updated"), pool)); + + if (info->wc_info->checksum) + SVN_ERR(svn_cmdline_printf(pool, _("Checksum: %s\n"), + svn_checksum_to_cstring( + info->wc_info->checksum, pool))); + + if (info->wc_info->conflicts) + { + int i; + + for (i = 0; i < info->wc_info->conflicts->nelts; i++) + { + const svn_wc_conflict_description2_t *conflict = + APR_ARRAY_IDX(info->wc_info->conflicts, i, + const svn_wc_conflict_description2_t *); + const char *desc; + const char *src_left_version; + const char *src_right_version; + + switch (conflict->kind) + { + case svn_wc_conflict_kind_text: + if (conflict->base_abspath) + SVN_ERR(svn_cmdline_printf(pool, + _("Conflict Previous Base File: %s\n"), + svn_cl__local_style_skip_ancestor( + path_prefix, conflict->base_abspath, + pool))); + + if (conflict->my_abspath) + SVN_ERR(svn_cmdline_printf(pool, + _("Conflict Previous Working File: %s\n"), + svn_cl__local_style_skip_ancestor( + path_prefix, conflict->my_abspath, + pool))); + + if (conflict->their_abspath) + SVN_ERR(svn_cmdline_printf(pool, + _("Conflict Current Base File: %s\n"), + svn_cl__local_style_skip_ancestor( + path_prefix, conflict->their_abspath, + pool))); + break; + + case svn_wc_conflict_kind_property: + SVN_ERR(svn_cmdline_printf(pool, + _("Conflict Properties File: %s\n"), + svn_dirent_local_style(conflict->their_abspath, + pool))); + break; + + case svn_wc_conflict_kind_tree: + SVN_ERR( + svn_cl__get_human_readable_tree_conflict_description( + &desc, conflict, pool)); + + src_left_version = + svn_cl__node_description(conflict->src_left_version, + info->repos_root_URL, pool); + + src_right_version = + svn_cl__node_description(conflict->src_right_version, + info->repos_root_URL, pool); + + SVN_ERR(svn_cmdline_printf(pool, "%s: %s\n", + _("Tree conflict"), desc)); + + if (src_left_version) + SVN_ERR(svn_cmdline_printf(pool, " %s: %s\n", + _("Source left"), /* (1) */ + src_left_version)); + /* (1): Sneaking in a space in "Source left" so that + * it is the same length as "Source right" while it still + * starts in the same column. That's just a tiny tweak in + * the English `svn'. */ + + if (src_right_version) + SVN_ERR(svn_cmdline_printf(pool, " %s: %s\n", + _("Source right"), + src_right_version)); + break; + } + } + } + } + + if (info->lock) + { + if (info->lock->token) + SVN_ERR(svn_cmdline_printf(pool, _("Lock Token: %s\n"), + info->lock->token)); + + if (info->lock->owner) + SVN_ERR(svn_cmdline_printf(pool, _("Lock Owner: %s\n"), + info->lock->owner)); + + if (info->lock->creation_date) + SVN_ERR(svn_cl__info_print_time(info->lock->creation_date, + _("Lock Created"), pool)); + + if (info->lock->expiration_date) + SVN_ERR(svn_cl__info_print_time(info->lock->expiration_date, + _("Lock Expires"), pool)); + + if (info->lock->comment) + { + int comment_lines; + /* NOTE: The stdio will handle newline translation. */ + comment_lines = svn_cstring_count_newlines(info->lock->comment) + 1; + SVN_ERR(svn_cmdline_printf(pool, + Q_("Lock Comment (%i line):\n%s\n", + "Lock Comment (%i lines):\n%s\n", + comment_lines), + comment_lines, + info->lock->comment)); + } + } + + if (info->wc_info && info->wc_info->changelist) + SVN_ERR(svn_cmdline_printf(pool, _("Changelist: %s\n"), + info->wc_info->changelist)); + + /* Print extra newline separator. */ + return svn_cmdline_printf(pool, "\n"); +} + + +/* This implements the `svn_opt_subcommand_t' interface. */ +svn_error_t * +svn_cl__info(apr_getopt_t *os, + void *baton, + apr_pool_t *pool) +{ + svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state; + svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx; + apr_array_header_t *targets = NULL; + apr_pool_t *subpool = svn_pool_create(pool); + int i; + svn_error_t *err; + svn_boolean_t seen_nonexistent_target = FALSE; + svn_opt_revision_t peg_revision; + svn_client_info_receiver2_t receiver; + const char *path_prefix; + + SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os, + opt_state->targets, + ctx, FALSE, pool)); + + /* Add "." if user passed 0 arguments. */ + svn_opt_push_implicit_dot_target(targets, pool); + + if (opt_state->xml) + { + receiver = print_info_xml; + + /* If output is not incremental, output the XML header and wrap + everything in a top-level element. This makes the output in + its entirety a well-formed XML document. */ + if (! opt_state->incremental) + SVN_ERR(svn_cl__xml_print_header("info", pool)); + } + else + { + receiver = print_info; + + if (opt_state->incremental) + return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, + _("'incremental' option only valid in XML " + "mode")); + } + + if (opt_state->depth == svn_depth_unknown) + opt_state->depth = svn_depth_empty; + + SVN_ERR(svn_dirent_get_absolute(&path_prefix, "", pool)); + + for (i = 0; i < targets->nelts; i++) + { + const char *truepath; + const char *target = APR_ARRAY_IDX(targets, i, const char *); + + svn_pool_clear(subpool); + SVN_ERR(svn_cl__check_cancel(ctx->cancel_baton)); + + /* Get peg revisions. */ + SVN_ERR(svn_opt_parse_path(&peg_revision, &truepath, target, subpool)); + + /* If no peg-rev was attached to a URL target, then assume HEAD. */ + if (svn_path_is_url(truepath)) + { + if (peg_revision.kind == svn_opt_revision_unspecified) + peg_revision.kind = svn_opt_revision_head; + } + else + { + SVN_ERR(svn_dirent_get_absolute(&truepath, truepath, subpool)); + } + + err = svn_client_info3(truepath, + &peg_revision, &(opt_state->start_revision), + opt_state->depth, TRUE, TRUE, + opt_state->changelists, + receiver, (void *) path_prefix, + ctx, subpool); + + if (err) + { + /* If one of the targets is a non-existent URL or wc-entry, + don't bail out. Just warn and move on to the next target. */ + if (err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND || + err->apr_err == SVN_ERR_RA_ILLEGAL_URL) + { + svn_handle_warning2(stderr, err, "svn: "); + svn_error_clear(svn_cmdline_fprintf(stderr, subpool, "\n")); + } + else + { + return svn_error_trace(err); + } + + svn_error_clear(err); + err = NULL; + seen_nonexistent_target = TRUE; + } + } + svn_pool_destroy(subpool); + + if (opt_state->xml && (! opt_state->incremental)) + SVN_ERR(svn_cl__xml_print_footer("info", pool)); + + if (seen_nonexistent_target) + return svn_error_create( + SVN_ERR_ILLEGAL_TARGET, NULL, + _("Could not display info for all targets because some " + "targets don't exist")); + else + return SVN_NO_ERROR; +} diff --git a/subversion/svn/list-cmd.c b/subversion/svn/list-cmd.c new file mode 100644 index 0000000..7bc5183 --- /dev/null +++ b/subversion/svn/list-cmd.c @@ -0,0 +1,328 @@ +/* + * list-cmd.c -- list a URL + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + +#include "svn_cmdline.h" +#include "svn_client.h" +#include "svn_error.h" +#include "svn_pools.h" +#include "svn_time.h" +#include "svn_xml.h" +#include "svn_dirent_uri.h" +#include "svn_path.h" +#include "svn_utf.h" +#include "svn_opt.h" + +#include "cl.h" + +#include "svn_private_config.h" + + + +/* Baton used when printing directory entries. */ +struct print_baton { + svn_boolean_t verbose; + svn_client_ctx_t *ctx; +}; + +/* This implements the svn_client_list_func_t API, printing a single + directory entry in text format. */ +static svn_error_t * +print_dirent(void *baton, + const char *path, + const svn_dirent_t *dirent, + const svn_lock_t *lock, + const char *abs_path, + apr_pool_t *pool) +{ + struct print_baton *pb = baton; + const char *entryname; + + if (pb->ctx->cancel_func) + SVN_ERR(pb->ctx->cancel_func(pb->ctx->cancel_baton)); + + if (strcmp(path, "") == 0) + { + if (dirent->kind == svn_node_file) + entryname = svn_dirent_basename(abs_path, pool); + else if (pb->verbose) + entryname = "."; + else + /* Don't bother to list if no useful information will be shown. */ + return SVN_NO_ERROR; + } + else + entryname = path; + + if (pb->verbose) + { + apr_time_t now = apr_time_now(); + apr_time_exp_t exp_time; + apr_status_t apr_err; + apr_size_t size; + char timestr[20]; + const char *sizestr, *utf8_timestr; + + /* svn_time_to_human_cstring gives us something *way* too long + to use for this, so we have to roll our own. We include + the year if the entry's time is not within half a year. */ + apr_time_exp_lt(&exp_time, dirent->time); + if (apr_time_sec(now - dirent->time) < (365 * 86400 / 2) + && apr_time_sec(dirent->time - now) < (365 * 86400 / 2)) + { + apr_err = apr_strftime(timestr, &size, sizeof(timestr), + _("%b %d %H:%M"), &exp_time); + } + else + { + apr_err = apr_strftime(timestr, &size, sizeof(timestr), + _("%b %d %Y"), &exp_time); + } + + /* if that failed, just zero out the string and print nothing */ + if (apr_err) + timestr[0] = '\0'; + + /* we need it in UTF-8. */ + SVN_ERR(svn_utf_cstring_to_utf8(&utf8_timestr, timestr, pool)); + + sizestr = apr_psprintf(pool, "%" SVN_FILESIZE_T_FMT, dirent->size); + + return svn_cmdline_printf + (pool, "%7ld %-8.8s %c %10s %12s %s%s\n", + dirent->created_rev, + dirent->last_author ? dirent->last_author : " ? ", + lock ? 'O' : ' ', + (dirent->kind == svn_node_file) ? sizestr : "", + utf8_timestr, + entryname, + (dirent->kind == svn_node_dir) ? "/" : ""); + } + else + { + return svn_cmdline_printf(pool, "%s%s\n", entryname, + (dirent->kind == svn_node_dir) + ? "/" : ""); + } +} + + +/* This implements the svn_client_list_func_t API, printing a single dirent + in XML format. */ +static svn_error_t * +print_dirent_xml(void *baton, + const char *path, + const svn_dirent_t *dirent, + const svn_lock_t *lock, + const char *abs_path, + apr_pool_t *pool) +{ + struct print_baton *pb = baton; + const char *entryname; + svn_stringbuf_t *sb; + + if (strcmp(path, "") == 0) + { + if (dirent->kind == svn_node_file) + entryname = svn_dirent_basename(abs_path, pool); + else if (pb->verbose) + entryname = "."; + else + /* Don't bother to list if no useful information will be shown. */ + return SVN_NO_ERROR; + } + else + entryname = path; + + if (pb->ctx->cancel_func) + SVN_ERR(pb->ctx->cancel_func(pb->ctx->cancel_baton)); + + sb = svn_stringbuf_create("", pool); + + svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "entry", + "kind", svn_cl__node_kind_str_xml(dirent->kind), + NULL); + + svn_cl__xml_tagged_cdata(&sb, pool, "name", entryname); + + if (dirent->kind == svn_node_file) + { + svn_cl__xml_tagged_cdata + (&sb, pool, "size", + apr_psprintf(pool, "%" SVN_FILESIZE_T_FMT, dirent->size)); + } + + svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "commit", + "revision", + apr_psprintf(pool, "%ld", dirent->created_rev), + NULL); + svn_cl__xml_tagged_cdata(&sb, pool, "author", dirent->last_author); + if (dirent->time) + svn_cl__xml_tagged_cdata(&sb, pool, "date", + svn_time_to_cstring(dirent->time, pool)); + svn_xml_make_close_tag(&sb, pool, "commit"); + + if (lock) + { + svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "lock", NULL); + svn_cl__xml_tagged_cdata(&sb, pool, "token", lock->token); + svn_cl__xml_tagged_cdata(&sb, pool, "owner", lock->owner); + svn_cl__xml_tagged_cdata(&sb, pool, "comment", lock->comment); + svn_cl__xml_tagged_cdata(&sb, pool, "created", + svn_time_to_cstring(lock->creation_date, + pool)); + if (lock->expiration_date != 0) + svn_cl__xml_tagged_cdata(&sb, pool, "expires", + svn_time_to_cstring + (lock->expiration_date, pool)); + svn_xml_make_close_tag(&sb, pool, "lock"); + } + + svn_xml_make_close_tag(&sb, pool, "entry"); + + return svn_cl__error_checked_fputs(sb->data, stdout); +} + + +/* This implements the `svn_opt_subcommand_t' interface. */ +svn_error_t * +svn_cl__list(apr_getopt_t *os, + void *baton, + apr_pool_t *pool) +{ + svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state; + svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx; + apr_array_header_t *targets; + int i; + apr_pool_t *subpool = svn_pool_create(pool); + apr_uint32_t dirent_fields; + struct print_baton pb; + svn_boolean_t seen_nonexistent_target = FALSE; + svn_error_t *err; + + SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os, + opt_state->targets, + ctx, FALSE, pool)); + + /* Add "." if user passed 0 arguments */ + svn_opt_push_implicit_dot_target(targets, pool); + + if (opt_state->xml) + { + /* The XML output contains all the information, so "--verbose" + does not apply. */ + if (opt_state->verbose) + return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, + _("'verbose' option invalid in XML mode")); + + /* If output is not incremental, output the XML header and wrap + everything in a top-level element. This makes the output in + its entirety a well-formed XML document. */ + if (! opt_state->incremental) + SVN_ERR(svn_cl__xml_print_header("lists", pool)); + } + else + { + if (opt_state->incremental) + return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, + _("'incremental' option only valid in XML " + "mode")); + } + + if (opt_state->verbose || opt_state->xml) + dirent_fields = SVN_DIRENT_ALL; + else + dirent_fields = SVN_DIRENT_KIND; /* the only thing we actually need... */ + + pb.ctx = ctx; + pb.verbose = opt_state->verbose; + + if (opt_state->depth == svn_depth_unknown) + opt_state->depth = svn_depth_immediates; + + /* For each target, try to list it. */ + for (i = 0; i < targets->nelts; i++) + { + const char *target = APR_ARRAY_IDX(targets, i, const char *); + const char *truepath; + svn_opt_revision_t peg_revision; + + svn_pool_clear(subpool); + + SVN_ERR(svn_cl__check_cancel(ctx->cancel_baton)); + + /* Get peg revisions. */ + SVN_ERR(svn_opt_parse_path(&peg_revision, &truepath, target, + subpool)); + + if (opt_state->xml) + { + svn_stringbuf_t *sb = svn_stringbuf_create("", pool); + svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "list", + "path", truepath[0] == '\0' ? "." : truepath, + NULL); + SVN_ERR(svn_cl__error_checked_fputs(sb->data, stdout)); + } + + err = svn_client_list2(truepath, &peg_revision, + &(opt_state->start_revision), + opt_state->depth, + dirent_fields, + (opt_state->xml || opt_state->verbose), + opt_state->xml ? print_dirent_xml : print_dirent, + &pb, ctx, subpool); + + if (err) + { + /* If one of the targets is a non-existent URL or wc-entry, + don't bail out. Just warn and move on to the next target. */ + if (err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND || + err->apr_err == SVN_ERR_FS_NOT_FOUND) + svn_handle_warning2(stderr, err, "svn: "); + else + return svn_error_trace(err); + + svn_error_clear(err); + err = NULL; + seen_nonexistent_target = TRUE; + } + + if (opt_state->xml) + { + svn_stringbuf_t *sb = svn_stringbuf_create("", pool); + svn_xml_make_close_tag(&sb, pool, "list"); + SVN_ERR(svn_cl__error_checked_fputs(sb->data, stdout)); + } + } + + svn_pool_destroy(subpool); + + if (opt_state->xml && ! opt_state->incremental) + SVN_ERR(svn_cl__xml_print_footer("lists", pool)); + + if (seen_nonexistent_target) + return svn_error_create( + SVN_ERR_ILLEGAL_TARGET, NULL, + _("Could not list all targets because some targets don't exist")); + else + return SVN_NO_ERROR; +} diff --git a/subversion/svn/lock-cmd.c b/subversion/svn/lock-cmd.c new file mode 100644 index 0000000..c2795da --- /dev/null +++ b/subversion/svn/lock-cmd.c @@ -0,0 +1,110 @@ +/* + * lock-cmd.c -- LOck a working copy path in the repository. + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + +/* ==================================================================== */ + + + +/*** Includes. ***/ + +#include "svn_pools.h" +#include "svn_client.h" +#include "svn_subst.h" +#include "svn_path.h" +#include "svn_error_codes.h" +#include "svn_error.h" +#include "svn_cmdline.h" +#include "cl.h" +#include "svn_private_config.h" + + +/*** Code. ***/ + +/* Get a lock comment, allocate it in POOL and store it in *COMMENT. */ +static svn_error_t * +get_comment(const char **comment, svn_client_ctx_t *ctx, + svn_cl__opt_state_t *opt_state, apr_pool_t *pool) +{ + svn_string_t *comment_string; + + if (opt_state->filedata) + { + /* Get it from the -F argument. */ + if (strlen(opt_state->filedata->data) < opt_state->filedata->len) + { + /* A message containing a zero byte can't be represented as a C + string. */ + return svn_error_create(SVN_ERR_CL_BAD_LOG_MESSAGE, NULL, + _("Lock comment contains a zero byte")); + } + comment_string = svn_string_create(opt_state->filedata->data, pool); + + } + else if (opt_state->message) + { + /* Get if from the -m option. */ + comment_string = svn_string_create(opt_state->message, pool); + } + else + { + *comment = NULL; + return SVN_NO_ERROR; + } + + /* Translate to UTF8/LF. */ + SVN_ERR(svn_subst_translate_string2(&comment_string, NULL, NULL, + comment_string, opt_state->encoding, + FALSE, pool, pool)); + *comment = comment_string->data; + + return SVN_NO_ERROR; +} + +/* This implements the `svn_opt_subcommand_t' interface. */ +svn_error_t * +svn_cl__lock(apr_getopt_t *os, + void *baton, + apr_pool_t *pool) +{ + svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state; + svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx; + apr_array_header_t *targets; + const char *comment; + + SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os, + opt_state->targets, + ctx, FALSE, pool)); + + /* We only support locking files, so '.' is not valid. */ + if (! targets->nelts) + return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0, NULL); + + SVN_ERR(svn_cl__assert_homogeneous_target_type(targets)); + + /* Get comment. */ + SVN_ERR(get_comment(&comment, ctx, opt_state, pool)); + + SVN_ERR(svn_cl__eat_peg_revisions(&targets, targets, pool)); + + return svn_client_lock(targets, comment, opt_state->force, ctx, pool); +} diff --git a/subversion/svn/log-cmd.c b/subversion/svn/log-cmd.c new file mode 100644 index 0000000..4057b0a --- /dev/null +++ b/subversion/svn/log-cmd.c @@ -0,0 +1,706 @@ +/* + * log-cmd.c -- Display log messages + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + +#define APR_WANT_STRFUNC +#define APR_WANT_STDIO +#include <apr_want.h> + +#include "svn_client.h" +#include "svn_compat.h" +#include "svn_dirent_uri.h" +#include "svn_string.h" +#include "svn_path.h" +#include "svn_error.h" +#include "svn_sorts.h" +#include "svn_xml.h" +#include "svn_time.h" +#include "svn_cmdline.h" +#include "svn_props.h" +#include "svn_pools.h" + +#include "cl.h" + +#include "svn_private_config.h" + + +/*** Code. ***/ + +/* Baton for log_entry_receiver() and log_entry_receiver_xml(). */ +struct log_receiver_baton +{ + /* Client context. */ + svn_client_ctx_t *ctx; + + /* The target of the log operation. */ + const char *target_path_or_url; + svn_opt_revision_t target_peg_revision; + + /* Don't print log message body nor its line count. */ + svn_boolean_t omit_log_message; + + /* Whether to show diffs in the log. (maps to --diff) */ + svn_boolean_t show_diff; + + /* Depth applied to diff output. */ + svn_depth_t depth; + + /* Diff arguments received from command line. */ + const char *diff_extensions; + + /* Stack which keeps track of merge revision nesting, using svn_revnum_t's */ + apr_array_header_t *merge_stack; + + /* Pool for persistent allocations. */ + apr_pool_t *pool; +}; + + +/* The separator between log messages. */ +#define SEP_STRING \ + "------------------------------------------------------------------------\n" + + +/* Implement `svn_log_entry_receiver_t', printing the logs in + * a human-readable and machine-parseable format. + * + * BATON is of type `struct log_receiver_baton'. + * + * First, print a header line. Then if CHANGED_PATHS is non-null, + * print all affected paths in a list headed "Changed paths:\n", + * immediately following the header line. Then print a newline + * followed by the message body, unless BATON->omit_log_message is true. + * + * Here are some examples of the output: + * + * $ svn log -r1847:1846 + * ------------------------------------------------------------------------ + * rev 1847: cmpilato | Wed 1 May 2002 15:44:26 | 7 lines + * + * Fix for Issue #694. + * + * * subversion/libsvn_repos/delta.c + * (delta_files): Rework the logic in this function to only call + * send_text_deltas if there are deltas to send, and within that case, + * only use a real delta stream if the caller wants real text deltas. + * + * ------------------------------------------------------------------------ + * rev 1846: whoever | Wed 1 May 2002 15:23:41 | 1 line + * + * imagine an example log message here + * ------------------------------------------------------------------------ + * + * Or: + * + * $ svn log -r1847:1846 -v + * ------------------------------------------------------------------------ + * rev 1847: cmpilato | Wed 1 May 2002 15:44:26 | 7 lines + * Changed paths: + * M /trunk/subversion/libsvn_repos/delta.c + * + * Fix for Issue #694. + * + * * subversion/libsvn_repos/delta.c + * (delta_files): Rework the logic in this function to only call + * send_text_deltas if there are deltas to send, and within that case, + * only use a real delta stream if the caller wants real text deltas. + * + * ------------------------------------------------------------------------ + * rev 1846: whoever | Wed 1 May 2002 15:23:41 | 1 line + * Changed paths: + * M /trunk/notes/fs_dumprestore.txt + * M /trunk/subversion/libsvn_repos/dump.c + * + * imagine an example log message here + * ------------------------------------------------------------------------ + * + * Or: + * + * $ svn log -r1847:1846 -q + * ------------------------------------------------------------------------ + * rev 1847: cmpilato | Wed 1 May 2002 15:44:26 + * ------------------------------------------------------------------------ + * rev 1846: whoever | Wed 1 May 2002 15:23:41 + * ------------------------------------------------------------------------ + * + * Or: + * + * $ svn log -r1847:1846 -qv + * ------------------------------------------------------------------------ + * rev 1847: cmpilato | Wed 1 May 2002 15:44:26 + * Changed paths: + * M /trunk/subversion/libsvn_repos/delta.c + * ------------------------------------------------------------------------ + * rev 1846: whoever | Wed 1 May 2002 15:23:41 + * Changed paths: + * M /trunk/notes/fs_dumprestore.txt + * M /trunk/subversion/libsvn_repos/dump.c + * ------------------------------------------------------------------------ + * + */ +static svn_error_t * +log_entry_receiver(void *baton, + svn_log_entry_t *log_entry, + apr_pool_t *pool) +{ + struct log_receiver_baton *lb = baton; + const char *author; + const char *date; + const char *message; + + if (lb->ctx->cancel_func) + SVN_ERR(lb->ctx->cancel_func(lb->ctx->cancel_baton)); + + svn_compat_log_revprops_out(&author, &date, &message, log_entry->revprops); + + if (log_entry->revision == 0 && message == NULL) + return SVN_NO_ERROR; + + if (! SVN_IS_VALID_REVNUM(log_entry->revision)) + { + apr_array_pop(lb->merge_stack); + return SVN_NO_ERROR; + } + + /* ### See http://subversion.tigris.org/issues/show_bug.cgi?id=807 + for more on the fallback fuzzy conversions below. */ + + if (author == NULL) + author = _("(no author)"); + + if (date && date[0]) + /* Convert date to a format for humans. */ + SVN_ERR(svn_cl__time_cstring_to_human_cstring(&date, date, pool)); + else + date = _("(no date)"); + + if (! lb->omit_log_message && message == NULL) + message = ""; + + SVN_ERR(svn_cmdline_printf(pool, + SEP_STRING "r%ld | %s | %s", + log_entry->revision, author, date)); + + if (message != NULL) + { + /* Number of lines in the msg. */ + int lines = svn_cstring_count_newlines(message) + 1; + + SVN_ERR(svn_cmdline_printf(pool, + Q_(" | %d line", " | %d lines", lines), + lines)); + } + + SVN_ERR(svn_cmdline_printf(pool, "\n")); + + if (log_entry->changed_paths2) + { + apr_array_header_t *sorted_paths; + int i; + + /* Get an array of sorted hash keys. */ + sorted_paths = svn_sort__hash(log_entry->changed_paths2, + svn_sort_compare_items_as_paths, pool); + + SVN_ERR(svn_cmdline_printf(pool, + _("Changed paths:\n"))); + for (i = 0; i < sorted_paths->nelts; i++) + { + svn_sort__item_t *item = &(APR_ARRAY_IDX(sorted_paths, i, + svn_sort__item_t)); + const char *path = item->key; + svn_log_changed_path2_t *log_item + = apr_hash_get(log_entry->changed_paths2, item->key, item->klen); + const char *copy_data = ""; + + if (lb->ctx->cancel_func) + SVN_ERR(lb->ctx->cancel_func(lb->ctx->cancel_baton)); + + if (log_item->copyfrom_path + && SVN_IS_VALID_REVNUM(log_item->copyfrom_rev)) + { + copy_data + = apr_psprintf(pool, + _(" (from %s:%ld)"), + log_item->copyfrom_path, + log_item->copyfrom_rev); + } + SVN_ERR(svn_cmdline_printf(pool, " %c %s%s\n", + log_item->action, path, + copy_data)); + } + } + + if (lb->merge_stack->nelts > 0) + { + int i; + + /* Print the result of merge line */ + if (log_entry->subtractive_merge) + SVN_ERR(svn_cmdline_printf(pool, _("Reverse merged via:"))); + else + SVN_ERR(svn_cmdline_printf(pool, _("Merged via:"))); + for (i = 0; i < lb->merge_stack->nelts; i++) + { + svn_revnum_t rev = APR_ARRAY_IDX(lb->merge_stack, i, svn_revnum_t); + + SVN_ERR(svn_cmdline_printf(pool, " r%ld%c", rev, + i == lb->merge_stack->nelts - 1 ? + '\n' : ',')); + } + } + + if (message != NULL) + { + /* A blank line always precedes the log message. */ + SVN_ERR(svn_cmdline_printf(pool, "\n%s\n", message)); + } + + /* Print a diff if requested. */ + if (lb->show_diff) + { + apr_file_t *outfile; + apr_file_t *errfile; + apr_array_header_t *diff_options; + apr_status_t status; + svn_opt_revision_t start_revision; + svn_opt_revision_t end_revision; + + if ((status = apr_file_open_stdout(&outfile, pool))) + return svn_error_wrap_apr(status, _("Can't open stdout")); + if ((status = apr_file_open_stderr(&errfile, pool))) + return svn_error_wrap_apr(status, _("Can't open stderr")); + + /* Fall back to "" to get options initialized either way. */ + if (lb->diff_extensions) + diff_options = svn_cstring_split(lb->diff_extensions, " \t\n\r", + TRUE, pool); + else + diff_options = NULL; + + start_revision.kind = svn_opt_revision_number; + start_revision.value.number = log_entry->revision - 1; + end_revision.kind = svn_opt_revision_number; + end_revision.value.number = log_entry->revision; + + SVN_ERR(svn_cmdline_printf(pool, _("\n"))); + SVN_ERR(svn_cmdline_fflush(stdout)); + SVN_ERR(svn_client_diff_peg5(diff_options, + lb->target_path_or_url, + &lb->target_peg_revision, + &start_revision, &end_revision, + NULL, + lb->depth, + FALSE, /* ignore ancestry */ + TRUE, /* no diff deleted */ + FALSE, /* show copies as adds */ + FALSE, /* ignore content type */ + FALSE, /* use git diff format */ + svn_cmdline_output_encoding(pool), + outfile, + errfile, + NULL, + lb->ctx, pool)); + SVN_ERR(svn_cmdline_printf(pool, _("\n"))); + } + + SVN_ERR(svn_cmdline_fflush(stdout)); + SVN_ERR(svn_cmdline_fflush(stderr)); + + if (log_entry->has_children) + APR_ARRAY_PUSH(lb->merge_stack, svn_revnum_t) = log_entry->revision; + + return SVN_NO_ERROR; +} + + +/* This implements `svn_log_entry_receiver_t', printing the logs in XML. + * + * BATON is of type `struct log_receiver_baton'. + * + * Here is an example of the output; note that the "<log>" and + * "</log>" tags are not emitted by this function: + * + * $ svn log --xml -r 1648:1649 + * <log> + * <logentry + * revision="1648"> + * <author>david</author> + * <date>2002-04-06T16:34:51.428043Z</date> + * <msg> * packages/rpm/subversion.spec : Now requires apache 2.0.36. + * </msg> + * </logentry> + * <logentry + * revision="1649"> + * <author>cmpilato</author> + * <date>2002-04-06T17:01:28.185136Z</date> + * <msg>Fix error handling when the $EDITOR is needed but unavailable. Ah + * ... now that's *much* nicer. + * + * * subversion/clients/cmdline/util.c + * (svn_cl__edit_externally): Clean up the "no external editor" + * error message. + * (svn_cl__get_log_message): Wrap "no external editor" + * errors with helpful hints about the -m and -F options. + * + * * subversion/libsvn_client/commit.c + * (svn_client_commit): Actually capture and propagate "no external + * editor" errors.</msg> + * </logentry> + * </log> + * + */ +static svn_error_t * +log_entry_receiver_xml(void *baton, + svn_log_entry_t *log_entry, + apr_pool_t *pool) +{ + struct log_receiver_baton *lb = baton; + /* Collate whole log message into sb before printing. */ + svn_stringbuf_t *sb = svn_stringbuf_create("", pool); + char *revstr; + const char *author; + const char *date; + const char *message; + + if (lb->ctx->cancel_func) + SVN_ERR(lb->ctx->cancel_func(lb->ctx->cancel_baton)); + + svn_compat_log_revprops_out(&author, &date, &message, log_entry->revprops); + + if (author) + author = svn_xml_fuzzy_escape(author, pool); + if (date) + date = svn_xml_fuzzy_escape(date, pool); + if (message) + message = svn_xml_fuzzy_escape(message, pool); + + if (log_entry->revision == 0 && message == NULL) + return SVN_NO_ERROR; + + if (! SVN_IS_VALID_REVNUM(log_entry->revision)) + { + svn_xml_make_close_tag(&sb, pool, "logentry"); + SVN_ERR(svn_cl__error_checked_fputs(sb->data, stdout)); + apr_array_pop(lb->merge_stack); + + return SVN_NO_ERROR; + } + + revstr = apr_psprintf(pool, "%ld", log_entry->revision); + /* <logentry revision="xxx"> */ + svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "logentry", + "revision", revstr, NULL); + + /* <author>xxx</author> */ + svn_cl__xml_tagged_cdata(&sb, pool, "author", author); + + /* Print the full, uncut, date. This is machine output. */ + /* According to the docs for svn_log_entry_receiver_t, either + NULL or the empty string represents no date. Avoid outputting an + empty date element. */ + if (date && date[0] == '\0') + date = NULL; + /* <date>xxx</date> */ + svn_cl__xml_tagged_cdata(&sb, pool, "date", date); + + if (log_entry->changed_paths2) + { + apr_hash_index_t *hi; + + /* <paths> */ + svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "paths", + NULL); + + for (hi = apr_hash_first(pool, log_entry->changed_paths2); + hi != NULL; + hi = apr_hash_next(hi)) + { + const char *path = svn__apr_hash_index_key(hi); + svn_log_changed_path2_t *log_item = svn__apr_hash_index_val(hi); + char action[2]; + + action[0] = log_item->action; + action[1] = '\0'; + if (log_item->copyfrom_path + && SVN_IS_VALID_REVNUM(log_item->copyfrom_rev)) + { + /* <path action="X" copyfrom-path="xxx" copyfrom-rev="xxx"> */ + revstr = apr_psprintf(pool, "%ld", + log_item->copyfrom_rev); + svn_xml_make_open_tag(&sb, pool, svn_xml_protect_pcdata, "path", + "action", action, + "copyfrom-path", log_item->copyfrom_path, + "copyfrom-rev", revstr, + "kind", svn_cl__node_kind_str_xml( + log_item->node_kind), + "text-mods", svn_tristate__to_word( + log_item->text_modified), + "prop-mods", svn_tristate__to_word( + log_item->props_modified), + NULL); + } + else + { + /* <path action="X"> */ + svn_xml_make_open_tag(&sb, pool, svn_xml_protect_pcdata, "path", + "action", action, + "kind", svn_cl__node_kind_str_xml(log_item->node_kind), NULL); + } + /* xxx</path> */ + svn_xml_escape_cdata_cstring(&sb, path, pool); + svn_xml_make_close_tag(&sb, pool, "path"); + } + + /* </paths> */ + svn_xml_make_close_tag(&sb, pool, "paths"); + } + + if (message != NULL) + { + /* <msg>xxx</msg> */ + svn_cl__xml_tagged_cdata(&sb, pool, "msg", message); + } + + svn_compat_log_revprops_clear(log_entry->revprops); + if (log_entry->revprops && apr_hash_count(log_entry->revprops) > 0) + { + svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "revprops", NULL); + SVN_ERR(svn_cl__print_xml_prop_hash(&sb, log_entry->revprops, + FALSE, /* name_only */ + pool)); + svn_xml_make_close_tag(&sb, pool, "revprops"); + } + + if (log_entry->has_children) + APR_ARRAY_PUSH(lb->merge_stack, svn_revnum_t) = log_entry->revision; + else + svn_xml_make_close_tag(&sb, pool, "logentry"); + + return svn_cl__error_checked_fputs(sb->data, stdout); +} + + +/* This implements the `svn_opt_subcommand_t' interface. */ +svn_error_t * +svn_cl__log(apr_getopt_t *os, + void *baton, + apr_pool_t *pool) +{ + svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state; + svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx; + apr_array_header_t *targets; + struct log_receiver_baton lb; + const char *target; + int i; + apr_array_header_t *revprops; + + if (!opt_state->xml) + { + if (opt_state->all_revprops) + return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, + _("'with-all-revprops' option only valid in" + " XML mode")); + if (opt_state->no_revprops) + return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, + _("'with-no-revprops' option only valid in" + " XML mode")); + if (opt_state->revprop_table != NULL) + return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, + _("'with-revprop' option only valid in" + " XML mode")); + } + else + { + if (opt_state->show_diff) + return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, + _("'diff' option is not supported in " + "XML mode")); + } + + if (opt_state->quiet && opt_state->show_diff) + return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, + _("'quiet' and 'diff' options are " + "mutually exclusive")); + if (opt_state->diff_cmd && (! opt_state->show_diff)) + return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, + _("'diff-cmd' option requires 'diff' " + "option")); + if (opt_state->internal_diff && (! opt_state->show_diff)) + return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, + _("'internal-diff' option requires " + "'diff' option")); + if (opt_state->extensions && (! opt_state->show_diff)) + return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, + _("'extensions' option requires 'diff' " + "option")); + + if (opt_state->depth != svn_depth_unknown && (! opt_state->show_diff)) + return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, + _("'depth' option requires 'diff' option")); + + SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os, + opt_state->targets, + ctx, FALSE, pool)); + + /* Add "." if user passed 0 arguments */ + svn_opt_push_implicit_dot_target(targets, pool); + + /* Determine if they really want a two-revision range. */ + if (opt_state->used_change_arg) + { + if (opt_state->used_revision_arg && opt_state->revision_ranges->nelts > 1) + { + return svn_error_create + (SVN_ERR_CLIENT_BAD_REVISION, NULL, + _("-c and -r are mutually exclusive")); + } + for (i = 0; i < opt_state->revision_ranges->nelts; i++) + { + svn_opt_revision_range_t *range; + range = APR_ARRAY_IDX(opt_state->revision_ranges, i, + svn_opt_revision_range_t *); + if (range->start.value.number < range->end.value.number) + range->start.value.number++; + else + range->end.value.number++; + } + } + + /* Parse the first target into path-or-url and peg revision. */ + target = APR_ARRAY_IDX(targets, 0, const char *); + SVN_ERR(svn_opt_parse_path(&lb.target_peg_revision, &lb.target_path_or_url, + target, pool)); + if (lb.target_peg_revision.kind == svn_opt_revision_unspecified) + lb.target_peg_revision.kind = (svn_path_is_url(target) + ? svn_opt_revision_head + : svn_opt_revision_working); + APR_ARRAY_IDX(targets, 0, const char *) = lb.target_path_or_url; + + if (svn_path_is_url(target)) + { + for (i = 1; i < targets->nelts; i++) + { + target = APR_ARRAY_IDX(targets, i, const char *); + + if (svn_path_is_url(target) || target[0] == '/') + return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, + _("Only relative paths can be specified" + " after a URL for 'svn log', " + "but '%s' is not a relative path"), + target); + } + } + + lb.ctx = ctx; + lb.omit_log_message = opt_state->quiet; + lb.show_diff = opt_state->show_diff; + lb.depth = opt_state->depth == svn_depth_unknown ? svn_depth_infinity + : opt_state->depth; + lb.diff_extensions = opt_state->extensions; + lb.merge_stack = apr_array_make(pool, 0, sizeof(svn_revnum_t)); + lb.pool = pool; + + if (opt_state->xml) + { + /* If output is not incremental, output the XML header and wrap + everything in a top-level element. This makes the output in + its entirety a well-formed XML document. */ + if (! opt_state->incremental) + SVN_ERR(svn_cl__xml_print_header("log", pool)); + + if (opt_state->all_revprops) + revprops = NULL; + else if(opt_state->no_revprops) + { + revprops = apr_array_make(pool, 0, sizeof(char *)); + } + else if (opt_state->revprop_table != NULL) + { + apr_hash_index_t *hi; + revprops = apr_array_make(pool, + apr_hash_count(opt_state->revprop_table), + sizeof(char *)); + for (hi = apr_hash_first(pool, opt_state->revprop_table); + hi != NULL; + hi = apr_hash_next(hi)) + { + const char *property = svn__apr_hash_index_key(hi); + svn_string_t *value = svn__apr_hash_index_val(hi); + + if (value && value->data[0] != '\0') + return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, + _("cannot assign with 'with-revprop'" + " option (drop the '=')")); + APR_ARRAY_PUSH(revprops, const char *) = property; + } + } + else + { + revprops = apr_array_make(pool, 3, sizeof(char *)); + APR_ARRAY_PUSH(revprops, const char *) = SVN_PROP_REVISION_AUTHOR; + APR_ARRAY_PUSH(revprops, const char *) = SVN_PROP_REVISION_DATE; + if (!opt_state->quiet) + APR_ARRAY_PUSH(revprops, const char *) = SVN_PROP_REVISION_LOG; + } + SVN_ERR(svn_client_log5(targets, + &lb.target_peg_revision, + opt_state->revision_ranges, + opt_state->limit, + opt_state->verbose, + opt_state->stop_on_copy, + opt_state->use_merge_history, + revprops, + log_entry_receiver_xml, + &lb, + ctx, + pool)); + + if (! opt_state->incremental) + SVN_ERR(svn_cl__xml_print_footer("log", pool)); + } + else /* default output format */ + { + revprops = apr_array_make(pool, 3, sizeof(char *)); + APR_ARRAY_PUSH(revprops, const char *) = SVN_PROP_REVISION_AUTHOR; + APR_ARRAY_PUSH(revprops, const char *) = SVN_PROP_REVISION_DATE; + if (!opt_state->quiet) + APR_ARRAY_PUSH(revprops, const char *) = SVN_PROP_REVISION_LOG; + SVN_ERR(svn_client_log5(targets, + &lb.target_peg_revision, + opt_state->revision_ranges, + opt_state->limit, + opt_state->verbose, + opt_state->stop_on_copy, + opt_state->use_merge_history, + revprops, + log_entry_receiver, + &lb, + ctx, + pool)); + + if (! opt_state->incremental) + SVN_ERR(svn_cmdline_printf(pool, SEP_STRING)); + } + + return SVN_NO_ERROR; +} diff --git a/subversion/svn/main.c b/subversion/svn/main.c new file mode 100644 index 0000000..8f3a7c4 --- /dev/null +++ b/subversion/svn/main.c @@ -0,0 +1,2709 @@ +/* + * main.c: Subversion command line client. + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + +/* ==================================================================== */ + + + +/*** Includes. ***/ + +#include <string.h> +#include <assert.h> + +#include <apr_strings.h> +#include <apr_tables.h> +#include <apr_general.h> +#include <apr_signal.h> + +#include "svn_cmdline.h" +#include "svn_pools.h" +#include "svn_wc.h" +#include "svn_client.h" +#include "svn_config.h" +#include "svn_string.h" +#include "svn_dirent_uri.h" +#include "svn_path.h" +#include "svn_delta.h" +#include "svn_diff.h" +#include "svn_error.h" +#include "svn_io.h" +#include "svn_opt.h" +#include "svn_utf.h" +#include "svn_auth.h" +#include "svn_hash.h" +#include "svn_version.h" +#include "cl.h" + +#include "private/svn_wc_private.h" +#include "private/svn_cmdline_private.h" + +#include "svn_private_config.h" + + +/*** Option Processing ***/ + +/* Add an identifier here for long options that don't have a short + option. Options that have both long and short options should just + use the short option letter as identifier. */ +typedef enum svn_cl__longopt_t { + opt_ancestor_path = SVN_OPT_FIRST_LONGOPT_ID, + opt_auth_password, + opt_auth_username, + opt_autoprops, + opt_changelist, + opt_config_dir, + opt_config_options, + opt_diff_cmd, + opt_dry_run, + opt_editor_cmd, + opt_encoding, + opt_force_log, + opt_force, + opt_keep_changelists, + opt_ignore_ancestry, + opt_ignore_externals, + opt_incremental, + opt_merge_cmd, + opt_native_eol, + opt_new_cmd, + opt_no_auth_cache, + opt_no_autoprops, + opt_no_diff_deleted, + opt_no_ignore, + opt_no_unlock, + opt_non_interactive, + opt_notice_ancestry, + opt_old_cmd, + opt_record_only, + opt_relocate, + opt_remove, + opt_revprop, + opt_stop_on_copy, + opt_strict, + opt_summarize, + opt_targets, + opt_depth, + opt_set_depth, + opt_version, + opt_xml, + opt_keep_local, + opt_with_revprop, + opt_with_all_revprops, + opt_with_no_revprops, + opt_parents, + opt_accept, + opt_show_revs, + opt_reintegrate, + opt_trust_server_cert, + opt_strip, + opt_show_copies_as_adds, + opt_ignore_keywords, + opt_reverse_diff, + opt_ignore_whitespace, + opt_diff, + opt_internal_diff, + opt_use_git_diff_format, + opt_allow_mixed_revisions +} svn_cl__longopt_t; + + +/* Option codes and descriptions for the command line client. + * + * The entire list must be terminated with an entry of nulls. + */ +const apr_getopt_option_t svn_cl__options[] = +{ + {"force", opt_force, 0, N_("force operation to run")}, + {"force-log", opt_force_log, 0, + N_("force validity of log message source")}, + {"help", 'h', 0, N_("show help on a subcommand")}, + {NULL, '?', 0, N_("show help on a subcommand")}, + {"message", 'm', 1, N_("specify log message ARG")}, + {"quiet", 'q', 0, N_("print nothing, or only summary information")}, + {"recursive", 'R', 0, N_("descend recursively, same as --depth=infinity")}, + {"non-recursive", 'N', 0, N_("obsolete; try --depth=files or --depth=immediates")}, + {"change", 'c', 1, + N_("the change made by revision ARG (like -r ARG-1:ARG)\n" + " " + "If ARG is negative this is like -r ARG:ARG-1")}, + {"revision", 'r', 1, + N_("ARG (some commands also take ARG1:ARG2 range)\n" + " " + "A revision argument can be one of:\n" + " " + " NUMBER revision number\n" + " " + " '{' DATE '}' revision at start of the date\n" + " " + " 'HEAD' latest in repository\n" + " " + " 'BASE' base rev of item's working copy\n" + " " + " 'COMMITTED' last commit at or before BASE\n" + " " + " 'PREV' revision just before COMMITTED")}, + {"file", 'F', 1, N_("read log message from file ARG")}, + {"incremental", opt_incremental, 0, + N_("give output suitable for concatenation")}, + {"encoding", opt_encoding, 1, + N_("treat value as being in charset encoding ARG")}, + {"version", opt_version, 0, N_("show program version information")}, + {"verbose", 'v', 0, N_("print extra information")}, + {"show-updates", 'u', 0, N_("display update information")}, + {"username", opt_auth_username, 1, N_("specify a username ARG")}, + {"password", opt_auth_password, 1, N_("specify a password ARG")}, + {"extensions", 'x', 1, + N_("Default: '-u'. When Subversion is invoking an\n" + " " + "external diff program, ARG is simply passed along\n" + " " + "to the program. But when Subversion is using its\n" + " " + "default internal diff implementation, or when\n" + " " + "Subversion is displaying blame annotations, ARG\n" + " " + "could be any of the following:\n" + " " + " -u (--unified):\n" + " " + " Output 3 lines of unified context.\n" + " " + " -b (--ignore-space-change):\n" + " " + " Ignore changes in the amount of white space.\n" + " " + " -w (--ignore-all-space):\n" + " " + " Ignore all white space.\n" + " " + " --ignore-eol-style:\n" + " " + " Ignore changes in EOL style.\n" + " " + " -p (--show-c-function):\n" + " " + " Show C function name in diff output.")}, + {"targets", opt_targets, 1, + N_("pass contents of file ARG as additional args")}, + {"depth", opt_depth, 1, + N_("limit operation by depth ARG ('empty', 'files',\n" + " " + "'immediates', or 'infinity')")}, + {"set-depth", opt_set_depth, 1, + N_("set new working copy depth to ARG ('exclude',\n" + " " + "'empty', 'files', 'immediates', or 'infinity')")}, + {"xml", opt_xml, 0, N_("output in XML")}, + {"strict", opt_strict, 0, N_("use strict semantics")}, + {"stop-on-copy", opt_stop_on_copy, 0, + N_("do not cross copies while traversing history")}, + {"no-ignore", opt_no_ignore, 0, + N_("disregard default and svn:ignore property ignores")}, + {"no-auth-cache", opt_no_auth_cache, 0, + N_("do not cache authentication tokens")}, + {"trust-server-cert", opt_trust_server_cert, 0, + N_("accept SSL server certificates from unknown\n" + " " + "certificate authorities without prompting (but only\n" + " " + "with '--non-interactive')") }, + {"non-interactive", opt_non_interactive, 0, + N_("do no interactive prompting")}, + {"dry-run", opt_dry_run, 0, + N_("try operation but make no changes")}, + {"no-diff-deleted", opt_no_diff_deleted, 0, + N_("do not print differences for deleted files")}, + {"notice-ancestry", opt_notice_ancestry, 0, + N_("notice ancestry when calculating differences")}, + {"ignore-ancestry", opt_ignore_ancestry, 0, + N_("ignore ancestry when calculating merges")}, + {"ignore-externals", opt_ignore_externals, 0, + N_("ignore externals definitions")}, + {"diff-cmd", opt_diff_cmd, 1, N_("use ARG as diff command")}, + {"diff3-cmd", opt_merge_cmd, 1, N_("use ARG as merge command")}, + {"editor-cmd", opt_editor_cmd, 1, N_("use ARG as external editor")}, + {"record-only", opt_record_only, 0, + N_("merge only mergeinfo differences")}, + {"old", opt_old_cmd, 1, N_("use ARG as the older target")}, + {"new", opt_new_cmd, 1, N_("use ARG as the newer target")}, + {"revprop", opt_revprop, 0, + N_("operate on a revision property (use with -r)")}, + {"relocate", opt_relocate, 0, N_("relocate via URL-rewriting")}, + {"config-dir", opt_config_dir, 1, + N_("read user configuration files from directory ARG")}, + {"config-option", opt_config_options, 1, + N_("set user configuration option in the format:\n" + " " + " FILE:SECTION:OPTION=[VALUE]\n" + " " + "For example:\n" + " " + " servers:global:http-library=serf")}, + {"auto-props", opt_autoprops, 0, N_("enable automatic properties")}, + {"no-auto-props", opt_no_autoprops, 0, N_("disable automatic properties")}, + {"native-eol", opt_native_eol, 1, + N_("use a different EOL marker than the standard\n" + " " + "system marker for files with the svn:eol-style\n" + " " + "property set to 'native'.\n" + " " + "ARG may be one of 'LF', 'CR', 'CRLF'")}, + {"limit", 'l', 1, N_("maximum number of log entries")}, + {"no-unlock", opt_no_unlock, 0, N_("don't unlock the targets")}, + {"summarize", opt_summarize, 0, N_("show a summary of the results")}, + {"remove", opt_remove, 0, N_("remove changelist association")}, + {"changelist", opt_changelist, 1, + N_("operate only on members of changelist ARG")}, + {"keep-changelists", opt_keep_changelists, 0, + N_("don't delete changelists after commit")}, + {"keep-local", opt_keep_local, 0, N_("keep path in working copy")}, + {"with-all-revprops", opt_with_all_revprops, 0, + N_("retrieve all revision properties")}, + {"with-no-revprops", opt_with_no_revprops, 0, + N_("retrieve no revision properties")}, + {"with-revprop", opt_with_revprop, 1, + N_("set revision property ARG in new revision\n" + " " + "using the name[=value] format")}, + {"parents", opt_parents, 0, N_("make intermediate directories")}, + {"use-merge-history", 'g', 0, + N_("use/display additional information from merge\n" + " " + "history")}, + {"accept", opt_accept, 1, + N_("specify automatic conflict resolution action\n" + " " + "('postpone', 'working', 'base', 'mine-conflict',\n" + " " + "'theirs-conflict', 'mine-full', 'theirs-full',\n" + " " + "'edit', 'launch')\n" + " " + "(shorthand: 'p', 'mc', 'tc', 'mf', 'tf', 'e', 'l')" + )}, + {"show-revs", opt_show_revs, 1, + N_("specify which collection of revisions to display\n" + " " + "('merged', 'eligible')")}, + {"reintegrate", opt_reintegrate, 0, + N_("merge a branch back into its parent branch")}, + {"strip", opt_strip, 1, + N_("number of leading path components to strip from\n" + " " + "paths parsed from the patch file. --strip 0\n" + " " + "is the default and leaves paths unmodified.\n" + " " + "--strip 1 would change the path\n" + " " + "'doc/fudge/crunchy.html' to 'fudge/crunchy.html'.\n" + " " + "--strip 2 would leave just 'crunchy.html'\n" + " " + "The expected component separator is '/' on all\n" + " " + "platforms. A leading '/' counts as one component.")}, + {"show-copies-as-adds", opt_show_copies_as_adds, 0, + N_("don't diff copied or moved files with their source")}, + {"ignore-keywords", opt_ignore_keywords, 0, + N_("don't expand keywords")}, + {"reverse-diff", opt_reverse_diff, 0, + N_("apply the unidiff in reverse")}, + {"ignore-whitespace", opt_ignore_whitespace, 0, + N_("ignore whitespace during pattern matching")}, + {"diff", opt_diff, 0, N_("produce diff output")}, /* maps to show_diff */ + {"internal-diff", opt_internal_diff, 0, + N_("override diff-cmd specified in config file")}, + {"git", opt_use_git_diff_format, 0, + N_("use git's extended diff format")}, + {"allow-mixed-revisions", opt_allow_mixed_revisions, 0, + N_("Allow merge into mixed-revision working copy.\n" + " " + "Use of this option is not recommended!\n" + " " + "Please run 'svn update' instead.")}, + + /* Long-opt Aliases + * + * These have NULL desriptions, but an option code that matches some + * other option (whose description should probably mention its aliases). + */ + + {"cl", opt_changelist, 1, NULL}, + + {0, 0, 0, 0}, +}; + + + +/*** Command dispatch. ***/ + +/* Our array of available subcommands. + * + * The entire list must be terminated with an entry of nulls. + * + * In most of the help text "PATH" is used where a working copy path is + * required, "URL" where a repository URL is required and "TARGET" when + * either a path or an url can be used. Hmm, should this be part of the + * help text? + */ + +/* Options that apply to all commands. (While not every command may + currently require authentication or be interactive, allowing every + command to take these arguments allows scripts to just pass them + willy-nilly to every invocation of 'svn') . */ +const int svn_cl__global_options[] = +{ opt_auth_username, opt_auth_password, opt_no_auth_cache, opt_non_interactive, + opt_trust_server_cert, opt_config_dir, opt_config_options, 0 +}; + +/* Options for giving a log message. (Some of these also have other uses.) + */ +#define SVN_CL__LOG_MSG_OPTIONS 'm', 'F', \ + opt_force_log, \ + opt_editor_cmd, \ + opt_encoding, \ + opt_with_revprop + +const svn_opt_subcommand_desc2_t svn_cl__cmd_table[] = +{ + { "add", svn_cl__add, {0}, N_ + ("Put files and directories under version control, scheduling\n" + "them for addition to repository. They will be added in next commit.\n" + "usage: add PATH...\n"), + {opt_targets, 'N', opt_depth, 'q', opt_force, opt_no_ignore, opt_autoprops, + opt_no_autoprops, opt_parents }, + {{opt_parents, N_("add intermediate parents")}} }, + + { "blame", svn_cl__blame, {"praise", "annotate", "ann"}, N_ + ("Output the content of specified files or\n" + "URLs with revision and author information in-line.\n" + "usage: blame TARGET[@REV]...\n" + "\n" + " If specified, REV determines in which revision the target is first\n" + " looked up.\n"), + {'r', 'v', 'g', opt_incremental, opt_xml, 'x', opt_force} }, + + { "cat", svn_cl__cat, {0}, N_ + ("Output the content of specified files or URLs.\n" + "usage: cat TARGET[@REV]...\n" + "\n" + " If specified, REV determines in which revision the target is first\n" + " looked up.\n"), + {'r'} }, + + { "changelist", svn_cl__changelist, {"cl"}, N_ + ("Associate (or dissociate) changelist CLNAME with the named files.\n" + "usage: 1. changelist CLNAME PATH...\n" + " 2. changelist --remove PATH...\n"), + { 'q', 'R', opt_depth, opt_remove, opt_targets, opt_changelist} }, + + { "checkout", svn_cl__checkout, {"co"}, N_ + ("Check out a working copy from a repository.\n" + "usage: checkout URL[@REV]... [PATH]\n" + "\n" + " If specified, REV determines in which revision the URL is first\n" + " looked up.\n" + "\n" + " If PATH is omitted, the basename of the URL will be used as\n" + " the destination. If multiple URLs are given each will be checked\n" + " out into a sub-directory of PATH, with the name of the sub-directory\n" + " being the basename of the URL.\n" + "\n" + " If --force is used, unversioned obstructing paths in the working\n" + " copy destination do not automatically cause the check out to fail.\n" + " If the obstructing path is the same type (file or directory) as the\n" + " corresponding path in the repository it becomes versioned but its\n" + " contents are left 'as-is' in the working copy. This means that an\n" + " obstructing directory's unversioned children may also obstruct and\n" + " become versioned. For files, any content differences between the\n" + " obstruction and the repository are treated like a local modification\n" + " to the working copy. All properties from the repository are applied\n" + " to the obstructing path.\n" + "\n" + " See also 'svn help update' for a list of possible characters\n" + " reporting the action taken.\n"), + {'r', 'q', 'N', opt_depth, opt_force, opt_ignore_externals} }, + + { "cleanup", svn_cl__cleanup, {0}, N_ + ("Recursively clean up the working copy, removing locks, resuming\n" + "unfinished operations, etc.\n" + "usage: cleanup [WCPATH...]\n"), + {opt_merge_cmd} }, + + { "commit", svn_cl__commit, {"ci"}, + N_("Send changes from your working copy to the repository.\n" + "usage: commit [PATH...]\n" + "\n" + " A log message must be provided, but it can be empty. If it is not\n" + " given by a --message or --file option, an editor will be started.\n" + " If any targets are (or contain) locked items, those will be\n" + " unlocked after a successful commit.\n"), + {'q', 'N', opt_depth, opt_targets, opt_no_unlock, SVN_CL__LOG_MSG_OPTIONS, + opt_changelist, opt_keep_changelists} }, + + { "copy", svn_cl__copy, {"cp"}, N_ + ("Duplicate something in working copy or repository, remembering\n" + "history.\n" + "usage: copy SRC[@REV]... DST\n" + "\n" + "When copying multiple sources, they will be added as children of DST,\n" + "which must be a directory.\n" + "\n" + " SRC and DST can each be either a working copy (WC) path or URL:\n" + " WC -> WC: copy and schedule for addition (with history)\n" + " WC -> URL: immediately commit a copy of WC to URL\n" + " URL -> WC: check out URL into WC, schedule for addition\n" + " URL -> URL: complete server-side copy; used to branch and tag\n" + " All the SRCs must be of the same type.\n" + "\n" + "WARNING: For compatibility with previous versions of Subversion,\n" + "copies performed using two working copy paths (WC -> WC) will not\n" + "contact the repository. As such, they may not, by default, be able\n" + "to propagate merge tracking information from the source of the copy\n" + "to the destination.\n"), + {'r', 'q', opt_ignore_externals, opt_parents, SVN_CL__LOG_MSG_OPTIONS} }, + + { "delete", svn_cl__delete, {"del", "remove", "rm"}, N_ + ("Remove files and directories from version control.\n" + "usage: 1. delete PATH...\n" + " 2. delete URL...\n" + "\n" + " 1. Each item specified by a PATH is scheduled for deletion upon\n" + " the next commit. Files, and directories that have not been\n" + " committed, are immediately removed from the working copy\n" + " unless the --keep-local option is given.\n" + " PATHs that are, or contain, unversioned or modified items will\n" + " not be removed unless the --force or --keep-local option is given.\n" + "\n" + " 2. Each item specified by a URL is deleted from the repository\n" + " via an immediate commit.\n"), + {opt_force, 'q', opt_targets, SVN_CL__LOG_MSG_OPTIONS, opt_keep_local} }, + + { "diff", svn_cl__diff, {"di"}, N_ + ("Display the differences between two revisions or paths.\n" + "usage: 1. diff [-c M | -r N[:M]] [TARGET[@REV]...]\n" + " 2. diff [-r N[:M]] --old=OLD-TGT[@OLDREV] [--new=NEW-TGT[@NEWREV]] \\\n" + " [PATH...]\n" + " 3. diff OLD-URL[@OLDREV] NEW-URL[@NEWREV]\n" + "\n" + " 1. Display the changes made to TARGETs as they are seen in REV between\n" + " two revisions. TARGETs may be all working copy paths or all URLs.\n" + " If TARGETs are working copy paths, N defaults to BASE and M to the\n" + " working copy; if URLs, N must be specified and M defaults to HEAD.\n" + " The '-c M' option is equivalent to '-r N:M' where N = M-1.\n" + " Using '-c -M' does the reverse: '-r M:N' where N = M-1.\n" + "\n" + " 2. Display the differences between OLD-TGT as it was seen in OLDREV and\n" + " NEW-TGT as it was seen in NEWREV. PATHs, if given, are relative to\n" + " OLD-TGT and NEW-TGT and restrict the output to differences for those\n" + " paths. OLD-TGT and NEW-TGT may be working copy paths or URL[@REV].\n" + " NEW-TGT defaults to OLD-TGT if not specified. -r N makes OLDREV default\n" + " to N, -r N:M makes OLDREV default to N and NEWREV default to M.\n" + "\n" + " 3. Shorthand for 'svn diff --old=OLD-URL[@OLDREV] --new=NEW-URL[@NEWREV]'\n" + "\n" + " Use just 'svn diff' to display local modifications in a working copy.\n"), + {'r', 'c', opt_old_cmd, opt_new_cmd, 'N', opt_depth, opt_diff_cmd, + opt_internal_diff, 'x', opt_no_diff_deleted, opt_show_copies_as_adds, + opt_notice_ancestry, opt_summarize, opt_changelist, opt_force, opt_xml, + opt_use_git_diff_format} }, + { "export", svn_cl__export, {0}, N_ + ("Create an unversioned copy of a tree.\n" + "usage: 1. export [-r REV] URL[@PEGREV] [PATH]\n" + " 2. export [-r REV] PATH1[@PEGREV] [PATH2]\n" + "\n" + " 1. Exports a clean directory tree from the repository specified by\n" + " URL, at revision REV if it is given, otherwise at HEAD, into\n" + " PATH. If PATH is omitted, the last component of the URL is used\n" + " for the local directory name.\n" + "\n" + " 2. Exports a clean directory tree from the working copy specified by\n" + " PATH1, at revision REV if it is given, otherwise at WORKING, into\n" + " PATH2. If PATH2 is omitted, the last component of the PATH1 is used\n" + " for the local directory name. If REV is not specified, all local\n" + " changes will be preserved. Files not under version control will\n" + " not be copied.\n" + "\n" + " If specified, PEGREV determines in which revision the target is first\n" + " looked up.\n"), + {'r', 'q', 'N', opt_depth, opt_force, opt_native_eol, opt_ignore_externals, + opt_ignore_keywords} }, + + { "help", svn_cl__help, {"?", "h"}, N_ + ("Describe the usage of this program or its subcommands.\n" + "usage: help [SUBCOMMAND...]\n"), + {0} }, + /* This command is also invoked if we see option "--help", "-h" or "-?". */ + + { "import", svn_cl__import, {0}, N_ + ("Commit an unversioned file or tree into the repository.\n" + "usage: import [PATH] URL\n" + "\n" + " Recursively commit a copy of PATH to URL.\n" + " If PATH is omitted '.' is assumed.\n" + " Parent directories are created as necessary in the repository.\n" + " If PATH is a directory, the contents of the directory are added\n" + " directly under URL.\n" + " Unversionable items such as device files and pipes are ignored\n" + " if --force is specified.\n"), + {'q', 'N', opt_depth, opt_autoprops, opt_force, opt_no_autoprops, + SVN_CL__LOG_MSG_OPTIONS, opt_no_ignore} }, + + { "info", svn_cl__info, {0}, N_ + ("Display information about a local or remote item.\n" + "usage: info [TARGET[@REV]...]\n" + "\n" + " Print information about each TARGET (default: '.').\n" + " TARGET may be either a working-copy path or URL. If specified, REV\n" + " determines in which revision the target is first looked up.\n"), + {'r', 'R', opt_depth, opt_targets, opt_incremental, opt_xml, opt_changelist} + }, + + { "list", svn_cl__list, {"ls"}, N_ + ("List directory entries in the repository.\n" + "usage: list [TARGET[@REV]...]\n" + "\n" + " List each TARGET file and the contents of each TARGET directory as\n" + " they exist in the repository. If TARGET is a working copy path, the\n" + " corresponding repository URL will be used. If specified, REV determines\n" + " in which revision the target is first looked up.\n" + "\n" + " The default TARGET is '.', meaning the repository URL of the current\n" + " working directory.\n" + "\n" + " With --verbose, the following fields will be shown for each item:\n" + "\n" + " Revision number of the last commit\n" + " Author of the last commit\n" + " If locked, the letter 'O'. (Use 'svn info URL' to see details)\n" + " Size (in bytes)\n" + " Date and time of the last commit\n"), + {'r', 'v', 'R', opt_depth, opt_incremental, opt_xml} }, + + { "lock", svn_cl__lock, {0}, N_ + ("Lock working copy paths or URLs in the repository, so that\n" + "no other user can commit changes to them.\n" + "usage: lock TARGET...\n" + "\n" + " Use --force to steal the lock from another user or working copy.\n"), + { opt_targets, 'm', 'F', opt_force_log, opt_encoding, opt_force }, + {{'F', N_("read lock comment from file ARG")}, + {'m', N_("specify lock comment ARG")}, + {opt_force_log, N_("force validity of lock comment source")}} }, + + { "log", svn_cl__log, {0}, N_ + ("Show the log messages for a set of revision(s) and/or path(s).\n" + "usage: 1. log [PATH][@REV]\n" + " 2. log URL[@REV] [PATH...]\n" + "\n" + " 1. Print the log messages for the URL corresponding to PATH\n" + " (default: '.'). If specified, REV is the revision in which the\n" + " URL is first looked up, and the default revision range is REV:1.\n" + " If REV is not specified, the default revision range is BASE:1,\n" + " since the URL might not exist in the HEAD revision.\n" + "\n" + " 2. Print the log messages for the PATHs (default: '.') under URL.\n" + " If specified, REV is the revision in which the URL is first\n" + " looked up, and the default revision range is REV:1; otherwise,\n" + " the URL is looked up in HEAD, and the default revision range is\n" + " HEAD:1.\n" + "\n" + " Multiple '-c' or '-r' options may be specified (but not a\n" + " combination of '-c' and '-r' options), and mixing of forward and\n" + " reverse ranges is allowed.\n" + "\n" + " With -v, also print all affected paths with each log message.\n" + " With -q, don't print the log message body itself (note that this is\n" + " compatible with -v).\n" + "\n" + " Each log message is printed just once, even if more than one of the\n" + " affected paths for that revision were explicitly requested. Logs\n" + " follow copy history by default. Use --stop-on-copy to disable this\n" + " behavior, which can be useful for determining branchpoints.\n" + "\n" + " The --depth option is only valid in combination with the --diff option\n" + " and limits the scope of the displayed diff to the specified depth.\n" + + "\n" + " Examples:\n" + " svn log\n" + " svn log foo.c\n" + " svn log bar.c@42\n" + " svn log http://www.example.com/repo/project/foo.c\n" + " svn log http://www.example.com/repo/project foo.c bar.c\n" + " svn log http://www.example.com/repo/project@50 foo.c bar.c\n"), + {'r', 'q', 'v', 'g', 'c', opt_targets, opt_stop_on_copy, opt_incremental, + opt_xml, 'l', opt_with_all_revprops, opt_with_no_revprops, opt_with_revprop, + opt_depth, opt_diff, opt_diff_cmd, opt_internal_diff, 'x'}, + {{opt_with_revprop, N_("retrieve revision property ARG")}, + {'c', N_("the change made in revision ARG")}} }, + + { "merge", svn_cl__merge, {0}, N_ + ( /* For this large section, let's keep it unindented for easier + * viewing/editing. It has been vim-treated with a textwidth=75 and 'gw' + * (with quotes and newlines removed). */ +"Merge changes into a working copy.\n" +"usage: 1. merge SOURCE[@REV] [TARGET_WCPATH]\n" +" (the 'sync' merge)\n" +" 2. merge [-c M[,N...] | -r N:M ...] SOURCE[@REV] [TARGET_WCPATH]\n" +" (the 'cherry-pick' merge)\n" +" 3. merge --reintegrate SOURCE[@REV] [TARGET_WCPATH]\n" +" (the 'reintegrate' merge)\n" +" 4. merge SOURCE1[@N] SOURCE2[@M] [TARGET_WCPATH]\n" +" (the '2-URL' merge)\n" +"\n" +" 1. This form is called a 'sync' (or 'catch-up') merge:\n" +"\n" +" svn merge SOURCE[@REV] [TARGET_WCPATH]\n" +"\n" +" A sync merge is used to fetch all the latest changes made on a parent\n" +" branch. In other words, the target branch has originally been created\n" +" by copying the source branch, and any changes committed on the source\n" +" branch since branching are applied to the target branch. This uses\n" +" merge tracking to skip all those revisions that have already been\n" +" merged, so a sync merge can be repeated periodically to stay up-to-\n" +" date with the source branch.\n" +"\n" +" SOURCE specifies the branch from where the changes will be pulled, and\n" +" TARGET_WCPATH specifies a working copy of the target branch to which\n" +" the changes will be applied. Normally SOURCE and TARGET_WCPATH should\n" +" each correspond to the root of a branch. (If you want to merge only a\n" +" subtree, then the subtree path must be included in both SOURCE and\n" +" TARGET_WCPATH; this is discouraged, to avoid subtree mergeinfo.)\n" +"\n" +" SOURCE is usually a URL. The optional '@REV' specifies both the peg\n" +" revision of the URL and the latest revision that will be considered\n" +" for merging; if REV is not specified, the HEAD revision is assumed. If\n" +" SOURCE is a working copy path, the corresponding URL of the path is\n" +" used, and the default value of 'REV' is the base revision (usually the\n" +" revision last updated to).\n" +"\n" +" TARGET_WCPATH is a working copy path; if omitted, '.' is assumed.\n" +"\n" +" - Sync Merge Example -\n" +"\n" +" A feature is being developed on a branch called 'feature', which has\n" +" originally been a copy of trunk. The feature branch has been regularly\n" +" synced with trunk to keep up with the changes made there. The previous\n" +" sync merges are not shown on this diagram, and the last of them was\n" +" done when HEAD was r100. Currently, HEAD is r200.\n" +"\n" +" feature +------------------------o-----\n" +" / ^\n" +" / ............ |\n" +" / . . /\n" +" trunk ------+------------L--------------R------\n" +" r100 r200\n" +"\n" +" Subversion will locate all the changes on 'trunk' that have not yet\n" +" been merged into the 'feature' branch. In this case that is a single\n" +" range, r100:200. In the diagram above, L marks the left side\n" +" (trunk@100) and R marks the right side (trunk@200) of the merge. The\n" +" difference between L and R will be applied to the target working copy\n" +" path. In this case, the working copy is a clean checkout of the entire\n" +" 'feature' branch.\n" +"\n" +" To perform this sync merge, have a clean working copy of the feature\n" +" branch and run the following command in its top-level directory:\n" +"\n" +" svn merge ^/trunk\n" +"\n" +" Note that the merge is now only in your local working copy and still\n" +" needs to be committed to the repository so that it can be seen by\n" +" others. You can review the changes and you may have to resolve\n" +" conflicts before you commit the merge.\n" +"\n" +"\n" +" 2. This form is called a 'cherry-pick' merge:\n" +"\n" +" svn merge [-c M[,N...] | -r N:M ...] SOURCE[@REV] [TARGET_WCPATH]\n" +"\n" +" A cherry-pick merge is used to merge specific revisions (or revision\n" +" ranges) from one branch to another. By default, this uses merge\n" +" tracking to automatically skip any revisions that have already been\n" +" merged to the target; you can use the --ignore-ancestry option to\n" +" disable such skipping.\n" +"\n" +" SOURCE is usually a URL. The optional '@REV' specifies only the peg\n" +" revision of the URL and does not affect the merge range; if REV is not\n" +" specified, the HEAD revision is assumed. If SOURCE is a working copy\n" +" path, the corresponding URL of the path is used, and the default value\n" +" of 'REV' is the base revision (usually the revision last updated to).\n" +"\n" +" TARGET_WCPATH is a working copy path; if omitted, '.' is assumed.\n" +"\n" +" The revision ranges to be merged are specified by the '-r' and/or '-c'\n" +" options. '-r N:M' refers to the difference in the history of the\n" +" source branch between revisions N and M. You can use '-c M' to merge\n" +" single revisions: '-c M' is equivalent to '-r <M-1>:M'. Each such\n" +" difference is applied to TARGET_WCPATH.\n" +"\n" +" If the mergeinfo in TARGET_WCPATH indicates that revisions within the\n" +" range were already merged, changes made in those revisions are not\n" +" merged again. If needed, the range is broken into multiple sub-ranges,\n" +" and each sub-range is merged separately.\n" +"\n" +" A 'reverse range' can be used to undo changes. For example, when\n" +" source and target refer to the same branch, a previously committed\n" +" revision can be 'undone'. In a reverse range, N is greater than M in\n" +" '-r N:M', or the '-c' option is used with a negative number: '-c -M'\n" +" is equivalent to '-r M:<M-1>'.\n" +"\n" +" Multiple '-c' and/or '-r' options may be specified and mixing of\n" +" forward and reverse ranges is allowed.\n" +"\n" +" - Cherry-pick Merge Example -\n" +"\n" +" A bug has been fixed on trunk in revision 50. This fix needs to\n" +" be merged from trunk onto the release branch.\n" +"\n" +" 1.x-release +-----------------------o-----\n" +" / ^\n" +" / |\n" +" / |\n" +" trunk ------+--------------------------LR-----\n" +" r50\n" +"\n" +" In the above diagram, L marks the left side (trunk@49) and R marks the\n" +" right side (trunk@50) of the merge. The difference between the left\n" +" and right side is applied to the target working copy path.\n" +"\n" +" Note that the difference between revision 49 and 50 is exactly those\n" +" changes that were committed in revision 50, not including changes\n" +" committed in revision 49.\n" +"\n" +" To perform the merge, have a clean working copy of the release branch\n" +" and run the following command in its top-level directory; remember\n" +" that the default target is '.':\n" +"\n" +" svn merge -c50 ^/trunk\n" +"\n" +" You can also cherry-pick several revisions and/or revision ranges:\n" +"\n" +" svn merge -c50,54,60 -r65:68 ^/trunk\n" +"\n" +"\n" +" 3. This form is called a 'reintegrate merge':\n" +"\n" +" svn merge --reintegrate SOURCE[@REV] [TARGET_WCPATH]\n" +"\n" +" In a reintegrate merge, an (e.g. feature) branch is merged back to its\n" +" originating branch. In other words, the source branch has originally\n" +" been created by copying the target branch, development has concluded\n" +" on the source branch and it should now be merged back into the target\n" +" branch.\n" +" \n" +" SOURCE is the URL of a branch to be merged back. If REV is specified,\n" +" it is used as the peg revision for SOURCE; if REV is not specified,\n" +" the HEAD revision is assumed.\n" +"\n" +" TARGET_WCPATH is a working copy of the branch the changes will be\n" +" applied to.\n" +"\n" +" - Reintegrate Merge Example -\n" +"\n" +" A feature has been developed on a branch called 'feature'. The feature\n" +" branch started as a copy of trunk@W. Work on the feature has completed\n" +" and it should be merged back into the trunk.\n" +"\n" +" The feature branch was last synced with trunk up to revision X. So the\n" +" difference between trunk@X and feature@HEAD contains the complete set\n" +" of changes that implement the feature, and no other changes. These\n" +" changes are applied to trunk.\n" +"\n" +" feature +--------------------------------R\n" +" / . \\\n" +" / ............. \\\n" +" / . v\n" +" trunk ------+--------------------L------------------o\n" +" rW rX\n" +"\n" +" In the diagram above, L marks the left side (trunk@X) and R marks the\n" +" right side (feature@HEAD) of the merge. The difference between the\n" +" left and right side is merged into trunk, the target.\n" +"\n" +" To perform the merge, have a clean working copy of trunk and run the\n" +" following command in its top-level directory:\n" +"\n" +" svn merge --reintegrate ^/feature\n" +"\n" +" To prevent unnecessary merge conflicts, a reintegrate merge requires\n" +" that TARGET_WCPATH is not a mixed-revision working copy, has no local\n" +" modifications, and has no switched subtrees.\n" +"\n" +" A reintegrate merge also requires that the source branch is coherently\n" +" synced with the target -- in the above example, this means that all\n" +" revisions between the branch point W and the last merged revision X\n" +" are merged to the feature branch, so that there are no unmerged\n" +" revisions in-between.\n" +"\n" +" After the reintegrate merge, the feature branch cannot be synced to\n" +" the trunk again without merge conflicts. If further work must be done\n" +" on the feature branch, it should be deleted and then re-created.\n" +"\n" +"\n" +" 4. This form is called a '2-URL merge':\n" +"\n" +" svn merge SOURCE1[@N] SOURCE2[@M] [TARGET_WCPATH]\n" +"\n" +" Two source URLs are specified, together with two revisions N and M.\n" +" The two sources are compared at the specified revisions, and the\n" +" difference is applied to TARGET_WCPATH, which is a path to a working\n" +" copy of another branch. The three branches involved can be completely\n" +" unrelated.\n" +"\n" +" You should use this merge variant only if the other variants do not\n" +" apply to your situation, as this variant can be quite complex to\n" +" master.\n" +"\n" +" If TARGET_WCPATH is omitted, a default value of '.' is assumed.\n" +" However, in the special case where both sources refer to a file node\n" +" with the same basename and a similarly named file is also found within\n" +" '.', the differences will be applied to that local file. The source\n" +" revisions default to HEAD if omitted.\n" +"\n" +" The sources can also be specified as working copy paths, in which case\n" +" the URLs of the merge sources are derived from the working copies.\n" +"\n" +" - 2-URL Merge Example -\n" +"\n" +" Two features have been developed on separate branches called 'foo' and\n" +" 'bar'. It has since become clear that 'bar' should be combined with\n" +" the 'foo' branch for further development before reintegration.\n" +"\n" +" Although both feature branches originate from trunk, they are not\n" +" directly related -- one is not a direct copy of the other. A 2-URL\n" +" merge is necessary.\n" +"\n" +" The 'bar' branch has been synced with trunk up to revision 500.\n" +" (If this revision number is not known, it can be located using the\n" +" 'svn log' and/or 'svn mergeinfo' commands.)\n" +" The difference between trunk@500 and bar@HEAD contains the complete\n" +" set of changes related to feature 'bar', and no other changes. These\n" +" changes are applied to the 'foo' branch.\n" +"\n" +" foo +-----------------------------------o\n" +" / ^\n" +" / /\n" +" / r500 /\n" +" trunk ------+------+-----------------L---------> /\n" +" \\ . /\n" +" \\ ............ /\n" +" \\ . /\n" +" bar +-----------------------------------R\n" +"\n" +" In the diagram above, L marks the left side (trunk@500) and R marks\n" +" the right side (bar@HEAD) of the merge. The difference between the\n" +" left and right side is applied to the target working copy path, in\n" +" this case a working copy of the 'foo' branch.\n" +"\n" +" To perform the merge, have a clean working copy of the 'foo' branch\n" +" and run the following command in its top-level directory:\n" +"\n" +" svn merge ^/trunk@500 ^/bar\n" +"\n" +" The exact changes applied by a 2-URL merge can be previewed with svn's\n" +" diff command, which is a good idea to verify if you do not have the\n" +" luxury of a clean working copy to merge to. In this case:\n" +"\n" +" svn diff ^/trunk@500 ^/bar@HEAD\n" +"\n" +"\n" +" The following applies to all types of merges:\n" +"\n" +" To prevent unnecessary merge conflicts, svn merge requires that\n" +" TARGET_WCPATH is not a mixed-revision working copy. Running 'svn update'\n" +" before starting a merge ensures that all items in the working copy are\n" +" based on the same revision.\n" +"\n" +" If possible, you should have no local modifications in the merge's target\n" +" working copy prior to the merge, to keep things simpler. It will be\n" +" easier to revert the merge and to understand the branch's history.\n" +"\n" +" Switched sub-paths should also be avoided during merging, as they may\n" +" cause incomplete merges and create subtree mergeinfo.\n" +"\n" +" For each merged item a line will be printed with characters reporting the\n" +" action taken. These characters have the following meaning:\n" +"\n" +" A Added\n" +" D Deleted\n" +" U Updated\n" +" C Conflict\n" +" G Merged\n" +" E Existed\n" +" R Replaced\n" +"\n" +" Characters in the first column report about the item itself.\n" +" Characters in the second column report about properties of the item.\n" +" A 'C' in the third column indicates a tree conflict, while a 'C' in\n" +" the first and second columns indicate textual conflicts in files\n" +" and in property values, respectively.\n" +"\n" +" - Merge Tracking -\n" +"\n" +" Subversion uses the svn:mergeinfo property to track merge history. This\n" +" property is considered at the start of a merge to determine what to merge\n" +" and it is updated at the conclusion of the merge to describe the merge\n" +" that took place. Mergeinfo is used only if the two sources are on the\n" +" same line of history -- if the first source is an ancestor of the second,\n" +" or vice-versa (i.e. if one has originally been created by copying the\n" +" other). This is verified and enforced when using sync merges and\n" +" reintegrate merges.\n" +"\n" +" The --ignore-ancestry option prevents merge tracking and thus ignores\n" +" mergeinfo, neither considering it nor recording it.\n" +"\n" +" - Merging from foreign repositories -\n" +"\n" +" Subversion does support merging from foreign repositories.\n" +" While all merge source URLs must point to the same repository, the merge\n" +" target working copy may come from a different repository than the source.\n" +" However, there are some caveats. Most notably, copies made in the\n" +" merge source will be transformed into plain additions in the merge\n" +" target. Also, merge-tracking is not supported for merges from foreign\n" +" repositories.\n"), + {'r', 'c', 'N', opt_depth, 'q', opt_force, opt_dry_run, opt_merge_cmd, + opt_record_only, 'x', opt_ignore_ancestry, opt_accept, opt_reintegrate, + opt_allow_mixed_revisions} }, + + { "mergeinfo", svn_cl__mergeinfo, {0}, N_ + ("Display merge-related information.\n" + "usage: mergeinfo SOURCE[@REV] [TARGET[@REV]]\n" + "\n" + " Display information related to merges (or potential merges) between\n" + " SOURCE and TARGET (default: '.'). Display the type of information\n" + " specified by the --show-revs option. If --show-revs isn't passed,\n" + " it defaults to --show-revs='merged'.\n" + "\n" + " The depth can be 'empty' or 'infinity'; the default is 'empty'.\n"), + {'r', 'R', opt_depth, opt_show_revs} }, + + { "mkdir", svn_cl__mkdir, {0}, N_ + ("Create a new directory under version control.\n" + "usage: 1. mkdir PATH...\n" + " 2. mkdir URL...\n" + "\n" + " Create version controlled directories.\n" + "\n" + " 1. Each directory specified by a working copy PATH is created locally\n" + " and scheduled for addition upon the next commit.\n" + "\n" + " 2. Each directory specified by a URL is created in the repository via\n" + " an immediate commit.\n" + "\n" + " In both cases, all the intermediate directories must already exist,\n" + " unless the --parents option is given.\n"), + {'q', opt_parents, SVN_CL__LOG_MSG_OPTIONS} }, + + { "move", svn_cl__move, {"mv", "rename", "ren"}, N_ + ("Move and/or rename something in working copy or repository.\n" + "usage: move SRC... DST\n" + "\n" + "When moving multiple sources, they will be added as children of DST,\n" + "which must be a directory.\n" + "\n" + " Note: this subcommand is equivalent to a 'copy' and 'delete'.\n" + " Note: the --revision option has no use and is deprecated.\n" + "\n" + " SRC and DST can both be working copy (WC) paths or URLs:\n" + " WC -> WC: move and schedule for addition (with history)\n" + " URL -> URL: complete server-side rename.\n" + " All the SRCs must be of the same type.\n"), + {'r', 'q', opt_force, opt_parents, SVN_CL__LOG_MSG_OPTIONS} }, + + { "patch", svn_cl__patch, {0}, N_ + ("Apply a patch to a working copy.\n" + "usage: patch PATCHFILE [WCPATH]\n" + "\n" + " Apply a unidiff patch in PATCHFILE to the working copy WCPATH.\n" + " If WCPATH is omitted, '.' is assumed.\n" + "\n" + " A unidiff patch suitable for application to a working copy can be\n" + " produced with the 'svn diff' command or third-party diffing tools.\n" + " Any non-unidiff content of PATCHFILE is ignored.\n" + "\n" + " Changes listed in the patch will either be applied or rejected.\n" + " If a change does not match at its exact line offset, it may be applied\n" + " earlier or later in the file if a match is found elsewhere for the\n" + " surrounding lines of context provided by the patch.\n" + " A change may also be applied with fuzz, which means that one\n" + " or more lines of context are ignored when matching the change.\n" + " If no matching context can be found for a change, the change conflicts\n" + " and will be written to a reject file with the extension .svnpatch.rej.\n" + "\n" + " For each patched file a line will be printed with characters reporting\n" + " the action taken. These characters have the following meaning:\n" + "\n" + " A Added\n" + " D Deleted\n" + " U Updated\n" + " C Conflict\n" + " G Merged (with local uncommitted changes)\n" + "\n" + " Changes applied with an offset or fuzz are reported on lines starting\n" + " with the '>' symbol. You should review such changes carefully.\n" + "\n" + " If the patch removes all content from a file, that file is scheduled\n" + " for deletion. If the patch creates a new file, that file is scheduled\n" + " for addition. Use 'svn revert' to undo deletions and additions you\n" + " do not agree with.\n" + "\n" + " Hint: If the patch file was created with Subversion, it will contain\n" + " the number of a revision N the patch will cleanly apply to\n" + " (look for lines like \"--- foo/bar.txt (revision N)\").\n" + " To avoid rejects, first update to the revision N using \n" + " 'svn update -r N', apply the patch, and then update back to the\n" + " HEAD revision. This way, conflicts can be resolved interactively.\n" + ), + {'q', opt_dry_run, opt_strip, opt_reverse_diff, + opt_ignore_whitespace} }, + + { "propdel", svn_cl__propdel, {"pdel", "pd"}, N_ + ("Remove a property from files, dirs, or revisions.\n" + "usage: 1. propdel PROPNAME [PATH...]\n" + " 2. propdel PROPNAME --revprop -r REV [TARGET]\n" + "\n" + " 1. Removes versioned props in working copy.\n" + " 2. Removes unversioned remote prop on repos revision.\n" + " TARGET only determines which repository to access.\n"), + {'q', 'R', opt_depth, 'r', opt_revprop, opt_changelist} }, + + { "propedit", svn_cl__propedit, {"pedit", "pe"}, N_ + ("Edit a property with an external editor.\n" + "usage: 1. propedit PROPNAME TARGET...\n" + " 2. propedit PROPNAME --revprop -r REV [TARGET]\n" + "\n" + " 1. Edits versioned prop in working copy or repository.\n" + " 2. Edits unversioned remote prop on repos revision.\n" + " TARGET only determines which repository to access.\n" + "\n" + "See 'svn help propset' for more on setting properties.\n"), + {'r', opt_revprop, SVN_CL__LOG_MSG_OPTIONS, opt_force} }, + + { "propget", svn_cl__propget, {"pget", "pg"}, N_ + ("Print the value of a property on files, dirs, or revisions.\n" + "usage: 1. propget PROPNAME [TARGET[@REV]...]\n" + " 2. propget PROPNAME --revprop -r REV [TARGET]\n" + "\n" + " 1. Prints versioned props. If specified, REV determines in which\n" + " revision the target is first looked up.\n" + " 2. Prints unversioned remote prop on repos revision.\n" + " TARGET only determines which repository to access.\n" + "\n" + " By default, this subcommand will add an extra newline to the end\n" + " of the property values so that the output looks pretty. Also,\n" + " whenever there are multiple paths involved, each property value\n" + " is prefixed with the path with which it is associated. Use the\n" + " --strict option to disable these beautifications (useful when\n" + " redirecting a binary property value to a file, but available only\n" + " if you supply a single TARGET to a non-recursive propget operation).\n"), + {'v', 'R', opt_depth, 'r', opt_revprop, opt_strict, opt_xml, + opt_changelist } }, + + { "proplist", svn_cl__proplist, {"plist", "pl"}, N_ + ("List all properties on files, dirs, or revisions.\n" + "usage: 1. proplist [TARGET[@REV]...]\n" + " 2. proplist --revprop -r REV [TARGET]\n" + "\n" + " 1. Lists versioned props. If specified, REV determines in which\n" + " revision the target is first looked up.\n" + " 2. Lists unversioned remote props on repos revision.\n" + " TARGET only determines which repository to access.\n"), + {'v', 'R', opt_depth, 'r', 'q', opt_revprop, opt_xml, opt_changelist } }, + + { "propset", svn_cl__propset, {"pset", "ps"}, N_ + ("Set the value of a property on files, dirs, or revisions.\n" + "usage: 1. propset PROPNAME PROPVAL PATH...\n" + " 2. propset PROPNAME --revprop -r REV PROPVAL [TARGET]\n" + "\n" + " 1. Changes a versioned file or directory property in a working copy.\n" + " 2. Changes an unversioned property on a repository revision.\n" + " (TARGET only determines which repository to access.)\n" + "\n" + " The value may be provided with the --file option instead of PROPVAL.\n" + "\n" + " Note: svn recognizes the following special versioned properties\n" + " but will store any arbitrary properties set:\n" + " svn:ignore - A newline separated list of file glob patterns to ignore.\n" + " svn:keywords - Keywords to be expanded. Valid keywords are:\n" + " URL, HeadURL - The URL for the head version of the object.\n" + " Author, LastChangedBy - The last person to modify the file.\n" + " Date, LastChangedDate - The date/time the object was last modified.\n" + " Rev, Revision, - The last revision the object changed.\n" + " LastChangedRevision\n" + " Id - A compressed summary of the previous\n" + " 4 keywords.\n" + " Header - Similar to Id but includes the full URL.\n" + " svn:executable - If present, make the file executable. Use\n" + " 'svn propdel svn:executable PATH...' to clear.\n" + " svn:eol-style - One of 'native', 'LF', 'CR', 'CRLF'.\n" + " svn:mime-type - The mimetype of the file. Used to determine\n" + " whether to merge the file, and how to serve it from Apache.\n" + " A mimetype beginning with 'text/' (or an absent mimetype) is\n" + " treated as text. Anything else is treated as binary.\n" + " svn:externals - A newline separated list of module specifiers,\n" + " each of which consists of a URL and a relative directory path,\n" + " similar to the syntax of the 'svn checkout' command:\n" + " http://example.com/repos/zig foo/bar\n" + " A revision to check out can optionally be specified to pin the\n" + " external to a known revision:\n" + " -r25 http://example.com/repos/zig foo/bar\n" + " To unambiguously identify an element at a path which has been\n" + " deleted (possibly even deleted multiple times in its history),\n" + " an optional peg revision can be appended to the URL:\n" + " -r25 http://example.com/repos/zig@42 foo/bar\n" + " Relative URLs are indicated by starting the URL with one\n" + " of the following strings:\n" + " ../ to the parent directory of the extracted external\n" + " ^/ to the repository root\n" + " // to the scheme\n" + " / to the server root\n" + " The ambiguous format 'relative_path relative_path' is taken as\n" + " 'relative_url relative_path' with peg revision support.\n" + " Lines in externals definitions starting with the '#' character\n" + " are considered comments and are ignored.\n" + " Subversion 1.4 and earlier only support the following formats\n" + " where peg revisions can only be specified using a -r modifier\n" + " and where URLs cannot be relative:\n" + " foo http://example.com/repos/zig\n" + " foo/bar -r 1234 http://example.com/repos/zag\n" + " Use of these formats is discouraged. They should only be used if\n" + " interoperability with 1.4 clients is desired.\n" + " svn:needs-lock - If present, indicates that the file should be locked\n" + " before it is modified. Makes the working copy file read-only\n" + " when it is not locked. Use 'svn propdel svn:needs-lock PATH...'\n" + " to clear.\n" + "\n" + " The svn:keywords, svn:executable, svn:eol-style, svn:mime-type and\n" + " svn:needs-lock properties cannot be set on a directory. A non-recursive\n" + " attempt will fail, and a recursive attempt will set the property\n" + " only on the file children of the directory.\n"), + {'F', opt_encoding, 'q', 'r', opt_targets, 'R', opt_depth, opt_revprop, + opt_force, opt_changelist }, + {{'F', N_("read property value from file ARG")}} }, + + { "relocate", svn_cl__relocate, {0}, N_ + ("Relocate the working copy to point to a different repository root URL.\n" + "usage: 1. relocate FROM-PREFIX TO-PREFIX [PATH...]\n" + " 2. relocate TO-URL [PATH]\n" + "\n" + " Rewrite working copy URL metadata to reflect a syntactic change only.\n" + " This is used when a repository's root URL changes (such as a scheme\n" + " or hostname change) but your working copy still reflects the same\n" + " directory within the same repository.\n" + "\n" + " 1. FROM-PREFIX and TO-PREFIX are initial substrings of the working\n" + " copy's current and new URLs, respectively. (You may specify the\n" + " complete old and new URLs if you wish.) Use 'svn info' to determine\n" + " the current working copy URL.\n" + "\n" + " 2. TO-URL is the (complete) new repository URL to use for PATH.\n" + "\n" + " Examples:\n" + " svn relocate http:// svn:// project1 project2\n" + " svn relocate http://www.example.com/repo/project \\\n" + " svn://svn.example.com/repo/project\n"), + {opt_ignore_externals} }, + + { "resolve", svn_cl__resolve, {0}, N_ + ("Resolve conflicts on working copy files or directories.\n" + "usage: resolve --accept=ARG [PATH...]\n" + "\n" + " Note: the --accept option is currently required.\n"), + {opt_targets, 'R', opt_depth, 'q', opt_accept}, + {{opt_accept, N_("specify automatic conflict resolution source\n" + " " + "('base', 'working', 'mine-conflict',\n" + " " + "'theirs-conflict', 'mine-full', 'theirs-full')")}} }, + + { "resolved", svn_cl__resolved, {0}, N_ + ("Remove 'conflicted' state on working copy files or directories.\n" + "usage: resolved PATH...\n" + "\n" + " Note: this subcommand does not semantically resolve conflicts or\n" + " remove conflict markers; it merely removes the conflict-related\n" + " artifact files and allows PATH to be committed again. It has been\n" + " deprecated in favor of running 'svn resolve --accept working'.\n"), + {opt_targets, 'R', opt_depth, 'q'} }, + + { "revert", svn_cl__revert, {0}, N_ + ("Restore pristine working copy file (undo most local edits).\n" + "usage: revert PATH...\n" + "\n" + " Note: this subcommand does not require network access, and resolves\n" + " any conflicted states.\n"), + {opt_targets, 'R', opt_depth, 'q', opt_changelist} }, + + { "status", svn_cl__status, {"stat", "st"}, N_ + ("Print the status of working copy files and directories.\n" + "usage: status [PATH...]\n" + "\n" + " With no args, print only locally modified items (no network access).\n" + " With -q, print only summary information about locally modified items.\n" + " With -u, add working revision and server out-of-date information.\n" + " With -v, print full revision information on every item.\n" + "\n" + " The first seven columns in the output are each one character wide:\n" + " First column: Says if item was added, deleted, or otherwise changed\n" + " ' ' no modifications\n" + " 'A' Added\n" + " 'C' Conflicted\n" + " 'D' Deleted\n" + " 'I' Ignored\n" + " 'M' Modified\n" + " 'R' Replaced\n" + " 'X' an unversioned directory created by an externals definition\n" + " '?' item is not under version control\n" + " '!' item is missing (removed by non-svn command) or incomplete\n" + " '~' versioned item obstructed by some item of a different kind\n" + " Second column: Modifications of a file's or directory's properties\n" + " ' ' no modifications\n" + " 'C' Conflicted\n" + " 'M' Modified\n" + " Third column: Whether the working copy directory is locked\n" + " ' ' not locked\n" + " 'L' locked\n" + " Fourth column: Scheduled commit will contain addition-with-history\n" + " ' ' no history scheduled with commit\n" + " '+' history scheduled with commit\n" + " Fifth column: Whether the item is switched or a file external\n" + " ' ' normal\n" + " 'S' the item has a Switched URL relative to the parent\n" + " 'X' a versioned file created by an eXternals definition\n" + " Sixth column: Repository lock token\n" + " (without -u)\n" + " ' ' no lock token\n" + " 'K' lock token present\n" + " (with -u)\n" + " ' ' not locked in repository, no lock token\n" + " 'K' locked in repository, lock toKen present\n" + " 'O' locked in repository, lock token in some Other working copy\n" + " 'T' locked in repository, lock token present but sTolen\n" + " 'B' not locked in repository, lock token present but Broken\n" + " Seventh column: Whether the item is the victim of a tree conflict\n" + " ' ' normal\n" + " 'C' tree-Conflicted\n" + " If the item is a tree conflict victim, an additional line is printed\n" + " after the item's status line, explaining the nature of the conflict.\n" + "\n" + " The out-of-date information appears in the ninth column (with -u):\n" + " '*' a newer revision exists on the server\n" + " ' ' the working copy is up to date\n" + "\n" + " Remaining fields are variable width and delimited by spaces:\n" + " The working revision (with -u or -v; '-' if the item is copied)\n" + " The last committed revision and last committed author (with -v)\n" + " The working copy path is always the final field, so it can\n" + " include spaces.\n" + "\n" + " The presence of a question mark ('?') where a working revision, last\n" + " committed revision, or last committed author was expected indicates\n" + " that the information is unknown or irrelevant given the state of the\n" + " item (for example, when the item is the result of a copy operation).\n" + " The question mark serves as a visual placeholder to facilitate parsing.\n" + "\n" + " Example output:\n" + " svn status wc\n" + " M wc/bar.c\n" + " A + wc/qax.c\n" + "\n" + " svn status -u wc\n" + " M 965 wc/bar.c\n" + " * 965 wc/foo.c\n" + " A + - wc/qax.c\n" + " Status against revision: 981\n" + "\n" + " svn status --show-updates --verbose wc\n" + " M 965 938 kfogel wc/bar.c\n" + " * 965 922 sussman wc/foo.c\n" + " A + - 687 joe wc/qax.c\n" + " 965 687 joe wc/zig.c\n" + " Status against revision: 981\n" + "\n" + " svn status\n" + " M wc/bar.c\n" + " ! C wc/qaz.c\n" + " > local missing, incoming edit upon update\n" + " D wc/qax.c\n"), + { 'u', 'v', 'N', opt_depth, 'q', opt_no_ignore, opt_incremental, opt_xml, + opt_ignore_externals, opt_changelist}, + {{'q', N_("don't print unversioned items")}} }, + + { "switch", svn_cl__switch, {"sw"}, N_ + ("Update the working copy to a different URL within the same repository.\n" + "usage: 1. switch URL[@PEGREV] [PATH]\n" + " 2. switch --relocate FROM-PREFIX TO-PREFIX [PATH...]\n" + "\n" + " 1. Update the working copy to mirror a new URL within the repository.\n" + " This behavior is similar to 'svn update', and is the way to\n" + " move a working copy to a branch or tag within the same repository.\n" + " If specified, PEGREV determines in which revision the target is first\n" + " looked up.\n" + "\n" + " If --force is used, unversioned obstructing paths in the working\n" + " copy do not automatically cause a failure if the switch attempts to\n" + " add the same path. If the obstructing path is the same type (file\n" + " or directory) as the corresponding path in the repository it becomes\n" + " versioned but its contents are left 'as-is' in the working copy.\n" + " This means that an obstructing directory's unversioned children may\n" + " also obstruct and become versioned. For files, any content differences\n" + " between the obstruction and the repository are treated like a local\n" + " modification to the working copy. All properties from the repository\n" + " are applied to the obstructing path.\n" + "\n" + " Use the --set-depth option to set a new working copy depth on the\n" + " targets of this operation.\n" + "\n" + " By default, Subversion will refuse to switch a working copy path to\n" + " a new URL with which it shares no common version control ancestry.\n" + " Use the '--ignore-ancestry' option to override this sanity check.\n" + "\n" + " 2. The '--relocate' option is deprecated. This syntax is equivalent to\n" + " 'svn relocate FROM-PREFIX TO-PREFIX [PATH]'.\n" + "\n" + " See also 'svn help update' for a list of possible characters\n" + " reporting the action taken.\n" + "\n" + " Examples:\n" + " svn switch ^/branches/1.x-release\n" + " svn switch --relocate http:// svn://\n" + " svn switch --relocate http://www.example.com/repo/project \\\n" + " svn://svn.example.com/repo/project\n"), + { 'r', 'N', opt_depth, opt_set_depth, 'q', opt_merge_cmd, opt_relocate, + opt_ignore_externals, opt_ignore_ancestry, opt_force, opt_accept} }, + + { "unlock", svn_cl__unlock, {0}, N_ + ("Unlock working copy paths or URLs.\n" + "usage: unlock TARGET...\n" + "\n" + " Use --force to break the lock.\n"), + { opt_targets, opt_force } }, + + { "update", svn_cl__update, {"up"}, N_ + ("Bring changes from the repository into the working copy.\n" + "usage: update [PATH...]\n" + "\n" + " If no revision is given, bring working copy up-to-date with HEAD rev.\n" + " Else synchronize working copy to revision given by -r.\n" + "\n" + " For each updated item a line will be printed with characters reporting\n" + " the action taken. These characters have the following meaning:\n" + "\n" + " A Added\n" + " D Deleted\n" + " U Updated\n" + " C Conflict\n" + " G Merged\n" + " E Existed\n" + " R Replaced\n" + "\n" + " Characters in the first column report about the item itself.\n" + " Characters in the second column report about properties of the item.\n" + " A 'B' in the third column signifies that the lock for the file has\n" + " been broken or stolen.\n" + " A 'C' in the fourth column indicates a tree conflict, while a 'C' in\n" + " the first and second columns indicate textual conflicts in files\n" + " and in property values, respectively.\n" + "\n" + " If --force is used, unversioned obstructing paths in the working\n" + " copy do not automatically cause a failure if the update attempts to\n" + " add the same path. If the obstructing path is the same type (file\n" + " or directory) as the corresponding path in the repository it becomes\n" + " versioned but its contents are left 'as-is' in the working copy.\n" + " This means that an obstructing directory's unversioned children may\n" + " also obstruct and become versioned. For files, any content differences\n" + " between the obstruction and the repository are treated like a local\n" + " modification to the working copy. All properties from the repository\n" + " are applied to the obstructing path. Obstructing paths are reported\n" + " in the first column with code 'E'.\n" + "\n" + " If the specified update target is missing from the working copy but its\n" + " immediate parent directory is present, checkout the target into its\n" + " parent directory at the specified depth. If --parents is specified,\n" + " create any missing parent directories of the target by checking them\n" + " out, too, at depth=empty.\n" + "\n" + " Use the --set-depth option to set a new working copy depth on the\n" + " targets of this operation.\n"), + {'r', 'N', opt_depth, opt_set_depth, 'q', opt_merge_cmd, opt_force, + opt_ignore_externals, opt_changelist, opt_editor_cmd, opt_accept, + opt_parents} }, + + { "upgrade", svn_cl__upgrade, {0}, N_ + ("Upgrade the metadata storage format for a working copy.\n" + "usage: upgrade [WCPATH...]\n" + "\n" + " Local modifications are preserved.\n"), + { 'q' } }, + + { NULL, NULL, {0}, NULL, {0} } +}; + + +/* Version compatibility check */ +static svn_error_t * +check_lib_versions(void) +{ + static const svn_version_checklist_t checklist[] = + { + { "svn_subr", svn_subr_version }, + { "svn_client", svn_client_version }, + { "svn_wc", svn_wc_version }, + { "svn_ra", svn_ra_version }, + { "svn_delta", svn_delta_version }, + { "svn_diff", svn_diff_version }, + { NULL, NULL } + }; + + SVN_VERSION_DEFINE(my_version); + return svn_ver_check_list(&my_version, checklist); +} + + +/* A flag to see if we've been cancelled by the client or not. */ +static volatile sig_atomic_t cancelled = FALSE; + +/* A signal handler to support cancellation. */ +static void +signal_handler(int signum) +{ + apr_signal(signum, SIG_IGN); + cancelled = TRUE; +} + +/* Our cancellation callback. */ +svn_error_t * +svn_cl__check_cancel(void *baton) +{ + if (cancelled) + return svn_error_create(SVN_ERR_CANCELLED, NULL, _("Caught signal")); + else + return SVN_NO_ERROR; +} + + +/*** Main. ***/ + +int +main(int argc, const char *argv[]) +{ + svn_error_t *err; + apr_allocator_t *allocator; + apr_pool_t *pool; + int opt_id; + apr_getopt_t *os; + svn_cl__opt_state_t opt_state = { 0, { 0 } }; + svn_client_ctx_t *ctx; + apr_array_header_t *received_opts; + int i; + const svn_opt_subcommand_desc2_t *subcommand = NULL; + const char *dash_m_arg = NULL, *dash_F_arg = NULL; + svn_cl__cmd_baton_t command_baton; + svn_auth_baton_t *ab; + svn_config_t *cfg_config; + svn_boolean_t descend = TRUE; + svn_boolean_t interactive_conflicts = FALSE; + apr_hash_t *changelists; + + /* Initialize the app. */ + if (svn_cmdline_init("svn", stderr) != EXIT_SUCCESS) + return EXIT_FAILURE; + + /* Create our top-level pool. Use a separate mutexless allocator, + * given this application is single threaded. + */ + if (apr_allocator_create(&allocator)) + return EXIT_FAILURE; + + apr_allocator_max_free_set(allocator, SVN_ALLOCATOR_RECOMMENDED_MAX_FREE); + + pool = svn_pool_create_ex(NULL, allocator); + apr_allocator_owner_set(allocator, pool); + + received_opts = apr_array_make(pool, SVN_OPT_MAX_OPTIONS, sizeof(int)); + + /* Check library versions */ + err = check_lib_versions(); + if (err) + return svn_cmdline_handle_exit_error(err, pool, "svn: "); + +#if defined(WIN32) || defined(__CYGWIN__) + /* Set the working copy administrative directory name. */ + if (getenv("SVN_ASP_DOT_NET_HACK")) + { + err = svn_wc_set_adm_dir("_svn", pool); + if (err) + return svn_cmdline_handle_exit_error(err, pool, "svn: "); + } +#endif + + /* Initialize the RA library. */ + err = svn_ra_initialize(pool); + if (err) + return svn_cmdline_handle_exit_error(err, pool, "svn: "); + + /* Init our changelists hash. */ + changelists = apr_hash_make(pool); + + /* Begin processing arguments. */ + opt_state.start_revision.kind = svn_opt_revision_unspecified; + opt_state.end_revision.kind = svn_opt_revision_unspecified; + opt_state.revision_ranges = + apr_array_make(pool, 0, sizeof(svn_opt_revision_range_t *)); + opt_state.depth = svn_depth_unknown; + opt_state.set_depth = svn_depth_unknown; + opt_state.accept_which = svn_cl__accept_unspecified; + opt_state.show_revs = svn_cl__show_revs_merged; + + /* No args? Show usage. */ + if (argc <= 1) + { + svn_cl__help(NULL, NULL, pool); + svn_pool_destroy(pool); + return EXIT_FAILURE; + } + + /* Else, parse options. */ + err = svn_cmdline__getopt_init(&os, argc, argv, pool); + if (err) + return svn_cmdline_handle_exit_error(err, pool, "svn: "); + + os->interleave = 1; + while (1) + { + const char *opt_arg; + const char *utf8_opt_arg; + + /* Parse the next option. */ + apr_status_t apr_err = apr_getopt_long(os, svn_cl__options, &opt_id, + &opt_arg); + if (APR_STATUS_IS_EOF(apr_err)) + break; + else if (apr_err) + { + svn_cl__help(NULL, NULL, pool); + svn_pool_destroy(pool); + return EXIT_FAILURE; + } + + /* Stash the option code in an array before parsing it. */ + APR_ARRAY_PUSH(received_opts, int) = opt_id; + + switch (opt_id) { + case 'l': + { + err = svn_cstring_atoi(&opt_state.limit, opt_arg); + if (err) + { + err = svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, err, + _("Non-numeric limit argument given")); + return svn_cmdline_handle_exit_error(err, pool, "svn: "); + } + if (opt_state.limit <= 0) + { + err = svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL, + _("Argument to --limit must be positive")); + return svn_cmdline_handle_exit_error(err, pool, "svn: "); + } + } + break; + case 'm': + /* Note that there's no way here to detect if the log message + contains a zero byte -- if it does, then opt_arg will just + be shorter than the user intended. Oh well. */ + opt_state.message = apr_pstrdup(pool, opt_arg); + dash_m_arg = opt_arg; + break; + case 'c': + { + apr_array_header_t *change_revs = + svn_cstring_split(opt_arg, ", \n\r\t\v", TRUE, pool); + + if (opt_state.old_target) + { + err = svn_error_create + (SVN_ERR_CL_ARG_PARSING_ERROR, NULL, + _("Can't specify -c with --old")); + return svn_cmdline_handle_exit_error(err, pool, "svn: "); + } + + for (i = 0; i < change_revs->nelts; i++) + { + char *end; + svn_revnum_t changeno, changeno_end; + svn_opt_revision_range_t *range; + const char *change_str = + APR_ARRAY_IDX(change_revs, i, const char *); + const char *s = change_str; + svn_boolean_t is_negative; + + /* Check for a leading minus to allow "-c -r42". + * The is_negative flag is used to handle "-c -42" and "-c -r42". + * The "-c r-42" case is handled by strtol() returning a + * negative number. */ + is_negative = (*s == '-'); + if (is_negative) + s++; + + /* Allow any number of 'r's to prefix a revision number. */ + while (*s == 'r') + s++; + changeno = changeno_end = strtol(s, &end, 10); + if (end != s && *end == '-') + { + if (changeno < 0 || is_negative) + { + err = svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, + NULL, + _("Negative number in range (%s)" + " not supported with -c"), + change_str); + return svn_cmdline_handle_exit_error(err, pool, "svn: "); + } + s = end + 1; + while (*s == 'r') + s++; + changeno_end = strtol(s, &end, 10); + } + if (end == change_str || *end != '\0') + { + err = svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, + _("Non-numeric change argument (%s) " + "given to -c"), change_str); + return svn_cmdline_handle_exit_error(err, pool, "svn: "); + } + + if (changeno == 0) + { + err = svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, + _("There is no change 0")); + return svn_cmdline_handle_exit_error(err, pool, "svn: "); + } + + if (is_negative) + changeno = -changeno; + + /* Figure out the range: + -c N -> -r N-1:N + -c -N -> -r N:N-1 + -c M-N -> -r M-1:N for M < N + -c M-N -> -r M:N-1 for M > N + -c -M-N -> error (too confusing/no valid use case) + */ + if (changeno > 0) + { + if (changeno <= changeno_end) + changeno--; + else + changeno_end--; + } + else + { + changeno = -changeno; + changeno_end = changeno - 1; + } + + range = apr_palloc(pool, sizeof(*range)); + range->start.value.number = changeno; + range->end.value.number = changeno_end; + + opt_state.used_change_arg = TRUE; + range->start.kind = svn_opt_revision_number; + range->end.kind = svn_opt_revision_number; + APR_ARRAY_PUSH(opt_state.revision_ranges, + svn_opt_revision_range_t *) = range; + } + } + break; + case 'r': + opt_state.used_revision_arg = TRUE; + if (svn_opt_parse_revision_to_range(opt_state.revision_ranges, + opt_arg, pool) != 0) + { + err = svn_utf_cstring_to_utf8(&utf8_opt_arg, opt_arg, pool); + if (! err) + err = svn_error_createf + (SVN_ERR_CL_ARG_PARSING_ERROR, NULL, + _("Syntax error in revision argument '%s'"), + utf8_opt_arg); + return svn_cmdline_handle_exit_error(err, pool, "svn: "); + } + break; + case 'v': + opt_state.verbose = TRUE; + break; + case 'u': + opt_state.update = TRUE; + break; + case 'h': + case '?': + opt_state.help = TRUE; + break; + case 'q': + opt_state.quiet = TRUE; + break; + case opt_incremental: + opt_state.incremental = TRUE; + break; + case 'F': + err = svn_utf_cstring_to_utf8(&utf8_opt_arg, opt_arg, pool); + if (! err) + err = svn_stringbuf_from_file2(&(opt_state.filedata), + utf8_opt_arg, pool); + if (err) + return svn_cmdline_handle_exit_error(err, pool, "svn: "); + dash_F_arg = opt_arg; + break; + case opt_targets: + { + svn_stringbuf_t *buffer, *buffer_utf8; + + /* We need to convert to UTF-8 now, even before we divide + the targets into an array, because otherwise we wouldn't + know what delimiter to use for svn_cstring_split(). */ + + err = svn_utf_cstring_to_utf8(&utf8_opt_arg, opt_arg, pool); + + if (! err) + err = svn_stringbuf_from_file2(&buffer, utf8_opt_arg, pool); + if (! err) + err = svn_utf_stringbuf_to_utf8(&buffer_utf8, buffer, pool); + if (err) + return svn_cmdline_handle_exit_error(err, pool, "svn: "); + opt_state.targets = svn_cstring_split(buffer_utf8->data, "\n\r", + TRUE, pool); + } + break; + case opt_force: + opt_state.force = TRUE; + break; + case opt_force_log: + opt_state.force_log = TRUE; + break; + case opt_dry_run: + opt_state.dry_run = TRUE; + break; + case opt_revprop: + opt_state.revprop = TRUE; + break; + case 'R': + opt_state.depth = svn_depth_infinity; + break; + case 'N': + descend = FALSE; + break; + case opt_depth: + err = svn_utf_cstring_to_utf8(&utf8_opt_arg, opt_arg, pool); + if (err) + return svn_cmdline_handle_exit_error + (svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, + _("Error converting depth " + "from locale to UTF-8")), pool, "svn: "); + opt_state.depth = svn_depth_from_word(utf8_opt_arg); + if (opt_state.depth == svn_depth_unknown + || opt_state.depth == svn_depth_exclude) + { + return svn_cmdline_handle_exit_error + (svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, + _("'%s' is not a valid depth; try " + "'empty', 'files', 'immediates', " + "or 'infinity'"), + utf8_opt_arg), pool, "svn: "); + } + break; + case opt_set_depth: + err = svn_utf_cstring_to_utf8(&utf8_opt_arg, opt_arg, pool); + if (err) + return svn_cmdline_handle_exit_error + (svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, + _("Error converting depth " + "from locale to UTF-8")), pool, "svn: "); + opt_state.set_depth = svn_depth_from_word(utf8_opt_arg); + /* svn_depth_exclude is okay for --set-depth. */ + if (opt_state.set_depth == svn_depth_unknown) + { + return svn_cmdline_handle_exit_error + (svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, + _("'%s' is not a valid depth; try " + "'exclude', 'empty', 'files', " + "'immediates', or 'infinity'"), + utf8_opt_arg), pool, "svn: "); + } + break; + case opt_version: + opt_state.version = TRUE; + break; + case opt_auth_username: + err = svn_utf_cstring_to_utf8(&opt_state.auth_username, + opt_arg, pool); + if (err) + return svn_cmdline_handle_exit_error(err, pool, "svn: "); + break; + case opt_auth_password: + err = svn_utf_cstring_to_utf8(&opt_state.auth_password, + opt_arg, pool); + if (err) + return svn_cmdline_handle_exit_error(err, pool, "svn: "); + break; + case opt_encoding: + opt_state.encoding = apr_pstrdup(pool, opt_arg); + break; + case opt_xml: + opt_state.xml = TRUE; + break; + case opt_stop_on_copy: + opt_state.stop_on_copy = TRUE; + break; + case opt_strict: + opt_state.strict = TRUE; + break; + case opt_no_ignore: + opt_state.no_ignore = TRUE; + break; + case opt_no_auth_cache: + opt_state.no_auth_cache = TRUE; + break; + case opt_non_interactive: + opt_state.non_interactive = TRUE; + break; + case opt_trust_server_cert: + opt_state.trust_server_cert = TRUE; + break; + case opt_no_diff_deleted: + opt_state.no_diff_deleted = TRUE; + break; + case opt_show_copies_as_adds: + opt_state.show_copies_as_adds = TRUE; + break; + case opt_notice_ancestry: + opt_state.notice_ancestry = TRUE; + break; + case opt_ignore_ancestry: + opt_state.ignore_ancestry = TRUE; + break; + case opt_ignore_externals: + opt_state.ignore_externals = TRUE; + break; + case opt_relocate: + opt_state.relocate = TRUE; + break; + case 'x': + err = svn_utf_cstring_to_utf8(&opt_state.extensions, opt_arg, pool); + if (err) + return svn_cmdline_handle_exit_error(err, pool, "svn: "); + break; + case opt_diff_cmd: + opt_state.diff_cmd = apr_pstrdup(pool, opt_arg); + break; + case opt_merge_cmd: + opt_state.merge_cmd = apr_pstrdup(pool, opt_arg); + break; + case opt_record_only: + opt_state.record_only = TRUE; + break; + case opt_editor_cmd: + opt_state.editor_cmd = apr_pstrdup(pool, opt_arg); + break; + case opt_old_cmd: + if (opt_state.used_change_arg) + { + err = svn_error_create + (SVN_ERR_CL_ARG_PARSING_ERROR, NULL, + _("Can't specify -c with --old")); + return svn_cmdline_handle_exit_error(err, pool, "svn: "); + } + opt_state.old_target = apr_pstrdup(pool, opt_arg); + break; + case opt_new_cmd: + opt_state.new_target = apr_pstrdup(pool, opt_arg); + break; + case opt_config_dir: + { + const char *path_utf8; + err = svn_utf_cstring_to_utf8(&path_utf8, opt_arg, pool); + if (err) + return svn_cmdline_handle_exit_error(err, pool, "svn: "); + opt_state.config_dir = svn_dirent_internal_style(path_utf8, pool); + } + break; + case opt_config_options: + if (!opt_state.config_options) + opt_state.config_options = + apr_array_make(pool, 1, + sizeof(svn_cmdline__config_argument_t*)); + + err = svn_utf_cstring_to_utf8(&opt_arg, opt_arg, pool); + if (!err) + err = svn_cmdline__parse_config_option(opt_state.config_options, + opt_arg, pool); + if (err) + return svn_cmdline_handle_exit_error(err, pool, "svn: "); + break; + case opt_autoprops: + opt_state.autoprops = TRUE; + break; + case opt_no_autoprops: + opt_state.no_autoprops = TRUE; + break; + case opt_native_eol: + if ( !strcmp("LF", opt_arg) || !strcmp("CR", opt_arg) || + !strcmp("CRLF", opt_arg)) + opt_state.native_eol = apr_pstrdup(pool, opt_arg); + else + { + err = svn_utf_cstring_to_utf8(&utf8_opt_arg, opt_arg, pool); + if (! err) + err = svn_error_createf + (SVN_ERR_CL_ARG_PARSING_ERROR, NULL, + _("Syntax error in native-eol argument '%s'"), + utf8_opt_arg); + return svn_cmdline_handle_exit_error(err, pool, "svn: "); + } + break; + case opt_no_unlock: + opt_state.no_unlock = TRUE; + break; + case opt_summarize: + opt_state.summarize = TRUE; + break; + case opt_remove: + opt_state.remove = TRUE; + break; + case opt_changelist: + opt_state.changelist = apr_pstrdup(pool, opt_arg); + if (opt_state.changelist[0] == '\0') + { + err = svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, + _("Changelist names must not be empty")); + return svn_cmdline_handle_exit_error(err, pool, "svn: "); + } + apr_hash_set(changelists, opt_state.changelist, + APR_HASH_KEY_STRING, (void *)1); + break; + case opt_keep_changelists: + opt_state.keep_changelists = TRUE; + break; + case opt_keep_local: + opt_state.keep_local = TRUE; + break; + case opt_with_all_revprops: + /* If --with-all-revprops is specified along with one or more + * --with-revprops options, --with-all-revprops takes precedence. */ + opt_state.all_revprops = TRUE; + break; + case opt_with_no_revprops: + opt_state.no_revprops = TRUE; + break; + case opt_with_revprop: + err = svn_opt_parse_revprop(&opt_state.revprop_table, opt_arg, pool); + if (err != SVN_NO_ERROR) + return svn_cmdline_handle_exit_error(err, pool, "svn: "); + break; + case opt_parents: + opt_state.parents = TRUE; + break; + case 'g': + opt_state.use_merge_history = TRUE; + break; + case opt_accept: + opt_state.accept_which = svn_cl__accept_from_word(opt_arg); + if (opt_state.accept_which == svn_cl__accept_invalid) + return svn_cmdline_handle_exit_error + (svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, + _("'%s' is not a valid --accept value"), + opt_arg), + pool, "svn: "); + break; + case opt_show_revs: + opt_state.show_revs = svn_cl__show_revs_from_word(opt_arg); + if (opt_state.show_revs == svn_cl__show_revs_invalid) + return svn_cmdline_handle_exit_error + (svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, + _("'%s' is not a valid --show-revs value"), + opt_arg), + pool, "svn: "); + break; + case opt_reintegrate: + opt_state.reintegrate = TRUE; + break; + case opt_strip: + { + err = svn_cstring_atoi(&opt_state.strip, opt_arg); + if (err) + { + err = svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, err, + _("Invalid strip count '%s'"), opt_arg); + return svn_cmdline_handle_exit_error(err, pool, "svn: "); + } + if (opt_state.strip < 0) + { + err = svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL, + _("Argument to --strip must be positive")); + return svn_cmdline_handle_exit_error(err, pool, "svn: "); + } + } + break; + case opt_ignore_keywords: + opt_state.ignore_keywords = TRUE; + break; + case opt_reverse_diff: + opt_state.reverse_diff = TRUE; + break; + case opt_ignore_whitespace: + opt_state.ignore_whitespace = TRUE; + break; + case opt_diff: + opt_state.show_diff = TRUE; + break; + case opt_internal_diff: + opt_state.internal_diff = TRUE; + break; + case opt_use_git_diff_format: + opt_state.use_git_diff_format = TRUE; + break; + case opt_allow_mixed_revisions: + opt_state.allow_mixed_rev = TRUE; + break; + default: + /* Hmmm. Perhaps this would be a good place to squirrel away + opts that commands like svn diff might need. Hmmm indeed. */ + break; + } + } + + /* Turn our hash of changelists into an array of unique ones. */ + err = svn_hash_keys(&(opt_state.changelists), changelists, pool); + if (err) + return svn_cmdline_handle_exit_error(err, pool, "svn: "); + + /* ### This really belongs in libsvn_client. The trouble is, + there's no one place there to run it from, no + svn_client_init(). We'd have to add it to all the public + functions that a client might call. It's unmaintainable to do + initialization from within libsvn_client itself, but it seems + burdensome to demand that all clients call svn_client_init() + before calling any other libsvn_client function... On the other + hand, the alternative is effectively to demand that they call + svn_config_ensure() instead, so maybe we should have a generic + init function anyway. Thoughts? */ + err = svn_config_ensure(opt_state.config_dir, pool); + if (err) + return svn_cmdline_handle_exit_error(err, pool, "svn: "); + + /* If the user asked for help, then the rest of the arguments are + the names of subcommands to get help on (if any), or else they're + just typos/mistakes. Whatever the case, the subcommand to + actually run is svn_cl__help(). */ + if (opt_state.help) + subcommand = svn_opt_get_canonical_subcommand2(svn_cl__cmd_table, "help"); + + /* If we're not running the `help' subcommand, then look for a + subcommand in the first argument. */ + if (subcommand == NULL) + { + if (os->ind >= os->argc) + { + if (opt_state.version) + { + /* Use the "help" subcommand to handle the "--version" option. */ + static const svn_opt_subcommand_desc2_t pseudo_cmd = + { "--version", svn_cl__help, {0}, "", + {opt_version, /* must accept its own option */ + 'q', /* brief output */ + opt_config_dir /* all commands accept this */ + } }; + + subcommand = &pseudo_cmd; + } + else + { + svn_error_clear + (svn_cmdline_fprintf(stderr, pool, + _("Subcommand argument required\n"))); + svn_cl__help(NULL, NULL, pool); + svn_pool_destroy(pool); + return EXIT_FAILURE; + } + } + else + { + const char *first_arg = os->argv[os->ind++]; + subcommand = svn_opt_get_canonical_subcommand2(svn_cl__cmd_table, + first_arg); + if (subcommand == NULL) + { + const char *first_arg_utf8; + err = svn_utf_cstring_to_utf8(&first_arg_utf8, first_arg, pool); + if (err) + return svn_cmdline_handle_exit_error(err, pool, "svn: "); + svn_error_clear + (svn_cmdline_fprintf(stderr, pool, + _("Unknown command: '%s'\n"), + first_arg_utf8)); + svn_cl__help(NULL, NULL, pool); + svn_pool_destroy(pool); + return EXIT_FAILURE; + } + } + } + + /* Check that the subcommand wasn't passed any inappropriate options. */ + for (i = 0; i < received_opts->nelts; i++) + { + opt_id = APR_ARRAY_IDX(received_opts, i, int); + + /* All commands implicitly accept --help, so just skip over this + when we see it. Note that we don't want to include this option + in their "accepted options" list because it would be awfully + redundant to display it in every commands' help text. */ + if (opt_id == 'h' || opt_id == '?') + continue; + + if (! svn_opt_subcommand_takes_option3(subcommand, opt_id, + svn_cl__global_options)) + { + const char *optstr; + const apr_getopt_option_t *badopt = + svn_opt_get_option_from_code2(opt_id, svn_cl__options, + subcommand, pool); + svn_opt_format_option(&optstr, badopt, FALSE, pool); + if (subcommand->name[0] == '-') + svn_cl__help(NULL, NULL, pool); + else + svn_error_clear + (svn_cmdline_fprintf + (stderr, pool, _("Subcommand '%s' doesn't accept option '%s'\n" + "Type 'svn help %s' for usage.\n"), + subcommand->name, optstr, subcommand->name)); + svn_pool_destroy(pool); + return EXIT_FAILURE; + } + } + + /* Only merge and log support multiple revisions/revision ranges. */ + if (subcommand->cmd_func != svn_cl__merge + && subcommand->cmd_func != svn_cl__log) + { + if (opt_state.revision_ranges->nelts > 1) + { + err = svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, + _("Multiple revision arguments " + "encountered; can't specify -c twice, " + "or both -c and -r")); + return svn_cmdline_handle_exit_error(err, pool, "svn: "); + } + } + + /* Disallow simultaneous use of both --depth and --set-depth. */ + if ((opt_state.depth != svn_depth_unknown) + && (opt_state.set_depth != svn_depth_unknown)) + { + err = svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, + _("--depth and --set-depth are mutually " + "exclusive")); + return svn_cmdline_handle_exit_error(err, pool, "svn: "); + } + + /* Disallow simultaneous use of both --with-all-revprops and + --with-no-revprops. */ + if (opt_state.all_revprops && opt_state.no_revprops) + { + err = svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, + _("--with-all-revprops and --with-no-revprops " + "are mutually exclusive")); + return svn_cmdline_handle_exit_error(err, pool, "svn: "); + } + + /* Disallow simultaneous use of both --with-revprop and + --with-no-revprops. */ + if (opt_state.revprop_table && opt_state.no_revprops) + { + err = svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, + _("--with-revprop and --with-no-revprops " + "are mutually exclusive")); + return svn_cmdline_handle_exit_error(err, pool, "svn: "); + } + + /* Disallow simultaneous use of both -m and -F, when they are + both used to pass a commit message or lock comment. ('propset' + takes the property value, not a commit message, from -F.) + */ + if (opt_state.filedata && opt_state.message + && subcommand->cmd_func != svn_cl__propset) + { + err = svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, + _("--message (-m) and --file (-F) " + "are mutually exclusive")); + return svn_cmdline_handle_exit_error(err, pool, "svn: "); + } + + /* --trust-server-cert can only be used with --non-interactive */ + if (opt_state.trust_server_cert && !opt_state.non_interactive) + { + err = svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, + _("--trust-server-cert requires " + "--non-interactive")); + return svn_cmdline_handle_exit_error(err, pool, "svn: "); + } + + /* Disallow simultaneous use of both --diff-cmd and + --internal-diff. */ + if (opt_state.diff_cmd && opt_state.internal_diff) + { + err = svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, + _("--diff-cmd and --internal-diff " + "are mutually exclusive")); + return svn_cmdline_handle_exit_error(err, pool, "svn: "); + } + + /* Ensure that 'revision_ranges' has at least one item, and make + 'start_revision' and 'end_revision' match that item. */ + if (opt_state.revision_ranges->nelts == 0) + { + svn_opt_revision_range_t *range = apr_palloc(pool, sizeof(*range)); + range->start.kind = svn_opt_revision_unspecified; + range->end.kind = svn_opt_revision_unspecified; + APR_ARRAY_PUSH(opt_state.revision_ranges, + svn_opt_revision_range_t *) = range; + } + opt_state.start_revision = APR_ARRAY_IDX(opt_state.revision_ranges, 0, + svn_opt_revision_range_t *)->start; + opt_state.end_revision = APR_ARRAY_IDX(opt_state.revision_ranges, 0, + svn_opt_revision_range_t *)->end; + + /* Create a client context object. */ + command_baton.opt_state = &opt_state; + if ((err = svn_client_create_context(&ctx, pool))) + return svn_cmdline_handle_exit_error(err, pool, "svn: "); + command_baton.ctx = ctx; + + /* If we're running a command that could result in a commit, verify + that any log message we were given on the command line makes + sense (unless we've also been instructed not to care). */ + if ((! opt_state.force_log) + && (subcommand->cmd_func == svn_cl__commit + || subcommand->cmd_func == svn_cl__copy + || subcommand->cmd_func == svn_cl__delete + || subcommand->cmd_func == svn_cl__import + || subcommand->cmd_func == svn_cl__mkdir + || subcommand->cmd_func == svn_cl__move + || subcommand->cmd_func == svn_cl__lock + || subcommand->cmd_func == svn_cl__propedit)) + { + /* If the -F argument is a file that's under revision control, + that's probably not what the user intended. */ + if (dash_F_arg) + { + svn_node_kind_t kind; + const char *local_abspath; + const char *fname_utf8 = svn_dirent_internal_style(dash_F_arg, pool); + + err = svn_dirent_get_absolute(&local_abspath, fname_utf8, pool); + + if (!err) + { + err = svn_wc_read_kind(&kind, ctx->wc_ctx, local_abspath, FALSE, + pool); + + if (!err && kind != svn_node_none && kind != svn_node_unknown) + { + if (subcommand->cmd_func != svn_cl__lock) + { + err = svn_error_create( + SVN_ERR_CL_LOG_MESSAGE_IS_VERSIONED_FILE, NULL, + _("Log message file is a versioned file; " + "use '--force-log' to override")); + } + else + { + err = svn_error_create( + SVN_ERR_CL_LOG_MESSAGE_IS_VERSIONED_FILE, NULL, + _("Lock comment file is a versioned file; " + "use '--force-log' to override")); + } + return svn_cmdline_handle_exit_error(err, pool, "svn: "); + } + } + svn_error_clear(err); + } + + /* If the -m argument is a file at all, that's probably not what + the user intended. */ + if (dash_m_arg) + { + apr_finfo_t finfo; + if (apr_stat(&finfo, dash_m_arg, + APR_FINFO_MIN, pool) == APR_SUCCESS) + { + if (subcommand->cmd_func != svn_cl__lock) + { + err = svn_error_create + (SVN_ERR_CL_LOG_MESSAGE_IS_PATHNAME, NULL, + _("The log message is a pathname " + "(was -F intended?); use '--force-log' to override")); + } + else + { + err = svn_error_create + (SVN_ERR_CL_LOG_MESSAGE_IS_PATHNAME, NULL, + _("The lock comment is a pathname " + "(was -F intended?); use '--force-log' to override")); + } + return svn_cmdline_handle_exit_error(err, pool, "svn: "); + } + } + } + + /* Relocation is infinite-depth only. */ + if (opt_state.relocate) + { + if (opt_state.depth != svn_depth_unknown) + { + err = svn_error_create(SVN_ERR_CL_MUTUALLY_EXCLUSIVE_ARGS, NULL, + _("--relocate and --depth are mutually " + "exclusive")); + return svn_cmdline_handle_exit_error(err, pool, "svn: "); + } + if (! descend) + { + err = svn_error_create( + SVN_ERR_CL_MUTUALLY_EXCLUSIVE_ARGS, NULL, + _("--relocate and --non-recursive (-N) are mutually " + "exclusive")); + return svn_cmdline_handle_exit_error(err, pool, "svn: "); + } + } + + /* Only a few commands can accept a revision range; the rest can take at + most one revision number. */ + if (subcommand->cmd_func != svn_cl__blame + && subcommand->cmd_func != svn_cl__diff + && subcommand->cmd_func != svn_cl__log + && subcommand->cmd_func != svn_cl__merge) + { + if (opt_state.end_revision.kind != svn_opt_revision_unspecified) + { + err = svn_error_create(SVN_ERR_CLIENT_REVISION_RANGE, NULL, NULL); + return svn_cmdline_handle_exit_error(err, pool, "svn: "); + } + } + + /* -N has a different meaning depending on the command */ + if (descend == FALSE) + { + if (subcommand->cmd_func == svn_cl__status) + { + opt_state.depth = svn_depth_immediates; + } + else if (subcommand->cmd_func == svn_cl__revert + || subcommand->cmd_func == svn_cl__add + || subcommand->cmd_func == svn_cl__commit) + { + /* In pre-1.5 Subversion, some commands treated -N like + --depth=empty, so force that mapping here. Anyway, with + revert it makes sense to be especially conservative, + since revert can lose data. */ + opt_state.depth = svn_depth_empty; + } + else + { + opt_state.depth = svn_depth_files; + } + } + + err = svn_config_get_config(&(ctx->config), + opt_state.config_dir, pool); + if (err) + { + /* Fallback to default config if the config directory isn't readable + or is not a directory. */ + if (APR_STATUS_IS_EACCES(err->apr_err) + || SVN__APR_STATUS_IS_ENOTDIR(err->apr_err)) + { + svn_handle_warning2(stderr, err, "svn: "); + svn_error_clear(err); + } + else + return svn_cmdline_handle_exit_error(err, pool, "svn: "); + } + + cfg_config = apr_hash_get(ctx->config, SVN_CONFIG_CATEGORY_CONFIG, + APR_HASH_KEY_STRING); + + /* Update the options in the config */ + if (opt_state.config_options) + { + svn_error_clear( + svn_cmdline__apply_config_options(ctx->config, + opt_state.config_options, + "svn: ", "--config-option")); + } + + /* XXX: Only diff_cmd for now, overlay rest later and stop passing + opt_state altogether? */ + if (opt_state.diff_cmd) + svn_config_set(cfg_config, SVN_CONFIG_SECTION_HELPERS, + SVN_CONFIG_OPTION_DIFF_CMD, opt_state.diff_cmd); + if (opt_state.merge_cmd) + svn_config_set(cfg_config, SVN_CONFIG_SECTION_HELPERS, + SVN_CONFIG_OPTION_DIFF3_CMD, opt_state.merge_cmd); + if (opt_state.internal_diff) + svn_config_set(cfg_config, SVN_CONFIG_SECTION_HELPERS, + SVN_CONFIG_OPTION_DIFF_CMD, NULL); + + /* Check for mutually exclusive args --auto-props and --no-auto-props */ + if (opt_state.autoprops && opt_state.no_autoprops) + { + err = svn_error_create(SVN_ERR_CL_MUTUALLY_EXCLUSIVE_ARGS, NULL, + _("--auto-props and --no-auto-props are " + "mutually exclusive")); + return svn_cmdline_handle_exit_error(err, pool, "svn: "); + } + + /* The --reintegrate option is mutually exclusive with both + --ignore-ancestry and --record-only. */ + if (opt_state.reintegrate) + { + if (opt_state.ignore_ancestry) + { + if (opt_state.record_only) + { + err = svn_error_create(SVN_ERR_CL_MUTUALLY_EXCLUSIVE_ARGS, NULL, + _("--reintegrate cannot be used with " + "--ignore-ancestry or " + "--record-only")); + return svn_cmdline_handle_exit_error(err, pool, "svn: "); + } + else + { + err = svn_error_create(SVN_ERR_CL_MUTUALLY_EXCLUSIVE_ARGS, NULL, + _("--reintegrate cannot be used with " + "--ignore-ancestry")); + return svn_cmdline_handle_exit_error(err, pool, "svn: "); + } + } + else if (opt_state.record_only) + { + err = svn_error_create(SVN_ERR_CL_MUTUALLY_EXCLUSIVE_ARGS, NULL, + _("--reintegrate cannot be used with " + "--record-only")); + return svn_cmdline_handle_exit_error(err, pool, "svn: "); + } + } + + /* Update auto-props-enable option, and populate the MIME types map, + for add/import commands */ + if (subcommand->cmd_func == svn_cl__add + || subcommand->cmd_func == svn_cl__import) + { + const char *mimetypes_file; + svn_config_get(cfg_config, &mimetypes_file, + SVN_CONFIG_SECTION_MISCELLANY, + SVN_CONFIG_OPTION_MIMETYPES_FILE, FALSE); + if (mimetypes_file && *mimetypes_file) + { + if ((err = svn_io_parse_mimetypes_file(&(ctx->mimetypes_map), + mimetypes_file, pool))) + svn_handle_error2(err, stderr, TRUE, "svn: "); + } + + if (opt_state.autoprops) + { + svn_config_set_bool(cfg_config, SVN_CONFIG_SECTION_MISCELLANY, + SVN_CONFIG_OPTION_ENABLE_AUTO_PROPS, TRUE); + } + if (opt_state.no_autoprops) + { + svn_config_set_bool(cfg_config, SVN_CONFIG_SECTION_MISCELLANY, + SVN_CONFIG_OPTION_ENABLE_AUTO_PROPS, FALSE); + } + } + + /* Update the 'keep-locks' runtime option */ + if (opt_state.no_unlock) + svn_config_set_bool(cfg_config, SVN_CONFIG_SECTION_MISCELLANY, + SVN_CONFIG_OPTION_NO_UNLOCK, TRUE); + + /* Set the log message callback function. Note that individual + subcommands will populate the ctx->log_msg_baton3. */ + ctx->log_msg_func3 = svn_cl__get_log_message; + + /* Set up the notifier. */ + if (((subcommand->cmd_func != svn_cl__status) && !opt_state.quiet) + || ((subcommand->cmd_func == svn_cl__status) && !opt_state.xml)) + { + err = svn_cl__get_notifier(&ctx->notify_func2, &ctx->notify_baton2, + FALSE, pool); + if (err) + return svn_cmdline_handle_exit_error(err, pool, "svn: "); + } + + /* Set up our cancellation support. */ + ctx->cancel_func = svn_cl__check_cancel; + apr_signal(SIGINT, signal_handler); +#ifdef SIGBREAK + /* SIGBREAK is a Win32 specific signal generated by ctrl-break. */ + apr_signal(SIGBREAK, signal_handler); +#endif +#ifdef SIGHUP + apr_signal(SIGHUP, signal_handler); +#endif +#ifdef SIGTERM + apr_signal(SIGTERM, signal_handler); +#endif + +#ifdef SIGPIPE + /* Disable SIGPIPE generation for the platforms that have it. */ + apr_signal(SIGPIPE, SIG_IGN); +#endif + +#ifdef SIGXFSZ + /* Disable SIGXFSZ generation for the platforms that have it, otherwise + * working with large files when compiled against an APR that doesn't have + * large file support will crash the program, which is uncool. */ + apr_signal(SIGXFSZ, SIG_IGN); +#endif + + /* Set up Authentication stuff. */ + if ((err = svn_cmdline_create_auth_baton(&ab, + opt_state.non_interactive, + opt_state.auth_username, + opt_state.auth_password, + opt_state.config_dir, + opt_state.no_auth_cache, + opt_state.trust_server_cert, + cfg_config, + ctx->cancel_func, + ctx->cancel_baton, + pool))) + svn_handle_error2(err, stderr, TRUE, "svn: "); + + ctx->auth_baton = ab; + + /* Set up conflict resolution callback. */ + if ((err = svn_config_get_bool(cfg_config, &interactive_conflicts, + SVN_CONFIG_SECTION_MISCELLANY, + SVN_CONFIG_OPTION_INTERACTIVE_CONFLICTS, + TRUE))) /* ### interactivity on by default. + we can change this. */ + svn_handle_error2(err, stderr, TRUE, "svn: "); + + if ((opt_state.accept_which == svn_cl__accept_unspecified + && (!interactive_conflicts || opt_state.non_interactive)) + || opt_state.accept_which == svn_cl__accept_postpone) + { + /* If no --accept option at all and we're non-interactive, we're + leaving the conflicts behind, so don't need the callback. Same if + the user said to postpone. */ + ctx->conflict_func = NULL; + ctx->conflict_baton = NULL; + } + else + { + svn_cmdline_prompt_baton_t *pb = apr_palloc(pool, sizeof(*pb)); + pb->cancel_func = ctx->cancel_func; + pb->cancel_baton = ctx->cancel_baton; + + if (opt_state.non_interactive) + { + if (opt_state.accept_which == svn_cl__accept_edit) + return svn_cmdline_handle_exit_error + (svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, + _("--accept=%s incompatible with" + " --non-interactive"), SVN_CL__ACCEPT_EDIT), + pool, "svn: "); + if (opt_state.accept_which == svn_cl__accept_launch) + return svn_cmdline_handle_exit_error + (svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, + _("--accept=%s incompatible with" + " --non-interactive"), + SVN_CL__ACCEPT_LAUNCH), + pool, "svn: "); + } + + ctx->conflict_func = svn_cl__conflict_handler; + ctx->conflict_baton = svn_cl__conflict_baton_make( + opt_state.accept_which, + ctx->config, + opt_state.editor_cmd, + pb, + pool); + } + + /* And now we finally run the subcommand. */ + err = (*subcommand->cmd_func)(os, &command_baton, pool); + if (err) + { + /* For argument-related problems, suggest using the 'help' + subcommand. */ + if (err->apr_err == SVN_ERR_CL_INSUFFICIENT_ARGS + || err->apr_err == SVN_ERR_CL_ARG_PARSING_ERROR) + { + err = svn_error_quick_wrap(err, + _("Try 'svn help' for more info")); + } + if (err->apr_err == SVN_ERR_WC_UPGRADE_REQUIRED) + { + err = svn_error_quick_wrap(err, + _("Please see the 'svn upgrade' command")); + } + + /* Issue #3014: + * Don't print anything on broken pipes. The pipe was likely + * closed by the process at the other end. We expect that + * process to perform error reporting as necessary. + * + * ### This assumes that there is only one error in a chain for + * ### SVN_ERR_IO_PIPE_WRITE_ERROR. See svn_cmdline_fputs(). */ + if (err->apr_err != SVN_ERR_IO_PIPE_WRITE_ERROR) + svn_handle_error2(err, stderr, FALSE, "svn: "); + + /* Tell the user about 'svn cleanup' if any error on the stack + was about locked working copies. */ + if (svn_error_find_cause(err, SVN_ERR_WC_LOCKED)) + svn_error_clear(svn_cmdline_fputs(_("svn: run 'svn cleanup' to " + "remove locks (type 'svn help " + "cleanup' for details)\n"), + stderr, pool)); + + svn_error_clear(err); + svn_pool_destroy(pool); + return EXIT_FAILURE; + } + else + { + /* Ensure that stdout is flushed, so the user will see any write errors. + This makes sure that output is not silently lost. */ + err = svn_cmdline_fflush(stdout); + if (err) + return svn_cmdline_handle_exit_error(err, pool, "svn: "); + + svn_pool_destroy(pool); + return EXIT_SUCCESS; + } +} diff --git a/subversion/svn/merge-cmd.c b/subversion/svn/merge-cmd.c new file mode 100644 index 0000000..57aad9f --- /dev/null +++ b/subversion/svn/merge-cmd.c @@ -0,0 +1,383 @@ +/* + * merge-cmd.c -- Merging changes into a working copy. + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + +/* ==================================================================== */ + + + +/*** Includes. ***/ + +#include "svn_client.h" +#include "svn_dirent_uri.h" +#include "svn_path.h" +#include "svn_error.h" +#include "svn_types.h" +#include "cl.h" + +#include "svn_private_config.h" + + +/*** Code. ***/ + + +/* This implements the `svn_opt_subcommand_t' interface. */ +svn_error_t * +svn_cl__merge(apr_getopt_t *os, + void *baton, + apr_pool_t *pool) +{ + svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state; + svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx; + apr_array_header_t *targets; + const char *sourcepath1 = NULL, *sourcepath2 = NULL, *targetpath = ""; + svn_boolean_t two_sources_specified = TRUE; + svn_error_t *err; + svn_opt_revision_t first_range_start, first_range_end, peg_revision1, + peg_revision2; + apr_array_header_t *options, *ranges_to_merge = opt_state->revision_ranges; + + /* Merge doesn't support specifying a revision or revision range + when using --reintegrate. */ + if (opt_state->reintegrate + && opt_state->start_revision.kind != svn_opt_revision_unspecified) + { + return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, + _("-r and -c can't be used with --reintegrate")); + } + + SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os, + opt_state->targets, + ctx, FALSE, pool)); + + /* For now, we require at least one source. That may change in + future versions of Subversion, for example if we have support for + negated mergeinfo. See this IRC conversation: + + <bhuvan> kfogel: yeah, i think you are correct; we should + specify the source url + + <kfogel> bhuvan: I'll change the help output and propose for + backport. Thanks. + + <bhuvan> kfogel: np; while we are at it, 'svn merge' simply + returns nothing; i think we should say: """svn: Not + enough arguments provided; try 'svn help' for more + info""" + + <kfogel> good idea + + <kfogel> (in the future, 'svn merge' might actually do + something, but that's all the more reason to make + sure it errors now) + + <cmpilato> actually, i'm pretty sure 'svn merge' does something + + <cmpilato> it says "please merge any unmerged changes from + myself to myself." + + <cmpilato> :-) + + <kfogel> har har + + <cmpilato> kfogel: i was serious. + + <kfogel> cmpilato: urrr, uh. Is that meaningful? Is there + ever a reason for a user to run it? + + <cmpilato> kfogel: not while we don't have support for negated + mergeinfo. + + <kfogel> cmpilato: do you concur that until it does something + useful it should error? + + <cmpilato> kfogel: yup. + + <kfogel> cool + */ + if (targets->nelts < 1) + { + return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0, + _("Merge source required")); + } + else /* Parse at least one, and possible two, sources. */ + { + SVN_ERR(svn_opt_parse_path(&peg_revision1, &sourcepath1, + APR_ARRAY_IDX(targets, 0, const char *), + pool)); + if (targets->nelts >= 2) + SVN_ERR(svn_opt_parse_path(&peg_revision2, &sourcepath2, + APR_ARRAY_IDX(targets, 1, const char *), + pool)); + } + + /* We could have one or two sources. Deliberately written to stay + correct even if we someday permit implied merge source. */ + if (targets->nelts <= 1) + { + two_sources_specified = FALSE; + } + else if (targets->nelts == 2) + { + if (svn_path_is_url(sourcepath1) && !svn_path_is_url(sourcepath2)) + two_sources_specified = FALSE; + } + + if (opt_state->revision_ranges->nelts > 0) + { + first_range_start = APR_ARRAY_IDX(opt_state->revision_ranges, 0, + svn_opt_revision_range_t *)->start; + first_range_end = APR_ARRAY_IDX(opt_state->revision_ranges, 0, + svn_opt_revision_range_t *)->end; + } + else + { + first_range_start.kind = first_range_end.kind = + svn_opt_revision_unspecified; + } + + /* If revision_ranges has at least one real range at this point, then + we know the user must have used the '-r' and/or '-c' switch(es). + This means we're *not* doing two distinct sources. */ + if (first_range_start.kind != svn_opt_revision_unspecified) + { + /* A revision *range* is required. */ + if (first_range_end.kind == svn_opt_revision_unspecified) + return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0, + _("Second revision required")); + + two_sources_specified = FALSE; + } + + if (! two_sources_specified) /* TODO: Switch order of if */ + { + if (targets->nelts > 2) + return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, + _("Too many arguments given")); + + /* Set the default value for unspecified paths and peg revision. */ + if (targets->nelts == 0) + { + peg_revision1.kind = svn_opt_revision_head; + } + else + { + /* targets->nelts is 1 ("svn merge SOURCE") or 2 ("svn merge + SOURCE WCPATH") here. */ + sourcepath2 = sourcepath1; + + if (peg_revision1.kind == svn_opt_revision_unspecified) + peg_revision1.kind = svn_path_is_url(sourcepath1) + ? svn_opt_revision_head : svn_opt_revision_working; + + if (targets->nelts == 2) + { + targetpath = APR_ARRAY_IDX(targets, 1, const char *); + if (svn_path_is_url(targetpath)) + return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, + _("Cannot specify a revision range " + "with two URLs")); + } + } + } + else /* using @rev syntax */ + { + if (targets->nelts < 2) + return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, NULL, NULL); + if (targets->nelts > 3) + return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, + _("Too many arguments given")); + + first_range_start = peg_revision1; + first_range_end = peg_revision2; + + /* Catch 'svn merge wc_path1 wc_path2 [target]' without explicit + revisions--since it ignores local modifications it may not do what + the user expects. Forcing the user to specify a repository + revision should avoid any confusion. */ + if ((first_range_start.kind == svn_opt_revision_unspecified + && ! svn_path_is_url(sourcepath1)) + || + (first_range_end.kind == svn_opt_revision_unspecified + && ! svn_path_is_url(sourcepath2))) + return svn_error_create + (SVN_ERR_CLIENT_BAD_REVISION, 0, + _("A working copy merge source needs an explicit revision")); + + /* Default peg revisions to each URL's youngest revision. */ + if (first_range_start.kind == svn_opt_revision_unspecified) + first_range_start.kind = svn_opt_revision_head; + if (first_range_end.kind == svn_opt_revision_unspecified) + first_range_end.kind = svn_opt_revision_head; + + /* Decide where to apply the delta (defaulting to "."). */ + if (targets->nelts == 3) + targetpath = APR_ARRAY_IDX(targets, 2, const char *); + } + + /* If no targetpath was specified, see if we can infer it from the + sourcepaths. */ + if (sourcepath1 && sourcepath2 && strcmp(targetpath, "") == 0) + { + /* If the sourcepath is a URL, it can only refer to a target in + the current working directory. However, if the sourcepath is + a local path, it can refer to a target somewhere deeper in + the directory structure. */ + if (svn_path_is_url(sourcepath1)) + { + const char *sp1_basename = svn_uri_basename(sourcepath1, pool); + const char *sp2_basename = svn_uri_basename(sourcepath2, pool); + + if (strcmp(sp1_basename, sp2_basename) == 0) + { + svn_node_kind_t kind; + + SVN_ERR(svn_io_check_path(sp1_basename, &kind, pool)); + if (kind == svn_node_file) + { + targetpath = sp1_basename; + } + } + } + else if (strcmp(sourcepath1, sourcepath2) == 0) + { + svn_node_kind_t kind; + const char *decoded_path = svn_path_uri_decode(sourcepath1, pool); + SVN_ERR(svn_io_check_path(decoded_path, &kind, pool)); + if (kind == svn_node_file) + { + targetpath = decoded_path; + } + } + } + + if (opt_state->extensions) + options = svn_cstring_split(opt_state->extensions, " \t\n\r", TRUE, pool); + else + options = NULL; + + /* More input validation. */ + if (opt_state->reintegrate) + { + if (opt_state->depth != svn_depth_unknown) + return svn_error_create(SVN_ERR_CL_MUTUALLY_EXCLUSIVE_ARGS, NULL, + _("--depth cannot be used with " + "--reintegrate")); + + if (opt_state->force) + return svn_error_create(SVN_ERR_CL_MUTUALLY_EXCLUSIVE_ARGS, NULL, + _("--force cannot be used with " + "--reintegrate")); + + if (two_sources_specified) + return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, + _("--reintegrate can only be used with " + "a single merge source")); + if (opt_state->allow_mixed_rev) + return svn_error_create(SVN_ERR_CL_MUTUALLY_EXCLUSIVE_ARGS, NULL, + _("--allow-mixed-revisions cannot be used " + "with --reintegrate")); + } + + if (! two_sources_specified) /* TODO: Switch order of if */ + { + /* If we don't have a source, use the target as the source. */ + if (! sourcepath1) + sourcepath1 = targetpath; + + /* If we don't have at least one valid revision range, pick a + good one that spans the entire set of revisions on our + source. */ + if ((first_range_start.kind == svn_opt_revision_unspecified) + && (first_range_end.kind == svn_opt_revision_unspecified)) + { + svn_opt_revision_range_t *range = apr_pcalloc(pool, sizeof(*range)); + ranges_to_merge = apr_array_make(pool, 1, sizeof(range)); + range->start.kind = svn_opt_revision_number; + range->start.value.number = 1; + range->end = peg_revision1; + APR_ARRAY_PUSH(ranges_to_merge, svn_opt_revision_range_t *) = range; + } + + if (opt_state->reintegrate) + err = svn_client_merge_reintegrate(sourcepath1, + &peg_revision1, + targetpath, + opt_state->dry_run, + options, ctx, pool); + else + err = svn_client_merge_peg4(sourcepath1, + ranges_to_merge, + &peg_revision1, + targetpath, + opt_state->depth, + opt_state->ignore_ancestry, + opt_state->force, + opt_state->record_only, + opt_state->dry_run, + opt_state->allow_mixed_rev, + options, + ctx, + pool); + } + else + { + if (svn_path_is_url(sourcepath1) != svn_path_is_url(sourcepath2)) + return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, + _("Merge sources must both be " + "either paths or URLs")); + err = svn_client_merge4(sourcepath1, + &first_range_start, + sourcepath2, + &first_range_end, + targetpath, + opt_state->depth, + opt_state->ignore_ancestry, + opt_state->force, + opt_state->record_only, + opt_state->dry_run, + opt_state->allow_mixed_rev, + options, + ctx, + pool); + } + + if (! opt_state->quiet) + SVN_ERR(svn_cl__print_conflict_stats(ctx->notify_baton2, pool)); + + if (err) + { + if(err->apr_err == SVN_ERR_CLIENT_INVALID_MERGEINFO_NO_MERGETRACKING) + { + err = svn_error_quick_wrap( + err, + _("Merge tracking not possible, use --ignore-ancestry or\n" + "fix invalid mergeinfo in target with 'svn propset'")); + } + else if (! opt_state->reintegrate) + { + return svn_cl__may_need_force(err); + } + } + + return svn_error_trace(err); +} diff --git a/subversion/svn/mergeinfo-cmd.c b/subversion/svn/mergeinfo-cmd.c new file mode 100644 index 0000000..5875e2d --- /dev/null +++ b/subversion/svn/mergeinfo-cmd.c @@ -0,0 +1,135 @@ +/* + * mergeinfo-cmd.c -- Query merge-relative info. + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + +/* ==================================================================== */ + + + +/*** Includes. ***/ + +#include "svn_pools.h" +#include "svn_client.h" +#include "svn_cmdline.h" +#include "svn_path.h" +#include "svn_error.h" +#include "svn_error_codes.h" +#include "svn_types.h" +#include "cl.h" + +#include "svn_private_config.h" + + +/*** Code. ***/ + +/* Implements the svn_log_entry_receiver_t interface. */ +static svn_error_t * +print_log_rev(void *baton, + svn_log_entry_t *log_entry, + apr_pool_t *pool) +{ + if (log_entry->non_inheritable) + SVN_ERR(svn_cmdline_printf(pool, "r%ld*\n", log_entry->revision)); + else + SVN_ERR(svn_cmdline_printf(pool, "r%ld\n", log_entry->revision)); + + return SVN_NO_ERROR; +} + +/* This implements the `svn_opt_subcommand_t' interface. */ +svn_error_t * +svn_cl__mergeinfo(apr_getopt_t *os, + void *baton, + apr_pool_t *pool) +{ + svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state; + svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx; + apr_array_header_t *targets; + const char *source, *target; + svn_opt_revision_t src_peg_revision, tgt_peg_revision; + /* Default to depth empty. */ + svn_depth_t depth = opt_state->depth == svn_depth_unknown + ? svn_depth_empty : opt_state->depth; + + SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os, + opt_state->targets, + ctx, FALSE, pool)); + + /* We expect a single source URL followed by a single target -- + nothing more, nothing less. */ + if (targets->nelts < 1) + return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, + _("Not enough arguments given")); + if (targets->nelts > 2) + return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, + _("Too many arguments given")); + + /* Parse the SOURCE-URL[@REV] argument. */ + SVN_ERR(svn_opt_parse_path(&src_peg_revision, &source, + APR_ARRAY_IDX(targets, 0, const char *), pool)); + + /* Parse the TARGET[@REV] argument (if provided). */ + if (targets->nelts == 2) + { + SVN_ERR(svn_opt_parse_path(&tgt_peg_revision, &target, + APR_ARRAY_IDX(targets, 1, const char *), + pool)); + } + else + { + target = ""; + tgt_peg_revision.kind = svn_opt_revision_unspecified; + } + + /* If no peg-rev was attached to the source URL, assume HEAD. */ + if (src_peg_revision.kind == svn_opt_revision_unspecified) + src_peg_revision.kind = svn_opt_revision_head; + + /* If no peg-rev was attached to a URL target, then assume HEAD; if + no peg-rev was attached to a non-URL target, then assume BASE. */ + if (tgt_peg_revision.kind == svn_opt_revision_unspecified) + { + if (svn_path_is_url(target)) + tgt_peg_revision.kind = svn_opt_revision_head; + else + tgt_peg_revision.kind = svn_opt_revision_base; + } + + /* Do the real work, depending on the requested data flavor. */ + if (opt_state->show_revs == svn_cl__show_revs_merged) + { + SVN_ERR(svn_client_mergeinfo_log(TRUE, target, &tgt_peg_revision, + source, &src_peg_revision, + print_log_rev, NULL, + TRUE, depth, NULL, ctx, + pool)); + } + else if (opt_state->show_revs == svn_cl__show_revs_eligible) + { + SVN_ERR(svn_client_mergeinfo_log(FALSE, target, &tgt_peg_revision, + source, &src_peg_revision, + print_log_rev, NULL, + TRUE, depth, NULL, ctx, + pool)); + } + return SVN_NO_ERROR; +} diff --git a/subversion/svn/mkdir-cmd.c b/subversion/svn/mkdir-cmd.c new file mode 100644 index 0000000..64cb4f9 --- /dev/null +++ b/subversion/svn/mkdir-cmd.c @@ -0,0 +1,104 @@ +/* + * mkdir-cmd.c -- Subversion mkdir command + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + +/* ==================================================================== */ + + + +/*** Includes. ***/ + +#include "svn_pools.h" +#include "svn_client.h" +#include "svn_error.h" +#include "svn_path.h" +#include "cl.h" + +#include "svn_private_config.h" + + +/*** Code. ***/ + +/* This implements the `svn_opt_subcommand_t' interface. */ +svn_error_t * +svn_cl__mkdir(apr_getopt_t *os, + void *baton, + apr_pool_t *pool) +{ + svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state; + svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx; + apr_array_header_t *targets; + svn_error_t *err; + + SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os, + opt_state->targets, + ctx, FALSE, pool)); + + if (! targets->nelts) + return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0, NULL); + + SVN_ERR(svn_cl__assert_homogeneous_target_type(targets)); + + if (! svn_path_is_url(APR_ARRAY_IDX(targets, 0, const char *))) + { + ctx->log_msg_func3 = NULL; + if (opt_state->message || opt_state->filedata || opt_state->revprop_table) + { + return svn_error_create + (SVN_ERR_CL_UNNECESSARY_LOG_MESSAGE, NULL, + _("Local, non-commit operations do not take a log message " + "or revision properties")); + } + } + else + { + SVN_ERR(svn_cl__make_log_msg_baton(&(ctx->log_msg_baton3), opt_state, + NULL, ctx->config, pool)); + } + + SVN_ERR(svn_cl__eat_peg_revisions(&targets, targets, pool)); + + err = svn_client_mkdir4(targets, opt_state->parents, + opt_state->revprop_table, + (opt_state->quiet ? NULL : svn_cl__print_commit_info), + NULL, ctx, pool); + + if (ctx->log_msg_func3) + err = svn_cl__cleanup_log_msg(ctx->log_msg_baton3, err, pool); + + if (err) + { + if (err->apr_err == APR_EEXIST) + return svn_error_quick_wrap + (err, _("Try 'svn add' or 'svn add --non-recursive' instead?")); + else if (!(opt_state->parents) && + (APR_STATUS_IS_ENOENT(err->apr_err) || /* in wc */ + err->apr_err == SVN_ERR_FS_NOT_DIRECTORY || + err->apr_err == SVN_ERR_FS_NOT_FOUND /* all ra layers */)) + return svn_error_quick_wrap + (err, _("Try 'svn mkdir --parents' instead?")); + else + return svn_error_trace(err); + } + + return SVN_NO_ERROR; +} diff --git a/subversion/svn/move-cmd.c b/subversion/svn/move-cmd.c new file mode 100644 index 0000000..795870a --- /dev/null +++ b/subversion/svn/move-cmd.c @@ -0,0 +1,101 @@ +/* + * move-cmd.c -- Subversion move command + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + +/* ==================================================================== */ + + + +/*** Includes. ***/ + +#include "svn_client.h" +#include "svn_error.h" +#include "svn_path.h" +#include "cl.h" + +#include "svn_private_config.h" + + + +/*** Code. ***/ + +/* This implements the `svn_opt_subcommand_t' interface. */ +svn_error_t * +svn_cl__move(apr_getopt_t *os, + void *baton, + apr_pool_t *pool) +{ + svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state; + svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx; + apr_array_header_t *targets; + const char *dst_path; + svn_error_t *err; + + SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os, + opt_state->targets, + ctx, TRUE, pool)); + + if (targets->nelts < 2) + return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0, NULL); + + if (opt_state->start_revision.kind != svn_opt_revision_unspecified + && opt_state->start_revision.kind != svn_opt_revision_head) + { + return svn_error_create + (SVN_ERR_UNSUPPORTED_FEATURE, NULL, + _("Cannot specify revisions (except HEAD) with move operations")); + } + + dst_path = APR_ARRAY_IDX(targets, targets->nelts - 1, const char *); + apr_array_pop(targets); + + if (! svn_path_is_url(dst_path)) + { + ctx->log_msg_func3 = NULL; + if (opt_state->message || opt_state->filedata || opt_state->revprop_table) + return svn_error_create + (SVN_ERR_CL_UNNECESSARY_LOG_MESSAGE, NULL, + _("Local, non-commit operations do not take a log message " + "or revision properties")); + } + + if (ctx->log_msg_func3) + SVN_ERR(svn_cl__make_log_msg_baton(&(ctx->log_msg_baton3), opt_state, + NULL, ctx->config, pool)); + + SVN_ERR(svn_cl__eat_peg_revisions(&targets, targets, pool)); + + err = svn_client_move6(targets, dst_path, + TRUE, opt_state->parents, opt_state->revprop_table, + (opt_state->quiet ? NULL : svn_cl__print_commit_info), + NULL, ctx, pool); + + if (err) + err = svn_cl__may_need_force(err); + + if (ctx->log_msg_func3) + SVN_ERR(svn_cl__cleanup_log_msg(ctx->log_msg_baton3, err, pool)); + else if (err) + return svn_error_trace(err); + + return SVN_NO_ERROR; +} diff --git a/subversion/svn/notify.c b/subversion/svn/notify.c new file mode 100644 index 0000000..a785e53 --- /dev/null +++ b/subversion/svn/notify.c @@ -0,0 +1,1083 @@ +/* + * notify.c: feedback handlers for cmdline client. + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + +/* ==================================================================== */ + + + +/*** Includes. ***/ + +#define APR_WANT_STDIO +#define APR_WANT_STRFUNC +#include <apr_want.h> + +#include "svn_cmdline.h" +#include "svn_pools.h" +#include "svn_dirent_uri.h" +#include "svn_path.h" +#include "cl.h" + +#include "svn_private_config.h" + + +/* Baton for notify and friends. */ +struct notify_baton +{ + svn_boolean_t received_some_change; + svn_boolean_t is_checkout; + svn_boolean_t is_export; + svn_boolean_t is_wc_to_repos_copy; + svn_boolean_t suppress_summary_lines; + svn_boolean_t sent_first_txdelta; + svn_boolean_t in_external; + svn_boolean_t had_print_error; /* Used to not keep printing error messages + when we've already had one print error. */ + + /* Conflict stats for update and merge. */ + unsigned int text_conflicts; + unsigned int prop_conflicts; + unsigned int tree_conflicts; + unsigned int skipped_paths; + + /* The cwd, for use in decomposing absolute paths. */ + const char *path_prefix; +}; + + +svn_error_t * +svn_cl__print_conflict_stats(void *notify_baton, apr_pool_t *pool) +{ + struct notify_baton *nb = notify_baton; + unsigned int text_conflicts; + unsigned int prop_conflicts; + unsigned int tree_conflicts; + unsigned int skipped_paths; + + text_conflicts = nb->text_conflicts; + prop_conflicts = nb->prop_conflicts; + tree_conflicts = nb->tree_conflicts; + skipped_paths = nb->skipped_paths; + + if (text_conflicts > 0 || prop_conflicts > 0 + || tree_conflicts > 0 || skipped_paths > 0) + SVN_ERR(svn_cmdline_printf(pool, "%s", _("Summary of conflicts:\n"))); + + if (text_conflicts > 0) + SVN_ERR(svn_cmdline_printf + (pool, _(" Text conflicts: %u\n"), text_conflicts)); + + if (prop_conflicts > 0) + SVN_ERR(svn_cmdline_printf + (pool, _(" Property conflicts: %u\n"), prop_conflicts)); + + if (tree_conflicts > 0) + SVN_ERR(svn_cmdline_printf + (pool, _(" Tree conflicts: %u\n"), tree_conflicts)); + + if (skipped_paths > 0) + SVN_ERR(svn_cmdline_printf + (pool, _(" Skipped paths: %u\n"), skipped_paths)); + + return SVN_NO_ERROR; +} + +/* This implements `svn_wc_notify_func2_t'. + * NOTE: This function can't fail, so we just ignore any print errors. */ +static void +notify(void *baton, const svn_wc_notify_t *n, apr_pool_t *pool) +{ + struct notify_baton *nb = baton; + char statchar_buf[5] = " "; + const char *path_local; + svn_error_t *err; + + if (n->url) + path_local = n->url; + else + { + if (n->path_prefix) + path_local = svn_cl__local_style_skip_ancestor(n->path_prefix, n->path, + pool); + else /* skip nb->path_prefix, if it's non-null */ + path_local = svn_cl__local_style_skip_ancestor(nb->path_prefix, n->path, + pool); + } + + switch (n->action) + { + case svn_wc_notify_skip: + nb->skipped_paths++; + if (n->content_state == svn_wc_notify_state_missing) + { + if ((err = svn_cmdline_printf + (pool, _("Skipped missing target: '%s'\n"), + path_local))) + goto print_error; + } + else if (n->content_state == svn_wc_notify_state_source_missing) + { + if ((err = svn_cmdline_printf + (pool, _("Skipped target: '%s' -- copy-source is missing\n"), + path_local))) + goto print_error; + } + else + { + if ((err = svn_cmdline_printf + (pool, _("Skipped '%s'\n"), path_local))) + goto print_error; + } + break; + case svn_wc_notify_update_skip_obstruction: + nb->skipped_paths++; + if ((err = svn_cmdline_printf( + pool, _("Skipped '%s' -- An obstructing working copy was found\n"), + path_local))) + goto print_error; + break; + case svn_wc_notify_update_skip_working_only: + nb->skipped_paths++; + if ((err = svn_cmdline_printf( + pool, _("Skipped '%s' -- Has no versioned parent\n"), + path_local))) + goto print_error; + break; + case svn_wc_notify_update_skip_access_denied: + nb->skipped_paths++; + if ((err = svn_cmdline_printf( + pool, _("Skipped '%s' -- Access denied\n"), + path_local))) + goto print_error; + break; + case svn_wc_notify_skip_conflicted: + nb->skipped_paths++; + if ((err = svn_cmdline_printf( + pool, _("Skipped '%s' -- Node remains in conflict\n"), + path_local))) + goto print_error; + break; + case svn_wc_notify_update_delete: + case svn_wc_notify_exclude: + nb->received_some_change = TRUE; + if ((err = svn_cmdline_printf(pool, "D %s\n", path_local))) + goto print_error; + break; + + case svn_wc_notify_update_external_removed: + nb->received_some_change = TRUE; + if (n->err && n->err->message) + { + if ((err = svn_cmdline_printf(pool, "Removed external '%s': %s\n", + path_local, n->err->message))) + goto print_error; + } + else + { + if ((err = svn_cmdline_printf(pool, "Removed external '%s'\n", + path_local))) + goto print_error; + } + break; + + case svn_wc_notify_update_replace: + nb->received_some_change = TRUE; + if ((err = svn_cmdline_printf(pool, "R %s\n", path_local))) + goto print_error; + break; + + case svn_wc_notify_update_add: + nb->received_some_change = TRUE; + if (n->content_state == svn_wc_notify_state_conflicted) + { + nb->text_conflicts++; + if ((err = svn_cmdline_printf(pool, "C %s\n", path_local))) + goto print_error; + } + else + { + if ((err = svn_cmdline_printf(pool, "A %s\n", path_local))) + goto print_error; + } + break; + + case svn_wc_notify_exists: + nb->received_some_change = TRUE; + if (n->content_state == svn_wc_notify_state_conflicted) + { + nb->text_conflicts++; + statchar_buf[0] = 'C'; + } + else + statchar_buf[0] = 'E'; + + if (n->prop_state == svn_wc_notify_state_conflicted) + { + nb->prop_conflicts++; + statchar_buf[1] = 'C'; + } + else if (n->prop_state == svn_wc_notify_state_merged) + statchar_buf[1] = 'G'; + + if ((err = svn_cmdline_printf(pool, "%s %s\n", statchar_buf, path_local))) + goto print_error; + break; + + case svn_wc_notify_restore: + if ((err = svn_cmdline_printf(pool, _("Restored '%s'\n"), + path_local))) + goto print_error; + break; + + case svn_wc_notify_revert: + if ((err = svn_cmdline_printf(pool, _("Reverted '%s'\n"), + path_local))) + goto print_error; + break; + + case svn_wc_notify_failed_revert: + if (( err = svn_cmdline_printf(pool, _("Failed to revert '%s' -- " + "try updating instead.\n"), + path_local))) + goto print_error; + break; + + case svn_wc_notify_resolved: + if ((err = svn_cmdline_printf(pool, + _("Resolved conflicted state of '%s'\n"), + path_local))) + goto print_error; + break; + + case svn_wc_notify_add: + /* We *should* only get the MIME_TYPE if PATH is a file. If we + do get it, and the mime-type is not textual, note that this + is a binary addition. */ + if (n->mime_type && (svn_mime_type_is_binary(n->mime_type))) + { + if ((err = svn_cmdline_printf(pool, "A (bin) %s\n", + path_local))) + goto print_error; + } + else + { + if ((err = svn_cmdline_printf(pool, "A %s\n", + path_local))) + goto print_error; + } + break; + + case svn_wc_notify_delete: + nb->received_some_change = TRUE; + if ((err = svn_cmdline_printf(pool, "D %s\n", + path_local))) + goto print_error; + break; + + case svn_wc_notify_patch: + { + nb->received_some_change = TRUE; + if (n->content_state == svn_wc_notify_state_conflicted) + { + nb->text_conflicts++; + statchar_buf[0] = 'C'; + } + else if (n->kind == svn_node_file) + { + if (n->content_state == svn_wc_notify_state_merged) + statchar_buf[0] = 'G'; + else if (n->content_state == svn_wc_notify_state_changed) + statchar_buf[0] = 'U'; + } + + if (n->prop_state == svn_wc_notify_state_conflicted) + { + nb->prop_conflicts++; + statchar_buf[1] = 'C'; + } + else if (n->prop_state == svn_wc_notify_state_changed) + statchar_buf[1] = 'U'; + + if (statchar_buf[0] != ' ' || statchar_buf[1] != ' ') + { + if ((err = svn_cmdline_printf(pool, "%s %s\n", + statchar_buf, path_local))) + goto print_error; + } + } + break; + + case svn_wc_notify_patch_applied_hunk: + nb->received_some_change = TRUE; + if (n->hunk_original_start != n->hunk_matched_line) + { + apr_uint64_t off; + const char *s; + const char *minus; + + if (n->hunk_matched_line > n->hunk_original_start) + { + off = n->hunk_matched_line - n->hunk_original_start; + minus = ""; + } + else + { + off = n->hunk_original_start - n->hunk_matched_line; + minus = "-"; + } + + /* ### We're creating the localized strings without + * ### APR_INT64_T_FMT since it isn't translator-friendly */ + if (n->hunk_fuzz) + { + + if (n->prop_name) + { + s = _("> applied hunk ## -%lu,%lu +%lu,%lu ## " + "with offset %s"); + + err = svn_cmdline_printf(pool, + apr_pstrcat(pool, s, + "%"APR_UINT64_T_FMT + " and fuzz %lu (%s)\n", + (char *)NULL), + n->hunk_original_start, + n->hunk_original_length, + n->hunk_modified_start, + n->hunk_modified_length, + minus, off, n->hunk_fuzz, + n->prop_name); + } + else + { + s = _("> applied hunk @@ -%lu,%lu +%lu,%lu @@ " + "with offset %s"); + + err = svn_cmdline_printf(pool, + apr_pstrcat(pool, s, + "%"APR_UINT64_T_FMT + " and fuzz %lu\n", + (char *)NULL), + n->hunk_original_start, + n->hunk_original_length, + n->hunk_modified_start, + n->hunk_modified_length, + minus, off, n->hunk_fuzz); + } + + if (err) + goto print_error; + } + else + { + + if (n->prop_name) + { + s = _("> applied hunk ## -%lu,%lu +%lu,%lu ## " + "with offset %s"); + err = svn_cmdline_printf(pool, + apr_pstrcat(pool, s, + "%"APR_UINT64_T_FMT" (%s)\n", + (char *)NULL), + n->hunk_original_start, + n->hunk_original_length, + n->hunk_modified_start, + n->hunk_modified_length, + minus, off, n->prop_name); + } + else + { + s = _("> applied hunk @@ -%lu,%lu +%lu,%lu @@ " + "with offset %s"); + err = svn_cmdline_printf(pool, + apr_pstrcat(pool, s, + "%"APR_UINT64_T_FMT"\n", + (char *)NULL), + n->hunk_original_start, + n->hunk_original_length, + n->hunk_modified_start, + n->hunk_modified_length, + minus, off); + } + + if (err) + goto print_error; + } + } + else if (n->hunk_fuzz) + { + if (n->prop_name) + err = svn_cmdline_printf(pool, + _("> applied hunk ## -%lu,%lu +%lu,%lu ## " + "with fuzz %lu (%s)\n"), + n->hunk_original_start, + n->hunk_original_length, + n->hunk_modified_start, + n->hunk_modified_length, + n->hunk_fuzz, + n->prop_name); + else + err = svn_cmdline_printf(pool, + _("> applied hunk @@ -%lu,%lu +%lu,%lu @@ " + "with fuzz %lu\n"), + n->hunk_original_start, + n->hunk_original_length, + n->hunk_modified_start, + n->hunk_modified_length, + n->hunk_fuzz); + if (err) + goto print_error; + + } + break; + + case svn_wc_notify_patch_rejected_hunk: + nb->received_some_change = TRUE; + + if (n->prop_name) + err = svn_cmdline_printf(pool, + _("> rejected hunk " + "## -%lu,%lu +%lu,%lu ## (%s)\n"), + n->hunk_original_start, + n->hunk_original_length, + n->hunk_modified_start, + n->hunk_modified_length, + n->prop_name); + else + err = svn_cmdline_printf(pool, + _("> rejected hunk " + "@@ -%lu,%lu +%lu,%lu @@\n"), + n->hunk_original_start, + n->hunk_original_length, + n->hunk_modified_start, + n->hunk_modified_length); + if (err) + goto print_error; + break; + + case svn_wc_notify_patch_hunk_already_applied: + nb->received_some_change = TRUE; + if (n->prop_name) + err = svn_cmdline_printf(pool, + _("> hunk " + "## -%lu,%lu +%lu,%lu ## " + "already applied (%s)\n"), + n->hunk_original_start, + n->hunk_original_length, + n->hunk_modified_start, + n->hunk_modified_length, + n->prop_name); + else + err = svn_cmdline_printf(pool, + _("> hunk " + "@@ -%lu,%lu +%lu,%lu @@ " + "already applied\n"), + n->hunk_original_start, + n->hunk_original_length, + n->hunk_modified_start, + n->hunk_modified_length); + if (err) + goto print_error; + break; + + case svn_wc_notify_update_update: + case svn_wc_notify_merge_record_info: + { + if (n->content_state == svn_wc_notify_state_conflicted) + { + nb->text_conflicts++; + statchar_buf[0] = 'C'; + } + else if (n->kind == svn_node_file) + { + if (n->content_state == svn_wc_notify_state_merged) + statchar_buf[0] = 'G'; + else if (n->content_state == svn_wc_notify_state_changed) + statchar_buf[0] = 'U'; + } + + if (n->prop_state == svn_wc_notify_state_conflicted) + { + nb->prop_conflicts++; + statchar_buf[1] = 'C'; + } + else if (n->prop_state == svn_wc_notify_state_merged) + statchar_buf[1] = 'G'; + else if (n->prop_state == svn_wc_notify_state_changed) + statchar_buf[1] = 'U'; + + if (n->lock_state == svn_wc_notify_lock_state_unlocked) + statchar_buf[2] = 'B'; + + if (statchar_buf[0] != ' ' || statchar_buf[1] != ' ') + nb->received_some_change = TRUE; + + if (statchar_buf[0] != ' ' || statchar_buf[1] != ' ' + || statchar_buf[2] != ' ') + { + if ((err = svn_cmdline_printf(pool, "%s %s\n", + statchar_buf, path_local))) + goto print_error; + } + } + break; + + case svn_wc_notify_update_external: + /* Remember that we're now "inside" an externals definition. */ + nb->in_external = TRUE; + + /* Currently this is used for checkouts and switches too. If we + want different output, we'll have to add new actions. */ + if ((err = svn_cmdline_printf(pool, + _("\nFetching external item into '%s':\n"), + path_local))) + goto print_error; + break; + + case svn_wc_notify_failed_external: + /* If we are currently inside the handling of an externals + definition, then we can simply present n->err as a warning + and feel confident that after this, we aren't handling that + externals definition any longer. */ + if (nb->in_external) + { + svn_handle_warning2(stderr, n->err, "svn: "); + nb->in_external = FALSE; + if ((err = svn_cmdline_printf(pool, "\n"))) + goto print_error; + } + /* Otherwise, we'll just print two warnings. Why? Because + svn_handle_warning2() only shows the single "best message", + but we have two pretty important ones: that the external at + '/some/path' didn't pan out, and then the more specific + reason why (from n->err). */ + else + { + svn_error_t *warn_err = + svn_error_createf(SVN_ERR_BASE, NULL, + _("Error handling externals definition for '%s':"), + path_local); + svn_handle_warning2(stderr, warn_err, "svn: "); + svn_error_clear(warn_err); + svn_handle_warning2(stderr, n->err, "svn: "); + } + break; + + case svn_wc_notify_update_started: + if (! (nb->suppress_summary_lines || + nb->in_external || + nb->is_checkout || + nb->is_export)) + { + if ((err = svn_cmdline_printf(pool, _("Updating '%s':\n"), + path_local))) + goto print_error; + } + break; + + case svn_wc_notify_update_completed: + { + if (! nb->suppress_summary_lines) + { + if (SVN_IS_VALID_REVNUM(n->revision)) + { + if (nb->is_export) + { + if ((err = svn_cmdline_printf + (pool, nb->in_external + ? _("Exported external at revision %ld.\n") + : _("Exported revision %ld.\n"), + n->revision))) + goto print_error; + } + else if (nb->is_checkout) + { + if ((err = svn_cmdline_printf + (pool, nb->in_external + ? _("Checked out external at revision %ld.\n") + : _("Checked out revision %ld.\n"), + n->revision))) + goto print_error; + } + else + { + if (nb->received_some_change) + { + nb->received_some_change = FALSE; + if ((err = svn_cmdline_printf + (pool, nb->in_external + ? _("Updated external to revision %ld.\n") + : _("Updated to revision %ld.\n"), + n->revision))) + goto print_error; + } + else + { + if ((err = svn_cmdline_printf + (pool, nb->in_external + ? _("External at revision %ld.\n") + : _("At revision %ld.\n"), + n->revision))) + goto print_error; + } + } + } + else /* no revision */ + { + if (nb->is_export) + { + if ((err = svn_cmdline_printf + (pool, nb->in_external + ? _("External export complete.\n") + : _("Export complete.\n")))) + goto print_error; + } + else if (nb->is_checkout) + { + if ((err = svn_cmdline_printf + (pool, nb->in_external + ? _("External checkout complete.\n") + : _("Checkout complete.\n")))) + goto print_error; + } + else + { + if ((err = svn_cmdline_printf + (pool, nb->in_external + ? _("External update complete.\n") + : _("Update complete.\n")))) + goto print_error; + } + } + } + } + + if (nb->in_external) + { + nb->in_external = FALSE; + if ((err = svn_cmdline_printf(pool, "\n"))) + goto print_error; + } + break; + + case svn_wc_notify_status_external: + if ((err = svn_cmdline_printf + (pool, _("\nPerforming status on external item at '%s':\n"), + path_local))) + goto print_error; + break; + + case svn_wc_notify_status_completed: + if (SVN_IS_VALID_REVNUM(n->revision)) + if ((err = svn_cmdline_printf(pool, + _("Status against revision: %6ld\n"), + n->revision))) + goto print_error; + break; + + case svn_wc_notify_commit_modified: + /* xgettext: Align the %s's on this and the following 4 messages */ + if ((err = svn_cmdline_printf(pool, + nb->is_wc_to_repos_copy + ? _("Sending copy of %s\n") + : _("Sending %s\n"), + path_local))) + goto print_error; + break; + + case svn_wc_notify_commit_added: + case svn_wc_notify_commit_copied: + if (n->mime_type && svn_mime_type_is_binary(n->mime_type)) + { + if ((err = svn_cmdline_printf(pool, + nb->is_wc_to_repos_copy + ? _("Adding copy of (bin) %s\n") + : _("Adding (bin) %s\n"), + path_local))) + goto print_error; + } + else + { + if ((err = svn_cmdline_printf(pool, + nb->is_wc_to_repos_copy + ? _("Adding copy of %s\n") + : _("Adding %s\n"), + path_local))) + goto print_error; + } + break; + + case svn_wc_notify_commit_deleted: + if ((err = svn_cmdline_printf(pool, + nb->is_wc_to_repos_copy + ? _("Deleting copy of %s\n") + : _("Deleting %s\n"), + path_local))) + goto print_error; + break; + + case svn_wc_notify_commit_replaced: + case svn_wc_notify_commit_copied_replaced: + if ((err = svn_cmdline_printf(pool, + nb->is_wc_to_repos_copy + ? _("Replacing copy of %s\n") + : _("Replacing %s\n"), + path_local))) + goto print_error; + break; + + case svn_wc_notify_commit_postfix_txdelta: + if (! nb->sent_first_txdelta) + { + nb->sent_first_txdelta = TRUE; + if ((err = svn_cmdline_printf(pool, + _("Transmitting file data ")))) + goto print_error; + } + + if ((err = svn_cmdline_printf(pool, "."))) + goto print_error; + break; + + case svn_wc_notify_locked: + if ((err = svn_cmdline_printf(pool, _("'%s' locked by user '%s'.\n"), + path_local, n->lock->owner))) + goto print_error; + break; + + case svn_wc_notify_unlocked: + if ((err = svn_cmdline_printf(pool, _("'%s' unlocked.\n"), + path_local))) + goto print_error; + break; + + case svn_wc_notify_failed_lock: + case svn_wc_notify_failed_unlock: + svn_handle_warning2(stderr, n->err, "svn: "); + break; + + case svn_wc_notify_changelist_set: + if ((err = svn_cmdline_printf(pool, "A [%s] %s\n", + n->changelist_name, path_local))) + goto print_error; + break; + + case svn_wc_notify_changelist_clear: + case svn_wc_notify_changelist_moved: + if ((err = svn_cmdline_printf(pool, + "D [%s] %s\n", + n->changelist_name, path_local))) + goto print_error; + break; + + case svn_wc_notify_merge_begin: + if (n->merge_range == NULL) + err = svn_cmdline_printf(pool, + _("--- Merging differences between " + "repository URLs into '%s':\n"), + path_local); + else if (n->merge_range->start == n->merge_range->end - 1 + || n->merge_range->start == n->merge_range->end) + err = svn_cmdline_printf(pool, _("--- Merging r%ld into '%s':\n"), + n->merge_range->end, path_local); + else if (n->merge_range->start - 1 == n->merge_range->end) + err = svn_cmdline_printf(pool, + _("--- Reverse-merging r%ld into '%s':\n"), + n->merge_range->start, path_local); + else if (n->merge_range->start < n->merge_range->end) + err = svn_cmdline_printf(pool, + _("--- Merging r%ld through r%ld into " + "'%s':\n"), + n->merge_range->start + 1, + n->merge_range->end, path_local); + else /* n->merge_range->start > n->merge_range->end - 1 */ + err = svn_cmdline_printf(pool, + _("--- Reverse-merging r%ld through r%ld " + "into '%s':\n"), + n->merge_range->start, + n->merge_range->end + 1, path_local); + if (err) + goto print_error; + break; + + case svn_wc_notify_merge_record_info_begin: + if (!n->merge_range) + { + err = svn_cmdline_printf(pool, + _("--- Recording mergeinfo for merge " + "between repository URLs into '%s':\n"), + path_local); + } + else + { + if (n->merge_range->start == n->merge_range->end - 1 + || n->merge_range->start == n->merge_range->end) + err = svn_cmdline_printf( + pool, + _("--- Recording mergeinfo for merge of r%ld into '%s':\n"), + n->merge_range->end, path_local); + else if (n->merge_range->start - 1 == n->merge_range->end) + err = svn_cmdline_printf( + pool, + _("--- Recording mergeinfo for reverse merge of r%ld into '%s':\n"), + n->merge_range->start, path_local); + else if (n->merge_range->start < n->merge_range->end) + err = svn_cmdline_printf( + pool, + _("--- Recording mergeinfo for merge of r%ld through r%ld into '%s':\n"), + n->merge_range->start + 1, n->merge_range->end, path_local); + else /* n->merge_range->start > n->merge_range->end - 1 */ + err = svn_cmdline_printf( + pool, + _("--- Recording mergeinfo for reverse merge of r%ld through r%ld into '%s':\n"), + n->merge_range->start, n->merge_range->end + 1, path_local); + } + + if (err) + goto print_error; + break; + + case svn_wc_notify_merge_elide_info: + if ((err = svn_cmdline_printf(pool, + _("--- Eliding mergeinfo from '%s':\n"), + path_local))) + goto print_error; + break; + + case svn_wc_notify_foreign_merge_begin: + if (n->merge_range == NULL) + err = svn_cmdline_printf(pool, + _("--- Merging differences between " + "foreign repository URLs into '%s':\n"), + path_local); + else if (n->merge_range->start == n->merge_range->end - 1 + || n->merge_range->start == n->merge_range->end) + err = svn_cmdline_printf(pool, + _("--- Merging (from foreign repository) " + "r%ld into '%s':\n"), + n->merge_range->end, path_local); + else if (n->merge_range->start - 1 == n->merge_range->end) + err = svn_cmdline_printf(pool, + _("--- Reverse-merging (from foreign " + "repository) r%ld into '%s':\n"), + n->merge_range->start, path_local); + else if (n->merge_range->start < n->merge_range->end) + err = svn_cmdline_printf(pool, + _("--- Merging (from foreign repository) " + "r%ld through r%ld into '%s':\n"), + n->merge_range->start + 1, + n->merge_range->end, path_local); + else /* n->merge_range->start > n->merge_range->end - 1 */ + err = svn_cmdline_printf(pool, + _("--- Reverse-merging (from foreign " + "repository) r%ld through r%ld into " + "'%s':\n"), + n->merge_range->start, + n->merge_range->end + 1, path_local); + if (err) + goto print_error; + break; + + case svn_wc_notify_tree_conflict: + nb->tree_conflicts++; + if ((err = svn_cmdline_printf(pool, " C %s\n", path_local))) + goto print_error; + break; + + case svn_wc_notify_update_shadowed_add: + nb->received_some_change = TRUE; + if ((err = svn_cmdline_printf(pool, " A %s\n", path_local))) + goto print_error; + break; + + case svn_wc_notify_update_shadowed_update: + nb->received_some_change = TRUE; + if ((err = svn_cmdline_printf(pool, " U %s\n", path_local))) + goto print_error; + break; + + case svn_wc_notify_update_shadowed_delete: + nb->received_some_change = TRUE; + if ((err = svn_cmdline_printf(pool, " D %s\n", path_local))) + goto print_error; + break; + + case svn_wc_notify_property_modified: + case svn_wc_notify_property_added: + err = svn_cmdline_printf(pool, + _("property '%s' set on '%s'\n"), + n->prop_name, path_local); + if (err) + goto print_error; + break; + + case svn_wc_notify_property_deleted: + err = svn_cmdline_printf(pool, + _("property '%s' deleted from '%s'.\n"), + n->prop_name, path_local); + if (err) + goto print_error; + break; + + case svn_wc_notify_property_deleted_nonexistent: + err = svn_cmdline_printf(pool, + _("Attempting to delete nonexistent " + "property '%s' on '%s'\n"), n->prop_name, + path_local); + if (err) + goto print_error; + break; + + case svn_wc_notify_revprop_set: + err = svn_cmdline_printf(pool, + _("property '%s' set on repository revision %ld\n"), + n->prop_name, n->revision); + if (err) + goto print_error; + break; + + case svn_wc_notify_revprop_deleted: + err = svn_cmdline_printf(pool, + _("property '%s' deleted from repository revision %ld\n"), + n->prop_name, n->revision); + if (err) + goto print_error; + break; + + case svn_wc_notify_upgraded_path: + err = svn_cmdline_printf(pool, _("Upgraded '%s'\n"), path_local); + if (err) + goto print_error; + break; + + case svn_wc_notify_url_redirect: + err = svn_cmdline_printf(pool, _("Redirecting to URL '%s':\n"), + n->url); + if (err) + goto print_error; + break; + + case svn_wc_notify_path_nonexistent: + err = svn_cmdline_printf(pool, _("'%s' is not under version control"), + path_local); + if (err) + goto print_error; + break; + + default: + break; + } + + if ((err = svn_cmdline_fflush(stdout))) + goto print_error; + + return; + + print_error: + /* If we had no errors before, print this error to stderr. Else, don't print + anything. The user already knows there were some output errors, + so there is no point in flooding her with an error per notification. */ + if (!nb->had_print_error) + { + nb->had_print_error = TRUE; + /* Issue #3014: + * Don't print anything on broken pipes. The pipe was likely + * closed by the process at the other end. We expect that + * process to perform error reporting as necessary. + * + * ### This assumes that there is only one error in a chain for + * ### SVN_ERR_IO_PIPE_WRITE_ERROR. See svn_cmdline_fputs(). */ + if (err->apr_err != SVN_ERR_IO_PIPE_WRITE_ERROR) + svn_handle_error2(err, stderr, FALSE, "svn: "); + } + svn_error_clear(err); +} + + +svn_error_t * +svn_cl__get_notifier(svn_wc_notify_func2_t *notify_func_p, + void **notify_baton_p, + svn_boolean_t suppress_summary_lines, + apr_pool_t *pool) +{ + struct notify_baton *nb = apr_pcalloc(pool, sizeof(*nb)); + + nb->received_some_change = FALSE; + nb->sent_first_txdelta = FALSE; + nb->is_checkout = FALSE; + nb->is_export = FALSE; + nb->is_wc_to_repos_copy = FALSE; + nb->suppress_summary_lines = suppress_summary_lines; + nb->in_external = FALSE; + nb->had_print_error = FALSE; + nb->text_conflicts = 0; + nb->prop_conflicts = 0; + nb->tree_conflicts = 0; + nb->skipped_paths = 0; + SVN_ERR(svn_dirent_get_absolute(&nb->path_prefix, "", pool)); + + *notify_func_p = notify; + *notify_baton_p = nb; + return SVN_NO_ERROR; +} + +svn_error_t * +svn_cl__notifier_mark_checkout(void *baton) +{ + struct notify_baton *nb = baton; + + nb->is_checkout = TRUE; + return SVN_NO_ERROR; +} + +svn_error_t * +svn_cl__notifier_mark_export(void *baton) +{ + struct notify_baton *nb = baton; + + nb->is_export = TRUE; + return SVN_NO_ERROR; +} + +svn_error_t * +svn_cl__notifier_mark_wc_to_repos_copy(void *baton) +{ + struct notify_baton *nb = baton; + + nb->is_wc_to_repos_copy = TRUE; + return SVN_NO_ERROR; +} + +void +svn_cl__check_externals_failed_notify_wrapper(void *baton, + const svn_wc_notify_t *n, + apr_pool_t *pool) +{ + struct svn_cl__check_externals_failed_notify_baton *nwb = baton; + + if (n->action == svn_wc_notify_failed_external) + nwb->had_externals_error = TRUE; + + if (nwb->wrapped_func) + nwb->wrapped_func(nwb->wrapped_baton, n, pool); +} + diff --git a/subversion/svn/patch-cmd.c b/subversion/svn/patch-cmd.c new file mode 100644 index 0000000..24abb47 --- /dev/null +++ b/subversion/svn/patch-cmd.c @@ -0,0 +1,98 @@ +/* + * patch-cmd.c -- Apply changes to a working copy. + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + +/* ==================================================================== */ + + + +/*** Includes. ***/ + +#include "svn_client.h" +#include "svn_dirent_uri.h" +#include "svn_path.h" +#include "svn_error.h" +#include "svn_types.h" +#include "cl.h" + +#include "svn_private_config.h" + + +/*** Code. ***/ + + +/* This implements the `svn_opt_subcommand_t' interface. */ +svn_error_t * +svn_cl__patch(apr_getopt_t *os, + void *baton, + apr_pool_t *pool) +{ + svn_cl__opt_state_t *opt_state; + svn_client_ctx_t *ctx; + apr_array_header_t *targets; + const char *abs_patch_path; + const char *patch_path; + const char *abs_target_path; + const char *target_path; + + opt_state = ((svn_cl__cmd_baton_t *)baton)->opt_state; + ctx = ((svn_cl__cmd_baton_t *)baton)->ctx; + + SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os, + opt_state->targets, + ctx, FALSE, pool)); + SVN_ERR(svn_cl__eat_peg_revisions(&targets, targets, pool)); + + if (targets->nelts < 1) + return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0, NULL); + + if (targets->nelts > 2) + return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, 0, NULL); + + patch_path = APR_ARRAY_IDX(targets, 0, const char *); + + SVN_ERR(svn_cl__check_target_is_local_path(patch_path)); + + SVN_ERR(svn_dirent_get_absolute(&abs_patch_path, patch_path, pool)); + + if (targets->nelts == 1) + target_path = ""; /* "" is the canonical form of "." */ + else + { + target_path = APR_ARRAY_IDX(targets, 1, const char *); + + SVN_ERR(svn_cl__check_target_is_local_path(target_path)); + } + SVN_ERR(svn_dirent_get_absolute(&abs_target_path, target_path, pool)); + + SVN_ERR(svn_client_patch(abs_patch_path, abs_target_path, + opt_state->dry_run, opt_state->strip, + opt_state->reverse_diff, + opt_state->ignore_whitespace, + TRUE, NULL, NULL, ctx, pool)); + + + if (! opt_state->quiet) + SVN_ERR(svn_cl__print_conflict_stats(ctx->notify_baton2, pool)); + + return SVN_NO_ERROR; +} diff --git a/subversion/svn/propdel-cmd.c b/subversion/svn/propdel-cmd.c new file mode 100644 index 0000000..28c9597 --- /dev/null +++ b/subversion/svn/propdel-cmd.c @@ -0,0 +1,103 @@ +/* + * propdel-cmd.c -- Remove property from files/dirs + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + +/* ==================================================================== */ + + + +/*** Includes. ***/ + +#include "svn_cmdline.h" +#include "svn_pools.h" +#include "svn_client.h" +#include "svn_error_codes.h" +#include "svn_error.h" +#include "svn_utf.h" +#include "svn_path.h" +#include "cl.h" + +#include "svn_private_config.h" + + +/*** Code. ***/ + +/* This implements the `svn_opt_subcommand_t' interface. */ +svn_error_t * +svn_cl__propdel(apr_getopt_t *os, + void *baton, + apr_pool_t *pool) +{ + svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state; + svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx; + const char *pname, *pname_utf8; + apr_array_header_t *args, *targets; + + /* Get the property's name (and a UTF-8 version of that name). */ + SVN_ERR(svn_opt_parse_num_args(&args, os, 1, pool)); + pname = APR_ARRAY_IDX(args, 0, const char *); + SVN_ERR(svn_utf_cstring_to_utf8(&pname_utf8, pname, pool)); + /* No need to check svn_prop_name_is_valid for *deleting* + properties, and it may even be useful to allow, in case invalid + properties sneaked through somehow. */ + + SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os, + opt_state->targets, + ctx, FALSE, pool)); + + + /* Add "." if user passed 0 file arguments */ + svn_opt_push_implicit_dot_target(targets, pool); + SVN_ERR(svn_cl__eat_peg_revisions(&targets, targets, pool)); + + if (opt_state->revprop) /* operate on a revprop */ + { + svn_revnum_t rev; + const char *URL; + + SVN_ERR(svn_cl__revprop_prepare(&opt_state->start_revision, targets, + &URL, ctx, pool)); + + /* Let libsvn_client do the real work. */ + SVN_ERR(svn_client_revprop_set2(pname_utf8, NULL, NULL, + URL, &(opt_state->start_revision), + &rev, FALSE, ctx, pool)); + } + else if (opt_state->start_revision.kind != svn_opt_revision_unspecified) + { + return svn_error_createf(SVN_ERR_CLIENT_PROPERTY_NAME, NULL, + _("Cannot specify revision for deleting versioned property '%s'"), + pname); + } + else /* operate on a normal, versioned property (not a revprop) */ + { + if (opt_state->depth == svn_depth_unknown) + opt_state->depth = svn_depth_empty; + + /* For each target, remove the property PNAME. */ + SVN_ERR(svn_client_propset_local(pname_utf8, NULL, targets, + opt_state->depth, FALSE, + opt_state->changelists, ctx, pool)); + } + + return SVN_NO_ERROR; +} diff --git a/subversion/svn/propedit-cmd.c b/subversion/svn/propedit-cmd.c new file mode 100644 index 0000000..0e36a34 --- /dev/null +++ b/subversion/svn/propedit-cmd.c @@ -0,0 +1,351 @@ +/* + * propedit-cmd.c -- Edit properties of files/dirs using $EDITOR + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + +/* ==================================================================== */ + + + +/*** Includes. ***/ + +#include "svn_cmdline.h" +#include "svn_wc.h" +#include "svn_pools.h" +#include "svn_client.h" +#include "svn_string.h" +#include "svn_dirent_uri.h" +#include "svn_path.h" +#include "svn_error.h" +#include "svn_utf.h" +#include "svn_props.h" +#include "cl.h" + +#include "private/svn_wc_private.h" + +#include "svn_private_config.h" + + +/*** Code. ***/ +struct commit_info_baton +{ + const char *pname_utf8; + const char *target_local; +}; + +static svn_error_t * +commit_info_handler(const svn_commit_info_t *commit_info, + void *baton, + apr_pool_t *pool) +{ + struct commit_info_baton *cib = baton; + + SVN_ERR(svn_cmdline_printf(pool, + _("Set new value for property '%s' on '%s'\n"), + cib->pname_utf8, cib->target_local)); + SVN_ERR(svn_cl__print_commit_info(commit_info, NULL, pool)); + + return SVN_NO_ERROR; +} + +/* This implements the `svn_opt_subcommand_t' interface. */ +svn_error_t * +svn_cl__propedit(apr_getopt_t *os, + void *baton, + apr_pool_t *pool) +{ + svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state; + svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx; + const char *pname, *pname_utf8; + apr_array_header_t *args, *targets; + + /* Validate the input and get the property's name (and a UTF-8 + version of that name). */ + SVN_ERR(svn_opt_parse_num_args(&args, os, 1, pool)); + pname = APR_ARRAY_IDX(args, 0, const char *); + SVN_ERR(svn_utf_cstring_to_utf8(&pname_utf8, pname, pool)); + if (! svn_prop_name_is_valid(pname_utf8)) + return svn_error_createf(SVN_ERR_CLIENT_PROPERTY_NAME, NULL, + _("'%s' is not a valid Subversion property name"), + pname_utf8); + if (opt_state->encoding && !svn_prop_needs_translation(pname_utf8)) + return svn_error_create + (SVN_ERR_UNSUPPORTED_FEATURE, NULL, + _("--encoding option applies only to textual" + " Subversion-controlled properties")); + + /* Suck up all the remaining arguments into a targets array */ + SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os, + opt_state->targets, + ctx, FALSE, pool)); + + /* We do our own notifications */ + ctx->notify_func2 = NULL; + + if (opt_state->revprop) /* operate on a revprop */ + { + svn_revnum_t rev; + const char *URL; + svn_string_t *propval; + svn_string_t original_propval; + const char *temp_dir; + + /* Implicit "." is okay for revision properties; it just helps + us find the right repository. */ + svn_opt_push_implicit_dot_target(targets, pool); + + SVN_ERR(svn_cl__revprop_prepare(&opt_state->start_revision, targets, + &URL, ctx, pool)); + + /* Fetch the current property. */ + SVN_ERR(svn_client_revprop_get(pname_utf8, &propval, + URL, &(opt_state->start_revision), + &rev, ctx, pool)); + + if (! propval) + { + propval = svn_string_create("", pool); + /* This is how we signify to svn_client_revprop_set2() that + we want it to check that the original value hasn't + changed, but that that original value was non-existent: */ + original_propval.data = NULL; /* and .len is ignored */ + } + else + { + original_propval = *propval; + } + + /* Run the editor on a temporary file which contains the + original property value... */ + SVN_ERR(svn_io_temp_dir(&temp_dir, pool)); + SVN_ERR(svn_cl__edit_string_externally + (&propval, NULL, + opt_state->editor_cmd, temp_dir, + propval, "svn-prop", + ctx->config, + svn_prop_needs_translation(pname_utf8), + opt_state->encoding, pool)); + + /* ...and re-set the property's value accordingly. */ + if (propval) + { + SVN_ERR(svn_client_revprop_set2(pname_utf8, + propval, &original_propval, + URL, &(opt_state->start_revision), + &rev, opt_state->force, ctx, pool)); + + SVN_ERR + (svn_cmdline_printf + (pool, + _("Set new value for property '%s' on revision %ld\n"), + pname_utf8, rev)); + } + else + { + SVN_ERR(svn_cmdline_printf + (pool, _("No changes to property '%s' on revision %ld\n"), + pname_utf8, rev)); + } + } + else if (opt_state->start_revision.kind != svn_opt_revision_unspecified) + { + return svn_error_createf + (SVN_ERR_CL_ARG_PARSING_ERROR, NULL, + _("Cannot specify revision for editing versioned property '%s'"), + pname_utf8); + } + else /* operate on a normal, versioned property (not a revprop) */ + { + apr_pool_t *subpool = svn_pool_create(pool); + struct commit_info_baton cib; + int i; + + /* The customary implicit dot rule has been prone to user error + * here. For example, Jon Trowbridge <trow@gnu.og> did + * + * $ svn propedit HACKING + * + * and then when he closed his editor, he was surprised to see + * + * Set new value for property 'HACKING' on '' + * + * ...meaning that the property named 'HACKING' had been set on + * the current working directory, with the value taken from the + * editor. So we don't do the implicit dot thing anymore; an + * explicit target is always required when editing a versioned + * property. + */ + if (targets->nelts == 0) + { + return svn_error_create + (SVN_ERR_CL_INSUFFICIENT_ARGS, NULL, + _("Explicit target argument required")); + } + + SVN_ERR(svn_cl__eat_peg_revisions(&targets, targets, pool)); + + cib.pname_utf8 = pname_utf8; + + /* For each target, edit the property PNAME. */ + for (i = 0; i < targets->nelts; i++) + { + apr_hash_t *props; + const char *target = APR_ARRAY_IDX(targets, i, const char *); + svn_string_t *propval, *edited_propval; + const char *base_dir = target; + const char *target_local; + const char *local_abspath; + svn_node_kind_t kind; + svn_opt_revision_t peg_revision; + svn_revnum_t base_rev = SVN_INVALID_REVNUM; + + svn_pool_clear(subpool); + SVN_ERR(svn_cl__check_cancel(ctx->cancel_baton)); + + if (!svn_path_is_url(target)) + SVN_ERR(svn_dirent_get_absolute(&local_abspath, target, subpool)); + + /* Propedits can only happen on HEAD or the working copy, so + the peg revision can be as unspecified. */ + peg_revision.kind = svn_opt_revision_unspecified; + + /* Fetch the current property. */ + SVN_ERR(svn_client_propget4(&props, pname_utf8, + svn_path_is_url(target) + ? target : local_abspath, + &peg_revision, + &(opt_state->start_revision), + &base_rev, svn_depth_empty, + NULL, ctx, subpool, subpool)); + + /* Get the property value. */ + propval = apr_hash_get(props, + svn_path_is_url(target) + ? target : local_abspath, + APR_HASH_KEY_STRING); + if (! propval) + propval = svn_string_create("", subpool); + + if (svn_path_is_url(target)) + { + /* For URLs, put the temporary file in the current directory. */ + base_dir = "."; + } + else + { + if (opt_state->message || opt_state->filedata || + opt_state->revprop_table) + { + return svn_error_create + (SVN_ERR_CL_UNNECESSARY_LOG_MESSAGE, NULL, + _("Local, non-commit operations do not take a log message " + "or revision properties")); + } + + /* Split the path if it is a file path. */ + SVN_ERR(svn_wc_read_kind(&kind, ctx->wc_ctx, local_abspath, FALSE, + subpool)); + + if (kind == svn_node_none) + return svn_error_createf( + SVN_ERR_ENTRY_NOT_FOUND, NULL, + _("'%s' does not appear to be a working copy path"), target); + if (kind == svn_node_file) + base_dir = svn_dirent_dirname(target, subpool); + } + + /* Run the editor on a temporary file which contains the + original property value... */ + SVN_ERR(svn_cl__edit_string_externally(&edited_propval, NULL, + opt_state->editor_cmd, + base_dir, + propval, + "svn-prop", + ctx->config, + svn_prop_needs_translation + (pname_utf8), + opt_state->encoding, + subpool)); + + target_local = svn_path_is_url(target) ? target + : svn_dirent_local_style(target, subpool); + cib.target_local = target_local; + + /* ...and re-set the property's value accordingly. */ + if (edited_propval && !svn_string_compare(propval, edited_propval)) + { + svn_error_t *err = SVN_NO_ERROR; + + svn_cl__check_boolean_prop_val(pname_utf8, edited_propval->data, + subpool); + + if (ctx->log_msg_func3) + SVN_ERR(svn_cl__make_log_msg_baton(&(ctx->log_msg_baton3), + opt_state, NULL, ctx->config, + subpool)); + if (svn_path_is_url(target)) + { + err = svn_client_propset_remote(pname_utf8, edited_propval, + target, opt_state->force, + base_rev, + opt_state->revprop_table, + commit_info_handler, &cib, + ctx, subpool); + } + else + { + apr_array_header_t *targs = apr_array_make(subpool, 1, + sizeof(const char *)); + + APR_ARRAY_PUSH(targs, const char *) = target; + err = svn_client_propset_local(pname_utf8, edited_propval, + targs, svn_depth_empty, + opt_state->force, NULL, + ctx, subpool); + } + + if (ctx->log_msg_func3) + SVN_ERR(svn_cl__cleanup_log_msg(ctx->log_msg_baton3, + err, pool)); + else if (err) + return svn_error_trace(err); + + /* Print a message if we successfully committed or if it + was just a wc propset (but not if the user aborted an URL + propedit). */ + if (!svn_path_is_url(target)) + SVN_ERR(svn_cmdline_printf( + subpool, _("Set new value for property '%s' on '%s'\n"), + pname_utf8, target_local)); + } + else + { + SVN_ERR + (svn_cmdline_printf + (subpool, _("No changes to property '%s' on '%s'\n"), + pname_utf8, target_local)); + } + } + svn_pool_destroy(subpool); + } + + return SVN_NO_ERROR; +} diff --git a/subversion/svn/propget-cmd.c b/subversion/svn/propget-cmd.c new file mode 100644 index 0000000..fcb6503 --- /dev/null +++ b/subversion/svn/propget-cmd.c @@ -0,0 +1,358 @@ +/* + * propget-cmd.c -- Print properties and values of files/dirs + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + +/* ==================================================================== */ + + + +/*** Includes. ***/ + +#include "svn_cmdline.h" +#include "svn_pools.h" +#include "svn_client.h" +#include "svn_string.h" +#include "svn_error_codes.h" +#include "svn_error.h" +#include "svn_utf.h" +#include "svn_sorts.h" +#include "svn_subst.h" +#include "svn_dirent_uri.h" +#include "svn_path.h" +#include "svn_props.h" +#include "svn_xml.h" +#include "cl.h" + +#include "private/svn_cmdline_private.h" + +#include "svn_private_config.h" + + +/*** Code. ***/ + +static svn_error_t * +stream_write(svn_stream_t *out, + const char *data, + apr_size_t len) +{ + apr_size_t write_len = len; + + /* We're gonna bail on an incomplete write here only because we know + that this stream is really stdout, which should never be blocking + on us. */ + SVN_ERR(svn_stream_write(out, data, &write_len)); + if (write_len != len) + return svn_error_create(SVN_ERR_STREAM_UNEXPECTED_EOF, NULL, + _("Error writing to stream")); + return SVN_NO_ERROR; +} + + +static svn_error_t * +print_properties_xml(const char *pname, + apr_hash_t *props, + apr_pool_t *pool) +{ + apr_array_header_t *sorted_props; + int i; + apr_pool_t *iterpool = svn_pool_create(pool); + + sorted_props = svn_sort__hash(props, svn_sort_compare_items_as_paths, pool); + for (i = 0; i < sorted_props->nelts; i++) + { + svn_sort__item_t item = APR_ARRAY_IDX(sorted_props, i, svn_sort__item_t); + const char *filename = item.key; + svn_string_t *propval = item.value; + svn_stringbuf_t *sb = NULL; + + svn_pool_clear(iterpool); + + svn_xml_make_open_tag(&sb, iterpool, svn_xml_normal, "target", + "path", filename, NULL); + svn_cmdline__print_xml_prop(&sb, pname, propval, iterpool); + svn_xml_make_close_tag(&sb, iterpool, "target"); + + SVN_ERR(svn_cl__error_checked_fputs(sb->data, stdout)); + } + + svn_pool_destroy(iterpool); + + return SVN_NO_ERROR; +} + + +/* Print the properties in PROPS to the stream OUT. PROPS is a hash mapping + * (const char *) path to (svn_string_t) property value. + * If IS_URL is true, all paths are URLs, else all paths are local paths. + * PNAME_UTF8 is the property name of all the properties. + * If PRINT_FILENAMES is true, print the item's path before each property. + * If OMIT_NEWLINE is true, don't add a newline at the end of each property. + * If LIKE_PROPLIST is true, print everything in a more verbose format + * like "svn proplist -v" does. + * */ +static svn_error_t * +print_properties(svn_stream_t *out, + svn_boolean_t is_url, + const char *pname_utf8, + apr_hash_t *props, + svn_boolean_t print_filenames, + svn_boolean_t omit_newline, + svn_boolean_t like_proplist, + apr_pool_t *pool) +{ + apr_array_header_t *sorted_props; + int i; + apr_pool_t *iterpool = svn_pool_create(pool); + const char *path_prefix; + + SVN_ERR(svn_dirent_get_absolute(&path_prefix, "", pool)); + + sorted_props = svn_sort__hash(props, svn_sort_compare_items_as_paths, pool); + for (i = 0; i < sorted_props->nelts; i++) + { + svn_sort__item_t item = APR_ARRAY_IDX(sorted_props, i, svn_sort__item_t); + const char *filename = item.key; + svn_string_t *propval = item.value; + + svn_pool_clear(iterpool); + + if (print_filenames) + { + const char *header; + + /* Print the file name. */ + + if (! is_url) + filename = svn_cl__local_style_skip_ancestor(path_prefix, filename, + iterpool); + + /* In verbose mode, print exactly same as "proplist" does; + * otherwise, print a brief header. */ + header = apr_psprintf(iterpool, like_proplist + ? _("Properties on '%s':\n") + : "%s - ", filename); + SVN_ERR(svn_cmdline_cstring_from_utf8(&header, header, iterpool)); + SVN_ERR(svn_subst_translate_cstring2(header, &header, + APR_EOL_STR, /* 'native' eol */ + FALSE, /* no repair */ + NULL, /* no keywords */ + FALSE, /* no expansion */ + iterpool)); + SVN_ERR(stream_write(out, header, strlen(header))); + } + + if (like_proplist) + { + /* Print the property name and value just as "proplist -v" does */ + apr_hash_t *hash = apr_hash_make(iterpool); + + apr_hash_set(hash, pname_utf8, APR_HASH_KEY_STRING, propval); + SVN_ERR(svn_cl__print_prop_hash(out, hash, FALSE, iterpool)); + } + else + { + /* If this is a special Subversion property, it is stored as + UTF8, so convert to the native format. */ + if (svn_prop_needs_translation(pname_utf8)) + SVN_ERR(svn_subst_detranslate_string(&propval, propval, + TRUE, iterpool)); + + SVN_ERR(stream_write(out, propval->data, propval->len)); + + if (! omit_newline) + SVN_ERR(stream_write(out, APR_EOL_STR, + strlen(APR_EOL_STR))); + } + } + + svn_pool_destroy(iterpool); + + return SVN_NO_ERROR; +} + + +/* This implements the `svn_opt_subcommand_t' interface. */ +svn_error_t * +svn_cl__propget(apr_getopt_t *os, + void *baton, + apr_pool_t *pool) +{ + svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state; + svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx; + const char *pname, *pname_utf8; + apr_array_header_t *args, *targets; + svn_stream_t *out; + + if (opt_state->verbose && (opt_state->revprop || opt_state->strict + || opt_state->xml)) + return svn_error_create(SVN_ERR_CL_MUTUALLY_EXCLUSIVE_ARGS, NULL, + _("--verbose cannot be used with --revprop or " + "--strict or --xml")); + + /* PNAME is first argument (and PNAME_UTF8 will be a UTF-8 version + thereof) */ + SVN_ERR(svn_opt_parse_num_args(&args, os, 1, pool)); + pname = APR_ARRAY_IDX(args, 0, const char *); + SVN_ERR(svn_utf_cstring_to_utf8(&pname_utf8, pname, pool)); + if (! svn_prop_name_is_valid(pname_utf8)) + return svn_error_createf(SVN_ERR_CLIENT_PROPERTY_NAME, NULL, + _("'%s' is not a valid Subversion property name"), + pname_utf8); + + SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os, + opt_state->targets, + ctx, FALSE, pool)); + + /* Add "." if user passed 0 file arguments */ + svn_opt_push_implicit_dot_target(targets, pool); + + /* Open a stream to stdout. */ + SVN_ERR(svn_stream_for_stdout(&out, pool)); + + if (opt_state->revprop) /* operate on a revprop */ + { + svn_revnum_t rev; + const char *URL; + svn_string_t *propval; + + SVN_ERR(svn_cl__revprop_prepare(&opt_state->start_revision, targets, + &URL, ctx, pool)); + + /* Let libsvn_client do the real work. */ + SVN_ERR(svn_client_revprop_get(pname_utf8, &propval, + URL, &(opt_state->start_revision), + &rev, ctx, pool)); + + if (propval != NULL) + { + if (opt_state->xml) + { + svn_stringbuf_t *sb = NULL; + char *revstr = apr_psprintf(pool, "%ld", rev); + + SVN_ERR(svn_cl__xml_print_header("properties", pool)); + + svn_xml_make_open_tag(&sb, pool, svn_xml_normal, + "revprops", + "rev", revstr, NULL); + + svn_cmdline__print_xml_prop(&sb, pname_utf8, propval, pool); + + svn_xml_make_close_tag(&sb, pool, "revprops"); + + SVN_ERR(svn_cl__error_checked_fputs(sb->data, stdout)); + SVN_ERR(svn_cl__xml_print_footer("properties", pool)); + } + else + { + svn_string_t *printable_val = propval; + + /* If this is a special Subversion property, it is stored as + UTF8 and LF, so convert to the native locale and eol-style. */ + + if (svn_prop_needs_translation(pname_utf8)) + SVN_ERR(svn_subst_detranslate_string(&printable_val, propval, + TRUE, pool)); + + SVN_ERR(stream_write(out, printable_val->data, + printable_val->len)); + if (! opt_state->strict) + SVN_ERR(stream_write(out, APR_EOL_STR, strlen(APR_EOL_STR))); + } + } + } + else /* operate on a normal, versioned property (not a revprop) */ + { + apr_pool_t *subpool = svn_pool_create(pool); + int i; + + if (opt_state->xml) + SVN_ERR(svn_cl__xml_print_header("properties", subpool)); + + if (opt_state->depth == svn_depth_unknown) + opt_state->depth = svn_depth_empty; + + /* Strict mode only makes sense for a single target. So make + sure we have only a single target, and that we're not being + asked to recurse on that target. */ + if (opt_state->strict + && ((targets->nelts > 1) || (opt_state->depth != svn_depth_empty))) + return svn_error_create + (SVN_ERR_CL_ARG_PARSING_ERROR, NULL, + _("Strict output of property values only available for single-" + "target, non-recursive propget operations")); + + for (i = 0; i < targets->nelts; i++) + { + const char *target = APR_ARRAY_IDX(targets, i, const char *); + apr_hash_t *props; + svn_boolean_t print_filenames; + svn_boolean_t omit_newline; + svn_boolean_t like_proplist; + const char *truepath; + svn_opt_revision_t peg_revision; + + svn_pool_clear(subpool); + SVN_ERR(svn_cl__check_cancel(ctx->cancel_baton)); + + /* Check for a peg revision. */ + SVN_ERR(svn_opt_parse_path(&peg_revision, &truepath, target, + subpool)); + + if (!svn_path_is_url(truepath)) + SVN_ERR(svn_dirent_get_absolute(&truepath, truepath, subpool)); + + SVN_ERR(svn_client_propget4(&props, pname_utf8, truepath, + &peg_revision, + &(opt_state->start_revision), + NULL, opt_state->depth, + opt_state->changelists, ctx, subpool, + subpool)); + + /* Any time there is more than one thing to print, or where + the path associated with a printed thing is not obvious, + we'll print filenames. That is, unless we've been told + not to do so with the --strict option. */ + print_filenames = ((opt_state->depth > svn_depth_empty + || targets->nelts > 1 + || apr_hash_count(props) > 1 + || opt_state->verbose) + && (! opt_state->strict)); + omit_newline = opt_state->strict; + like_proplist = opt_state->verbose && !opt_state->strict; + + if (opt_state->xml) + SVN_ERR(print_properties_xml(pname_utf8, props, subpool)); + else + SVN_ERR(print_properties(out, svn_path_is_url(target), pname_utf8, + props, print_filenames, omit_newline, + like_proplist, subpool)); + } + + if (opt_state->xml) + SVN_ERR(svn_cl__xml_print_footer("properties", subpool)); + + svn_pool_destroy(subpool); + } + + return SVN_NO_ERROR; +} diff --git a/subversion/svn/proplist-cmd.c b/subversion/svn/proplist-cmd.c new file mode 100644 index 0000000..64cb055 --- /dev/null +++ b/subversion/svn/proplist-cmd.c @@ -0,0 +1,249 @@ +/* + * proplist-cmd.c -- List properties of files/dirs + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + +/* ==================================================================== */ + + + +/*** Includes. ***/ + +#include "svn_cmdline.h" +#include "svn_pools.h" +#include "svn_client.h" +#include "svn_error_codes.h" +#include "svn_error.h" +#include "svn_dirent_uri.h" +#include "svn_path.h" +#include "svn_xml.h" +#include "cl.h" + +#include "svn_private_config.h" + +typedef struct proplist_baton_t +{ + svn_cl__opt_state_t *opt_state; + svn_boolean_t is_url; +} proplist_baton_t; + + +/*** Code. ***/ + +/* This implements the svn_proplist_receiver_t interface, printing XML to + stdout. */ +static svn_error_t * +proplist_receiver_xml(void *baton, + const char *path, + apr_hash_t *prop_hash, + apr_pool_t *pool) +{ + svn_cl__opt_state_t *opt_state = ((proplist_baton_t *)baton)->opt_state; + svn_boolean_t is_url = ((proplist_baton_t *)baton)->is_url; + svn_stringbuf_t *sb = NULL; + const char *name_local; + + if (! is_url) + name_local = svn_dirent_local_style(path, pool); + else + name_local = path; + + /* "<target ...>" */ + svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "target", + "path", name_local, NULL); + + SVN_ERR(svn_cl__print_xml_prop_hash(&sb, prop_hash, (! opt_state->verbose), + pool)); + + /* "</target>" */ + svn_xml_make_close_tag(&sb, pool, "target"); + + return svn_cl__error_checked_fputs(sb->data, stdout); +} + + +/* This implements the svn_proplist_receiver_t interface. */ +static svn_error_t * +proplist_receiver(void *baton, + const char *path, + apr_hash_t *prop_hash, + apr_pool_t *pool) +{ + svn_cl__opt_state_t *opt_state = ((proplist_baton_t *)baton)->opt_state; + svn_boolean_t is_url = ((proplist_baton_t *)baton)->is_url; + const char *name_local; + + if (! is_url) + name_local = svn_dirent_local_style(path, pool); + else + name_local = path; + + if (!opt_state->quiet) + SVN_ERR(svn_cmdline_printf(pool, _("Properties on '%s':\n"), name_local)); + return svn_cl__print_prop_hash(NULL, prop_hash, (! opt_state->verbose), + pool); +} + + +/* This implements the `svn_opt_subcommand_t' interface. */ +svn_error_t * +svn_cl__proplist(apr_getopt_t *os, + void *baton, + apr_pool_t *scratch_pool) +{ + svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state; + svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx; + apr_array_header_t *targets; + apr_array_header_t *errors = apr_array_make(scratch_pool, 0, + sizeof(apr_status_t)); + + SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os, + opt_state->targets, + ctx, FALSE, + scratch_pool)); + + /* Add "." if user passed 0 file arguments */ + svn_opt_push_implicit_dot_target(targets, scratch_pool); + + if (opt_state->revprop) /* operate on revprops */ + { + svn_revnum_t rev; + const char *URL; + apr_hash_t *proplist; + + SVN_ERR(svn_cl__revprop_prepare(&opt_state->start_revision, targets, + &URL, ctx, scratch_pool)); + + /* Let libsvn_client do the real work. */ + SVN_ERR(svn_client_revprop_list(&proplist, + URL, &(opt_state->start_revision), + &rev, ctx, scratch_pool)); + + if (opt_state->xml) + { + svn_stringbuf_t *sb = NULL; + char *revstr = apr_psprintf(scratch_pool, "%ld", rev); + + SVN_ERR(svn_cl__xml_print_header("properties", scratch_pool)); + + svn_xml_make_open_tag(&sb, scratch_pool, svn_xml_normal, + "revprops", + "rev", revstr, NULL); + SVN_ERR(svn_cl__print_xml_prop_hash + (&sb, proplist, (! opt_state->verbose), scratch_pool)); + svn_xml_make_close_tag(&sb, scratch_pool, "revprops"); + + SVN_ERR(svn_cl__error_checked_fputs(sb->data, stdout)); + SVN_ERR(svn_cl__xml_print_footer("properties", scratch_pool)); + } + else + { + SVN_ERR + (svn_cmdline_printf(scratch_pool, + _("Unversioned properties on revision %ld:\n"), + rev)); + + SVN_ERR(svn_cl__print_prop_hash + (NULL, proplist, (! opt_state->verbose), scratch_pool)); + } + } + else /* operate on normal, versioned properties (not revprops) */ + { + int i; + apr_pool_t *iterpool; + svn_proplist_receiver_t pl_receiver; + + if (opt_state->xml) + { + SVN_ERR(svn_cl__xml_print_header("properties", scratch_pool)); + pl_receiver = proplist_receiver_xml; + } + else + { + pl_receiver = proplist_receiver; + } + + if (opt_state->depth == svn_depth_unknown) + opt_state->depth = svn_depth_empty; + + iterpool = svn_pool_create(scratch_pool); + for (i = 0; i < targets->nelts; i++) + { + const char *target = APR_ARRAY_IDX(targets, i, const char *); + proplist_baton_t pl_baton; + const char *truepath; + svn_opt_revision_t peg_revision; + + svn_pool_clear(iterpool); + SVN_ERR(svn_cl__check_cancel(ctx->cancel_baton)); + + pl_baton.is_url = svn_path_is_url(target); + pl_baton.opt_state = opt_state; + + /* Check for a peg revision. */ + SVN_ERR(svn_opt_parse_path(&peg_revision, &truepath, target, + iterpool)); + + SVN_ERR(svn_cl__try( + svn_client_proplist3(truepath, &peg_revision, + &(opt_state->start_revision), + opt_state->depth, + opt_state->changelists, + pl_receiver, &pl_baton, + ctx, iterpool), + errors, opt_state->quiet, + SVN_ERR_UNVERSIONED_RESOURCE, + SVN_ERR_ENTRY_NOT_FOUND, + SVN_NO_ERROR)); + } + svn_pool_destroy(iterpool); + + if (opt_state->xml) + SVN_ERR(svn_cl__xml_print_footer("properties", scratch_pool)); + + /* Error out *after* we closed the XML element */ + if (errors->nelts > 0) + { + svn_error_t *err; + + err = svn_error_create(SVN_ERR_ILLEGAL_TARGET, NULL, NULL); + for (i = 0; i < errors->nelts; i++) + { + apr_status_t status = APR_ARRAY_IDX(errors, i, apr_status_t); + + if (status == SVN_ERR_ENTRY_NOT_FOUND) + err = svn_error_quick_wrap(err, + _("Could not display properties " + "of all targets because some " + "targets don't exist")); + else if (status == SVN_ERR_UNVERSIONED_RESOURCE) + err = svn_error_quick_wrap(err, + _("Could not display properties " + "of all targets because some " + "targets are not versioned")); + } + + return svn_error_trace(err); + } + } + + return SVN_NO_ERROR; +} diff --git a/subversion/svn/props.c b/subversion/svn/props.c new file mode 100644 index 0000000..f08f18c --- /dev/null +++ b/subversion/svn/props.c @@ -0,0 +1,224 @@ +/* + * props.c: Utility functions for property handling + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + +/* ==================================================================== */ + + + +/*** Includes. ***/ + +#include <apr_hash.h> +#include "svn_cmdline.h" +#include "svn_string.h" +#include "svn_error.h" +#include "svn_sorts.h" +#include "svn_subst.h" +#include "svn_props.h" +#include "svn_string.h" +#include "svn_opt.h" +#include "svn_xml.h" +#include "svn_base64.h" +#include "cl.h" + +#include "private/svn_cmdline_private.h" + +#include "svn_private_config.h" + + + +svn_error_t * +svn_cl__revprop_prepare(const svn_opt_revision_t *revision, + const apr_array_header_t *targets, + const char **URL, + svn_client_ctx_t *ctx, + apr_pool_t *pool) +{ + const char *target; + + if (revision->kind != svn_opt_revision_number + && revision->kind != svn_opt_revision_date + && revision->kind != svn_opt_revision_head) + return svn_error_create + (SVN_ERR_CL_ARG_PARSING_ERROR, NULL, + _("Must specify the revision as a number, a date or 'HEAD' " + "when operating on a revision property")); + + /* There must be exactly one target at this point. If it was optional and + unspecified by the user, the caller has already added the implicit '.'. */ + if (targets->nelts != 1) + return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, + _("Wrong number of targets specified")); + + /* (The docs say the target must be either a URL or implicit '.', but + explicit WC targets are also accepted.) */ + target = APR_ARRAY_IDX(targets, 0, const char *); + SVN_ERR(svn_client_url_from_path2(URL, target, ctx, pool, pool)); + if (*URL == NULL) + return svn_error_create + (SVN_ERR_UNVERSIONED_RESOURCE, NULL, + _("Either a URL or versioned item is required")); + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_cl__print_prop_hash(svn_stream_t *out, + apr_hash_t *prop_hash, + svn_boolean_t names_only, + apr_pool_t *pool) +{ + apr_array_header_t *sorted_props; + int i; + + sorted_props = svn_sort__hash(prop_hash, svn_sort_compare_items_lexically, + pool); + for (i = 0; i < sorted_props->nelts; i++) + { + svn_sort__item_t item = APR_ARRAY_IDX(sorted_props, i, svn_sort__item_t); + const char *pname = item.key; + svn_string_t *propval = item.value; + const char *pname_stdout; + apr_size_t len; + + if (svn_prop_needs_translation(pname)) + SVN_ERR(svn_subst_detranslate_string(&propval, propval, + TRUE, pool)); + + SVN_ERR(svn_cmdline_cstring_from_utf8(&pname_stdout, pname, pool)); + + if (out) + { + pname_stdout = apr_psprintf(pool, " %s\n", pname_stdout); + SVN_ERR(svn_subst_translate_cstring2(pname_stdout, &pname_stdout, + APR_EOL_STR, /* 'native' eol */ + FALSE, /* no repair */ + NULL, /* no keywords */ + FALSE, /* no expansion */ + pool)); + + len = strlen(pname_stdout); + SVN_ERR(svn_stream_write(out, pname_stdout, &len)); + } + else + { + /* ### We leave these printfs for now, since if propval wasn't + translated above, we don't know anything about its encoding. + In fact, it might be binary data... */ + printf(" %s\n", pname_stdout); + } + + if (!names_only) + { + /* Add an extra newline to the value before indenting, so that + * every line of output has the indentation whether the value + * already ended in a newline or not. */ + const char *newval = apr_psprintf(pool, "%s\n", propval->data); + const char *indented_newval = svn_cl__indent_string(newval, + " ", + pool); + if (out) + { + len = strlen(indented_newval); + SVN_ERR(svn_stream_write(out, indented_newval, &len)); + } + else + { + printf("%s", indented_newval); + } + } + } + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_cl__print_xml_prop_hash(svn_stringbuf_t **outstr, + apr_hash_t *prop_hash, + svn_boolean_t names_only, + apr_pool_t *pool) +{ + apr_array_header_t *sorted_props; + int i; + + if (*outstr == NULL) + *outstr = svn_stringbuf_create("", pool); + + sorted_props = svn_sort__hash(prop_hash, svn_sort_compare_items_lexically, + pool); + for (i = 0; i < sorted_props->nelts; i++) + { + svn_sort__item_t item = APR_ARRAY_IDX(sorted_props, i, svn_sort__item_t); + const char *pname = item.key; + svn_string_t *propval = item.value; + + if (names_only) + { + svn_xml_make_open_tag(outstr, pool, svn_xml_self_closing, "property", + "name", pname, NULL); + } + else + { + const char *pname_out; + + if (svn_prop_needs_translation(pname)) + SVN_ERR(svn_subst_detranslate_string(&propval, propval, + TRUE, pool)); + + SVN_ERR(svn_cmdline_cstring_from_utf8(&pname_out, pname, pool)); + + svn_cmdline__print_xml_prop(outstr, pname_out, propval, pool); + } + } + + return SVN_NO_ERROR; +} + + +void +svn_cl__check_boolean_prop_val(const char *propname, const char *propval, + apr_pool_t *pool) +{ + svn_stringbuf_t *propbuf; + + if (!svn_prop_is_boolean(propname)) + return; + + propbuf = svn_stringbuf_create(propval, pool); + svn_stringbuf_strip_whitespace(propbuf); + + if (propbuf->data[0] == '\0' + || strcmp(propbuf->data, "no") == 0 + || strcmp(propbuf->data, "off") == 0 + || strcmp(propbuf->data, "false") == 0) + { + svn_error_t *err = svn_error_createf + (SVN_ERR_BAD_PROPERTY_VALUE, NULL, + _("To turn off the %s property, use 'svn propdel';\n" + "setting the property to '%s' will not turn it off."), + propname, propval); + svn_handle_warning2(stderr, err, "svn: "); + svn_error_clear(err); + } +} + diff --git a/subversion/svn/propset-cmd.c b/subversion/svn/propset-cmd.c new file mode 100644 index 0000000..33b4b5d --- /dev/null +++ b/subversion/svn/propset-cmd.c @@ -0,0 +1,183 @@ +/* + * propset-cmd.c -- Set property values on files/dirs + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + +/* ==================================================================== */ + + + +/*** Includes. ***/ + +#include "svn_cmdline.h" +#include "svn_pools.h" +#include "svn_client.h" +#include "svn_string.h" +#include "svn_error.h" +#include "svn_utf.h" +#include "svn_subst.h" +#include "svn_path.h" +#include "svn_props.h" +#include "cl.h" + +#include "svn_private_config.h" + + +/*** Code. ***/ + +/* This implements the `svn_opt_subcommand_t' interface. */ +svn_error_t * +svn_cl__propset(apr_getopt_t *os, + void *baton, + apr_pool_t *scratch_pool) +{ + svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state; + svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx; + const char *pname, *pname_utf8; + svn_string_t *propval = NULL; + svn_boolean_t propval_came_from_cmdline; + apr_array_header_t *args, *targets; + + /* PNAME and PROPVAL expected as first 2 arguments if filedata was + NULL, else PNAME alone will precede the targets. Get a UTF-8 + version of the name, too. */ + SVN_ERR(svn_opt_parse_num_args(&args, os, + opt_state->filedata ? 1 : 2, scratch_pool)); + pname = APR_ARRAY_IDX(args, 0, const char *); + SVN_ERR(svn_utf_cstring_to_utf8(&pname_utf8, pname, scratch_pool)); + if (! svn_prop_name_is_valid(pname_utf8)) + return svn_error_createf(SVN_ERR_CLIENT_PROPERTY_NAME, NULL, + _("'%s' is not a valid Subversion property name"), + pname_utf8); + + /* Get the PROPVAL from either an external file, or from the command + line. */ + if (opt_state->filedata) + { + propval = svn_string_create_from_buf(opt_state->filedata, scratch_pool); + propval_came_from_cmdline = FALSE; + } + else + { + propval = svn_string_create(APR_ARRAY_IDX(args, 1, const char *), + scratch_pool); + propval_came_from_cmdline = TRUE; + } + + /* We only want special Subversion property values to be in UTF-8 + and LF line endings. All other propvals are taken literally. */ + if (svn_prop_needs_translation(pname_utf8)) + SVN_ERR(svn_subst_translate_string2(&propval, NULL, NULL, propval, + opt_state->encoding, FALSE, + scratch_pool, scratch_pool)); + else if (opt_state->encoding) + return svn_error_create + (SVN_ERR_UNSUPPORTED_FEATURE, NULL, + _("--encoding option applies only to textual" + " Subversion-controlled properties")); + + /* Suck up all the remaining arguments into a targets array */ + + SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os, + opt_state->targets, + ctx, FALSE, + scratch_pool)); + + /* Implicit "." is okay for revision properties; it just helps + us find the right repository. */ + if (opt_state->revprop) + svn_opt_push_implicit_dot_target(targets, scratch_pool); + + SVN_ERR(svn_cl__eat_peg_revisions(&targets, targets, scratch_pool)); + + if (opt_state->revprop) /* operate on a revprop */ + { + svn_revnum_t rev; + const char *URL; + + SVN_ERR(svn_cl__revprop_prepare(&opt_state->start_revision, targets, + &URL, ctx, scratch_pool)); + + /* Let libsvn_client do the real work. */ + SVN_ERR(svn_client_revprop_set2(pname_utf8, propval, NULL, + URL, &(opt_state->start_revision), + &rev, opt_state->force, ctx, + scratch_pool)); + } + else if (opt_state->start_revision.kind != svn_opt_revision_unspecified) + { + return svn_error_createf + (SVN_ERR_CL_ARG_PARSING_ERROR, NULL, + _("Cannot specify revision for setting versioned property '%s'"), + pname); + } + else /* operate on a normal, versioned property (not a revprop) */ + { + if (opt_state->depth == svn_depth_unknown) + opt_state->depth = svn_depth_empty; + + /* The customary implicit dot rule has been prone to user error + * here. People would do intuitive things like + * + * $ svn propset svn:executable script + * + * and then be surprised to get an error like: + * + * svn: Illegal target for the requested operation + * svn: Cannot set svn:executable on a directory () + * + * So we don't do the implicit dot thing anymore. A * target + * must always be explicitly provided when setting a versioned + * property. See + * + * http://subversion.tigris.org/issues/show_bug.cgi?id=924 + * + * for more details. + */ + + if (targets->nelts == 0) + { + if (propval_came_from_cmdline) + { + return svn_error_createf + (SVN_ERR_CL_INSUFFICIENT_ARGS, NULL, + _("Explicit target required ('%s' interpreted as prop value)"), + propval->data); + } + else + { + return svn_error_create + (SVN_ERR_CL_INSUFFICIENT_ARGS, NULL, + _("Explicit target argument required")); + } + } + + SVN_ERR(svn_client_propset_local(pname_utf8, propval, targets, + opt_state->depth, opt_state->force, + opt_state->changelists, ctx, + scratch_pool)); + + if (! opt_state->quiet) + svn_cl__check_boolean_prop_val(pname_utf8, propval->data, scratch_pool); + } + + return SVN_NO_ERROR; +} diff --git a/subversion/svn/relocate-cmd.c b/subversion/svn/relocate-cmd.c new file mode 100644 index 0000000..f07d4ae --- /dev/null +++ b/subversion/svn/relocate-cmd.c @@ -0,0 +1,120 @@ +/* + * relocate-cmd.c -- Update working tree administrative data to + * account for repository change-of-address. + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + +/* ==================================================================== */ + + + +/*** Includes. ***/ + +#include "svn_wc.h" +#include "svn_client.h" +#include "svn_dirent_uri.h" +#include "svn_path.h" +#include "svn_error.h" +#include "svn_pools.h" +#include "cl.h" + +#include "svn_private_config.h" + +/*** Code. ***/ + +/* This implements the `svn_opt_subcommand_t' interface. */ +svn_error_t * +svn_cl__relocate(apr_getopt_t *os, + void *baton, + apr_pool_t *scratch_pool) +{ + svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state; + svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx; + svn_boolean_t ignore_externals = opt_state->ignore_externals; + apr_array_header_t *targets; + const char *from = NULL, *to = NULL, *path; + + /* We've got two different syntaxes to support: + + 1. relocate FROM-PREFIX TO-PREFIX [PATH ...] + 2. relocate TO-URL [PATH] + */ + SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os, + opt_state->targets, + ctx, FALSE, + scratch_pool)); + if (targets->nelts < 1) + return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0, NULL); + + /* If we have a single target, we're in form #2. If we have two + targets and the first is a URL and the second is not, we're also + in form #2. */ + if ((targets->nelts == 1) || + ((targets->nelts == 2) + && (svn_path_is_url(APR_ARRAY_IDX(targets, 0, const char *))) + && (! svn_path_is_url(APR_ARRAY_IDX(targets, 1, const char *))))) + + { + to = APR_ARRAY_IDX(targets, 0, const char *); + path = (targets->nelts == 2) ? APR_ARRAY_IDX(targets, 1, const char *) + : ""; + + SVN_ERR(svn_client_url_from_path2(&from, path, ctx, + scratch_pool, scratch_pool)); + SVN_ERR(svn_client_relocate2(path, from, to, ignore_externals, + ctx, scratch_pool)); + } + /* ... Everything else is form #1. */ + else + { + from = APR_ARRAY_IDX(targets, 0, const char *); + to = APR_ARRAY_IDX(targets, 1, const char *); + + if (targets->nelts == 2) + { + SVN_ERR(svn_client_relocate2("", from, to, ignore_externals, + ctx, scratch_pool)); + } + else + { + apr_pool_t *subpool = svn_pool_create(scratch_pool); + int i; + + /* Target working copy root dir must be local. */ + for (i = 2; i < targets->nelts; i++) + { + path = APR_ARRAY_IDX(targets, i, const char *); + SVN_ERR(svn_cl__check_target_is_local_path(path)); + } + + for (i = 2; i < targets->nelts; i++) + { + svn_pool_clear(subpool); + path = APR_ARRAY_IDX(targets, i, const char *); + SVN_ERR(svn_client_relocate2(path, from, to, ignore_externals, + ctx, subpool)); + } + svn_pool_destroy(subpool); + } + } + + return SVN_NO_ERROR; +} diff --git a/subversion/svn/resolve-cmd.c b/subversion/svn/resolve-cmd.c new file mode 100644 index 0000000..4ce57be --- /dev/null +++ b/subversion/svn/resolve-cmd.c @@ -0,0 +1,119 @@ +/* + * resolve-cmd.c -- Subversion resolve subcommand + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + +/* ==================================================================== */ + + + +/*** Includes. ***/ +#define APR_WANT_STDIO +#include <apr_want.h> + +#include "svn_path.h" +#include "svn_client.h" +#include "svn_error.h" +#include "svn_pools.h" +#include "cl.h" + +#include "svn_private_config.h" + + + +/*** Code. ***/ + +/* This implements the `svn_opt_subcommand_t' interface. */ +svn_error_t * +svn_cl__resolve(apr_getopt_t *os, + void *baton, + apr_pool_t *scratch_pool) +{ + svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state; + svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx; + svn_wc_conflict_choice_t conflict_choice; + svn_error_t *err; + apr_array_header_t *targets; + int i; + apr_pool_t *iterpool; + + switch (opt_state->accept_which) + { + case svn_cl__accept_working: + conflict_choice = svn_wc_conflict_choose_merged; + break; + case svn_cl__accept_base: + conflict_choice = svn_wc_conflict_choose_base; + break; + case svn_cl__accept_theirs_conflict: + conflict_choice = svn_wc_conflict_choose_theirs_conflict; + break; + case svn_cl__accept_mine_conflict: + conflict_choice = svn_wc_conflict_choose_mine_conflict; + break; + case svn_cl__accept_theirs_full: + conflict_choice = svn_wc_conflict_choose_theirs_full; + break; + case svn_cl__accept_mine_full: + conflict_choice = svn_wc_conflict_choose_mine_full; + break; + case svn_cl__accept_unspecified: + return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, + _("missing --accept option")); + default: + return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, + _("invalid 'accept' ARG")); + } + + SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os, + opt_state->targets, + ctx, FALSE, + scratch_pool)); + if (! targets->nelts) + return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0, NULL); + + if (opt_state->depth == svn_depth_unknown) + opt_state->depth = svn_depth_empty; + + SVN_ERR(svn_cl__eat_peg_revisions(&targets, targets, scratch_pool)); + + SVN_ERR(svn_cl__check_targets_are_local_paths(targets)); + + iterpool = svn_pool_create(scratch_pool); + for (i = 0; i < targets->nelts; i++) + { + const char *target = APR_ARRAY_IDX(targets, i, const char *); + svn_pool_clear(iterpool); + SVN_ERR(svn_cl__check_cancel(ctx->cancel_baton)); + err = svn_client_resolve(target, + opt_state->depth, conflict_choice, + ctx, + iterpool); + if (err) + { + svn_handle_warning2(stderr, err, "svn: "); + svn_error_clear(err); + } + } + svn_pool_destroy(iterpool); + + return SVN_NO_ERROR; +} diff --git a/subversion/svn/resolved-cmd.c b/subversion/svn/resolved-cmd.c new file mode 100644 index 0000000..0db3b4b --- /dev/null +++ b/subversion/svn/resolved-cmd.c @@ -0,0 +1,91 @@ +/* + * resolved-cmd.c -- Subversion resolved subcommand + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + +/* ==================================================================== */ + + + +/*** Includes. ***/ +#define APR_WANT_STDIO +#include <apr_want.h> + +#include "svn_path.h" +#include "svn_client.h" +#include "svn_error.h" +#include "svn_pools.h" +#include "cl.h" + +#include "svn_private_config.h" + + + +/*** Code. ***/ + +/* This implements the `svn_opt_subcommand_t' interface. */ +svn_error_t * +svn_cl__resolved(apr_getopt_t *os, + void *baton, + apr_pool_t *scratch_pool) +{ + svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state; + svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx; + svn_error_t *err; + apr_array_header_t *targets; + apr_pool_t *iterpool; + int i; + + SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os, + opt_state->targets, + ctx, FALSE, + scratch_pool)); + if (! targets->nelts) + return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0, NULL); + + if (opt_state->depth == svn_depth_unknown) + opt_state->depth = svn_depth_empty; + + SVN_ERR(svn_cl__eat_peg_revisions(&targets, targets, scratch_pool)); + + SVN_ERR(svn_cl__check_targets_are_local_paths(targets)); + + iterpool = svn_pool_create(scratch_pool); + for (i = 0; i < targets->nelts; i++) + { + const char *target = APR_ARRAY_IDX(targets, i, const char *); + svn_pool_clear(iterpool); + SVN_ERR(svn_cl__check_cancel(ctx->cancel_baton)); + err = svn_client_resolve(target, + opt_state->depth, + svn_wc_conflict_choose_merged, + ctx, + iterpool); + if (err) + { + svn_handle_warning2(stderr, err, "svn: "); + svn_error_clear(err); + } + } + svn_pool_destroy(iterpool); + + return SVN_NO_ERROR; +} diff --git a/subversion/svn/revert-cmd.c b/subversion/svn/revert-cmd.c new file mode 100644 index 0000000..d17aeb6 --- /dev/null +++ b/subversion/svn/revert-cmd.c @@ -0,0 +1,81 @@ +/* + * revert-cmd.c -- Subversion revert command + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + +/* ==================================================================== */ + + + +/*** Includes. ***/ + +#include "svn_path.h" +#include "svn_client.h" +#include "svn_error_codes.h" +#include "svn_error.h" +#include "cl.h" + +#include "svn_private_config.h" + + +/*** Code. ***/ + +/* This implements the `svn_opt_subcommand_t' interface. */ +svn_error_t * +svn_cl__revert(apr_getopt_t *os, + void *baton, + apr_pool_t *scratch_pool) +{ + svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state; + svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx; + apr_array_header_t *targets = NULL; + svn_error_t *err; + + SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os, + opt_state->targets, + ctx, FALSE, + scratch_pool)); + + /* Revert has no implicit dot-target `.', so don't you put that code here! */ + if (! targets->nelts) + return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0, NULL); + + /* Revert is especially conservative, by default it is as + nonrecursive as possible. */ + if (opt_state->depth == svn_depth_unknown) + opt_state->depth = svn_depth_empty; + + SVN_ERR(svn_cl__eat_peg_revisions(&targets, targets, scratch_pool)); + + SVN_ERR(svn_cl__check_targets_are_local_paths(targets)); + + err = svn_client_revert2(targets, opt_state->depth, + opt_state->changelists, ctx, scratch_pool); + if (err + && (err->apr_err == SVN_ERR_WC_INVALID_OPERATION_DEPTH) + && (! SVN_DEPTH_IS_RECURSIVE(opt_state->depth))) + { + err = svn_error_quick_wrap + (err, _("Try 'svn revert --depth infinity' instead?")); + } + + return svn_error_trace(err); +} diff --git a/subversion/svn/schema/blame.rnc b/subversion/svn/schema/blame.rnc new file mode 100644 index 0000000..b6a1e41 --- /dev/null +++ b/subversion/svn/schema/blame.rnc @@ -0,0 +1,42 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# +# +# XML RELAX NG schema for Subversion command-line client output +# For "svn blame" + + +include "common.rnc" + +start = blame + +blame = element blame { target* } + +## Information for one blamed file. +target = element target { attlist.target, entry* } +attlist.target &= attribute path { target.type } + +## Information for one line of a blamed file. +## NOTE: The order of entries in a target element is insignificant. +entry = element entry { attlist.entry, commit?, merged? } +attlist.entry &= + ## Line number. + attribute line-number { xsd:integer { minInclusive = "1" } } + +## The merged commit +merged = element merged { attlist.merged, commit } +attlist.merged &= attribute path { string } diff --git a/subversion/svn/schema/common.rnc b/subversion/svn/schema/common.rnc new file mode 100644 index 0000000..95729e3 --- /dev/null +++ b/subversion/svn/schema/common.rnc @@ -0,0 +1,77 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# +# +# XML RELAX NG schema for Subversion command-line client output +# Common declarations + +# Data types. + +## A revision number. +revnum.type = xsd:nonNegativeInteger + +## A user name. +username.type = string + +## A path or URL. +target.type = string | xsd:anyURI + +## An UUID. +uuid.type = string + +## An MD5 checksum. +md5sum.type = xsd:hexBinary { length = "16" } + +# Common elements + +## Commit info. +commit = element commit { attlist.commit, author?, date? } +attlist.commit &= attribute revision { revnum.type } + +author = element author { username.type } + +date = element date { xsd:dateTime } + +## Lock info stored in repository or working copy. +lock = + element lock { + \token, owner, comment?, created, expires? + } + +## Lock token. +\token = element token { xsd:anyURI } + +## Lock owner. +owner = element owner { username.type } + +## Lock comment. +comment = element comment { text } + +## Creation date. +created = element created { xsd:dateTime } + +## Expiration date. +expires = element expires { xsd:dateTime } + +## Node and revision properties. +property = element property { attlist.property, text } +attlist.property &= + ## The property name + attribute name { string }, + ## The encoding of the element content. If not present, the value + ## is the raw content of the element. + attribute encoding { "base64" }? diff --git a/subversion/svn/schema/diff.rnc b/subversion/svn/schema/diff.rnc new file mode 100644 index 0000000..7ac51da --- /dev/null +++ b/subversion/svn/schema/diff.rnc @@ -0,0 +1,39 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# +# +# XML RELAX NG schema for Subversion command-line client output +# For "svn diff --summarize --xml" + +include "common.rnc" + +start = diff + +diff = element diff { paths } + +paths = element paths { path* } + +## A path entry +path = element path { attlist.path, text } +attlist.path &= + ## The props of the entry. + attribute props { "none" | "modified" }, + ## The kind of the entry. + attribute kind { "dir" | "file" }, + ## The action performed against this path. This terminology + ## was chosen for consistencey from 'svn list'. + attribute item { "none" | "added" | "modified" | "deleted" } diff --git a/subversion/svn/schema/info.rnc b/subversion/svn/schema/info.rnc new file mode 100644 index 0000000..6ed2643 --- /dev/null +++ b/subversion/svn/schema/info.rnc @@ -0,0 +1,124 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# +# +# XML RELAX NG schema for Subversion command-line client output +# For "svn info" + +include "common.rnc" + +start = info + +info = element info { entry* } + +entry = + element entry { + attlist.entry, url?, repository?, wc-info?, commit?, conflict?, lock?, + tree-conflict? + } +attlist.entry &= + ## Local path. + attribute path { string }, + ## Path type. + attribute kind { "file" | "dir" }, + ## Revision number of path/URL. + attribute revision { revnum.type } + +## URL of this item in the repository. +url = element url { xsd:anyURI } + +## Information of this item's repository. +repository = element repository { root?, uuid? } + +## URL of the repository. +root = element root { xsd:anyURI } + +## UUID of the repository. +uuid = element uuid { uuid.type } + +## Info in the working copy entry. +wc-info = + element wc-info { + wcroot-abspath?, + schedule?, + changelist?, + copy-from-url?, + copy-from-rev?, + depth?, + text-updated?, + prop-updated?, + checksum? + } + +wcroot-abspath = element wcroot-abspath { string } + +schedule = + element schedule { "normal" | "add" | "delete" | "replace" | "none" } + +## The name of the changelist that the path may be a member of. +changelist = element changelist { string } + +copy-from-url = element copy-from-url { xsd:anyURI } + +copy-from-rev = element copy-from-rev { revnum.type } + +# Date when text was last updated. +text-updated = element text-updated { xsd:dateTime } + +# Date when properties were last updated. +prop-updated = element prop-updated { xsd:dateTime } + +checksum = element checksum { md5sum.type } + +conflict = + element conflict { + prev-base-file, + prev-wc-file?, + cur-base-file, + prop-file? + } + +## Previous base file. +prev-base-file = element prev-base-file { string } + +## Previous WC file. +prev-wc-file = element prev-wc-file { string } + +## Current base file. +cur-base-file = element cur-base-file { string } + +## Current properties file. +prop-file = element prop-file { string } + +## Depth of this directory, always "infinity" for non-directories +depth = element depth { "infinity" | "immediates" | "files" | "empty" } + +tree-conflict = + element tree-conflict { attlist.tree-conflict } + +attlist.tree-conflict &= + ## Local path to the original victim. + attribute victim { string }, + ## Path type. + attribute kind { "file" | "dir" }, + ## Operation causing the tree conflict. + attribute operation { "update" | "merge" | "switch" }, + ## Operation's action on the victim. + attribute action { "edit" | "add" | "delete" }, + ## Local reason for the conflict. + attribute reason { "edit" | "obstruction" | "delete" | "add" | + "missing" | "unversioned" } diff --git a/subversion/svn/schema/list.rnc b/subversion/svn/schema/list.rnc new file mode 100644 index 0000000..13d5897 --- /dev/null +++ b/subversion/svn/schema/list.rnc @@ -0,0 +1,45 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# +# +# XML RELAX NG schema for Subversion command-line client output +# For "svn list" + +include "common.rnc" + +start = lists + +lists = element lists { \list+ } + +## A target to the list command. +\list = element list { attlist.list, entry* } +attlist.list &= + ## Local path or repository URL. + attribute path { target.type } + +## A directory entry. +entry = element entry { attlist.entry, name, size?, commit, lock? } +attlist.entry &= + ## The kind of the entry. + attribute kind { "dir" | "file" } + +## Name of the file or directory. +name = element name { string } + +## File size in bytes. +size = element size { xsd:nonNegativeInteger } + diff --git a/subversion/svn/schema/log.rnc b/subversion/svn/schema/log.rnc new file mode 100644 index 0000000..14a8b7e --- /dev/null +++ b/subversion/svn/schema/log.rnc @@ -0,0 +1,55 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# +# +# XML RELAX NG schema for Subversion command-line client output +# For "svn log" + +include "common.rnc" + +start = log + +log = element log { logentry* } + +logentry = + element logentry { attlist.logentry, author?, date?, paths?, msg?, revprops?, logentry* } +attlist.logentry &= + attribute revision { revnum.type } + +## Changed paths information. +paths = element paths { path+ } + +## Path within repository. +path = element path { attlist.path, text } +attlist.path &= + ## "action code": A)dd, D)elete, R)eplace or M)odify + attribute action { "A" | "D" | "R" | "M" }, + ## kind is "" when repository was < 1.6 when committing + attribute kind { "file" | "dir" | "" }, + attribute text-mods { "true" | "false" }?, + attribute prop-mods { "true" | "false" }?, + ( + ## The copyfrom path within repository. + attribute copyfrom-path { text }, + ## Copyfrom revision number. + attribute copyfrom-rev { revnum.type })? + +## Log message. +msg = element msg { text } + +## Revision properties. +revprops = element revprops { property+ } diff --git a/subversion/svn/schema/props.rnc b/subversion/svn/schema/props.rnc new file mode 100644 index 0000000..260c93e --- /dev/null +++ b/subversion/svn/schema/props.rnc @@ -0,0 +1,36 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# +# +# XML RELAX NG schema for Subversion command-line client output +# For "svn proplist" + +include "common.rnc" + +start = properties + +properties = element properties { target* | revprops } + +target = element target { attlist.target, property* } +attlist.target &= + ## The target path. + attribute path { string } + +revprops = element revprops { attlist.revprops, property*} +attlist.revprops &= + ## The revision + attribute rev { revnum.type } diff --git a/subversion/svn/schema/status.rnc b/subversion/svn/schema/status.rnc new file mode 100644 index 0000000..6e55fa1 --- /dev/null +++ b/subversion/svn/schema/status.rnc @@ -0,0 +1,88 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# +# +# XML RELAX NG schema for Subversion command-line client output +# For "svn status" + +# The DTD compatibility annotations namespace, used for adding default +# attribute values. +namespace a = "http://relaxng.org/ns/compatibility/annotations/1.0" + +include "common.rnc" + +start = status + +status = element status { (target | changelist)* } + +target = element target { attlist.target, entry*, against? } +attlist.target &= + ## The target path. + attribute path { string } + +changelist = element changelist { attlist.changelist, entry*, against? } +attlist.changelist &= + ## The changelist name. + attribute name { string } + +## Status information for a path under the target. +entry = element entry { attlist.entry, wc-status, repos-status? } +attlist.entry &= + ## Path inside the target. + attribute path { text } + +## Status of the entry in the working copy. +wc-status = element wc-status { attlist.wc-status, commit?, lock? } + +attlist.wc-status &= + ## Item/text status. + attribute item { + "added" | "conflicted" | "deleted" | "external" | "ignored" | + "incomplete" | "merged" | "missing" | "modified" | "none" | + "normal" | "obstructed" | "replaced" | "unversioned" + }, + ## Properties status. + attribute props { "conflicted" | "modified" | "normal" | "none" }, + ## Base revision number. + attribute revision { revnum.type }?, + ## WC directory locked. + [ a:defaultValue = "false" ] + attribute wc-locked { "true" | "false" }?, + ## Add with history. + [ a:defaultValue = "false" ] + attribute copied { "true" | "false" }?, + # Item switched relative to its parent. + [ a:defaultValue = "false" ] + attribute switched { "true" | "false" }?, + ## Tree-conflict status of the item. + [ a:defaultValue = "false" ] + attribute tree-conflicted { "true" | "false" }? + +## Status in repository (if --update was specified). +repos-status = element repos-status { attlist.repos-status, lock? } +attlist.repos-status &= + ## Text/item status in the repository. + attribute item { + "added" | "deleted" | "modified" | "replaced" | "none" + }, + ## Properties status in repository. + attribute props { "modified" | "none" } + +against = element against { attlist.against, empty } +attlist.against &= + ## Revision number at which the repository information was obtained. + attribute revision { revnum.type } diff --git a/subversion/svn/status-cmd.c b/subversion/svn/status-cmd.c new file mode 100644 index 0000000..74d2847 --- /dev/null +++ b/subversion/svn/status-cmd.c @@ -0,0 +1,408 @@ +/* + * status-cmd.c -- Display status information in current directory + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + +/* ==================================================================== */ + + + +/*** Includes. ***/ + +#include "svn_string.h" +#include "svn_wc.h" +#include "svn_client.h" +#include "svn_error_codes.h" +#include "svn_error.h" +#include "svn_pools.h" +#include "svn_xml.h" +#include "svn_dirent_uri.h" +#include "svn_path.h" +#include "svn_cmdline.h" +#include "cl.h" + +#include "svn_private_config.h" +#include "private/svn_wc_private.h" + + + +/*** Code. ***/ + +struct status_baton +{ + /* These fields all correspond to the ones in the + svn_cl__print_status() interface. */ + svn_boolean_t detailed; + svn_boolean_t show_last_committed; + svn_boolean_t skip_unrecognized; + svn_boolean_t repos_locks; + + apr_hash_t *cached_changelists; + apr_pool_t *cl_pool; /* where cached changelists are allocated */ + + svn_boolean_t had_print_error; /* To avoid printing lots of errors if we get + errors while printing to stdout */ + svn_boolean_t xml_mode; + + /* Conflict stats. */ + unsigned int text_conflicts; + unsigned int prop_conflicts; + unsigned int tree_conflicts; + + svn_client_ctx_t *ctx; +}; + + +struct status_cache +{ + const char *path; + svn_client_status_t *status; +}; + +/* Print conflict stats accumulated in status baton SB. + * Do temporary allocations in POOL. */ +static svn_error_t * +print_conflict_stats(struct status_baton *sb, apr_pool_t *pool) +{ + if (sb->text_conflicts > 0 || sb->prop_conflicts > 0 || + sb->tree_conflicts > 0) + SVN_ERR(svn_cmdline_printf(pool, "%s", _("Summary of conflicts:\n"))); + + if (sb->text_conflicts > 0) + SVN_ERR(svn_cmdline_printf + (pool, _(" Text conflicts: %u\n"), sb->text_conflicts)); + + if (sb->prop_conflicts > 0) + SVN_ERR(svn_cmdline_printf + (pool, _(" Property conflicts: %u\n"), sb->prop_conflicts)); + + if (sb->tree_conflicts > 0) + SVN_ERR(svn_cmdline_printf + (pool, _(" Tree conflicts: %u\n"), sb->tree_conflicts)); + + return SVN_NO_ERROR; +} + +/* Prints XML target element with path attribute TARGET, using POOL for + temporary allocations. */ +static svn_error_t * +print_start_target_xml(const char *target, apr_pool_t *pool) +{ + svn_stringbuf_t *sb = svn_stringbuf_create("", pool); + + svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "target", + "path", target, NULL); + + return svn_cl__error_checked_fputs(sb->data, stdout); +} + + +/* Finish a target element by optionally printing an against element if + * REPOS_REV is a valid revision number, and then printing an target end tag. + * Use POOL for temporary allocations. */ +static svn_error_t * +print_finish_target_xml(svn_revnum_t repos_rev, + apr_pool_t *pool) +{ + svn_stringbuf_t *sb = svn_stringbuf_create("", pool); + + if (SVN_IS_VALID_REVNUM(repos_rev)) + { + const char *repos_rev_str; + repos_rev_str = apr_psprintf(pool, "%ld", repos_rev); + svn_xml_make_open_tag(&sb, pool, svn_xml_self_closing, "against", + "revision", repos_rev_str, NULL); + } + + svn_xml_make_close_tag(&sb, pool, "target"); + + return svn_cl__error_checked_fputs(sb->data, stdout); +} + + +/* Function which *actually* causes a status structure to be output to + the user. Called by both print_status() and svn_cl__status(). */ +static svn_error_t * +print_status_normal_or_xml(void *baton, + const char *path, + const svn_client_status_t *status, + apr_pool_t *pool) +{ + struct status_baton *sb = baton; + + if (sb->xml_mode) + return svn_cl__print_status_xml(path, status, sb->ctx, pool); + else + return svn_cl__print_status(path, status, sb->detailed, + sb->show_last_committed, + sb->skip_unrecognized, + sb->repos_locks, + &sb->text_conflicts, + &sb->prop_conflicts, + &sb->tree_conflicts, + sb->ctx, + pool); +} + + +/* A status callback function for printing STATUS for PATH. */ +static svn_error_t * +print_status(void *baton, + const char *path, + const svn_client_status_t *status, + apr_pool_t *pool) +{ + struct status_baton *sb = baton; + const char *local_abspath = status->local_abspath; + + /* ### The revision information with associates are based on what + * ### _read_info() returns. The svn_wc_status_func4_t callback is + * ### suppposed to handle the gathering of additional information from the + * ### WORKING nodes on its own. Until we've agreed on how the CLI should + * ### handle the revision information, we use this appproach to stay compat + * ### with our testsuite. */ + if (status->versioned + && !SVN_IS_VALID_REVNUM(status->revision) + && !status->copied + && (status->node_status == svn_wc_status_deleted + || status->node_status == svn_wc_status_replaced)) + { + svn_client_status_t *twks = svn_client_status_dup(status, sb->cl_pool); + + /* Copied is FALSE, so either we have a local addition, or we have + a delete that directly shadows a BASE node */ + + switch(status->node_status) + { + case svn_wc_status_replaced: + /* Just retrieve the revision below the replacement. + The other fields are filled by a copy. + (With ! copied, we know we have a BASE node) + + ### Is this really what we want to provide? */ + SVN_ERR(svn_wc__node_get_pre_ng_status_data(&twks->revision, + NULL, NULL, NULL, + sb->ctx->wc_ctx, + local_abspath, + sb->cl_pool, pool)); + break; + case svn_wc_status_deleted: + /* Retrieve some data from the original version below the delete */ + SVN_ERR(svn_wc__node_get_pre_ng_status_data(&twks->revision, + &twks->changed_rev, + &twks->changed_date, + &twks->changed_author, + sb->ctx->wc_ctx, + local_abspath, + sb->cl_pool, pool)); + break; + + default: + /* This space intentionally left blank. */ + break; + } + + status = twks; + } + + /* If the path is part of a changelist, then we don't print + the item, but instead dup & cache the status structure for later. */ + if (status->changelist) + { + /* The hash maps a changelist name to an array of status_cache + structures. */ + apr_array_header_t *path_array; + const char *cl_key = apr_pstrdup(sb->cl_pool, status->changelist); + struct status_cache *scache = apr_pcalloc(sb->cl_pool, sizeof(*scache)); + scache->path = apr_pstrdup(sb->cl_pool, path); + scache->status = svn_client_status_dup(status, sb->cl_pool); + + path_array = + apr_hash_get(sb->cached_changelists, cl_key, APR_HASH_KEY_STRING); + if (path_array == NULL) + { + path_array = apr_array_make(sb->cl_pool, 1, + sizeof(struct status_cache *)); + apr_hash_set(sb->cached_changelists, cl_key, + APR_HASH_KEY_STRING, path_array); + } + + APR_ARRAY_PUSH(path_array, struct status_cache *) = scache; + return SVN_NO_ERROR; + } + + return print_status_normal_or_xml(baton, path, status, pool); +} + +/* This implements the `svn_opt_subcommand_t' interface. */ +svn_error_t * +svn_cl__status(apr_getopt_t *os, + void *baton, + apr_pool_t *scratch_pool) +{ + svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state; + svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx; + apr_array_header_t *targets; + apr_pool_t *iterpool; + apr_hash_t *master_cl_hash = apr_hash_make(scratch_pool); + int i; + svn_opt_revision_t rev; + struct status_baton sb; + + SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os, + opt_state->targets, + ctx, FALSE, + scratch_pool)); + + /* Add "." if user passed 0 arguments */ + svn_opt_push_implicit_dot_target(targets, scratch_pool); + + SVN_ERR(svn_cl__check_targets_are_local_paths(targets)); + + /* We want our -u statuses to be against HEAD. */ + rev.kind = svn_opt_revision_head; + + sb.had_print_error = FALSE; + + if (opt_state->xml) + { + /* If output is not incremental, output the XML header and wrap + everything in a top-level element. This makes the output in + its entirety a well-formed XML document. */ + if (! opt_state->incremental) + SVN_ERR(svn_cl__xml_print_header("status", scratch_pool)); + } + else + { + if (opt_state->incremental) + return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, + _("'incremental' option only valid in XML " + "mode")); + } + + sb.detailed = (opt_state->verbose || opt_state->update); + sb.show_last_committed = opt_state->verbose; + sb.skip_unrecognized = opt_state->quiet; + sb.repos_locks = opt_state->update; + sb.xml_mode = opt_state->xml; + sb.cached_changelists = master_cl_hash; + sb.cl_pool = scratch_pool; + sb.text_conflicts = 0; + sb.prop_conflicts = 0; + sb.tree_conflicts = 0; + sb.ctx = ctx; + + SVN_ERR(svn_cl__eat_peg_revisions(&targets, targets, scratch_pool)); + + iterpool = svn_pool_create(scratch_pool); + for (i = 0; i < targets->nelts; i++) + { + const char *target = APR_ARRAY_IDX(targets, i, const char *); + svn_revnum_t repos_rev = SVN_INVALID_REVNUM; + + svn_pool_clear(iterpool); + + SVN_ERR(svn_cl__check_cancel(ctx->cancel_baton)); + + if (opt_state->xml) + SVN_ERR(print_start_target_xml(svn_dirent_local_style(target, iterpool), + iterpool)); + + /* Retrieve a hash of status structures with the information + requested by the user. */ + SVN_ERR(svn_cl__try(svn_client_status5(&repos_rev, ctx, target, &rev, + opt_state->depth, + opt_state->verbose, + opt_state->update, + opt_state->no_ignore, + opt_state->ignore_externals, + FALSE /* depth_as_sticky */, + opt_state->changelists, + print_status, &sb, + iterpool), + NULL, opt_state->quiet, + /* not versioned: */ + SVN_ERR_WC_NOT_WORKING_COPY, + SVN_NO_ERROR)); + + if (opt_state->xml) + SVN_ERR(print_finish_target_xml(repos_rev, iterpool)); + } + + /* If any paths were cached because they were associatied with + changelists, we can now display them as grouped changelists. */ + if (apr_hash_count(master_cl_hash) > 0) + { + apr_hash_index_t *hi; + svn_stringbuf_t *buf; + + if (opt_state->xml) + buf = svn_stringbuf_create("", scratch_pool); + + for (hi = apr_hash_first(scratch_pool, master_cl_hash); hi; + hi = apr_hash_next(hi)) + { + const char *changelist_name = svn__apr_hash_index_key(hi); + apr_array_header_t *path_array = svn__apr_hash_index_val(hi); + int j; + + /* ### TODO: For non-XML output, we shouldn't print the + ### leading \n on the first changelist if there were no + ### non-changelist entries. */ + if (opt_state->xml) + { + svn_stringbuf_set(buf, ""); + svn_xml_make_open_tag(&buf, scratch_pool, svn_xml_normal, + "changelist", "name", changelist_name, + NULL); + SVN_ERR(svn_cl__error_checked_fputs(buf->data, stdout)); + } + else + SVN_ERR(svn_cmdline_printf(scratch_pool, + _("\n--- Changelist '%s':\n"), + changelist_name)); + + for (j = 0; j < path_array->nelts; j++) + { + struct status_cache *scache = + APR_ARRAY_IDX(path_array, j, struct status_cache *); + SVN_ERR(print_status_normal_or_xml(&sb, scache->path, + scache->status, scratch_pool)); + } + + if (opt_state->xml) + { + svn_stringbuf_set(buf, ""); + svn_xml_make_close_tag(&buf, scratch_pool, "changelist"); + SVN_ERR(svn_cl__error_checked_fputs(buf->data, stdout)); + } + } + } + svn_pool_destroy(iterpool); + + if (opt_state->xml && (! opt_state->incremental)) + SVN_ERR(svn_cl__xml_print_footer("status", scratch_pool)); + + if (! opt_state->quiet && ! opt_state->xml) + SVN_ERR(print_conflict_stats(&sb, scratch_pool)); + + return SVN_NO_ERROR; +} diff --git a/subversion/svn/status.c b/subversion/svn/status.c new file mode 100644 index 0000000..0c5bddb --- /dev/null +++ b/subversion/svn/status.c @@ -0,0 +1,444 @@ +/* + * status.c: the command-line's portion of the "svn status" command + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + +/* ==================================================================== */ + + + +/*** Includes. ***/ +#include "svn_cmdline.h" +#include "svn_wc.h" +#include "svn_dirent_uri.h" +#include "svn_xml.h" +#include "svn_time.h" +#include "cl.h" +#include "svn_private_config.h" +#include "tree-conflicts.h" +#include "private/svn_wc_private.h" + +/* Return the single character representation of STATUS */ +static char +generate_status_code(enum svn_wc_status_kind status) +{ + switch (status) + { + case svn_wc_status_none: return ' '; + case svn_wc_status_normal: return ' '; + case svn_wc_status_added: return 'A'; + case svn_wc_status_missing: return '!'; + case svn_wc_status_incomplete: return '!'; + case svn_wc_status_deleted: return 'D'; + case svn_wc_status_replaced: return 'R'; + case svn_wc_status_modified: return 'M'; + case svn_wc_status_conflicted: return 'C'; + case svn_wc_status_obstructed: return '~'; + case svn_wc_status_ignored: return 'I'; + case svn_wc_status_external: return 'X'; + case svn_wc_status_unversioned: return '?'; + default: return '?'; + } +} + +/* Return the combined STATUS as shown in 'svn status' based + on the node status and text status */ +static enum svn_wc_status_kind +combined_status(const svn_client_status_t *status) +{ + enum svn_wc_status_kind new_status = status->node_status; + + switch (status->node_status) + { + case svn_wc_status_conflicted: + if (!status->versioned && status->conflicted) + { + /* Report unversioned tree conflict victims as missing: '!' */ + new_status = svn_wc_status_missing; + break; + } + /* fall through */ + case svn_wc_status_modified: + /* This value might be the property status */ + new_status = status->text_status; + break; + default: + break; + } + + return new_status; +} + +/* Return the combined repository STATUS as shown in 'svn status' based + on the repository node status and repository text status */ +static enum svn_wc_status_kind +combined_repos_status(const svn_client_status_t *status) +{ + if (status->repos_node_status == svn_wc_status_modified) + return status->repos_text_status; + + return status->repos_node_status; +} + +/* Return the single character representation of the switched column + status. */ +static char +generate_switch_column_code(const svn_client_status_t *status) +{ + if (status->switched) + return 'S'; + else if (status->file_external) + return 'X'; + else + return ' '; +} + +/* Return the detailed string representation of STATUS */ +static const char * +generate_status_desc(enum svn_wc_status_kind status) +{ + switch (status) + { + case svn_wc_status_none: return "none"; + case svn_wc_status_normal: return "normal"; + case svn_wc_status_added: return "added"; + case svn_wc_status_missing: return "missing"; + case svn_wc_status_incomplete: return "incomplete"; + case svn_wc_status_deleted: return "deleted"; + case svn_wc_status_replaced: return "replaced"; + case svn_wc_status_modified: return "modified"; + case svn_wc_status_conflicted: return "conflicted"; + case svn_wc_status_obstructed: return "obstructed"; + case svn_wc_status_ignored: return "ignored"; + case svn_wc_status_external: return "external"; + case svn_wc_status_unversioned: return "unversioned"; + default: + SVN_ERR_MALFUNCTION_NO_RETURN(); + } +} + + +/* Print STATUS and PATH in a format determined by DETAILED and + SHOW_LAST_COMMITTED. */ +static svn_error_t * +print_status(const char *path, + svn_boolean_t detailed, + svn_boolean_t show_last_committed, + svn_boolean_t repos_locks, + const svn_client_status_t *status, + unsigned int *text_conflicts, + unsigned int *prop_conflicts, + unsigned int *tree_conflicts, + svn_client_ctx_t *ctx, + apr_pool_t *pool) +{ + enum svn_wc_status_kind node_status = status->node_status; + enum svn_wc_status_kind prop_status = status->prop_status; + char tree_status_code = ' '; + const char *tree_desc_line = ""; + + /* For historic reasons svn ignores the property status for added nodes, even + if these nodes were copied and have local property changes. + + Note that it doesn't do this on replacements, or children of copies. + + ### Our test suite would catch more errors if we reported property + changes on copies. */ + if (node_status == svn_wc_status_added) + prop_status = svn_wc_status_none; + + /* To indicate this node is the victim of a tree conflict, we show + 'C' in the tree-conflict column, overriding any other status. + We also print a separate line describing the nature of the tree + conflict. */ + if (status->conflicted) + { + const char *desc; + const char *local_abspath = status->local_abspath; + svn_boolean_t text_conflicted; + svn_boolean_t prop_conflicted; + svn_boolean_t tree_conflicted; + + if (status->versioned) + { + svn_error_t *err; + + err = svn_wc_conflicted_p3(&text_conflicted, + &prop_conflicted, + &tree_conflicted, ctx->wc_ctx, + local_abspath, pool); + + if (err && err->apr_err == SVN_ERR_WC_UPGRADE_REQUIRED) + { + svn_error_clear(err); + text_conflicted = FALSE; + prop_conflicted = FALSE; + tree_conflicted = FALSE; + } + else + SVN_ERR(err); + } + else + { + text_conflicted = FALSE; + prop_conflicted = FALSE; + tree_conflicted = TRUE; + } + + if (tree_conflicted) + { + const svn_wc_conflict_description2_t *tree_conflict; + SVN_ERR(svn_wc__get_tree_conflict(&tree_conflict, ctx->wc_ctx, + local_abspath, pool, pool)); + SVN_ERR_ASSERT(tree_conflict != NULL); + + tree_status_code = 'C'; + SVN_ERR(svn_cl__get_human_readable_tree_conflict_description( + &desc, tree_conflict, pool)); + tree_desc_line = apr_psprintf(pool, "\n > %s", desc); + (*tree_conflicts)++; + } + else if (text_conflicted) + (*text_conflicts)++; + else if (prop_conflicted) + (*prop_conflicts)++; + } + + if (detailed) + { + char ood_status, lock_status; + const char *working_rev; + + if (! status->versioned) + working_rev = ""; + else if (status->copied + || ! SVN_IS_VALID_REVNUM(status->revision)) + working_rev = "-"; + else + working_rev = apr_psprintf(pool, "%ld", status->revision); + + if (status->repos_node_status != svn_wc_status_none) + ood_status = '*'; + else + ood_status = ' '; + + if (repos_locks) + { + if (status->repos_lock) + { + if (status->lock) + { + if (strcmp(status->repos_lock->token, status->lock->token) + == 0) + lock_status = 'K'; + else + lock_status = 'T'; + } + else + lock_status = 'O'; + } + else if (status->lock) + lock_status = 'B'; + else + lock_status = ' '; + } + else + lock_status = (status->lock) ? 'K' : ' '; + + if (show_last_committed) + { + const char *commit_rev; + const char *commit_author; + + if (SVN_IS_VALID_REVNUM(status->changed_rev)) + commit_rev = apr_psprintf(pool, "%ld", status->changed_rev); + else if (status->versioned) + commit_rev = " ? "; + else + commit_rev = ""; + + if (status->changed_author) + commit_author = status->changed_author; + else if (status->versioned) + commit_author = " ? "; + else + commit_author = ""; + + SVN_ERR + (svn_cmdline_printf(pool, + "%c%c%c%c%c%c%c %c %6s %6s %-12s %s%s\n", + generate_status_code(combined_status(status)), + generate_status_code(prop_status), + status->wc_is_locked ? 'L' : ' ', + status->copied ? '+' : ' ', + generate_switch_column_code(status), + lock_status, + tree_status_code, + ood_status, + working_rev, + commit_rev, + commit_author, + path, + tree_desc_line)); + } + else + SVN_ERR( + svn_cmdline_printf(pool, "%c%c%c%c%c%c%c %c %6s %s%s\n", + generate_status_code(combined_status(status)), + generate_status_code(prop_status), + status->wc_is_locked ? 'L' : ' ', + status->copied ? '+' : ' ', + generate_switch_column_code(status), + lock_status, + tree_status_code, + ood_status, + working_rev, + path, + tree_desc_line)); + } + else + SVN_ERR( + svn_cmdline_printf(pool, "%c%c%c%c%c%c%c %s%s\n", + generate_status_code(combined_status(status)), + generate_status_code(prop_status), + status->wc_is_locked ? 'L' : ' ', + status->copied ? '+' : ' ', + generate_switch_column_code(status), + ((status->lock) + ? 'K' : ' '), + tree_status_code, + path, + tree_desc_line)); + + return svn_cmdline_fflush(stdout); +} + + +svn_error_t * +svn_cl__print_status_xml(const char *path, + const svn_client_status_t *status, + svn_client_ctx_t *ctx, + apr_pool_t *pool) +{ + svn_stringbuf_t *sb = svn_stringbuf_create("", pool); + apr_hash_t *att_hash; + const char *local_abspath = status->local_abspath; + svn_boolean_t tree_conflicted = FALSE; + + if (status->node_status == svn_wc_status_none + && status->repos_node_status == svn_wc_status_none) + return SVN_NO_ERROR; + + if (status->conflicted) + SVN_ERR(svn_wc_conflicted_p3(NULL, NULL, &tree_conflicted, + ctx->wc_ctx, local_abspath, pool)); + + svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "entry", + "path", svn_dirent_local_style(path, pool), NULL); + + att_hash = apr_hash_make(pool); + apr_hash_set(att_hash, "item", APR_HASH_KEY_STRING, + generate_status_desc(combined_status(status))); + + apr_hash_set(att_hash, "props", APR_HASH_KEY_STRING, + generate_status_desc( + (status->node_status != svn_wc_status_deleted) + ? status->prop_status + : svn_wc_status_none)); + if (status->wc_is_locked) + apr_hash_set(att_hash, "wc-locked", APR_HASH_KEY_STRING, "true"); + if (status->copied) + apr_hash_set(att_hash, "copied", APR_HASH_KEY_STRING, "true"); + if (status->switched) + apr_hash_set(att_hash, "switched", APR_HASH_KEY_STRING, "true"); + if (status->file_external) + apr_hash_set(att_hash, "file-external", APR_HASH_KEY_STRING, "true"); + if (status->versioned && ! status->copied) + apr_hash_set(att_hash, "revision", APR_HASH_KEY_STRING, + apr_psprintf(pool, "%ld", status->revision)); + if (tree_conflicted) + apr_hash_set(att_hash, "tree-conflicted", APR_HASH_KEY_STRING, + "true"); + svn_xml_make_open_tag_hash(&sb, pool, svn_xml_normal, "wc-status", + att_hash); + + if (SVN_IS_VALID_REVNUM(status->changed_rev)) + { + svn_cl__print_xml_commit(&sb, status->changed_rev, + status->changed_author, + svn_time_to_cstring(status->changed_date, + pool), + pool); + } + + if (status->lock) + svn_cl__print_xml_lock(&sb, status->lock, pool); + + svn_xml_make_close_tag(&sb, pool, "wc-status"); + + if (status->repos_node_status != svn_wc_status_none + || status->repos_lock) + { + svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "repos-status", + "item", + generate_status_desc(combined_repos_status(status)), + "props", + generate_status_desc(status->repos_prop_status), + NULL); + if (status->repos_lock) + svn_cl__print_xml_lock(&sb, status->repos_lock, pool); + + svn_xml_make_close_tag(&sb, pool, "repos-status"); + } + + svn_xml_make_close_tag(&sb, pool, "entry"); + + return svn_cl__error_checked_fputs(sb->data, stdout); +} + +/* Called by status-cmd.c */ +svn_error_t * +svn_cl__print_status(const char *path, + const svn_client_status_t *status, + svn_boolean_t detailed, + svn_boolean_t show_last_committed, + svn_boolean_t skip_unrecognized, + svn_boolean_t repos_locks, + unsigned int *text_conflicts, + unsigned int *prop_conflicts, + unsigned int *tree_conflicts, + svn_client_ctx_t *ctx, + apr_pool_t *pool) +{ + if (! status + || (skip_unrecognized + && !(status->versioned + || status->conflicted + || status->node_status == svn_wc_status_external)) + || (status->node_status == svn_wc_status_none + && status->repos_node_status == svn_wc_status_none)) + return SVN_NO_ERROR; + + return print_status(svn_dirent_local_style(path, pool), + detailed, show_last_committed, repos_locks, status, + text_conflicts, prop_conflicts, tree_conflicts, + ctx, pool); +} diff --git a/subversion/svn/svn.1 b/subversion/svn/svn.1 new file mode 100644 index 0000000..f4ad251 --- /dev/null +++ b/subversion/svn/svn.1 @@ -0,0 +1,47 @@ +.\" +.\" +.\" Licensed to the Apache Software Foundation (ASF) under one +.\" or more contributor license agreements. See the NOTICE file +.\" distributed with this work for additional information +.\" regarding copyright ownership. The ASF licenses this file +.\" to you under the Apache License, Version 2.0 (the +.\" "License"); you may not use this file except in compliance +.\" with the License. You may obtain a copy of the License at +.\" +.\" http://www.apache.org/licenses/LICENSE-2.0 +.\" +.\" Unless required by applicable law or agreed to in writing, +.\" software distributed under the License is distributed on an +.\" "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +.\" KIND, either express or implied. See the License for the +.\" specific language governing permissions and limitations +.\" under the License. +.\" +.\" +.\" You can view this file with: +.\" nroff -man [filename] +.\" +.TH svn 1 +.SH NAME +svn \- Subversion command line client tool +.SH SYNOPSIS +.TP +\fBsvn\fP \fIcommand\fP [\fIoptions\fP] [\fIargs\fP] +.SH OVERVIEW +Subversion is a version control system, which allows you to keep old +versions of files and directories (usually source code), keep a log of +who, when, and why changes occurred, etc., like CVS, RCS or SCCS. +\fBSubversion\fP keeps a single copy of the master sources. This copy +is called the source ``repository''; it contains all the information +to permit extracting previous versions of those files at any time. + +For more information about the Subversion project, visit +http://subversion.apache.org. + +Documentation for Subversion and its tools, including detailed usage +explanations of the \fBsvn\fP, \fBsvnadmin\fP, \fBsvnserve\fP and +\fBsvnlook\fP programs, historical background, philosophical +approaches and reasonings, etc., can be found at +http://svnbook.red-bean.com/. + +Run `svn help' to access the built-in tool documentation. diff --git a/subversion/svn/switch-cmd.c b/subversion/svn/switch-cmd.c new file mode 100644 index 0000000..9c3bc14 --- /dev/null +++ b/subversion/svn/switch-cmd.c @@ -0,0 +1,187 @@ +/* + * switch-cmd.c -- Bring work tree in sync with a different URL + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + +/* ==================================================================== */ + + + +/*** Includes. ***/ + +#include "svn_wc.h" +#include "svn_client.h" +#include "svn_dirent_uri.h" +#include "svn_path.h" +#include "svn_error.h" +#include "svn_pools.h" +#include "cl.h" + +#include "svn_private_config.h" + +/*** Code. ***/ + +static svn_error_t * +rewrite_urls(const apr_array_header_t *targets, + svn_boolean_t ignore_externals, + svn_client_ctx_t *ctx, + apr_pool_t *pool) +{ + apr_pool_t *subpool; + const char *from; + const char *to; + + if (targets->nelts < 2) + return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0, NULL); + + from = APR_ARRAY_IDX(targets, 0, const char *); + to = APR_ARRAY_IDX(targets, 1, const char *); + + /* "--relocate http https" and "--relocate http://foo svn://bar" are OK, + but things like "--relocate http://foo svn" are not */ + if (svn_path_is_url(from) != svn_path_is_url(to)) + return svn_error_createf + (SVN_ERR_INCORRECT_PARAMS, NULL, + _("'%s' to '%s' is not a valid relocation"), from, to); + + subpool = svn_pool_create(pool); + + if (targets->nelts == 2) + { + SVN_ERR(svn_client_relocate2("", from, to, ignore_externals, + ctx, pool)); + } + else + { + int i; + + for (i = 2; i < targets->nelts; i++) + { + const char *target = APR_ARRAY_IDX(targets, i, const char *); + svn_pool_clear(subpool); + SVN_ERR(svn_client_relocate2(target, from, to, + ignore_externals, ctx, subpool)); + } + } + + svn_pool_destroy(subpool); + return SVN_NO_ERROR; +} + + +/* This implements the `svn_opt_subcommand_t' interface. */ +svn_error_t * +svn_cl__switch(apr_getopt_t *os, + void *baton, + apr_pool_t *scratch_pool) +{ + svn_error_t *err; + svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state; + svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx; + apr_array_header_t *targets; + const char *target, *switch_url; + svn_opt_revision_t peg_revision; + svn_depth_t depth; + svn_boolean_t depth_is_sticky; + struct svn_cl__check_externals_failed_notify_baton nwb; + + /* This command should discover (or derive) exactly two cmdline + arguments: a local path to update ("target"), and a new url to + switch to ("switch_url"). */ + SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os, + opt_state->targets, + ctx, FALSE, + scratch_pool)); + + /* handle only-rewrite case specially */ + if (opt_state->relocate) + return rewrite_urls(targets, opt_state->ignore_externals, + ctx, scratch_pool); + + if (targets->nelts < 1) + return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0, NULL); + if (targets->nelts > 2) + return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, 0, NULL); + + /* Get the required SWITCH_URL and its optional PEG_REVISION, and the + * optional TARGET argument. */ + SVN_ERR(svn_opt_parse_path(&peg_revision, &switch_url, + APR_ARRAY_IDX(targets, 0, const char *), + scratch_pool)); + if (targets->nelts == 1) + target = ""; + else + target = APR_ARRAY_IDX(targets, 1, const char *); + + /* Validate the switch_url */ + if (! svn_path_is_url(switch_url)) + return svn_error_createf(SVN_ERR_BAD_URL, NULL, + _("'%s' does not appear to be a URL"), switch_url); + + SVN_ERR(svn_cl__check_target_is_local_path(target)); + + /* Deal with depthstuffs. */ + if (opt_state->set_depth != svn_depth_unknown) + { + depth = opt_state->set_depth; + depth_is_sticky = TRUE; + } + else + { + depth = opt_state->depth; + depth_is_sticky = FALSE; + } + + nwb.wrapped_func = ctx->notify_func2; + nwb.wrapped_baton = ctx->notify_baton2; + nwb.had_externals_error = FALSE; + ctx->notify_func2 = svn_cl__check_externals_failed_notify_wrapper; + ctx->notify_baton2 = &nwb; + + /* Do the 'switch' update. */ + err = svn_client_switch3(NULL, target, switch_url, &peg_revision, + &(opt_state->start_revision), depth, + depth_is_sticky, opt_state->ignore_externals, + opt_state->force, opt_state->ignore_ancestry, + ctx, scratch_pool); + if (err) + { + if (err->apr_err == SVN_ERR_CLIENT_UNRELATED_RESOURCES) + return svn_error_createf(SVN_ERR_CLIENT_UNRELATED_RESOURCES, err, + _("Path '%s' does not share common version " + "control ancestry with the requested switch " + "location. Use --ignore-ancestry to " + "disable this check."), + svn_dirent_local_style(target, + scratch_pool)); + return err; + } + + if (! opt_state->quiet) + SVN_ERR(svn_cl__print_conflict_stats(nwb.wrapped_baton, scratch_pool)); + + if (nwb.had_externals_error) + return svn_error_create(SVN_ERR_CL_ERROR_PROCESSING_EXTERNALS, NULL, + _("Failure occurred processing one or more " + "externals definitions")); + + return SVN_NO_ERROR; +} diff --git a/subversion/svn/tree-conflicts.c b/subversion/svn/tree-conflicts.c new file mode 100644 index 0000000..b374924 --- /dev/null +++ b/subversion/svn/tree-conflicts.c @@ -0,0 +1,196 @@ +/* + * tree-conflicts.c: Tree conflicts. + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + +#include "tree-conflicts.h" +#include "svn_xml.h" +#include "svn_dirent_uri.h" +#include "svn_path.h" +#include "private/svn_token.h" + +#include "cl.h" + +#include "svn_private_config.h" + + +/* A map for svn_wc_conflict_action_t values to human-readable strings */ +static const svn_token_map_t map_conflict_action_human[] = +{ + { N_("edit"), svn_wc_conflict_action_edit }, + { N_("delete"), svn_wc_conflict_action_delete }, + { N_("add"), svn_wc_conflict_action_add }, + { N_("replace"), svn_wc_conflict_action_replace }, + { NULL, 0 } +}; + +/* A map for svn_wc_conflict_action_t values to XML strings */ +static const svn_token_map_t map_conflict_action_xml[] = +{ + { "edit", svn_wc_conflict_action_edit }, + { "delete", svn_wc_conflict_action_delete }, + { "add", svn_wc_conflict_action_add }, + { "replace", svn_wc_conflict_action_replace }, + { NULL, 0 } +}; + +/* A map for svn_wc_conflict_reason_t values to human-readable strings */ +static const svn_token_map_t map_conflict_reason_human[] = +{ + { N_("edit"), svn_wc_conflict_reason_edited }, + { N_("delete"), svn_wc_conflict_reason_deleted }, + { N_("missing"), svn_wc_conflict_reason_missing }, + { N_("obstruction"), svn_wc_conflict_reason_obstructed }, + { N_("add"), svn_wc_conflict_reason_added }, + { N_("replace"), svn_wc_conflict_reason_replaced }, + { N_("unversioned"), svn_wc_conflict_reason_unversioned }, + { NULL, 0 } +}; + +/* A map for svn_wc_conflict_reason_t values to XML strings */ +static const svn_token_map_t map_conflict_reason_xml[] = +{ + { "edit", svn_wc_conflict_reason_edited }, + { "delete", svn_wc_conflict_reason_deleted }, + { "missing", svn_wc_conflict_reason_missing }, + { "obstruction", svn_wc_conflict_reason_obstructed }, + { "add", svn_wc_conflict_reason_added }, + { "replace", svn_wc_conflict_reason_replaced }, + { "unversioned", svn_wc_conflict_reason_unversioned }, + { NULL, 0 } +}; + +/* Return a localized string representation of CONFLICT->action. */ +static const char * +action_str(const svn_wc_conflict_description2_t *conflict) +{ + return _(svn_token__to_word(map_conflict_action_human, conflict->action)); +} + +/* Return a localized string representation of CONFLICT->reason. */ +static const char * +reason_str(const svn_wc_conflict_description2_t *conflict) +{ + return _(svn_token__to_word(map_conflict_reason_human, conflict->reason)); +} + + +svn_error_t * +svn_cl__get_human_readable_tree_conflict_description( + const char **desc, + const svn_wc_conflict_description2_t *conflict, + apr_pool_t *pool) +{ + const char *action, *reason, *operation; + reason = reason_str(conflict); + action = action_str(conflict); + operation = svn_cl__operation_str_human_readable(conflict->operation, pool); + SVN_ERR_ASSERT(action && reason); + *desc = apr_psprintf(pool, _("local %s, incoming %s upon %s"), + reason, action, operation); + return SVN_NO_ERROR; +} + + +/* Helper for svn_cl__append_tree_conflict_info_xml(). + * Appends the attributes of the given VERSION to ATT_HASH. + * SIDE is the content of the version tag's side="..." attribute, + * currently one of "source-left" or "source-right".*/ +static svn_error_t * +add_conflict_version_xml(svn_stringbuf_t **pstr, + const char *side, + const svn_wc_conflict_version_t *version, + apr_pool_t *pool) +{ + apr_hash_t *att_hash = apr_hash_make(pool); + + + apr_hash_set(att_hash, "side", APR_HASH_KEY_STRING, side); + + if (version->repos_url) + apr_hash_set(att_hash, "repos-url", APR_HASH_KEY_STRING, + version->repos_url); + + if (version->path_in_repos) + apr_hash_set(att_hash, "path-in-repos", APR_HASH_KEY_STRING, + version->path_in_repos); + + if (SVN_IS_VALID_REVNUM(version->peg_rev)) + apr_hash_set(att_hash, "revision", APR_HASH_KEY_STRING, + apr_ltoa(pool, version->peg_rev)); + + if (version->node_kind != svn_node_unknown) + apr_hash_set(att_hash, "kind", APR_HASH_KEY_STRING, + svn_cl__node_kind_str_xml(version->node_kind)); + + svn_xml_make_open_tag_hash(pstr, pool, svn_xml_self_closing, + "version", att_hash); + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_cl__append_tree_conflict_info_xml( + svn_stringbuf_t *str, + const svn_wc_conflict_description2_t *conflict, + apr_pool_t *pool) +{ + apr_hash_t *att_hash = apr_hash_make(pool); + const char *tmp; + + apr_hash_set(att_hash, "victim", APR_HASH_KEY_STRING, + svn_dirent_basename(conflict->local_abspath, pool)); + + apr_hash_set(att_hash, "kind", APR_HASH_KEY_STRING, + svn_cl__node_kind_str_xml(conflict->node_kind)); + + apr_hash_set(att_hash, "operation", APR_HASH_KEY_STRING, + svn_cl__operation_str_xml(conflict->operation, pool)); + + tmp = svn_token__to_word(map_conflict_action_xml, conflict->action); + apr_hash_set(att_hash, "action", APR_HASH_KEY_STRING, tmp); + + tmp = svn_token__to_word(map_conflict_reason_xml, conflict->reason); + apr_hash_set(att_hash, "reason", APR_HASH_KEY_STRING, tmp); + + /* Open the tree-conflict tag. */ + svn_xml_make_open_tag_hash(&str, pool, svn_xml_normal, + "tree-conflict", att_hash); + + /* Add child tags for OLDER_VERSION and THEIR_VERSION. */ + + if (conflict->src_left_version) + SVN_ERR(add_conflict_version_xml(&str, + "source-left", + conflict->src_left_version, + pool)); + + if (conflict->src_right_version) + SVN_ERR(add_conflict_version_xml(&str, + "source-right", + conflict->src_right_version, + pool)); + + svn_xml_make_close_tag(&str, pool, "tree-conflict"); + + return SVN_NO_ERROR; +} + diff --git a/subversion/svn/tree-conflicts.h b/subversion/svn/tree-conflicts.h new file mode 100644 index 0000000..7a01604 --- /dev/null +++ b/subversion/svn/tree-conflicts.h @@ -0,0 +1,68 @@ +/* + * tree-conflicts.h: Tree conflicts. + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + + + +#ifndef SVN_TREE_CONFLICTS_H +#define SVN_TREE_CONFLICTS_H + +/*** Includes. ***/ +#include <apr_pools.h> + +#include "svn_types.h" +#include "svn_string.h" +#include "svn_wc.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + + + +/** + * Return in @a desc a possibly localized human readable + * description of a tree conflict described by @a conflict. + * + * Allocate the result in @a pool. + */ +svn_error_t * +svn_cl__get_human_readable_tree_conflict_description( + const char **desc, + const svn_wc_conflict_description2_t *conflict, + apr_pool_t *pool); + +/** + * Append to @a str an XML representation of the tree conflict data + * for @a conflict, in a format suitable for 'svn info --xml'. + */ +svn_error_t * +svn_cl__append_tree_conflict_info_xml( + svn_stringbuf_t *str, + const svn_wc_conflict_description2_t *conflict, + apr_pool_t *pool); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* SVN_TREE_CONFLICTS_H */ diff --git a/subversion/svn/unlock-cmd.c b/subversion/svn/unlock-cmd.c new file mode 100644 index 0000000..0f94d2a --- /dev/null +++ b/subversion/svn/unlock-cmd.c @@ -0,0 +1,68 @@ +/* + * unlock-cmd.c -- Unlock a working copy path. + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + +/* ==================================================================== */ + + + +/*** Includes. ***/ + +#include "svn_path.h" +#include "svn_pools.h" +#include "svn_client.h" +#include "svn_error_codes.h" +#include "svn_error.h" +#include "svn_cmdline.h" +#include "cl.h" +#include "svn_private_config.h" + + +/*** Code. ***/ + + +/* This implements the `svn_opt_subcommand_t' interface. */ +svn_error_t * +svn_cl__unlock(apr_getopt_t *os, + void *baton, + apr_pool_t *scratch_pool) +{ + svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state; + svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx; + apr_array_header_t *targets; + + SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os, + opt_state->targets, + ctx, FALSE, + scratch_pool)); + + /* We don't support unlock on directories, so "." is not relevant. */ + if (! targets->nelts) + return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0, NULL); + + SVN_ERR(svn_cl__eat_peg_revisions(&targets, targets, scratch_pool)); + + SVN_ERR(svn_cl__assert_homogeneous_target_type(targets)); + + return svn_error_trace( + svn_client_unlock(targets, opt_state->force, ctx, scratch_pool)); +} diff --git a/subversion/svn/update-cmd.c b/subversion/svn/update-cmd.c new file mode 100644 index 0000000..68d01f0 --- /dev/null +++ b/subversion/svn/update-cmd.c @@ -0,0 +1,181 @@ +/* + * update-cmd.c -- Bring work tree in sync with repository + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + +/* ==================================================================== */ + + + +/*** Includes. ***/ + +#include "svn_pools.h" +#include "svn_client.h" +#include "svn_path.h" +#include "svn_error_codes.h" +#include "svn_error.h" +#include "cl.h" + +#include "svn_private_config.h" + + +/*** Code. ***/ + +/* Print an update summary when there's more than one target to report + about. Each (const char *) path in TARGETS is an absolute or relative + dirent, and each (svn_revnum_t) entry in RESULT_REVS is the corresponding + updated revision, or SVN_INVALID_REVNUM if not a valid target. */ +static svn_error_t * +print_update_summary(apr_array_header_t *targets, + apr_array_header_t *result_revs, + apr_pool_t *scratch_pool) +{ + int i; + const char *path_prefix; + apr_pool_t *iterpool; + + if (targets->nelts < 2) + return SVN_NO_ERROR; + + SVN_ERR(svn_dirent_get_absolute(&path_prefix, "", scratch_pool)); + SVN_ERR(svn_cmdline_printf(scratch_pool, _("Summary of updates:\n"))); + + iterpool = svn_pool_create(scratch_pool); + + for (i = 0; i < targets->nelts; i++) + { + const char *path = APR_ARRAY_IDX(targets, i, const char *); + svn_revnum_t rev = SVN_INVALID_REVNUM; + + svn_pool_clear(iterpool); + + /* PATH shouldn't be a URL. */ + SVN_ERR_ASSERT(! svn_path_is_url(path)); + + /* Grab the result revision from the corresponding slot in our + RESULT_REVS array. */ + if (i < result_revs->nelts) + rev = APR_ARRAY_IDX(result_revs, i, svn_revnum_t); + + /* No result rev? We must have skipped this path. At any rate, + nothing to report here. */ + if (! SVN_IS_VALID_REVNUM(rev)) + continue; + + /* Convert to an absolute path if it's not already. */ + if (! svn_dirent_is_absolute(path)) + SVN_ERR(svn_dirent_get_absolute(&path, path, iterpool)); + + /* Print an update summary for this target, removing the current + working directory prefix from PATH (if PATH is at or under + $CWD), and converting the path to local style for display. */ + SVN_ERR(svn_cmdline_printf(iterpool, _(" Updated '%s' to r%ld.\n"), + svn_cl__local_style_skip_ancestor( + path_prefix, path, iterpool), + rev)); + } + + svn_pool_destroy(iterpool); + return SVN_NO_ERROR; +} + +/* This implements the `svn_opt_subcommand_t' interface. */ +svn_error_t * +svn_cl__update(apr_getopt_t *os, + void *baton, + apr_pool_t *scratch_pool) +{ + svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state; + svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx; + apr_array_header_t *targets; + svn_depth_t depth; + svn_boolean_t depth_is_sticky; + struct svn_cl__check_externals_failed_notify_baton nwb; + apr_array_header_t *result_revs; + + SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os, + opt_state->targets, + ctx, FALSE, + scratch_pool)); + + /* Add "." if user passed 0 arguments */ + svn_opt_push_implicit_dot_target(targets, scratch_pool); + + SVN_ERR(svn_cl__eat_peg_revisions(&targets, targets, scratch_pool)); + + SVN_ERR(svn_cl__check_targets_are_local_paths(targets)); + + /* If using changelists, convert targets into a set of paths that + match the specified changelist(s). */ + if (opt_state->changelists) + { + svn_depth_t cl_depth = opt_state->depth; + if (cl_depth == svn_depth_unknown) + cl_depth = svn_depth_infinity; + SVN_ERR(svn_cl__changelist_paths(&targets, + opt_state->changelists, targets, + cl_depth, ctx, scratch_pool, + scratch_pool)); + } + + /* Deal with depthstuffs. */ + if (opt_state->set_depth != svn_depth_unknown) + { + depth = opt_state->set_depth; + depth_is_sticky = TRUE; + } + else + { + depth = opt_state->depth; + depth_is_sticky = FALSE; + } + + nwb.wrapped_func = ctx->notify_func2; + nwb.wrapped_baton = ctx->notify_baton2; + nwb.had_externals_error = FALSE; + ctx->notify_func2 = svn_cl__check_externals_failed_notify_wrapper; + ctx->notify_baton2 = &nwb; + + SVN_ERR(svn_client_update4(&result_revs, targets, + &(opt_state->start_revision), + depth, depth_is_sticky, + opt_state->ignore_externals, + opt_state->force, TRUE /* adds_as_modification */, + opt_state->parents, + ctx, scratch_pool)); + + if (! opt_state->quiet) + { + SVN_ERR(print_update_summary(targets, result_revs, scratch_pool)); + + /* ### Layering problem: This call assumes that the baton we're + * passing is the one that was originally provided by + * svn_cl__get_notifier(), but that isn't promised. */ + SVN_ERR(svn_cl__print_conflict_stats(nwb.wrapped_baton, scratch_pool)); + } + + if (nwb.had_externals_error) + return svn_error_create(SVN_ERR_CL_ERROR_PROCESSING_EXTERNALS, NULL, + _("Failure occurred processing one or more " + "externals definitions")); + + return SVN_NO_ERROR; +} diff --git a/subversion/svn/upgrade-cmd.c b/subversion/svn/upgrade-cmd.c new file mode 100644 index 0000000..e2df143 --- /dev/null +++ b/subversion/svn/upgrade-cmd.c @@ -0,0 +1,78 @@ +/* + * upgrade-cmd.c -- Upgrade a working copy. + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + +/* ==================================================================== */ + + + +/*** Includes. ***/ + +#include "svn_pools.h" +#include "svn_client.h" +#include "svn_error_codes.h" +#include "svn_error.h" +#include "svn_path.h" +#include "cl.h" +#include "svn_private_config.h" + + +/*** Code. ***/ + + +/* This implements the `svn_opt_subcommand_t' interface. */ +svn_error_t * +svn_cl__upgrade(apr_getopt_t *os, + void *baton, + apr_pool_t *scratch_pool) +{ + svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state; + svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx; + apr_array_header_t *targets; + apr_pool_t *iterpool; + int i; + + SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os, + opt_state->targets, + ctx, FALSE, + scratch_pool)); + + /* Add "." if user passed 0 arguments */ + svn_opt_push_implicit_dot_target(targets, scratch_pool); + + SVN_ERR(svn_cl__eat_peg_revisions(&targets, targets, scratch_pool)); + + SVN_ERR(svn_cl__check_targets_are_local_paths(targets)); + + iterpool = svn_pool_create(scratch_pool); + for (i = 0; i < targets->nelts; i++) + { + const char *target = APR_ARRAY_IDX(targets, i, const char *); + + svn_pool_clear(iterpool); + SVN_ERR(svn_cl__check_cancel(ctx->cancel_baton)); + SVN_ERR(svn_client_upgrade(target, ctx, scratch_pool)); + } + svn_pool_destroy(iterpool); + + return SVN_NO_ERROR; +} diff --git a/subversion/svn/util.c b/subversion/svn/util.c new file mode 100644 index 0000000..be8de9a --- /dev/null +++ b/subversion/svn/util.c @@ -0,0 +1,1421 @@ +/* + * util.c: Subversion command line client utility functions. Any + * functions that need to be shared across subcommands should be put + * in here. + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + +/* ==================================================================== */ + + + +/*** Includes. ***/ + +#include <string.h> +#include <ctype.h> +#include <assert.h> + +#include <apr_env.h> +#include <apr_errno.h> +#include <apr_file_info.h> +#include <apr_strings.h> +#include <apr_tables.h> +#include <apr_general.h> +#include <apr_lib.h> + +#include "svn_pools.h" +#include "svn_error.h" +#include "svn_ctype.h" +#include "svn_client.h" +#include "svn_cmdline.h" +#include "svn_string.h" +#include "svn_dirent_uri.h" +#include "svn_path.h" +#include "svn_hash.h" +#include "svn_io.h" +#include "svn_utf.h" +#include "svn_subst.h" +#include "svn_config.h" +#include "svn_xml.h" +#include "svn_time.h" +#include "svn_private_config.h" +#include "cl.h" + +#include "private/svn_token.h" +#include "private/svn_opt_private.h" +#include "private/svn_client_private.h" +#include "private/svn_string_private.h" + + + + +svn_error_t * +svn_cl__print_commit_info(const svn_commit_info_t *commit_info, + void *baton, + apr_pool_t *pool) +{ + if (SVN_IS_VALID_REVNUM(commit_info->revision)) + SVN_ERR(svn_cmdline_printf(pool, _("\nCommitted revision %ld%s.\n"), + commit_info->revision, + commit_info->revision == 42 && + getenv("SVN_I_LOVE_PANGALACTIC_GARGLE_BLASTERS") + ? _(" (the answer to life, the universe, " + "and everything)") + : "")); + + /* Writing to stdout, as there maybe systems that consider the + * presence of stderr as an indication of commit failure. + * OTOH, this is only of informational nature to the user as + * the commit has succeeded. */ + if (commit_info->post_commit_err) + SVN_ERR(svn_cmdline_printf(pool, _("\nWarning: %s\n"), + commit_info->post_commit_err)); + + return SVN_NO_ERROR; +} + + +/* Helper for the next two functions. Set *EDITOR to some path to an + editor binary. Sources to search include: the EDITOR_CMD argument + (if not NULL), $SVN_EDITOR, the runtime CONFIG variable (if CONFIG + is not NULL), $VISUAL, $EDITOR. Return + SVN_ERR_CL_NO_EXTERNAL_EDITOR if no binary can be found. */ +static svn_error_t * +find_editor_binary(const char **editor, + const char *editor_cmd, + apr_hash_t *config) +{ + const char *e; + struct svn_config_t *cfg; + + /* Use the editor specified on the command line via --editor-cmd, if any. */ + e = editor_cmd; + + /* Otherwise look for the Subversion-specific environment variable. */ + if (! e) + e = getenv("SVN_EDITOR"); + + /* If not found then fall back on the config file. */ + if (! e) + { + cfg = config ? apr_hash_get(config, SVN_CONFIG_CATEGORY_CONFIG, + APR_HASH_KEY_STRING) : NULL; + svn_config_get(cfg, &e, SVN_CONFIG_SECTION_HELPERS, + SVN_CONFIG_OPTION_EDITOR_CMD, NULL); + } + + /* If not found yet then try general purpose environment variables. */ + if (! e) + e = getenv("VISUAL"); + if (! e) + e = getenv("EDITOR"); + +#ifdef SVN_CLIENT_EDITOR + /* If still not found then fall back on the hard-coded default. */ + if (! e) + e = SVN_CLIENT_EDITOR; +#endif + + /* Error if there is no editor specified */ + if (e) + { + const char *c; + + for (c = e; *c; c++) + if (!svn_ctype_isspace(*c)) + break; + + if (! *c) + return svn_error_create + (SVN_ERR_CL_NO_EXTERNAL_EDITOR, NULL, + _("The EDITOR, SVN_EDITOR or VISUAL environment variable or " + "'editor-cmd' run-time configuration option is empty or " + "consists solely of whitespace. Expected a shell command.")); + } + else + return svn_error_create + (SVN_ERR_CL_NO_EXTERNAL_EDITOR, NULL, + _("None of the environment variables SVN_EDITOR, VISUAL or EDITOR are " + "set, and no 'editor-cmd' run-time configuration option was found")); + + *editor = e; + return SVN_NO_ERROR; +} + + +/* Use the visual editor to edit files. This requires that the file name itself + be shell-safe, although the path to reach that file need not be shell-safe. + */ +svn_error_t * +svn_cl__edit_file_externally(const char *path, + const char *editor_cmd, + apr_hash_t *config, + apr_pool_t *pool) +{ + const char *editor, *cmd, *base_dir, *file_name, *base_dir_apr; + char *old_cwd; + int sys_err; + apr_status_t apr_err; + + svn_dirent_split(&base_dir, &file_name, path, pool); + + SVN_ERR(find_editor_binary(&editor, editor_cmd, config)); + + apr_err = apr_filepath_get(&old_cwd, APR_FILEPATH_NATIVE, pool); + if (apr_err) + return svn_error_wrap_apr(apr_err, _("Can't get working directory")); + + /* APR doesn't like "" directories */ + if (base_dir[0] == '\0') + base_dir_apr = "."; + else + SVN_ERR(svn_path_cstring_from_utf8(&base_dir_apr, base_dir, pool)); + + apr_err = apr_filepath_set(base_dir_apr, pool); + if (apr_err) + return svn_error_wrap_apr + (apr_err, _("Can't change working directory to '%s'"), base_dir); + + cmd = apr_psprintf(pool, "%s %s", editor, file_name); + sys_err = system(cmd); + + apr_err = apr_filepath_set(old_cwd, pool); + if (apr_err) + svn_handle_error2(svn_error_wrap_apr + (apr_err, _("Can't restore working directory")), + stderr, TRUE /* fatal */, "svn: "); + + if (sys_err) + /* Extracting any meaning from sys_err is platform specific, so just + use the raw value. */ + return svn_error_createf(SVN_ERR_EXTERNAL_PROGRAM, NULL, + _("system('%s') returned %d"), cmd, sys_err); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_cl__merge_file_externally(const char *base_path, + const char *their_path, + const char *my_path, + const char *merged_path, + const char *wc_path, + apr_hash_t *config, + svn_boolean_t *remains_in_conflict, + apr_pool_t *pool) +{ + char *merge_tool; + /* Error if there is no editor specified */ + if (apr_env_get(&merge_tool, "SVN_MERGE", pool) != APR_SUCCESS) + { + struct svn_config_t *cfg; + merge_tool = NULL; + cfg = config ? apr_hash_get(config, SVN_CONFIG_CATEGORY_CONFIG, + APR_HASH_KEY_STRING) : NULL; + /* apr_env_get wants char **, this wants const char ** */ + svn_config_get(cfg, (const char **)&merge_tool, + SVN_CONFIG_SECTION_HELPERS, + SVN_CONFIG_OPTION_MERGE_TOOL_CMD, NULL); + } + + if (merge_tool) + { + const char *c; + + for (c = merge_tool; *c; c++) + if (!svn_ctype_isspace(*c)) + break; + + if (! *c) + return svn_error_create + (SVN_ERR_CL_NO_EXTERNAL_MERGE_TOOL, NULL, + _("The SVN_MERGE environment variable is empty or " + "consists solely of whitespace. Expected a shell command.\n")); + } + else + return svn_error_create + (SVN_ERR_CL_NO_EXTERNAL_MERGE_TOOL, NULL, + _("The environment variable SVN_MERGE and the merge-tool-cmd run-time " + "configuration option were not set.\n")); + + { + const char *arguments[7] = { 0 }; + char *cwd; + int exitcode; + + apr_status_t status = apr_filepath_get(&cwd, APR_FILEPATH_NATIVE, pool); + if (status != 0) + return svn_error_wrap_apr(status, NULL); + + arguments[0] = merge_tool; + arguments[1] = base_path; + arguments[2] = their_path; + arguments[3] = my_path; + arguments[4] = merged_path; + arguments[5] = wc_path; + arguments[6] = NULL; + + SVN_ERR(svn_io_run_cmd(svn_dirent_internal_style(cwd, pool), merge_tool, + arguments, &exitcode, NULL, TRUE, NULL, NULL, NULL, + pool)); + /* Exit code 0 means the merge was successful. + * Exit code 1 means the file was left in conflict but it + * is OK to continue with the merge. + * Any other exit code means there was a real problem. */ + if (exitcode != 0 && exitcode != 1) + return svn_error_createf + (SVN_ERR_EXTERNAL_PROGRAM, NULL, + _("The external merge tool exited with exit code %d"), exitcode); + else if (remains_in_conflict) + *remains_in_conflict = exitcode == 1; + } + return SVN_NO_ERROR; +} + +svn_error_t * +svn_cl__edit_string_externally(svn_string_t **edited_contents /* UTF-8! */, + const char **tmpfile_left /* UTF-8! */, + const char *editor_cmd, + const char *base_dir /* UTF-8! */, + const svn_string_t *contents /* UTF-8! */, + const char *filename, + apr_hash_t *config, + svn_boolean_t as_text, + const char *encoding, + apr_pool_t *pool) +{ + const char *editor; + const char *cmd; + apr_file_t *tmp_file; + const char *tmpfile_name; + const char *tmpfile_native; + const char *tmpfile_apr, *base_dir_apr; + svn_string_t *translated_contents; + apr_status_t apr_err, apr_err2; + apr_size_t written; + apr_finfo_t finfo_before, finfo_after; + svn_error_t *err = SVN_NO_ERROR, *err2; + char *old_cwd; + int sys_err; + svn_boolean_t remove_file = TRUE; + + SVN_ERR(find_editor_binary(&editor, editor_cmd, config)); + + /* Convert file contents from UTF-8/LF if desired. */ + if (as_text) + { + const char *translated; + SVN_ERR(svn_subst_translate_cstring2(contents->data, &translated, + APR_EOL_STR, FALSE, + NULL, FALSE, pool)); + translated_contents = svn_string_create("", pool); + if (encoding) + SVN_ERR(svn_utf_cstring_from_utf8_ex2(&translated_contents->data, + translated, encoding, pool)); + else + SVN_ERR(svn_utf_cstring_from_utf8(&translated_contents->data, + translated, pool)); + translated_contents->len = strlen(translated_contents->data); + } + else + translated_contents = svn_string_dup(contents, pool); + + /* Move to BASE_DIR to avoid getting characters that need quoting + into tmpfile_name */ + apr_err = apr_filepath_get(&old_cwd, APR_FILEPATH_NATIVE, pool); + if (apr_err) + return svn_error_wrap_apr(apr_err, _("Can't get working directory")); + + /* APR doesn't like "" directories */ + if (base_dir[0] == '\0') + base_dir_apr = "."; + else + SVN_ERR(svn_path_cstring_from_utf8(&base_dir_apr, base_dir, pool)); + apr_err = apr_filepath_set(base_dir_apr, pool); + if (apr_err) + { + return svn_error_wrap_apr + (apr_err, _("Can't change working directory to '%s'"), base_dir); + } + + /*** From here on, any problems that occur require us to cd back!! ***/ + + /* Ask the working copy for a temporary file named FILENAME-something. */ + err = svn_io_open_uniquely_named(&tmp_file, &tmpfile_name, + "" /* dirpath */, + filename, + ".tmp", + svn_io_file_del_none, pool, pool); + + if (err && (APR_STATUS_IS_EACCES(err->apr_err) || err->apr_err == EROFS)) + { + const char *temp_dir_apr; + + svn_error_clear(err); + + SVN_ERR(svn_io_temp_dir(&base_dir, pool)); + + SVN_ERR(svn_path_cstring_from_utf8(&temp_dir_apr, base_dir, pool)); + apr_err = apr_filepath_set(temp_dir_apr, pool); + if (apr_err) + { + return svn_error_wrap_apr + (apr_err, _("Can't change working directory to '%s'"), base_dir); + } + + err = svn_io_open_uniquely_named(&tmp_file, &tmpfile_name, + "" /* dirpath */, + filename, + ".tmp", + svn_io_file_del_none, pool, pool); + } + + if (err) + goto cleanup2; + + /*** From here on, any problems that occur require us to cleanup + the file we just created!! ***/ + + /* Dump initial CONTENTS to TMP_FILE. */ + apr_err = apr_file_write_full(tmp_file, translated_contents->data, + translated_contents->len, &written); + + apr_err2 = apr_file_close(tmp_file); + if (! apr_err) + apr_err = apr_err2; + + /* Make sure the whole CONTENTS were written, else return an error. */ + if (apr_err) + { + err = svn_error_wrap_apr(apr_err, _("Can't write to '%s'"), + tmpfile_name); + goto cleanup; + } + + err = svn_path_cstring_from_utf8(&tmpfile_apr, tmpfile_name, pool); + if (err) + goto cleanup; + + /* Get information about the temporary file before the user has + been allowed to edit its contents. */ + apr_err = apr_stat(&finfo_before, tmpfile_apr, + APR_FINFO_MTIME, pool); + if (apr_err) + { + err = svn_error_wrap_apr(apr_err, _("Can't stat '%s'"), tmpfile_name); + goto cleanup; + } + + /* Backdate the file a little bit in case the editor is very fast + and doesn't change the size. (Use two seconds, since some + filesystems have coarse granularity.) It's OK if this call + fails, so we don't check its return value.*/ + apr_file_mtime_set(tmpfile_apr, finfo_before.mtime - 2000, pool); + + /* Stat it again to get the mtime we actually set. */ + apr_err = apr_stat(&finfo_before, tmpfile_apr, + APR_FINFO_MTIME | APR_FINFO_SIZE, pool); + if (apr_err) + { + err = svn_error_wrap_apr(apr_err, _("Can't stat '%s'"), tmpfile_name); + goto cleanup; + } + + /* Prepare the editor command line. */ + err = svn_utf_cstring_from_utf8(&tmpfile_native, tmpfile_name, pool); + if (err) + goto cleanup; + cmd = apr_psprintf(pool, "%s %s", editor, tmpfile_native); + + /* If the caller wants us to leave the file around, return the path + of the file we'll use, and make a note not to destroy it. */ + if (tmpfile_left) + { + *tmpfile_left = svn_dirent_join(base_dir, tmpfile_name, pool); + remove_file = FALSE; + } + + /* Now, run the editor command line. */ + sys_err = system(cmd); + if (sys_err != 0) + { + /* Extracting any meaning from sys_err is platform specific, so just + use the raw value. */ + err = svn_error_createf(SVN_ERR_EXTERNAL_PROGRAM, NULL, + _("system('%s') returned %d"), cmd, sys_err); + goto cleanup; + } + + /* Get information about the temporary file after the assumed editing. */ + apr_err = apr_stat(&finfo_after, tmpfile_apr, + APR_FINFO_MTIME | APR_FINFO_SIZE, pool); + if (apr_err) + { + err = svn_error_wrap_apr(apr_err, _("Can't stat '%s'"), tmpfile_name); + goto cleanup; + } + + /* If the file looks changed... */ + if ((finfo_before.mtime != finfo_after.mtime) || + (finfo_before.size != finfo_after.size)) + { + svn_stringbuf_t *edited_contents_s; + err = svn_stringbuf_from_file2(&edited_contents_s, tmpfile_name, pool); + if (err) + goto cleanup; + + *edited_contents = svn_stringbuf__morph_into_string(edited_contents_s); + + /* Translate back to UTF8/LF if desired. */ + if (as_text) + { + err = svn_subst_translate_string2(edited_contents, FALSE, FALSE, + *edited_contents, encoding, FALSE, + pool, pool); + if (err) + { + err = svn_error_quick_wrap + (err, + _("Error normalizing edited contents to internal format")); + goto cleanup; + } + } + } + else + { + /* No edits seem to have been made */ + *edited_contents = NULL; + } + + cleanup: + if (remove_file) + { + /* Remove the file from disk. */ + err2 = svn_io_remove_file2(tmpfile_name, FALSE, pool); + + /* Only report remove error if there was no previous error. */ + if (! err && err2) + err = err2; + else + svn_error_clear(err2); + } + + cleanup2: + /* If we against all probability can't cd back, all further relative + file references would be screwed up, so we have to abort. */ + apr_err = apr_filepath_set(old_cwd, pool); + if (apr_err) + { + svn_handle_error2(svn_error_wrap_apr + (apr_err, _("Can't restore working directory")), + stderr, TRUE /* fatal */, "svn: "); + } + + return svn_error_trace(err); +} + + +/* A svn_client_ctx_t's log_msg_baton3, for use with + svn_cl__make_log_msg_baton(). */ +struct log_msg_baton +{ + const char *editor_cmd; /* editor specified via --editor-cmd, else NULL */ + const char *message; /* the message. */ + const char *message_encoding; /* the locale/encoding of the message. */ + const char *base_dir; /* the base directory for an external edit. UTF-8! */ + const char *tmpfile_left; /* the tmpfile left by an external edit. UTF-8! */ + svn_boolean_t non_interactive; /* if true, don't pop up an editor */ + apr_hash_t *config; /* client configuration hash */ + svn_boolean_t keep_locks; /* Keep repository locks? */ + apr_pool_t *pool; /* a pool. */ +}; + + +svn_error_t * +svn_cl__make_log_msg_baton(void **baton, + svn_cl__opt_state_t *opt_state, + const char *base_dir /* UTF-8! */, + apr_hash_t *config, + apr_pool_t *pool) +{ + struct log_msg_baton *lmb = apr_palloc(pool, sizeof(*lmb)); + + if (opt_state->filedata) + { + if (strlen(opt_state->filedata->data) < opt_state->filedata->len) + { + /* The data contains a zero byte, and therefore can't be + represented as a C string. Punt now; it's probably not + a deliberate encoding, and even if it is, we still + can't handle it. */ + return svn_error_create(SVN_ERR_CL_BAD_LOG_MESSAGE, NULL, + _("Log message contains a zero byte")); + } + lmb->message = opt_state->filedata->data; + } + else + { + lmb->message = opt_state->message; + } + + lmb->editor_cmd = opt_state->editor_cmd; + if (opt_state->encoding) + { + lmb->message_encoding = opt_state->encoding; + } + else if (config) + { + svn_config_t *cfg = apr_hash_get(config, SVN_CONFIG_CATEGORY_CONFIG, + APR_HASH_KEY_STRING); + svn_config_get(cfg, &(lmb->message_encoding), + SVN_CONFIG_SECTION_MISCELLANY, + SVN_CONFIG_OPTION_LOG_ENCODING, + NULL); + } + + lmb->base_dir = base_dir ? base_dir : ""; + lmb->tmpfile_left = NULL; + lmb->config = config; + lmb->keep_locks = opt_state->no_unlock; + lmb->non_interactive = opt_state->non_interactive; + lmb->pool = pool; + *baton = lmb; + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_cl__cleanup_log_msg(void *log_msg_baton, + svn_error_t *commit_err, + apr_pool_t *pool) +{ + struct log_msg_baton *lmb = log_msg_baton; + svn_error_t *err; + + /* If there was no tmpfile left, or there is no log message baton, + return COMMIT_ERR. */ + if ((! lmb) || (! lmb->tmpfile_left)) + return commit_err; + + /* If there was no commit error, cleanup the tmpfile and return. */ + if (! commit_err) + return svn_io_remove_file2(lmb->tmpfile_left, FALSE, lmb->pool); + + /* There was a commit error; there is a tmpfile. Leave the tmpfile + around, and add message about its presence to the commit error + chain. Then return COMMIT_ERR. If the conversion from UTF-8 to + native encoding fails, we have to compose that error with the + commit error chain, too. */ + + err = svn_error_createf(commit_err->apr_err, NULL, + _(" '%s'"), + svn_dirent_local_style(lmb->tmpfile_left, pool)); + svn_error_compose(commit_err, + svn_error_create(commit_err->apr_err, err, + _("Your commit message was left in " + "a temporary file:"))); + return commit_err; +} + + +/* Remove line-starting PREFIX and everything after it from BUFFER. + If NEW_LEN is non-NULL, return the new length of BUFFER in + *NEW_LEN. */ +static void +truncate_buffer_at_prefix(apr_size_t *new_len, + char *buffer, + const char *prefix) +{ + char *substring = buffer; + + assert(buffer && prefix); + + /* Initialize *NEW_LEN. */ + if (new_len) + *new_len = strlen(buffer); + + while (1) + { + /* Find PREFIX in BUFFER. */ + substring = strstr(substring, prefix); + if (! substring) + return; + + /* We found PREFIX. Is it really a PREFIX? Well, if it's the first + thing in the file, or if the character before it is a + line-terminator character, it sure is. */ + if ((substring == buffer) + || (*(substring - 1) == '\r') + || (*(substring - 1) == '\n')) + { + *substring = '\0'; + if (new_len) + *new_len = substring - buffer; + } + else if (substring) + { + /* Well, it wasn't really a prefix, so just advance by 1 + character and continue. */ + substring++; + } + } + + /* NOTREACHED */ +} + + +#define EDITOR_EOF_PREFIX _("--This line, and those below, will be ignored--") + +svn_error_t * +svn_cl__get_log_message(const char **log_msg, + const char **tmp_file, + const apr_array_header_t *commit_items, + void *baton, + apr_pool_t *pool) +{ + svn_stringbuf_t *default_msg = NULL; + struct log_msg_baton *lmb = baton; + svn_stringbuf_t *message = NULL; + + /* Set default message. */ + default_msg = svn_stringbuf_create(APR_EOL_STR, pool); + svn_stringbuf_appendcstr(default_msg, EDITOR_EOF_PREFIX); + svn_stringbuf_appendcstr(default_msg, APR_EOL_STR APR_EOL_STR); + + *tmp_file = NULL; + if (lmb->message) + { + svn_stringbuf_t *log_msg_buf = svn_stringbuf_create(lmb->message, pool); + svn_string_t *log_msg_str = apr_pcalloc(pool, sizeof(*log_msg_str)); + + /* Trim incoming messages of the EOF marker text and the junk + that follows it. */ + truncate_buffer_at_prefix(&(log_msg_buf->len), log_msg_buf->data, + EDITOR_EOF_PREFIX); + + /* Make a string from a stringbuf, sharing the data allocation. */ + log_msg_str->data = log_msg_buf->data; + log_msg_str->len = log_msg_buf->len; + SVN_ERR_W(svn_subst_translate_string2(&log_msg_str, FALSE, FALSE, + log_msg_str, lmb->message_encoding, + FALSE, pool, pool), + _("Error normalizing log message to internal format")); + + *log_msg = log_msg_str->data; + return SVN_NO_ERROR; + } + + if (! commit_items->nelts) + { + *log_msg = ""; + return SVN_NO_ERROR; + } + + while (! message) + { + /* We still don't have a valid commit message. Use $EDITOR to + get one. Note that svn_cl__edit_externally will still return + a UTF-8'ized log message. */ + int i; + svn_stringbuf_t *tmp_message = svn_stringbuf_dup(default_msg, pool); + svn_error_t *err = SVN_NO_ERROR; + svn_string_t *msg_string = svn_string_create("", pool); + + for (i = 0; i < commit_items->nelts; i++) + { + svn_client_commit_item3_t *item + = APR_ARRAY_IDX(commit_items, i, svn_client_commit_item3_t *); + const char *path = item->path; + char text_mod = '_', prop_mod = ' ', unlock = ' '; + + if (! path) + path = item->url; + else if (! *path) + path = "."; + + if (! svn_path_is_url(path) && lmb->base_dir) + path = svn_dirent_is_child(lmb->base_dir, path, pool); + + /* If still no path, then just use current directory. */ + if (! path) + path = "."; + + if ((item->state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE) + && (item->state_flags & SVN_CLIENT_COMMIT_ITEM_ADD)) + text_mod = 'R'; + else if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_ADD) + text_mod = 'A'; + else if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE) + text_mod = 'D'; + else if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_TEXT_MODS) + text_mod = 'M'; + + if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_PROP_MODS) + prop_mod = 'M'; + + if (! lmb->keep_locks + && item->state_flags & SVN_CLIENT_COMMIT_ITEM_LOCK_TOKEN) + unlock = 'U'; + + svn_stringbuf_appendbyte(tmp_message, text_mod); + svn_stringbuf_appendbyte(tmp_message, prop_mod); + svn_stringbuf_appendbyte(tmp_message, unlock); + if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_IS_COPY) + /* History included via copy/move. */ + svn_stringbuf_appendcstr(tmp_message, "+ "); + else + svn_stringbuf_appendcstr(tmp_message, " "); + svn_stringbuf_appendcstr(tmp_message, path); + svn_stringbuf_appendcstr(tmp_message, APR_EOL_STR); + } + + msg_string->data = tmp_message->data; + msg_string->len = tmp_message->len; + + /* Use the external edit to get a log message. */ + if (! lmb->non_interactive) + { + err = svn_cl__edit_string_externally(&msg_string, &lmb->tmpfile_left, + lmb->editor_cmd, lmb->base_dir, + msg_string, "svn-commit", + lmb->config, TRUE, + lmb->message_encoding, + pool); + } + else /* non_interactive flag says we can't pop up an editor, so error */ + { + return svn_error_create + (SVN_ERR_CL_INSUFFICIENT_ARGS, NULL, + _("Cannot invoke editor to get log message " + "when non-interactive")); + } + + /* Dup the tmpfile path into its baton's pool. */ + *tmp_file = lmb->tmpfile_left = apr_pstrdup(lmb->pool, + lmb->tmpfile_left); + + /* If the edit returned an error, handle it. */ + if (err) + { + if (err->apr_err == SVN_ERR_CL_NO_EXTERNAL_EDITOR) + err = svn_error_quick_wrap + (err, _("Could not use external editor to fetch log message; " + "consider setting the $SVN_EDITOR environment variable " + "or using the --message (-m) or --file (-F) options")); + return svn_error_trace(err); + } + + if (msg_string) + message = svn_stringbuf_create_from_string(msg_string, pool); + + /* Strip the prefix from the buffer. */ + if (message) + truncate_buffer_at_prefix(&message->len, message->data, + EDITOR_EOF_PREFIX); + + if (message) + { + /* We did get message, now check if it is anything more than just + white space as we will consider white space only as empty */ + apr_size_t len; + + for (len = 0; len < message->len; len++) + { + /* FIXME: should really use an UTF-8 whitespace test + rather than svn_ctype_isspace, which is ASCII only */ + if (! svn_ctype_isspace(message->data[len])) + break; + } + if (len == message->len) + message = NULL; + } + + if (! message) + { + const char *reply; + SVN_ERR(svn_cmdline_prompt_user2 + (&reply, + _("\nLog message unchanged or not specified\n" + "(a)bort, (c)ontinue, (e)dit:\n"), NULL, pool)); + if (reply) + { + int letter = apr_tolower(reply[0]); + + /* If the user chooses to abort, we cleanup the + temporary file and exit the loop with a NULL + message. */ + if ('a' == letter) + { + SVN_ERR(svn_io_remove_file2(lmb->tmpfile_left, FALSE, pool)); + *tmp_file = lmb->tmpfile_left = NULL; + break; + } + + /* If the user chooses to continue, we make an empty + message, which will cause us to exit the loop. We + also cleanup the temporary file. */ + if ('c' == letter) + { + SVN_ERR(svn_io_remove_file2(lmb->tmpfile_left, FALSE, pool)); + *tmp_file = lmb->tmpfile_left = NULL; + message = svn_stringbuf_create("", pool); + } + + /* If the user chooses anything else, the loop will + continue on the NULL message. */ + } + } + } + + *log_msg = message ? message->data : NULL; + return SVN_NO_ERROR; +} + + +/* ### The way our error wrapping currently works, the error returned + * from here will look as though it originates in this source file, + * instead of in the caller's source file. This can be a bit + * misleading, until one starts debugging. Ideally, there'd be a way + * to wrap an error while preserving its FILE/LINE info. + */ +svn_error_t * +svn_cl__may_need_force(svn_error_t *err) +{ + if (err + && (err->apr_err == SVN_ERR_UNVERSIONED_RESOURCE || + err->apr_err == SVN_ERR_CLIENT_MODIFIED)) + { + /* Should this svn_error_compose a new error number? Probably not, + the error hasn't changed. */ + err = svn_error_quick_wrap + (err, _("Use --force to override this restriction (local modifications " + "may be lost)")); + } + + return svn_error_trace(err); +} + + +svn_error_t * +svn_cl__error_checked_fputs(const char *string, FILE* stream) +{ + /* On POSIX systems, errno will be set on an error in fputs, but this might + not be the case on other platforms. We reset errno and only + use it if it was set by the below fputs call. Else, we just return + a generic error. */ + errno = 0; + + if (fputs(string, stream) == EOF) + { + if (errno) + return svn_error_wrap_apr(errno, _("Write error")); + else + return svn_error_create(SVN_ERR_IO_WRITE_ERROR, NULL, NULL); + } + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_cl__try(svn_error_t *err, + apr_array_header_t *errors_seen, + svn_boolean_t quiet, + ...) +{ + if (err) + { + apr_status_t apr_err; + va_list ap; + + va_start(ap, quiet); + while ((apr_err = va_arg(ap, apr_status_t)) != SVN_NO_ERROR) + { + if (errors_seen) + { + int i; + svn_boolean_t add = TRUE; + + /* Don't report duplicate error codes. */ + for (i = 0; i < errors_seen->nelts; i++) + { + if (APR_ARRAY_IDX(errors_seen, i, + apr_status_t) == err->apr_err) + { + add = FALSE; + break; + } + } + if (add) + APR_ARRAY_PUSH(errors_seen, apr_status_t) = err->apr_err; + } + if (err->apr_err == apr_err) + { + if (! quiet) + svn_handle_warning2(stderr, err, "svn: "); + svn_error_clear(err); + return SVN_NO_ERROR; + } + } + va_end(ap); + } + + return svn_error_trace(err); +} + + +void +svn_cl__xml_tagged_cdata(svn_stringbuf_t **sb, + apr_pool_t *pool, + const char *tagname, + const char *string) +{ + if (string) + { + svn_xml_make_open_tag(sb, pool, svn_xml_protect_pcdata, + tagname, NULL); + svn_xml_escape_cdata_cstring(sb, string, pool); + svn_xml_make_close_tag(sb, pool, tagname); + } +} + + +void +svn_cl__print_xml_commit(svn_stringbuf_t **sb, + svn_revnum_t revision, + const char *author, + const char *date, + apr_pool_t *pool) +{ + /* "<commit ...>" */ + svn_xml_make_open_tag(sb, pool, svn_xml_normal, "commit", + "revision", + apr_psprintf(pool, "%ld", revision), NULL); + + /* "<author>xx</author>" */ + if (author) + svn_cl__xml_tagged_cdata(sb, pool, "author", author); + + /* "<date>xx</date>" */ + if (date) + svn_cl__xml_tagged_cdata(sb, pool, "date", date); + + /* "</commit>" */ + svn_xml_make_close_tag(sb, pool, "commit"); +} + + +void +svn_cl__print_xml_lock(svn_stringbuf_t **sb, + const svn_lock_t *lock, + apr_pool_t *pool) +{ + /* "<lock>" */ + svn_xml_make_open_tag(sb, pool, svn_xml_normal, "lock", NULL); + + /* "<token>xx</token>" */ + svn_cl__xml_tagged_cdata(sb, pool, "token", lock->token); + + /* "<owner>xx</owner>" */ + svn_cl__xml_tagged_cdata(sb, pool, "owner", lock->owner); + + /* "<comment>xx</comment>" */ + svn_cl__xml_tagged_cdata(sb, pool, "comment", lock->comment); + + /* "<created>xx</created>" */ + svn_cl__xml_tagged_cdata(sb, pool, "created", + svn_time_to_cstring(lock->creation_date, pool)); + + /* "<expires>xx</expires>" */ + if (lock->expiration_date != 0) + svn_cl__xml_tagged_cdata(sb, pool, "expires", + svn_time_to_cstring(lock->expiration_date, pool)); + + /* "</lock>" */ + svn_xml_make_close_tag(sb, pool, "lock"); +} + + +svn_error_t * +svn_cl__xml_print_header(const char *tagname, + apr_pool_t *pool) +{ + svn_stringbuf_t *sb = svn_stringbuf_create("", pool); + + /* <?xml version="1.0" encoding="UTF-8"?> */ + svn_xml_make_header2(&sb, "UTF-8", pool); + + /* "<TAGNAME>" */ + svn_xml_make_open_tag(&sb, pool, svn_xml_normal, tagname, NULL); + + return svn_cl__error_checked_fputs(sb->data, stdout); +} + + +svn_error_t * +svn_cl__xml_print_footer(const char *tagname, + apr_pool_t *pool) +{ + svn_stringbuf_t *sb = svn_stringbuf_create("", pool); + + /* "</TAGNAME>" */ + svn_xml_make_close_tag(&sb, pool, tagname); + return svn_cl__error_checked_fputs(sb->data, stdout); +} + + +/* A map for svn_node_kind_t values to XML strings */ +static const svn_token_map_t map_node_kind_xml[] = +{ + { "none", svn_node_none }, + { "file", svn_node_file }, + { "dir", svn_node_dir }, + { "", svn_node_unknown }, + { NULL, 0 } +}; + +/* A map for svn_node_kind_t values to human-readable strings */ +static const svn_token_map_t map_node_kind_human[] = +{ + { N_("none"), svn_node_none }, + { N_("file"), svn_node_file }, + { N_("dir"), svn_node_dir }, + { "", svn_node_unknown }, + { NULL, 0 } +}; + +const char * +svn_cl__node_kind_str_xml(svn_node_kind_t kind) +{ + return svn_token__to_word(map_node_kind_xml, kind); +} + +const char * +svn_cl__node_kind_str_human_readable(svn_node_kind_t kind) +{ + return _(svn_token__to_word(map_node_kind_human, kind)); +} + + +/* A map for svn_wc_operation_t values to XML strings */ +static const svn_token_map_t map_wc_operation_xml[] = +{ + { "none", svn_wc_operation_none }, + { "update", svn_wc_operation_update }, + { "switch", svn_wc_operation_switch }, + { "merge", svn_wc_operation_merge }, + { NULL, 0 } +}; + +/* A map for svn_wc_operation_t values to human-readable strings */ +static const svn_token_map_t map_wc_operation_human[] = +{ + { N_("none"), svn_wc_operation_none }, + { N_("update"), svn_wc_operation_update }, + { N_("switch"), svn_wc_operation_switch }, + { N_("merge"), svn_wc_operation_merge }, + { NULL, 0 } +}; + +const char * +svn_cl__operation_str_xml(svn_wc_operation_t operation, apr_pool_t *pool) +{ + return svn_token__to_word(map_wc_operation_xml, operation); +} + +const char * +svn_cl__operation_str_human_readable(svn_wc_operation_t operation, + apr_pool_t *pool) +{ + return _(svn_token__to_word(map_wc_operation_human, operation)); +} + + +svn_error_t * +svn_cl__args_to_target_array_print_reserved(apr_array_header_t **targets, + apr_getopt_t *os, + const apr_array_header_t *known_targets, + svn_client_ctx_t *ctx, + svn_boolean_t keep_last_origpath_on_truepath_collision, + apr_pool_t *pool) +{ + svn_error_t *err = svn_client_args_to_target_array2(targets, + os, + known_targets, + ctx, + keep_last_origpath_on_truepath_collision, + pool); + if (err) + { + if (err->apr_err == SVN_ERR_RESERVED_FILENAME_SPECIFIED) + { + svn_handle_error2(err, stderr, FALSE, "svn: Skipping argument: "); + svn_error_clear(err); + } + else + return svn_error_trace(err); + } + return SVN_NO_ERROR; +} + + +/* Helper for svn_cl__get_changelist(); implements + svn_changelist_receiver_t. */ +static svn_error_t * +changelist_receiver(void *baton, + const char *path, + const char *changelist, + apr_pool_t *pool) +{ + /* No need to check CHANGELIST; our caller only asked about one of them. */ + apr_array_header_t *paths = baton; + APR_ARRAY_PUSH(paths, const char *) = apr_pstrdup(paths->pool, path); + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_cl__changelist_paths(apr_array_header_t **paths, + const apr_array_header_t *changelists, + const apr_array_header_t *targets, + svn_depth_t depth, + svn_client_ctx_t *ctx, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + apr_array_header_t *found; + apr_hash_t *paths_hash; + apr_pool_t *iterpool; + int i; + + if (! (changelists && changelists->nelts)) + { + *paths = (apr_array_header_t *)targets; + return SVN_NO_ERROR; + } + + found = apr_array_make(scratch_pool, 8, sizeof(const char *)); + iterpool = svn_pool_create(scratch_pool); + for (i = 0; i < targets->nelts; i++) + { + const char *target = APR_ARRAY_IDX(targets, i, const char *); + svn_pool_clear(iterpool); + SVN_ERR(svn_client_get_changelists(target, changelists, depth, + changelist_receiver, found, + ctx, iterpool)); + } + svn_pool_destroy(iterpool); + + SVN_ERR(svn_hash_from_cstring_keys(&paths_hash, found, result_pool)); + return svn_error_trace(svn_hash_keys(paths, paths_hash, result_pool)); +} + +svn_cl__show_revs_t +svn_cl__show_revs_from_word(const char *word) +{ + if (strcmp(word, SVN_CL__SHOW_REVS_MERGED) == 0) + return svn_cl__show_revs_merged; + if (strcmp(word, SVN_CL__SHOW_REVS_ELIGIBLE) == 0) + return svn_cl__show_revs_eligible; + /* word is an invalid flavor. */ + return svn_cl__show_revs_invalid; +} + + +svn_error_t * +svn_cl__time_cstring_to_human_cstring(const char **human_cstring, + const char *data, + apr_pool_t *pool) +{ + svn_error_t *err; + apr_time_t when; + + err = svn_time_from_cstring(&when, data, pool); + if (err && err->apr_err == SVN_ERR_BAD_DATE) + { + svn_error_clear(err); + + *human_cstring = _("(invalid date)"); + return SVN_NO_ERROR; + } + else if (err) + return svn_error_trace(err); + + *human_cstring = svn_time_to_human_cstring(when, pool); + + return SVN_NO_ERROR; +} + + +/* Return a copy, allocated in POOL, of the next line of text from *STR + * up to and including a CR and/or an LF. Change *STR to point to the + * remainder of the string after the returned part. If there are no + * characters to be returned, return NULL; never return an empty string. + */ +static const char * +next_line(const char **str, apr_pool_t *pool) +{ + const char *start = *str; + const char *p = *str; + + /* n.b. Throughout this fn, we never read any character after a '\0'. */ + /* Skip over all non-EOL characters, if any. */ + while (*p != '\r' && *p != '\n' && *p != '\0') + p++; + /* Skip over \r\n or \n\r or \r or \n, if any. */ + if (*p == '\r' || *p == '\n') + { + char c = *p++; + + if ((c == '\r' && *p == '\n') || (c == '\n' && *p == '\r')) + p++; + } + + /* Now p points after at most one '\n' and/or '\r'. */ + *str = p; + + if (p == start) + return NULL; + + return svn_string_ncreate(start, p - start, pool)->data; +} + +const char * +svn_cl__indent_string(const char *str, + const char *indent, + apr_pool_t *pool) +{ + svn_stringbuf_t *out = svn_stringbuf_create("", pool); + const char *line; + + while ((line = next_line(&str, pool))) + { + svn_stringbuf_appendcstr(out, indent); + svn_stringbuf_appendcstr(out, line); + } + return out->data; +} + +const char * +svn_cl__node_description(const svn_wc_conflict_version_t *node, + const char *wc_repos_root_URL, + apr_pool_t *pool) +{ + const char *root_str = "^"; + const char *path_str = "..."; + + if (!node) + /* Printing "(none)" the harder way to ensure conformity (mostly with + * translations). */ + return apr_psprintf(pool, "(%s)", + svn_cl__node_kind_str_human_readable(svn_node_none)); + + /* Construct a "caret notation" ^/URL if NODE matches WC_REPOS_ROOT_URL. + * Otherwise show the complete URL, and if we can't, show dots. */ + + if (node->repos_url && + (wc_repos_root_URL == NULL || + strcmp(node->repos_url, wc_repos_root_URL) != 0)) + root_str = node->repos_url; + + if (node->path_in_repos) + path_str = node->path_in_repos; + + return apr_psprintf(pool, "(%s) %s@%ld", + svn_cl__node_kind_str_human_readable(node->node_kind), + svn_path_url_add_component2(root_str, path_str, pool), + node->peg_rev); +} + +svn_error_t * +svn_cl__eat_peg_revisions(apr_array_header_t **true_targets_p, + const apr_array_header_t *targets, + apr_pool_t *pool) +{ + int i; + apr_array_header_t *true_targets; + + true_targets = apr_array_make(pool, targets->nelts, sizeof(const char *)); + + for (i = 0; i < targets->nelts; i++) + { + const char *target = APR_ARRAY_IDX(targets, i, const char *); + const char *true_target; + + SVN_ERR(svn_opt__split_arg_at_peg_revision(&true_target, NULL, + target, pool)); + APR_ARRAY_PUSH(true_targets, const char *) = true_target; + } + + SVN_ERR_ASSERT(true_targets_p); + *true_targets_p = true_targets; + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_cl__assert_homogeneous_target_type(const apr_array_header_t *targets) +{ + svn_error_t *err; + + err = svn_client__assert_homogeneous_target_type(targets); + if (err && err->apr_err == SVN_ERR_ILLEGAL_TARGET) + return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, err, + _("Cannot mix repository and working copy " + "targets")); + return err; +} + +svn_error_t * +svn_cl__check_target_is_local_path(const char *target) +{ + if (svn_path_is_url(target)) + return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, + _("'%s' is not a local path"), target); + return SVN_NO_ERROR; +} + +svn_error_t * +svn_cl__check_targets_are_local_paths(const apr_array_header_t *targets) +{ + int i; + + for (i = 0; i < targets->nelts; i++) + { + const char *target = APR_ARRAY_IDX(targets, i, const char *); + + SVN_ERR(svn_cl__check_target_is_local_path(target)); + } + return SVN_NO_ERROR; +} + +const char * +svn_cl__local_style_skip_ancestor(const char *parent_path, + const char *path, + apr_pool_t *pool) +{ + const char *relpath = NULL; + + if (parent_path) + relpath = svn_dirent_skip_ancestor(parent_path, path); + + return svn_dirent_local_style(relpath ? relpath : path, pool); +} |