diff options
author | peter <peter@FreeBSD.org> | 2013-06-18 02:07:41 +0000 |
---|---|---|
committer | peter <peter@FreeBSD.org> | 2013-06-18 02:07:41 +0000 |
commit | d25dac7fcc6acc838b71bbda8916fd9665c709ab (patch) | |
tree | 135691142dc0e75a5e5d97b5074d03436435b8e0 /subversion/svn | |
download | FreeBSD-src-d25dac7fcc6acc838b71bbda8916fd9665c709ab.zip FreeBSD-src-d25dac7fcc6acc838b71bbda8916fd9665c709ab.tar.gz |
Import trimmed svn-1.8.0-rc3
Diffstat (limited to 'subversion/svn')
57 files changed, 19175 insertions, 0 deletions
diff --git a/subversion/svn/add-cmd.c b/subversion/svn/add-cmd.c new file mode 100644 index 0000000..44f73c7 --- /dev/null +++ b/subversion/svn/add-cmd.c @@ -0,0 +1,113 @@ +/* + * 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_add5(target, + opt_state->depth, + opt_state->force, opt_state->no_ignore, + opt_state->no_autoprops, 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..174a199 --- /dev/null +++ b/subversion/svn/blame-cmd.c @@ -0,0 +1,419 @@ +/* + * 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_puts(out, "G ")); + use_merged = TRUE; + } + else + SVN_ERR(svn_stream_puts(out, " ")); + } + + 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_empty(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 " + "(use --force to treat as text): " + "'%s'\n"), + target)); + } + else if (err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND || + err->apr_err == SVN_ERR_ENTRY_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..551420e --- /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-conflicts.c b/subversion/svn/cl-conflicts.c new file mode 100644 index 0000000..440c9d7 --- /dev/null +++ b/subversion/svn/cl-conflicts.c @@ -0,0 +1,454 @@ +/* + * 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 "cl-conflicts.h" +#include "svn_hash.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 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 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 }, + { "moved-away", svn_wc_conflict_reason_moved_away }, + { "moved-here", svn_wc_conflict_reason_moved_here }, + { NULL, 0 } +}; + +static const svn_token_map_t map_conflict_kind_xml[] = +{ + { "text", svn_wc_conflict_kind_text }, + { "property", svn_wc_conflict_kind_property }, + { "tree", svn_wc_conflict_kind_tree }, + { NULL, 0 } +}; + +/* Return a localised string representation of the local part of a conflict; + NULL for non-localised odd cases. */ +static const char * +local_reason_str(svn_node_kind_t kind, svn_wc_conflict_reason_t reason) +{ + switch (kind) + { + case svn_node_file: + switch (reason) + { + case svn_wc_conflict_reason_edited: + return _("local file edit"); + case svn_wc_conflict_reason_obstructed: + return _("local file obstruction"); + case svn_wc_conflict_reason_deleted: + return _("local file delete"); + case svn_wc_conflict_reason_missing: + return _("local file missing"); + case svn_wc_conflict_reason_unversioned: + return _("local file unversioned"); + case svn_wc_conflict_reason_added: + return _("local file add"); + case svn_wc_conflict_reason_replaced: + return _("local file replace"); + case svn_wc_conflict_reason_moved_away: + return _("local file moved away"); + case svn_wc_conflict_reason_moved_here: + return _("local file moved here"); + } + break; + case svn_node_dir: + switch (reason) + { + case svn_wc_conflict_reason_edited: + return _("local dir edit"); + case svn_wc_conflict_reason_obstructed: + return _("local dir obstruction"); + case svn_wc_conflict_reason_deleted: + return _("local dir delete"); + case svn_wc_conflict_reason_missing: + return _("local dir missing"); + case svn_wc_conflict_reason_unversioned: + return _("local dir unversioned"); + case svn_wc_conflict_reason_added: + return _("local dir add"); + case svn_wc_conflict_reason_replaced: + return _("local dir replace"); + case svn_wc_conflict_reason_moved_away: + return _("local dir moved away"); + case svn_wc_conflict_reason_moved_here: + return _("local dir moved here"); + } + break; + case svn_node_symlink: + case svn_node_none: + case svn_node_unknown: + break; + } + return NULL; +} + +/* Return a localised string representation of the incoming part of a + conflict; NULL for non-localised odd cases. */ +static const char * +incoming_action_str(svn_node_kind_t kind, svn_wc_conflict_action_t action) +{ + switch (kind) + { + case svn_node_file: + switch (action) + { + case svn_wc_conflict_action_edit: + return _("incoming file edit"); + case svn_wc_conflict_action_add: + return _("incoming file add"); + case svn_wc_conflict_action_delete: + return _("incoming file delete"); + case svn_wc_conflict_action_replace: + return _("incoming file replace"); + } + break; + case svn_node_dir: + switch (action) + { + case svn_wc_conflict_action_edit: + return _("incoming dir edit"); + case svn_wc_conflict_action_add: + return _("incoming dir add"); + case svn_wc_conflict_action_delete: + return _("incoming dir delete"); + case svn_wc_conflict_action_replace: + return _("incoming dir replace"); + } + break; + case svn_node_symlink: + case svn_node_none: + case svn_node_unknown: + break; + } + return NULL; +} + +/* Return a localised string representation of the operation part of a + conflict. */ +static const char * +operation_str(svn_wc_operation_t operation) +{ + switch (operation) + { + case svn_wc_operation_update: return _("upon update"); + case svn_wc_operation_switch: return _("upon switch"); + case svn_wc_operation_merge: return _("upon merge"); + case svn_wc_operation_none: return _("upon none"); + } + SVN_ERR_MALFUNCTION_NO_RETURN(); + return NULL; +} + +svn_error_t * +svn_cl__get_human_readable_prop_conflict_description( + const char **desc, + const svn_wc_conflict_description2_t *conflict, + apr_pool_t *pool) +{ + const char *reason_str, *action_str; + + /* We provide separately translatable strings for the values that we + * know about, and a fall-back in case any other values occur. */ + switch (conflict->reason) + { + case svn_wc_conflict_reason_edited: + reason_str = _("local edit"); + break; + case svn_wc_conflict_reason_added: + reason_str = _("local add"); + break; + case svn_wc_conflict_reason_deleted: + reason_str = _("local delete"); + break; + case svn_wc_conflict_reason_obstructed: + reason_str = _("local obstruction"); + break; + default: + reason_str = apr_psprintf(pool, _("local %s"), + svn_token__to_word(map_conflict_reason_xml, + conflict->reason)); + break; + } + switch (conflict->action) + { + case svn_wc_conflict_action_edit: + action_str = _("incoming edit"); + break; + case svn_wc_conflict_action_add: + action_str = _("incoming add"); + break; + case svn_wc_conflict_action_delete: + action_str = _("incoming delete"); + break; + default: + action_str = apr_psprintf(pool, _("incoming %s"), + svn_token__to_word(map_conflict_action_xml, + conflict->action)); + break; + } + SVN_ERR_ASSERT(reason_str && action_str); + *desc = apr_psprintf(pool, _("%s, %s %s"), + reason_str, action_str, + operation_str(conflict->operation)); + return SVN_NO_ERROR; +} + +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; + svn_node_kind_t incoming_kind; + + /* Determine the node kind of the incoming change. */ + incoming_kind = svn_node_unknown; + if (conflict->action == svn_wc_conflict_action_edit || + conflict->action == svn_wc_conflict_action_delete) + { + /* Change is acting on 'src_left' version of the node. */ + if (conflict->src_left_version) + incoming_kind = conflict->src_left_version->node_kind; + } + else if (conflict->action == svn_wc_conflict_action_add || + conflict->action == svn_wc_conflict_action_replace) + { + /* Change is acting on 'src_right' version of the node. + * + * ### For 'replace', the node kind is ambiguous. However, src_left + * ### is NULL for replace, so we must use src_right. */ + if (conflict->src_right_version) + incoming_kind = conflict->src_right_version->node_kind; + } + + reason = local_reason_str(conflict->node_kind, conflict->reason); + action = incoming_action_str(incoming_kind, conflict->action); + operation = operation_str(conflict->operation); + SVN_ERR_ASSERT(operation); + + if (action && reason) + { + *desc = apr_psprintf(pool, _("%s, %s %s"), + reason, action, operation); + } + else + { + /* A catch-all message for very rare or nominally impossible cases. + It will not be pretty, but is closer to an internal error than + an ordinary user-facing string. */ + *desc = apr_psprintf(pool, _("local: %s %s incoming: %s %s %s"), + svn_node_kind_to_word(conflict->node_kind), + svn_token__to_word(map_conflict_reason_xml, + conflict->reason), + svn_node_kind_to_word(incoming_kind), + svn_token__to_word(map_conflict_action_xml, + conflict->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); + + + svn_hash_sets(att_hash, "side", side); + + if (version->repos_url) + svn_hash_sets(att_hash, "repos-url", version->repos_url); + + if (version->path_in_repos) + svn_hash_sets(att_hash, "path-in-repos", version->path_in_repos); + + if (SVN_IS_VALID_REVNUM(version->peg_rev)) + svn_hash_sets(att_hash, "revision", apr_ltoa(pool, version->peg_rev)); + + if (version->node_kind != svn_node_unknown) + svn_hash_sets(att_hash, "kind", + 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; +} + + +static svn_error_t * +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; + + svn_hash_sets(att_hash, "victim", + svn_dirent_basename(conflict->local_abspath, pool)); + + svn_hash_sets(att_hash, "kind", + svn_cl__node_kind_str_xml(conflict->node_kind)); + + svn_hash_sets(att_hash, "operation", + svn_cl__operation_str_xml(conflict->operation, pool)); + + tmp = svn_token__to_word(map_conflict_action_xml, conflict->action); + svn_hash_sets(att_hash, "action", tmp); + + tmp = svn_token__to_word(map_conflict_reason_xml, conflict->reason); + svn_hash_sets(att_hash, "reason", 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; +} + +svn_error_t * +svn_cl__append_conflict_info_xml(svn_stringbuf_t *str, + const svn_wc_conflict_description2_t *conflict, + apr_pool_t *scratch_pool) +{ + apr_hash_t *att_hash; + const char *kind; + if (conflict->kind == svn_wc_conflict_kind_tree) + { + /* Uses other element type */ + return svn_error_trace( + append_tree_conflict_info_xml(str, conflict, scratch_pool)); + } + + att_hash = apr_hash_make(scratch_pool); + + svn_hash_sets(att_hash, "operation", + svn_cl__operation_str_xml(conflict->operation, scratch_pool)); + + + kind = svn_token__to_word(map_conflict_kind_xml, conflict->kind); + svn_hash_sets(att_hash, "type", kind); + + svn_hash_sets(att_hash, "operation", + svn_cl__operation_str_xml(conflict->operation, scratch_pool)); + + + /* "<conflict>" */ + svn_xml_make_open_tag_hash(&str, scratch_pool, + svn_xml_normal, "conflict", att_hash); + + if (conflict->src_left_version) + SVN_ERR(add_conflict_version_xml(&str, + "source-left", + conflict->src_left_version, + scratch_pool)); + + if (conflict->src_right_version) + SVN_ERR(add_conflict_version_xml(&str, + "source-right", + conflict->src_right_version, + scratch_pool)); + + switch (conflict->kind) + { + case svn_wc_conflict_kind_text: + /* "<prev-base-file> xx </prev-base-file>" */ + svn_cl__xml_tagged_cdata(&str, scratch_pool, "prev-base-file", + conflict->base_abspath); + + /* "<prev-wc-file> xx </prev-wc-file>" */ + svn_cl__xml_tagged_cdata(&str, scratch_pool, "prev-wc-file", + conflict->my_abspath); + + /* "<cur-base-file> xx </cur-base-file>" */ + svn_cl__xml_tagged_cdata(&str, scratch_pool, "cur-base-file", + conflict->their_abspath); + + break; + + case svn_wc_conflict_kind_property: + /* "<prop-file> xx </prop-file>" */ + svn_cl__xml_tagged_cdata(&str, scratch_pool, "prop-file", + conflict->their_abspath); + break; + + default: + case svn_wc_conflict_kind_tree: + SVN_ERR_MALFUNCTION(); /* Handled separately */ + break; + } + + /* "</conflict>" */ + svn_xml_make_close_tag(&str, scratch_pool, "conflict"); + + return SVN_NO_ERROR; +} diff --git a/subversion/svn/cl-conflicts.h b/subversion/svn/cl-conflicts.h new file mode 100644 index 0000000..07591a0 --- /dev/null +++ b/subversion/svn/cl-conflicts.h @@ -0,0 +1,80 @@ +/* + * conflicts.h: Conflicts 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. + * ==================================================================== + */ + + + +#ifndef SVN_CONFLICTS_H +#define SVN_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 property conflict described by @a conflict. + * + * Allocate the result in @a pool. + */ +svn_error_t * +svn_cl__get_human_readable_prop_conflict_description( + const char **desc, + const svn_wc_conflict_description2_t *conflict, + apr_pool_t *pool); + +/** + * 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 conflict data + * for @a conflict, in a format suitable for 'svn info --xml'. + */ +svn_error_t * +svn_cl__append_conflict_info_xml( + svn_stringbuf_t *str, + const svn_wc_conflict_description2_t *conflict, + apr_pool_t *pool); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* SVN_CONFLICTS_H */ diff --git a/subversion/svn/cl.h b/subversion/svn/cl.h new file mode 100644 index 0000000..f7ebee6 --- /dev/null +++ b/subversion/svn/cl.h @@ -0,0 +1,852 @@ +/* + * 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. */ + svn_cl__accept_mine_conflict, + + /* Resolve the conflicted hunks by choosing the corresponding text + from the post-conflict base copy file. */ + 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 */ + 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 */ + struct + { + const char *diff_cmd; /* the external diff command to use */ + svn_boolean_t internal_diff; /* override diff_cmd in config file */ + svn_boolean_t no_diff_added; /* do not show diffs for deleted files */ + 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 summarize; /* create a summary of a diff */ + svn_boolean_t use_git_diff_format; /* Use git's extended diff format */ + svn_boolean_t ignore_properties; /* ignore properties */ + svn_boolean_t properties_only; /* Show properties only */ + svn_boolean_t patch_compatible; /* Output compatible with GNU patch */ + } diff; + 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 *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 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 allow_mixed_rev; /* Allow operation on mixed-revision WC */ + svn_boolean_t include_externals; /* Recurses (in)to file & dir externals */ + svn_boolean_t show_inherited_props; /* get inherited properties */ + apr_array_header_t* search_patterns; /* pattern arguments for --search */ +} 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 svn.c for documentation. */ +extern const svn_opt_subcommand_desc2_t svn_cl__cmd_table[]; + +/* See definition in svn.c for documentation. */ +extern const int svn_cl__global_options[]; + +/* See definition in svn.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. */ + +/* Opaque baton type for svn_cl__conflict_func_interactive(). */ +typedef struct svn_cl__interactive_conflict_baton_t + svn_cl__interactive_conflict_baton_t; + +/* Conflict stats for operations such as update and merge. */ +typedef struct svn_cl__conflict_stats_t svn_cl__conflict_stats_t; + +/* Return a new, initialized, conflict stats structure, allocated in + * POOL. */ +svn_cl__conflict_stats_t * +svn_cl__conflict_stats_create(apr_pool_t *pool); + +/* Update CONFLICT_STATS to reflect that a conflict on PATH_LOCAL of kind + * CONFLICT_KIND is resolved. (There is no support for updating the + * 'skipped paths' stats, since skips cannot be 'resolved'.) */ +void +svn_cl__conflict_stats_resolved(svn_cl__conflict_stats_t *conflict_stats, + const char *path_local, + svn_wc_conflict_kind_t conflict_kind); + + +/* Create and return an baton for use with svn_cl__conflict_func_interactive + * in *B, allocated from RESULT_POOL, and initialised with the values + * ACCEPT_WHICH, CONFIG, EDITOR_CMD, CANCEL_FUNC and CANCEL_BATON. */ +svn_error_t * +svn_cl__get_conflict_func_interactive_baton( + svn_cl__interactive_conflict_baton_t **b, + svn_cl__accept_t accept_which, + apr_hash_t *config, + const char *editor_cmd, + svn_cl__conflict_stats_t *conflict_stats, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *result_pool); + +/* A callback capable of doing interactive conflict resolution. + + The BATON must come from svn_cl__get_conflict_func_interactive_baton(). + Resolves based on the --accept option if one was given to that function, + otherwise prompts the user to choose one of the three fulltexts, edit + the merged file on the spot, or just skip the conflict (to be resolved + later), among other options. + + Implements svn_wc_conflict_resolver_func2_t. + */ +svn_error_t * +svn_cl__conflict_func_interactive(svn_wc_conflict_result_t **result, + const svn_wc_conflict_description2_t *desc, + void *baton, + apr_pool_t *result_pool, + apr_pool_t *scratch_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 SUPPRESS_EXTERNALS_PLACEHOLDERS is set, avoid printing + externals placeholder lines ("X lines"). + + 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. + + Use CWD_ABSPATH -- the absolute path of the current working + directory -- to shorten PATH into something relative to that + directory as necessary. +*/ +svn_error_t * +svn_cl__print_status(const char *cwd_abspath, + const char *path, + const svn_client_status_t *status, + svn_boolean_t suppress_externals_placeholders, + 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. + + Use CWD_ABSPATH -- the absolute path of the current working + directory -- to shorten PATH into something relative to that + directory as necessary. + */ +svn_error_t * +svn_cl__print_status_xml(const char *cwd_abspath, + const char *path, + const svn_client_status_t *status, + svn_client_ctx_t *ctx, + 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 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); + +/* Like svn_cl__merge_file_externally, but using a built-in merge tool + * with help from an external editor specified by EDITOR_CMD. */ +svn_error_t * +svn_cl__merge_file(const char *base_path, + const char *their_path, + const char *my_path, + const char *merged_path, + const char *wc_path, + const char *path_prefix, + const char *editor_cmd, + apr_hash_t *config, + svn_boolean_t *remains_in_conflict, + apr_pool_t *scratch_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. + */ +svn_error_t * +svn_cl__get_notifier(svn_wc_notify_func2_t *notify_func_p, + void **notify_baton_p, + svn_cl__conflict_stats_t *conflict_stats, + 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 the conflict stats accumulated in BATON, which is the + * notifier baton from svn_cl__get_notifier(). + * Return any error encountered during printing. + */ +svn_error_t * +svn_cl__notifier_print_conflict_stats(void *baton, apr_pool_t *scratch_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); + + +/* What use is a property name intended for. + Used by svn_cl__check_svn_prop_name to customize error messages. */ +typedef enum svn_cl__prop_use_e + { + svn_cl__prop_use_set, /* setting the property */ + svn_cl__prop_use_edit, /* editing the property */ + svn_cl__prop_use_use /* using the property name */ + } +svn_cl__prop_use_t; + +/* If PROPNAME looks like but is not identical to one of the svn: + * poperties, raise an error and suggest a better spelling. Names that + * raise errors look like this: + * + * - start with svn: but do not exactly match a known property; or, + * - start with a 3-letter prefix that differs in only one letter + * from "svn:", and the rest exactly matches a known propery. + * + * If REVPROP is TRUE, only check revision property names; otherwise + * only check node property names. + * + * Use SCRATCH_POOL for temporary allocations. + */ +svn_error_t * +svn_cl__check_svn_prop_name(const char *propname, + svn_boolean_t revprop, + svn_cl__prop_use_t prop_use, + apr_pool_t *scratch_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 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 shallow copy of @a targets with any + * empty peg revision specifier snipped off the end of each element. If any + * target has a non-empty peg revision specifier, throw an error. The user + * may have specified a peg revision where it doesn't make sense to do so, + * or may have forgotten to escape an '@' character in a filename. + * + * This function is useful for subcommands for which peg revisions + * do not make any sense. Such subcommands still need to allow an empty + * peg revision 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. + * + * 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); + +/* Check that PATH_OR_URL1@REVISION1 is related to PATH_OR_URL2@REVISION2. + * Raise an error if not. + * + * ### Ideally we would also check that they are on different lines of + * history. That is easy in common cases, but to give a correct answer in + * general we need to know the operative revision(s) as well. For example, + * when one location is the branch point from which the other branch was + * copied. + */ +svn_error_t * +svn_cl__check_related_source_and_target(const char *path_or_url1, + const svn_opt_revision_t *revision1, + const char *path_or_url2, + const svn_opt_revision_t *revision2, + svn_client_ctx_t *ctx, + apr_pool_t *pool); + +/* If the user is setting a mime-type to mark one of the TARGETS as binary, + * as determined by property name PROPNAME and value PROPVAL, then check + * whether Subversion's own binary-file detection recognizes the target as + * a binary file. If Subversion doesn't consider the target to be a binary + * file, assume the user is making an error and print a warning to inform + * the user that some operations might fail on the file in the future. */ +svn_error_t * +svn_cl__propset_print_binary_mime_type_warning(apr_array_header_t *targets, + const char *propname, + const svn_string_t *propval, + apr_pool_t *scratch_pool); + +/* A wrapper around the deprecated svn_client_merge_reintegrate. */ +svn_error_t * +svn_cl__deprecated_merge_reintegrate(const char *source_path_or_url, + const svn_opt_revision_t *src_peg_revision, + const char *target_wcpath, + svn_boolean_t dry_run, + const apr_array_header_t *merge_options, + svn_client_ctx_t *ctx, + 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..19f0bdf --- /dev/null +++ b/subversion/svn/client_errors.h @@ -0,0 +1,97 @@ +/* + * 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) + +#error "Need to update err_defn for r1464679 and un-typo 'CDMLINE'" + +#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..2d04c69 --- /dev/null +++ b/subversion/svn/commit-cmd.c @@ -0,0 +1,186 @@ +/* + * 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_hash.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 = svn_hash_gets(ctx->config, SVN_CONFIG_CATEGORY_CONFIG); + 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_commit6(targets, + opt_state->depth, + no_unlock, + opt_state->keep_changelists, + TRUE /* commit_as_operations */, + opt_state->include_externals, /* file externals */ + opt_state->include_externals, /* dir externals */ + 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..096a189 --- /dev/null +++ b/subversion/svn/conflict-callbacks.c @@ -0,0 +1,1369 @@ +/* + * 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_STRFUNC +#include <apr_want.h> + +#include "svn_hash.h" +#include "svn_cmdline.h" +#include "svn_client.h" +#include "svn_dirent_uri.h" +#include "svn_types.h" +#include "svn_pools.h" +#include "svn_sorts.h" +#include "svn_utf.h" + +#include "cl.h" +#include "cl-conflicts.h" + +#include "private/svn_cmdline_private.h" + +#include "svn_private_config.h" + +#define ARRAY_LEN(ary) ((sizeof (ary)) / (sizeof ((ary)[0]))) + + + +struct svn_cl__interactive_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; + const char *path_prefix; + svn_boolean_t quit; + svn_cl__conflict_stats_t *conflict_stats; +}; + +svn_error_t * +svn_cl__get_conflict_func_interactive_baton( + svn_cl__interactive_conflict_baton_t **b, + svn_cl__accept_t accept_which, + apr_hash_t *config, + const char *editor_cmd, + svn_cl__conflict_stats_t *conflict_stats, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *result_pool) +{ + svn_cmdline_prompt_baton_t *pb = apr_palloc(result_pool, sizeof(*pb)); + pb->cancel_func = cancel_func; + pb->cancel_baton = cancel_baton; + + *b = apr_palloc(result_pool, sizeof(**b)); + (*b)->accept_which = accept_which; + (*b)->config = config; + (*b)->editor_cmd = editor_cmd; + (*b)->external_failed = FALSE; + (*b)->pb = pb; + SVN_ERR(svn_dirent_get_absolute(&(*b)->path_prefix, "", result_pool)); + (*b)->quit = FALSE; + (*b)->conflict_stats = conflict_stats; + + return SVN_NO_ERROR; +} + +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 that shows incoming conflicting changes + * corresponding to the conflict described in DESC. */ +static svn_error_t * +show_diff(const svn_wc_conflict_description2_t *desc, + const char *path_prefix, + apr_pool_t *pool) +{ + const char *path1, *path2; + const char *label1, *label2; + svn_diff_t *diff; + svn_stream_t *output; + svn_diff_file_options_t *options; + + if (desc->merged_file) + { + /* For conflicts recorded by the 'merge' operation, show a diff between + * 'mine' (the working version of the file as it appeared before the + * 'merge' operation was run) and 'merged' (the version of the file + * as it appears after the merge operation). + * + * For conflicts recorded by the 'update' and 'switch' operations, + * show a diff beween 'theirs' (the new pristine version of the + * file) and 'merged' (the version of the file as it appears with + * local changes merged with the new pristine version). + * + * This way, the diff is always minimal and clearly identifies changes + * brought into the working copy by the update/switch/merge operation. */ + if (desc->operation == svn_wc_operation_merge) + { + path1 = desc->my_abspath; + label1 = _("MINE"); + } + else + { + path1 = desc->their_abspath; + label1 = _("THEIRS"); + } + path2 = desc->merged_file; + label2 = _("MERGED"); + } + else + { + /* There's no merged file, but we can show the + difference between mine and theirs. */ + path1 = desc->their_abspath; + label1 = _("THEIRS"); + path2 = desc->my_abspath; + label2 = _("MINE"); + } + + label1 = apr_psprintf(pool, "%s\t- %s", + svn_cl__local_style_skip_ancestor( + path_prefix, path1, pool), label1); + label2 = apr_psprintf(pool, "%s\t- %s", + svn_cl__local_style_skip_ancestor( + path_prefix, path2, pool), label2); + + 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, + label1, label2, + 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_description2_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_abspath, + desc->my_abspath, + desc->their_abspath, + 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_abspath, + desc->my_abspath, + desc->their_abspath, + _("||||||| ORIGINAL"), + _("<<<<<<< MINE (select with 'mc')"), + _(">>>>>>> THEIRS (select with 'tc')"), + "=======", + svn_diff_conflict_display_only_conflicts, + pool); +} + +/* Perform a 3-way merge of the conflicting values of a property, + * and write the result to the OUTPUT stream. + * + * If MERGED_ABSPATH is non-NULL, use it as 'my' version instead of + * DESC->MY_ABSPATH. + * + * Assume the values are printable UTF-8 text. + */ +static svn_error_t * +merge_prop_conflict(svn_stream_t *output, + const svn_wc_conflict_description2_t *desc, + const char *merged_abspath, + apr_pool_t *pool) +{ + const char *base_abspath = desc->base_abspath; + const char *my_abspath = desc->my_abspath; + const char *their_abspath = desc->their_abspath; + svn_diff_file_options_t *options = svn_diff_file_options_create(pool); + svn_diff_t *diff; + + /* If any of the property values is missing, use an empty file instead + * for the purpose of showing a diff. */ + if (! base_abspath || ! my_abspath || ! their_abspath) + { + const char *empty_file; + + SVN_ERR(svn_io_open_unique_file3(NULL, &empty_file, + NULL, svn_io_file_del_on_pool_cleanup, + pool, pool)); + if (! base_abspath) + base_abspath = empty_file; + if (! my_abspath) + my_abspath = empty_file; + if (! their_abspath) + their_abspath = empty_file; + } + + options->ignore_eol_style = TRUE; + SVN_ERR(svn_diff_file_diff3_2(&diff, + base_abspath, + merged_abspath ? merged_abspath : my_abspath, + their_abspath, + options, pool)); + SVN_ERR(svn_diff_file_output_merge2(output, diff, + base_abspath, + merged_abspath ? merged_abspath + : my_abspath, + their_abspath, + _("||||||| ORIGINAL"), + _("<<<<<<< MINE"), + _(">>>>>>> THEIRS"), + "=======", + svn_diff_conflict_display_modified_original_latest, + pool)); + + return SVN_NO_ERROR; +} + +/* Display the conflicting values of a property as a 3-way diff. + * + * If MERGED_ABSPATH is non-NULL, show it as 'my' version instead of + * DESC->MY_ABSPATH. + * + * Assume the values are printable UTF-8 text. + */ +static svn_error_t * +show_prop_conflict(const svn_wc_conflict_description2_t *desc, + const char *merged_abspath, + apr_pool_t *pool) +{ + svn_stream_t *output; + + SVN_ERR(svn_stream_for_stdout(&output, pool)); + SVN_ERR(merge_prop_conflict(output, desc, merged_abspath, pool)); + + return SVN_NO_ERROR; +} + +/* Run an external editor, passing it the MERGED_FILE, 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 char *merged_file, + svn_cl__interactive_conflict_baton_t *b, + apr_pool_t *pool) +{ + svn_error_t *err; + + if (merged_file) + { + err = svn_cmdline__edit_file_externally(merged_file, b->editor_cmd, + b->config, pool); + if (err && (err->apr_err == SVN_ERR_CL_NO_EXTERNAL_EDITOR)) + { + svn_error_t *root_err = svn_error_root_cause(err); + + SVN_ERR(svn_cmdline_fprintf(stderr, pool, "%s\n", + root_err->message ? root_err->message : + _("No editor found."))); + svn_error_clear(err); + } + else if (err && (err->apr_err == SVN_ERR_EXTERNAL_PROGRAM)) + { + svn_error_t *root_err = svn_error_root_cause(err); + + SVN_ERR(svn_cmdline_fprintf(stderr, pool, "%s\n", + root_err->message ? root_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 editor, passing it the 'merged' property in DESC. + * The tool to use is determined by B->editor_cmd, B->config and + * environment variables; see svn_cl__edit_file_externally() for details. */ +static svn_error_t * +edit_prop_conflict(const char **merged_file_path, + const svn_wc_conflict_description2_t *desc, + svn_cl__interactive_conflict_baton_t *b, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + apr_file_t *file; + const char *file_path; + svn_boolean_t performed_edit = FALSE; + svn_stream_t *merged_prop; + + SVN_ERR(svn_io_open_unique_file3(&file, &file_path, NULL, + svn_io_file_del_on_pool_cleanup, + result_pool, scratch_pool)); + merged_prop = svn_stream_from_aprfile2(file, TRUE /* disown */, + scratch_pool); + SVN_ERR(merge_prop_conflict(merged_prop, desc, NULL, scratch_pool)); + SVN_ERR(svn_stream_close(merged_prop)); + SVN_ERR(svn_io_file_flush_to_disk(file, scratch_pool)); + SVN_ERR(open_editor(&performed_edit, file_path, b, scratch_pool)); + *merged_file_path = (performed_edit ? file_path : NULL); + + 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_description2_t *desc, + svn_cl__interactive_conflict_baton_t *b, + apr_pool_t *pool) +{ + svn_error_t *err; + + err = svn_cl__merge_file_externally(desc->base_abspath, desc->their_abspath, + desc->my_abspath, desc->merged_file, + desc->local_abspath, 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, " + "try '(m) merge' instead.\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, " + "try '(m) merge' instead."))); + svn_error_clear(err); + } + else if (err) + return svn_error_trace(err); + else if (performed_edit) + *performed_edit = TRUE; + + return SVN_NO_ERROR; +} + + +/* Maximum line length for the prompt string. */ +#define MAX_PROMPT_WIDTH 70 + +/* Description of a resolver option */ +typedef struct resolver_option_t +{ + const char *code; /* one or two characters */ + const char *short_desc; /* label in prompt (localized) */ + const char *long_desc; /* longer description (localized) */ + svn_wc_conflict_choice_t choice; /* or -1 if not a simple choice */ +} resolver_option_t; + +/* Resolver options for a text conflict */ +/* (opt->code == "" causes a blank line break in help_string()) */ +static const resolver_option_t text_conflict_options[] = +{ + /* Translators: keep long_desc below 70 characters (wrap with a left + margin of 9 spaces if needed); don't translate the words within square + brackets. */ + { "e", N_("edit file"), N_("change merged file in an editor" + " [edit]"), + -1 }, + { "df", N_("show diff"), N_("show all changes made to merged file"), + -1 }, + { "r", N_("resolved"), N_("accept merged version of file"), + svn_wc_conflict_choose_merged }, + { "", "", "", svn_wc_conflict_choose_unspecified }, + { "dc", N_("display conflict"), N_("show all conflicts " + "(ignoring merged version)"), -1 }, + { "mc", N_("my side of conflict"), N_("accept my version for all conflicts " + "(same) [mine-conflict]"), + svn_wc_conflict_choose_mine_conflict }, + { "tc", N_("their side of conflict"), N_("accept their version for all " + "conflicts (same)" + " [theirs-conflict]"), + svn_wc_conflict_choose_theirs_conflict }, + { "", "", "", svn_wc_conflict_choose_unspecified }, + { "mf", N_("my version"), N_("accept my version of entire file (even " + "non-conflicts) [mine-full]"), + svn_wc_conflict_choose_mine_full }, + { "tf", N_("their version"), N_("accept their version of entire file " + "(same) [theirs-full]"), + svn_wc_conflict_choose_theirs_full }, + { "", "", "", svn_wc_conflict_choose_unspecified }, + { "p", N_("postpone"), N_("mark the conflict to be resolved later" + " [postpone]"), + svn_wc_conflict_choose_postpone }, + { "m", N_("merge"), N_("use internal merge tool to resolve " + "conflict"), -1 }, + { "l", N_("launch tool"), N_("launch external tool to resolve " + "conflict [launch]"), -1 }, + { "q", N_("quit resolution"), N_("postpone all remaining conflicts"), + svn_wc_conflict_choose_postpone }, + { "s", N_("show all options"), N_("show this list (also 'h', '?')"), -1 }, + { NULL } +}; + +/* Resolver options for a property conflict */ +static const resolver_option_t prop_conflict_options[] = +{ + { "p", N_("postpone"), N_("mark the conflict to be resolved later" + " [postpone]"), + svn_wc_conflict_choose_postpone }, + { "mf", N_("my version"), N_("accept my version of entire file (even " + "non-conflicts) [mine-full]"), + svn_wc_conflict_choose_mine_full }, + { "tf", N_("their version"), N_("accept their version of entire file " + "(same) [theirs-full]"), + svn_wc_conflict_choose_theirs_full }, + { "dc", N_("display conflict"), N_("show conflicts in this property"), -1 }, + { "e", N_("edit property"), N_("change merged property value in an editor" + " [edit]"), -1 }, + { "r", N_("resolved"), N_("accept edited version of property"), + svn_wc_conflict_choose_merged }, + { "q", N_("quit resolution"), N_("postpone all remaining conflicts"), + svn_wc_conflict_choose_postpone }, + { "h", N_("help"), N_("show this help (also '?')"), -1 }, + { NULL } +}; + +/* Resolver options for an obstructued addition */ +static const resolver_option_t obstructed_add_options[] = +{ + { "p", N_("postpone"), N_("mark the conflict to be resolved later" + " [postpone]"), + svn_wc_conflict_choose_postpone }, + { "mf", N_("my version"), N_("accept pre-existing item (ignore " + "upstream addition) [mine-full]"), + svn_wc_conflict_choose_mine_full }, + { "tf", N_("their version"), N_("accept incoming item (overwrite " + "pre-existing item) [theirs-full]"), + svn_wc_conflict_choose_theirs_full }, + { "q", N_("quit resolution"), N_("postpone all remaining conflicts"), + svn_wc_conflict_choose_postpone }, + { "h", N_("help"), N_("show this help (also '?')"), -1 }, + { NULL } +}; + +/* Resolver options for a tree conflict */ +static const resolver_option_t tree_conflict_options[] = +{ + { "p", N_("postpone"), N_("resolve the conflict later [postpone]"), + svn_wc_conflict_choose_postpone }, + { "r", N_("resolved"), N_("accept current working copy state"), + svn_wc_conflict_choose_merged }, + { "q", N_("quit resolution"), N_("postpone all remaining conflicts"), + svn_wc_conflict_choose_postpone }, + { "h", N_("help"), N_("show this help (also '?')"), -1 }, + { NULL } +}; + +static const resolver_option_t tree_conflict_options_update_moved_away[] = +{ + { "p", N_("postpone"), N_("resolve the conflict later [postpone]"), + svn_wc_conflict_choose_postpone }, + { "mc", N_("my side of conflict"), N_("apply update to the move destination" + " [mine-conflict]"), + svn_wc_conflict_choose_mine_conflict }, + { "r", N_("resolved"), N_("mark resolved " + "(the move will become a copy)"), + svn_wc_conflict_choose_merged }, + { "q", N_("quit resolution"), N_("postpone all remaining conflicts"), + svn_wc_conflict_choose_postpone }, + { "h", N_("help"), N_("show this help (also '?')"), -1 }, + { NULL } +}; + +static const resolver_option_t tree_conflict_options_update_deleted[] = +{ + { "p", N_("postpone"), N_("resolve the conflict later [postpone]"), + svn_wc_conflict_choose_postpone }, + { "mc", N_("my side of conflict"), N_("keep any moves affected " + "by this deletion [mine-conflict]"), + svn_wc_conflict_choose_mine_conflict }, + { "r", N_("resolved"), N_("mark resolved (any affected moves will " + "become copies)"), + svn_wc_conflict_choose_merged }, + { "q", N_("quit resolution"), N_("postpone all remaining conflicts"), + svn_wc_conflict_choose_postpone }, + { "h", N_("help"), N_("show this help (also '?')"), -1 }, + { NULL } +}; + +static const resolver_option_t tree_conflict_options_update_replaced[] = +{ + { "p", N_("postpone"), N_("resolve the conflict later [postpone]"), + svn_wc_conflict_choose_postpone }, + { "mc", N_("my side of conflict"), N_("keep any moves affected by this " + "replacement [mine-conflict]"), + svn_wc_conflict_choose_mine_conflict }, + { "r", N_("resolved"), N_("mark resolved (any affected moves will " + "become copies)"), + svn_wc_conflict_choose_merged }, + { "q", N_("quit resolution"), N_("postpone all remaining conflicts"), + svn_wc_conflict_choose_postpone }, + { "h", N_("help"), N_("show this help (also '?')"), -1 }, + { NULL } +}; + + +/* Return a pointer to the option description in OPTIONS matching the + * one- or two-character OPTION_CODE. Return NULL if not found. */ +static const resolver_option_t * +find_option(const resolver_option_t *options, + const char *option_code) +{ + const resolver_option_t *opt; + + for (opt = options; opt->code; opt++) + { + /* Ignore code "" (blank lines) which is not a valid answer. */ + if (opt->code[0] && strcmp(opt->code, option_code) == 0) + return opt; + } + return NULL; +} + +/* Return a prompt string listing the options OPTIONS. If OPTION_CODES is + * non-null, select only the options whose codes are mentioned in it. */ +static const char * +prompt_string(const resolver_option_t *options, + const char *const *option_codes, + apr_pool_t *pool) +{ + const char *result = _("Select:"); + int left_margin = svn_utf_cstring_utf8_width(result); + const char *line_sep = apr_psprintf(pool, "\n%*s", left_margin, ""); + int this_line_len = left_margin; + svn_boolean_t first = TRUE; + + while (1) + { + const resolver_option_t *opt; + const char *s; + int slen; + + if (option_codes) + { + if (! *option_codes) + break; + opt = find_option(options, *option_codes++); + } + else + { + opt = options++; + if (! opt->code) + break; + } + + if (! first) + result = apr_pstrcat(pool, result, ",", (char *)NULL); + s = apr_psprintf(pool, _(" (%s) %s"), + opt->code, _(opt->short_desc)); + slen = svn_utf_cstring_utf8_width(s); + /* Break the line if adding the next option would make it too long */ + if (this_line_len + slen > MAX_PROMPT_WIDTH) + { + result = apr_pstrcat(pool, result, line_sep, (char *)NULL); + this_line_len = left_margin; + } + result = apr_pstrcat(pool, result, s, (char *)NULL); + this_line_len += slen; + first = FALSE; + } + return apr_pstrcat(pool, result, ": ", (char *)NULL); +} + +/* Return a help string listing the OPTIONS. */ +static const char * +help_string(const resolver_option_t *options, + apr_pool_t *pool) +{ + const char *result = ""; + const resolver_option_t *opt; + + for (opt = options; opt->code; opt++) + { + /* Append a line describing OPT, or a blank line if its code is "". */ + if (opt->code[0]) + { + const char *s = apr_psprintf(pool, " (%s)", opt->code); + + result = apr_psprintf(pool, "%s%-6s - %s\n", + result, s, _(opt->long_desc)); + } + else + { + result = apr_pstrcat(pool, result, "\n", (char *)NULL); + } + } + result = apr_pstrcat(pool, result, + _("Words in square brackets are the corresponding " + "--accept option arguments.\n"), + (char *)NULL); + return result; +} + +/* Prompt the user with CONFLICT_OPTIONS, restricted to the options listed + * in OPTIONS_TO_SHOW if that is non-null. Set *OPT to point to the chosen + * one of CONFLICT_OPTIONS (not necessarily one of OPTIONS_TO_SHOW), or to + * NULL if the answer was not one of them. + * + * If the answer is the (globally recognized) 'help' option, then display + * the help (on stderr) and return with *OPT == NULL. + */ +static svn_error_t * +prompt_user(const resolver_option_t **opt, + const resolver_option_t *conflict_options, + const char *const *options_to_show, + void *prompt_baton, + apr_pool_t *scratch_pool) +{ + const char *prompt + = prompt_string(conflict_options, options_to_show, scratch_pool); + const char *answer; + + SVN_ERR(svn_cmdline_prompt_user2(&answer, prompt, prompt_baton, scratch_pool)); + if (strcmp(answer, "h") == 0 || strcmp(answer, "?") == 0) + { + SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, "\n%s\n", + help_string(conflict_options, + scratch_pool))); + *opt = NULL; + } + else + { + *opt = find_option(conflict_options, answer); + if (! *opt) + { + SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, + _("Unrecognized option.\n\n"))); + } + } + return SVN_NO_ERROR; +} + +/* Ask the user what to do about the text conflict described by DESC. + * Return the answer in RESULT. B is the conflict baton for this + * conflict resolution session. + * SCRATCH_POOL is used for temporary allocations. */ +static svn_error_t * +handle_text_conflict(svn_wc_conflict_result_t *result, + const svn_wc_conflict_description2_t *desc, + svn_cl__interactive_conflict_baton_t *b, + apr_pool_t *scratch_pool) +{ + apr_pool_t *iterpool = svn_pool_create(scratch_pool); + 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; + + SVN_ERR_ASSERT(desc->kind == svn_wc_conflict_kind_text); + + SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, + _("Conflict discovered in file '%s'.\n"), + svn_cl__local_style_skip_ancestor( + b->path_prefix, desc->local_abspath, + scratch_pool))); + + /* 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_abspath) + || (!desc->base_abspath && desc->my_abspath && desc->their_abspath)) + diff_allowed = TRUE; + + while (TRUE) + { + const char *options[ARRAY_LEN(text_conflict_options)]; + const char **next_option = options; + const resolver_option_t *opt; + + svn_pool_clear(iterpool); + + *next_option++ = "p"; + if (diff_allowed) + { + *next_option++ = "df"; + *next_option++ = "e"; + *next_option++ = "m"; + + if (knows_something) + *next_option++ = "r"; + + if (! desc->is_binary) + { + *next_option++ = "mc"; + *next_option++ = "tc"; + } + } + else + { + if (knows_something) + *next_option++ = "r"; + *next_option++ = "mf"; + *next_option++ = "tf"; + } + *next_option++ = "s"; + *next_option++ = NULL; + + SVN_ERR(prompt_user(&opt, text_conflict_options, options, b->pb, + iterpool)); + if (! opt) + continue; + + if (strcmp(opt->code, "q") == 0) + { + result->choice = opt->choice; + b->accept_which = svn_cl__accept_postpone; + b->quit = TRUE; + break; + } + else if (strcmp(opt->code, "s") == 0) + { + SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, "\n%s\n", + help_string(text_conflict_options, + iterpool))); + } + else if (strcmp(opt->code, "dc") == 0) + { + if (desc->is_binary) + { + SVN_ERR(svn_cmdline_fprintf(stderr, iterpool, + _("Invalid option; cannot " + "display conflicts for a " + "binary file.\n\n"))); + continue; + } + else if (! (desc->my_abspath && desc->base_abspath && + desc->their_abspath)) + { + SVN_ERR(svn_cmdline_fprintf(stderr, iterpool, + _("Invalid option; original " + "files not available.\n\n"))); + continue; + } + SVN_ERR(show_conflicts(desc, iterpool)); + knows_something = TRUE; + } + else if (strcmp(opt->code, "df") == 0) + { + if (! diff_allowed) + { + SVN_ERR(svn_cmdline_fprintf(stderr, iterpool, + _("Invalid option; there's no " + "merged version to diff.\n\n"))); + continue; + } + + SVN_ERR(show_diff(desc, b->path_prefix, iterpool)); + knows_something = TRUE; + } + else if (strcmp(opt->code, "e") == 0 || strcmp(opt->code, ":-E") == 0) + { + SVN_ERR(open_editor(&performed_edit, desc->merged_file, b, iterpool)); + if (performed_edit) + knows_something = TRUE; + } + else if (strcmp(opt->code, "m") == 0 || strcmp(opt->code, ":-g") == 0 || + strcmp(opt->code, "=>-") == 0 || strcmp(opt->code, ":>.") == 0) + { + if (desc->kind != svn_wc_conflict_kind_text) + { + SVN_ERR(svn_cmdline_fprintf(stderr, iterpool, + _("Invalid option; can only " + "resolve text conflicts with " + "the internal merge tool." + "\n\n"))); + continue; + } + + if (desc->base_abspath && desc->their_abspath && + desc->my_abspath && desc->merged_file) + { + svn_boolean_t remains_in_conflict; + + SVN_ERR(svn_cl__merge_file(desc->base_abspath, + desc->their_abspath, + desc->my_abspath, + desc->merged_file, + desc->local_abspath, + b->path_prefix, + b->editor_cmd, + b->config, + &remains_in_conflict, + iterpool)); + knows_something = !remains_in_conflict; + } + else + SVN_ERR(svn_cmdline_fprintf(stderr, iterpool, + _("Invalid option.\n\n"))); + } + else if (strcmp(opt->code, "l") == 0 || strcmp(opt->code, ":-l") == 0) + { + /* ### This check should be earlier as it's nasty to offer an option + * and then when the user chooses it say 'Invalid option'. */ + /* ### 'merged_file' shouldn't be necessary *before* we launch the + * resolver: it should be the *result* of doing so. */ + if (desc->base_abspath && desc->their_abspath && + desc->my_abspath && desc->merged_file) + { + SVN_ERR(launch_resolver(&performed_edit, desc, b, iterpool)); + if (performed_edit) + knows_something = TRUE; + } + else + SVN_ERR(svn_cmdline_fprintf(stderr, iterpool, + _("Invalid option.\n\n"))); + } + else if (opt->choice != -1) + { + if ((opt->choice == svn_wc_conflict_choose_mine_conflict + || opt->choice == svn_wc_conflict_choose_theirs_conflict) + && desc->is_binary) + { + SVN_ERR(svn_cmdline_fprintf(stderr, iterpool, + _("Invalid option; cannot choose " + "based on conflicts in a " + "binary file.\n\n"))); + continue; + } + + /* 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 (result->choice == svn_wc_conflict_choose_merged + && ! knows_something) + { + SVN_ERR(svn_cmdline_fprintf( + stderr, iterpool, + _("Invalid option; use diff/edit/merge/launch " + "before choosing 'resolved'.\n\n"))); + continue; + } + + result->choice = opt->choice; + if (performed_edit) + result->save_merged = TRUE; + break; + } + } + svn_pool_destroy(iterpool); + + return SVN_NO_ERROR; +} + +/* Ask the user what to do about the property conflict described by DESC. + * Return the answer in RESULT. B is the conflict baton for this + * conflict resolution session. + * SCRATCH_POOL is used for temporary allocations. */ +static svn_error_t * +handle_prop_conflict(svn_wc_conflict_result_t *result, + const svn_wc_conflict_description2_t *desc, + svn_cl__interactive_conflict_baton_t *b, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + apr_pool_t *iterpool; + const char *message; + const char *merged_file_path = NULL; + svn_boolean_t resolved_allowed = FALSE; + + /* ### Work around a historical bug in the provider: the path to the + * conflict description file was put in the 'theirs' field, and + * 'theirs' was put in the 'merged' field. */ + ((svn_wc_conflict_description2_t *)desc)->their_abspath = desc->merged_file; + ((svn_wc_conflict_description2_t *)desc)->merged_file = NULL; + + SVN_ERR_ASSERT(desc->kind == svn_wc_conflict_kind_property); + + SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, + _("Conflict for property '%s' discovered" + " on '%s'.\n"), + desc->property_name, + svn_cl__local_style_skip_ancestor( + b->path_prefix, desc->local_abspath, + scratch_pool))); + + SVN_ERR(svn_cl__get_human_readable_prop_conflict_description(&message, desc, + scratch_pool)); + SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, "%s\n", message)); + + iterpool = svn_pool_create(scratch_pool); + while (TRUE) + { + const resolver_option_t *opt; + const char *options[ARRAY_LEN(prop_conflict_options)]; + const char **next_option = options; + + *next_option++ = "p"; + *next_option++ = "mf"; + *next_option++ = "tf"; + *next_option++ = "dc"; + *next_option++ = "e"; + if (resolved_allowed) + *next_option++ = "r"; + *next_option++ = "q"; + *next_option++ = "h"; + *next_option++ = NULL; + + svn_pool_clear(iterpool); + + SVN_ERR(prompt_user(&opt, prop_conflict_options, options, b->pb, + iterpool)); + if (! opt) + continue; + + if (strcmp(opt->code, "q") == 0) + { + result->choice = opt->choice; + b->accept_which = svn_cl__accept_postpone; + b->quit = TRUE; + break; + } + else if (strcmp(opt->code, "dc") == 0) + { + SVN_ERR(show_prop_conflict(desc, merged_file_path, scratch_pool)); + } + else if (strcmp(opt->code, "e") == 0) + { + SVN_ERR(edit_prop_conflict(&merged_file_path, desc, b, + result_pool, scratch_pool)); + resolved_allowed = (merged_file_path != NULL); + } + else if (strcmp(opt->code, "r") == 0) + { + if (! resolved_allowed) + { + SVN_ERR(svn_cmdline_fprintf(stderr, iterpool, + _("Invalid option; please edit the property " + "first.\n\n"))); + continue; + } + + result->merged_file = merged_file_path; + result->choice = svn_wc_conflict_choose_merged; + break; + } + else if (opt->choice != -1) + { + result->choice = opt->choice; + break; + } + } + svn_pool_destroy(iterpool); + + return SVN_NO_ERROR; +} + +/* Ask the user what to do about the tree conflict described by DESC. + * Return the answer in RESULT. B is the conflict baton for this + * conflict resolution session. + * SCRATCH_POOL is used for temporary allocations. */ +static svn_error_t * +handle_tree_conflict(svn_wc_conflict_result_t *result, + const svn_wc_conflict_description2_t *desc, + svn_cl__interactive_conflict_baton_t *b, + apr_pool_t *scratch_pool) +{ + const char *readable_desc; + apr_pool_t *iterpool; + + SVN_ERR(svn_cl__get_human_readable_tree_conflict_description( + &readable_desc, desc, scratch_pool)); + SVN_ERR(svn_cmdline_fprintf( + stderr, scratch_pool, + _("Tree conflict on '%s'\n > %s\n"), + svn_cl__local_style_skip_ancestor(b->path_prefix, + desc->local_abspath, + scratch_pool), + readable_desc)); + + iterpool = svn_pool_create(scratch_pool); + while (1) + { + const resolver_option_t *opt; + const resolver_option_t *tc_opts; + + svn_pool_clear(iterpool); + + if (desc->operation == svn_wc_operation_update || + desc->operation == svn_wc_operation_switch) + { + if (desc->reason == svn_wc_conflict_reason_moved_away) + tc_opts = tree_conflict_options_update_moved_away; + else if (desc->reason == svn_wc_conflict_reason_deleted) + tc_opts = tree_conflict_options_update_deleted; + else if (desc->reason == svn_wc_conflict_reason_replaced) + tc_opts = tree_conflict_options_update_replaced; + else + tc_opts = tree_conflict_options; + } + else + tc_opts = tree_conflict_options; + + SVN_ERR(prompt_user(&opt, tc_opts, NULL, b->pb, iterpool)); + if (! opt) + continue; + + if (strcmp(opt->code, "q") == 0) + { + result->choice = opt->choice; + b->accept_which = svn_cl__accept_postpone; + b->quit = TRUE; + break; + } + else if (opt->choice != -1) + { + result->choice = opt->choice; + break; + } + } + svn_pool_destroy(iterpool); + + return SVN_NO_ERROR; +} + +/* Ask the user what to do about the obstructed add described by DESC. + * Return the answer in RESULT. B is the conflict baton for this + * conflict resolution session. + * SCRATCH_POOL is used for temporary allocations. */ +static svn_error_t * +handle_obstructed_add(svn_wc_conflict_result_t *result, + const svn_wc_conflict_description2_t *desc, + svn_cl__interactive_conflict_baton_t *b, + apr_pool_t *scratch_pool) +{ + apr_pool_t *iterpool; + + SVN_ERR(svn_cmdline_fprintf( + stderr, scratch_pool, + _("Conflict discovered when trying to add '%s'.\n" + "An object of the same name already exists.\n"), + svn_cl__local_style_skip_ancestor(b->path_prefix, + desc->local_abspath, + scratch_pool))); + + iterpool = svn_pool_create(scratch_pool); + while (1) + { + const resolver_option_t *opt; + + svn_pool_clear(iterpool); + + SVN_ERR(prompt_user(&opt, obstructed_add_options, NULL, b->pb, + iterpool)); + if (! opt) + continue; + + if (strcmp(opt->code, "q") == 0) + { + result->choice = opt->choice; + b->accept_which = svn_cl__accept_postpone; + b->quit = TRUE; + break; + } + else if (opt->choice != -1) + { + result->choice = opt->choice; + break; + } + } + svn_pool_destroy(iterpool); + + return SVN_NO_ERROR; +} + +/* The body of svn_cl__conflict_func_interactive(). */ +static svn_error_t * +conflict_func_interactive(svn_wc_conflict_result_t **result, + const svn_wc_conflict_description2_t *desc, + void *baton, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_cl__interactive_conflict_baton_t *b = baton; + svn_error_t *err; + + /* Start out assuming we're going to postpone the conflict. */ + *result = svn_wc_create_conflict_result(svn_wc_conflict_choose_postpone, + NULL, result_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: + /* If the caller didn't merge the property values, then I guess + * 'choose working' means 'choose mine'... */ + if (! desc->merged_file) + (*result)->merged_file = desc->my_abspath; + (*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_cmdline__edit_file_externally(desc->merged_file, + b->editor_cmd, b->config, + scratch_pool); + if (err && (err->apr_err == SVN_ERR_CL_NO_EXTERNAL_EDITOR)) + { + SVN_ERR(svn_cmdline_fprintf(stderr, scratch_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, scratch_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_abspath && desc->their_abspath + && desc->my_abspath && 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_abspath, + desc->their_abspath, + desc->my_abspath, + desc->merged_file, + desc->local_abspath, + b->config, + &remains_in_conflict, + scratch_pool); + if (err && err->apr_err == SVN_ERR_CL_NO_EXTERNAL_MERGE_TOOL) + { + SVN_ERR(svn_cmdline_fprintf(stderr, scratch_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, scratch_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. */ + + /* Handle the most common cases, which is either: + + Conflicting edits on a file's text, or + Conflicting edits on a property. + */ + if (((desc->kind == svn_wc_conflict_kind_text) + && (desc->action == svn_wc_conflict_action_edit) + && (desc->reason == svn_wc_conflict_reason_edited))) + SVN_ERR(handle_text_conflict(*result, desc, b, scratch_pool)); + else if (desc->kind == svn_wc_conflict_kind_property) + SVN_ERR(handle_prop_conflict(*result, desc, b, result_pool, scratch_pool)); + + /* + 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)) + SVN_ERR(handle_obstructed_add(*result, desc, b, scratch_pool)); + + else if (desc->kind == svn_wc_conflict_kind_tree) + SVN_ERR(handle_tree_conflict(*result, desc, b, scratch_pool)); + + else /* other types of conflicts -- do nothing about them. */ + { + (*result)->choice = svn_wc_conflict_choose_postpone; + } + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_cl__conflict_func_interactive(svn_wc_conflict_result_t **result, + const svn_wc_conflict_description2_t *desc, + void *baton, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_cl__interactive_conflict_baton_t *b = baton; + + SVN_ERR(conflict_func_interactive(result, desc, baton, + result_pool, scratch_pool)); + + /* If we are resolving a conflict, adjust the summary of conflicts. */ + if ((*result)->choice != svn_wc_conflict_choose_postpone) + { + const char *local_path + = svn_cl__local_style_skip_ancestor( + b->path_prefix, desc->local_abspath, scratch_pool); + + svn_cl__conflict_stats_resolved(b->conflict_stats, local_path, + desc->kind); + } + return SVN_NO_ERROR; +} diff --git a/subversion/svn/copy-cmd.c b/subversion/svn/copy-cmd.c new file mode 100644 index 0000000..e6fbd4b --- /dev/null +++ b/subversion/svn/copy-cmd.c @@ -0,0 +1,184 @@ +/* + * 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)); + + err = svn_opt_parse_path(peg_revision, &src, target, pool); + + if (err) + { + /* Issue #3606: 'svn cp .@HEAD target' gives + svn: '@HEAD' is just a peg revision. Maybe try '@HEAD@' instead? + + This is caused by a first round of canonicalization in + svn_cl__args_to_target_array_print_reserved(). Undo that in an + attempt to fix this issue without revving many apis. + */ + if (*target == '@' && err->apr_err == SVN_ERR_BAD_FILENAME) + { + svn_error_t *err2; + + err2 = svn_opt_parse_path(peg_revision, &src, + apr_pstrcat(pool, ".", target, + (const char *)NULL), pool); + + if (err2) + { + /* Fix attempt failed; return original error */ + svn_error_clear(err2); + } + else + { + /* Error resolved. Use path */ + svn_error_clear(err); + err = NULL; + } + } + + if (err) + return svn_error_trace(err); + } + + source->path = src; + source->revision = &(opt_state->start_revision); + source->peg_revision = peg_revision; + + APR_ARRAY_PUSH(sources, svn_client_copy_source_t *) = source; + } + + /* Get DST_PATH (the target path or URL) and check that no peg revision is + * specified for it. */ + { + const char *tgt = APR_ARRAY_IDX(targets, targets->nelts - 1, const char *); + svn_opt_revision_t peg; + + SVN_ERR(svn_opt_parse_path(&peg, &dst_path, tgt, pool)); + if (peg.kind != svn_opt_revision_unspecified) + return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL, + _("'%s': a peg revision is not allowed here"), + tgt); + } + + /* 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_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/deprecated.c b/subversion/svn/deprecated.c new file mode 100644 index 0000000..6115573 --- /dev/null +++ b/subversion/svn/deprecated.c @@ -0,0 +1,41 @@ +/* + * deprecated.c: Wrappers to call deprecated functions. + * + * ==================================================================== + * 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 SVN_DEPRECATED +#include "cl.h" +#include "svn_client.h" + +svn_error_t * +svn_cl__deprecated_merge_reintegrate(const char *source_path_or_url, + const svn_opt_revision_t *src_peg_revision, + const char *target_wcpath, + svn_boolean_t dry_run, + const apr_array_header_t *merge_options, + svn_client_ctx_t *ctx, + apr_pool_t *pool) +{ + SVN_ERR(svn_client_merge_reintegrate(source_path_or_url, src_peg_revision, + target_wcpath, dry_run, merge_options, + ctx, pool)); + return SVN_NO_ERROR; +} diff --git a/subversion/svn/diff-cmd.c b/subversion/svn/diff-cmd.c new file mode 100644 index 0000000..2cbd202 --- /dev/null +++ b/subversion/svn/diff-cmd.c @@ -0,0 +1,476 @@ +/* + * 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"; + } +} + +/* Baton for summarize_xml and summarize_regular */ +struct summarize_baton_t +{ + const char *anchor; +}; + +/* 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) +{ + struct summarize_baton_t *b = baton; + /* Full path to the object being diffed. This is created by taking the + * baton, and appending the target's relative path. */ + const char *path = b->anchor; + svn_stringbuf_t *sb = svn_stringbuf_create_empty(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) +{ + struct summarize_baton_t *b = baton; + const char *path = b->anchor; + + /* 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; + svn_stream_t *outstream; + svn_stream_t *errstream; + const char *old_target, *new_target; + apr_pool_t *iterpool; + svn_boolean_t pegged_diff = FALSE; + svn_boolean_t show_copies_as_adds = + opt_state->diff.patch_compatible || opt_state->diff.show_copies_as_adds; + svn_boolean_t ignore_properties = + opt_state->diff.patch_compatible || opt_state->diff.ignore_properties; + int i; + struct summarize_baton_t summarize_baton; + 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 streams representing stdout and stderr, which is where + we'll have the external 'diff' program print to. */ + SVN_ERR(svn_stream_for_stdout(&outstream, pool)); + SVN_ERR(svn_stream_for_stderr(&errstream, pool)); + + if (opt_state->xml) + { + svn_stringbuf_t *sb; + + /* Check that the --summarize is passed as well. */ + if (!opt_state->diff.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_empty(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) + { + /* A 2-target diff where one or both targets are URLs. These are + * shorthands for some 'svn diff --old X --new Y' invocations. */ + + 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; + + /* Set default start/end revisions based on target types, in the same + * manner as done for the corresponding '--old X --new Y' cases, + * (note that we have an explicit --new target) */ + 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_working; + + 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->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 + : opt_state->old_target); + + 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; + + /* For URLs, default to HEAD. For WC paths, default to WORKING if + * new target is explicit; if new target is implicitly the same as + * old target, then default the old to BASE and new to WORKING. */ + 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 + : (opt_state->new_target + ? svn_opt_revision_working : 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_W(svn_cl__assert_homogeneous_target_type(targets), + _("'svn diff [-r N[:M]] [TARGET[@REV]...]' does not support mixed " + "target types. Try using the --old and --new options or one of " + "the shorthand invocations listed in 'svn help diff'.")); + + 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->diff.summarize) + { + summarize_baton.anchor = target1; + + SVN_ERR(svn_client_diff_summarize2( + target1, + &opt_state->start_revision, + target2, + &opt_state->end_revision, + opt_state->depth, + ! opt_state->diff.notice_ancestry, + opt_state->changelists, + summarize_func, &summarize_baton, + ctx, iterpool)); + } + else + SVN_ERR(svn_client_diff6( + options, + target1, + &(opt_state->start_revision), + target2, + &(opt_state->end_revision), + NULL, + opt_state->depth, + ! opt_state->diff.notice_ancestry, + opt_state->diff.no_diff_added, + opt_state->diff.no_diff_deleted, + show_copies_as_adds, + opt_state->force, + ignore_properties, + opt_state->diff.properties_only, + opt_state->diff.use_git_diff_format, + svn_cmdline_output_encoding(pool), + outstream, + errstream, + 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->diff.summarize) + { + summarize_baton.anchor = truepath; + SVN_ERR(svn_client_diff_summarize_peg2( + truepath, + &peg_revision, + &opt_state->start_revision, + &opt_state->end_revision, + opt_state->depth, + ! opt_state->diff.notice_ancestry, + opt_state->changelists, + summarize_func, &summarize_baton, + ctx, iterpool)); + } + else + SVN_ERR(svn_client_diff_peg6( + options, + truepath, + &peg_revision, + &opt_state->start_revision, + &opt_state->end_revision, + NULL, + opt_state->depth, + ! opt_state->diff.notice_ancestry, + opt_state->diff.no_diff_added, + opt_state->diff.no_diff_deleted, + show_copies_as_adds, + opt_state->force, + ignore_properties, + opt_state->diff.properties_only, + opt_state->diff.use_git_diff_format, + svn_cmdline_output_encoding(pool), + outstream, + errstream, + opt_state->changelists, + ctx, iterpool)); + } + } + + if (opt_state->xml) + { + svn_stringbuf_t *sb = svn_stringbuf_create_empty(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..75b6723 --- /dev/null +++ b/subversion/svn/export-cmd.c @@ -0,0 +1,128 @@ +/* + * 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) + { + svn_error_t *externals_err; + + externals_err = svn_error_create(SVN_ERR_CL_ERROR_PROCESSING_EXTERNALS, + NULL, + _("Failure occurred processing one or " + "more externals definitions")); + return svn_error_compose_create(externals_err, err); + } + + return svn_error_trace(err); +} diff --git a/subversion/svn/file-merge.c b/subversion/svn/file-merge.c new file mode 100644 index 0000000..43ebba9 --- /dev/null +++ b/subversion/svn/file-merge.c @@ -0,0 +1,959 @@ +/* + * file-merge.c: internal file merge tool + * + * ==================================================================== + * 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. + * ==================================================================== + */ + +/* This is an interactive file merge tool with an interface similar to + * the interactive mode of the UNIX sdiff ("side-by-side diff") utility. + * The merge tool is driven by Subversion's diff code and user input. */ + +#include "svn_cmdline.h" +#include "svn_dirent_uri.h" +#include "svn_error.h" +#include "svn_pools.h" +#include "svn_io.h" +#include "svn_utf.h" +#include "svn_xml.h" + +#include "cl.h" + +#include "svn_private_config.h" +#include "private/svn_utf_private.h" +#include "private/svn_cmdline_private.h" +#include "private/svn_dep_compat.h" + +#if APR_HAVE_SYS_IOCTL_H +#include <sys/ioctl.h> +#endif + +#if APR_HAVE_UNISTD_H +#include <unistd.h> +#endif + +#include <fcntl.h> +#include <stdlib.h> + +/* Baton for functions in this file which implement svn_diff_output_fns_t. */ +struct file_merge_baton { + /* The files being merged. */ + apr_file_t *original_file; + apr_file_t *modified_file; + apr_file_t *latest_file; + + /* Counters to keep track of the current line in each file. */ + svn_linenum_t current_line_original; + svn_linenum_t current_line_modified; + svn_linenum_t current_line_latest; + + /* The merge result is written to this file. */ + apr_file_t *merged_file; + + /* Whether the merged file remains in conflict after the merge. */ + svn_boolean_t remains_in_conflict; + + /* External editor command for editing chunks. */ + const char *editor_cmd; + + /* The client configuration hash. */ + apr_hash_t *config; + + /* Wether the merge should be aborted. */ + svn_boolean_t abort_merge; + + /* Pool for temporary allocations. */ + apr_pool_t *scratch_pool; +} file_merge_baton; + +/* Copy LEN lines from SOURCE_FILE to the MERGED_FILE, starting at + * line START. The CURRENT_LINE is the current line in the source file. + * The new current line is returned in *NEW_CURRENT_LINE. */ +static svn_error_t * +copy_to_merged_file(svn_linenum_t *new_current_line, + apr_file_t *merged_file, + apr_file_t *source_file, + apr_off_t start, + apr_off_t len, + svn_linenum_t current_line, + apr_pool_t *scratch_pool) +{ + apr_pool_t *iterpool; + svn_stringbuf_t *line; + apr_size_t lines_read; + apr_size_t lines_copied; + svn_boolean_t eof; + svn_linenum_t orig_current_line = current_line; + + lines_read = 0; + iterpool = svn_pool_create(scratch_pool); + while (current_line < start) + { + svn_pool_clear(iterpool); + + SVN_ERR(svn_io_file_readline(source_file, &line, NULL, &eof, + APR_SIZE_MAX, iterpool, iterpool)); + if (eof) + break; + + current_line++; + lines_read++; + } + + lines_copied = 0; + while (lines_copied < len) + { + apr_size_t bytes_written; + const char *eol_str; + + svn_pool_clear(iterpool); + + SVN_ERR(svn_io_file_readline(source_file, &line, &eol_str, &eof, + APR_SIZE_MAX, iterpool, iterpool)); + if (eol_str) + svn_stringbuf_appendcstr(line, eol_str); + SVN_ERR(svn_io_file_write_full(merged_file, line->data, line->len, + &bytes_written, iterpool)); + if (bytes_written != line->len) + return svn_error_create(SVN_ERR_IO_WRITE_ERROR, NULL, + _("Could not write data to merged file")); + if (eof) + break; + lines_copied++; + } + svn_pool_destroy(iterpool); + + *new_current_line = orig_current_line + lines_read + lines_copied; + + return SVN_NO_ERROR; +} + +/* Copy common data to the merged file. */ +static svn_error_t * +file_merge_output_common(void *output_baton, + apr_off_t original_start, + apr_off_t original_length, + apr_off_t modified_start, + apr_off_t modified_length, + apr_off_t latest_start, + apr_off_t latest_length) +{ + struct file_merge_baton *b = output_baton; + + if (b->abort_merge) + return SVN_NO_ERROR; + + SVN_ERR(copy_to_merged_file(&b->current_line_original, + b->merged_file, + b->original_file, + original_start, + original_length, + b->current_line_original, + b->scratch_pool)); + return SVN_NO_ERROR; +} + +/* Original/latest match up, but modified differs. + * Copy modified data to the merged file. */ +static svn_error_t * +file_merge_output_diff_modified(void *output_baton, + apr_off_t original_start, + apr_off_t original_length, + apr_off_t modified_start, + apr_off_t modified_length, + apr_off_t latest_start, + apr_off_t latest_length) +{ + struct file_merge_baton *b = output_baton; + + if (b->abort_merge) + return SVN_NO_ERROR; + + SVN_ERR(copy_to_merged_file(&b->current_line_modified, + b->merged_file, + b->modified_file, + modified_start, + modified_length, + b->current_line_modified, + b->scratch_pool)); + + return SVN_NO_ERROR; +} + +/* Original/modified match up, but latest differs. + * Copy latest data to the merged file. */ +static svn_error_t * +file_merge_output_diff_latest(void *output_baton, + apr_off_t original_start, + apr_off_t original_length, + apr_off_t modified_start, + apr_off_t modified_length, + apr_off_t latest_start, + apr_off_t latest_length) +{ + struct file_merge_baton *b = output_baton; + + if (b->abort_merge) + return SVN_NO_ERROR; + + SVN_ERR(copy_to_merged_file(&b->current_line_latest, + b->merged_file, + b->latest_file, + latest_start, + latest_length, + b->current_line_latest, + b->scratch_pool)); + + return SVN_NO_ERROR; +} + +/* Modified/latest match up, but original differs. + * Copy latest data to the merged file. */ +static svn_error_t * +file_merge_output_diff_common(void *output_baton, + apr_off_t original_start, + apr_off_t original_length, + apr_off_t modified_start, + apr_off_t modified_length, + apr_off_t latest_start, + apr_off_t latest_length) +{ + struct file_merge_baton *b = output_baton; + + if (b->abort_merge) + return SVN_NO_ERROR; + + SVN_ERR(copy_to_merged_file(&b->current_line_latest, + b->merged_file, + b->latest_file, + latest_start, + latest_length, + b->current_line_latest, + b->scratch_pool)); + return SVN_NO_ERROR; +} + + +/* Return LEN lines within the diff chunk staring at line START + * in a *LINES array of svn_stringbuf_t* elements. + * Store the resulting current in in *NEW_CURRENT_LINE. */ +static svn_error_t * +read_diff_chunk(apr_array_header_t **lines, + svn_linenum_t *new_current_line, + apr_file_t *file, + svn_linenum_t current_line, + apr_off_t start, + apr_off_t len, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_stringbuf_t *line; + const char *eol_str; + svn_boolean_t eof; + apr_pool_t *iterpool; + + *lines = apr_array_make(result_pool, 0, sizeof(svn_stringbuf_t *)); + + /* Skip lines before start of range. */ + iterpool = svn_pool_create(scratch_pool); + while (current_line < start) + { + svn_pool_clear(iterpool); + SVN_ERR(svn_io_file_readline(file, &line, NULL, &eof, APR_SIZE_MAX, + iterpool, iterpool)); + if (eof) + return SVN_NO_ERROR; + current_line++; + } + svn_pool_destroy(iterpool); + + /* Now read the lines. */ + do + { + SVN_ERR(svn_io_file_readline(file, &line, &eol_str, &eof, APR_SIZE_MAX, + result_pool, scratch_pool)); + if (eol_str) + svn_stringbuf_appendcstr(line, eol_str); + APR_ARRAY_PUSH(*lines, svn_stringbuf_t *) = line; + if (eof) + break; + current_line++; + } + while ((*lines)->nelts < len); + + *new_current_line = current_line; + + return SVN_NO_ERROR; +} + +/* Return the terminal width in number of columns. */ +static int +get_term_width(void) +{ + char *columns_env; +#ifdef TIOCGWINSZ + int fd; + + fd = open("/dev/tty", O_RDONLY, 0); + if (fd != -1) + { + struct winsize ws; + int error; + + error = ioctl(fd, TIOCGWINSZ, &ws); + close(fd); + if (error != -1) + { + if (ws.ws_col < 80) + return 80; + return ws.ws_col; + } + } +#elif defined WIN32 + CONSOLE_SCREEN_BUFFER_INFO csbi; + + if (GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &csbi)) + { + if (csbi.dwSize.X < 80) + return 80; + return csbi.dwSize.X; + } +#endif + + columns_env = getenv("COLUMNS"); + if (columns_env) + { + svn_error_t *err; + int cols; + + err = svn_cstring_atoi(&cols, columns_env); + if (err) + { + svn_error_clear(err); + return 80; + } + + if (cols < 80) + return 80; + return cols; + } + else + return 80; +} + +#define LINE_DISPLAY_WIDTH ((get_term_width() / 2) - 2) + +/* Prepare LINE for display, pruning or extending it to an appropriate + * display width, and stripping the EOL marker, if any. + * This function assumes that the data in LINE is encoded in UTF-8. */ +static const char * +prepare_line_for_display(const char *line, apr_pool_t *pool) +{ + svn_stringbuf_t *buf = svn_stringbuf_create(line, pool); + size_t width; + size_t line_width = LINE_DISPLAY_WIDTH; + apr_pool_t *iterpool; + + /* Trim EOL. */ + if (buf->len >= 2 && + buf->data[buf->len - 2] == '\r' && + buf->data[buf->len - 1] == '\n') + svn_stringbuf_chop(buf, 2); + else if (buf->len >= 1 && + (buf->data[buf->len - 1] == '\n' || + buf->data[buf->len - 1] == '\r')) + svn_stringbuf_chop(buf, 1); + + /* Determine the on-screen width of the line. */ + width = svn_utf_cstring_utf8_width(buf->data); + if (width == -1) + { + /* Determining the width failed. Try to get rid of unprintable + * characters in the line buffer. */ + buf = svn_stringbuf_create(svn_xml_fuzzy_escape(buf->data, pool), pool); + width = svn_utf_cstring_utf8_width(buf->data); + if (width == -1) + width = buf->len; /* fallback: buffer length */ + } + + /* Trim further in case line is still too long, or add padding in case + * it is too short. */ + iterpool = svn_pool_create(pool); + while (width > line_width) + { + const char *last_valid; + + svn_pool_clear(iterpool); + + svn_stringbuf_chop(buf, 1); + + /* Be careful not to invalidate the UTF-8 string by trimming + * just part of a character. */ + last_valid = svn_utf__last_valid(buf->data, buf->len); + if (last_valid < buf->data + buf->len) + svn_stringbuf_chop(buf, (buf->data + buf->len) - last_valid); + + width = svn_utf_cstring_utf8_width(buf->data); + if (width == -1) + width = buf->len; /* fallback: buffer length */ + } + svn_pool_destroy(iterpool); + + while (width == 0 || width < line_width) + { + svn_stringbuf_appendbyte(buf, ' '); + width++; + } + + SVN_ERR_ASSERT_NO_RETURN(width == line_width); + return buf->data; +} + +/* Merge CHUNK1 and CHUNK2 into a new chunk with conflict markers. */ +static apr_array_header_t * +merge_chunks_with_conflict_markers(apr_array_header_t *chunk1, + apr_array_header_t *chunk2, + apr_pool_t *result_pool) +{ + apr_array_header_t *merged_chunk; + int i; + + merged_chunk = apr_array_make(result_pool, 0, sizeof(svn_stringbuf_t *)); + /* ### would be nice to show filenames next to conflict markers */ + APR_ARRAY_PUSH(merged_chunk, svn_stringbuf_t *) = + svn_stringbuf_create("<<<<<<<\n", result_pool); + for (i = 0; i < chunk1->nelts; i++) + { + APR_ARRAY_PUSH(merged_chunk, svn_stringbuf_t *) = + APR_ARRAY_IDX(chunk1, i, svn_stringbuf_t*); + } + APR_ARRAY_PUSH(merged_chunk, svn_stringbuf_t *) = + svn_stringbuf_create("=======\n", result_pool); + for (i = 0; i < chunk2->nelts; i++) + { + APR_ARRAY_PUSH(merged_chunk, svn_stringbuf_t *) = + APR_ARRAY_IDX(chunk2, i, svn_stringbuf_t*); + } + APR_ARRAY_PUSH(merged_chunk, svn_stringbuf_t *) = + svn_stringbuf_create(">>>>>>>\n", result_pool); + + return merged_chunk; +} + +/* Edit CHUNK and return the result in *MERGED_CHUNK allocated in POOL. */ +static svn_error_t * +edit_chunk(apr_array_header_t **merged_chunk, + apr_array_header_t *chunk, + const char *editor_cmd, + apr_hash_t *config, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + apr_file_t *temp_file; + const char *temp_file_name; + int i; + apr_off_t pos; + svn_boolean_t eof; + svn_error_t *err; + apr_pool_t *iterpool; + + SVN_ERR(svn_io_open_unique_file3(&temp_file, &temp_file_name, NULL, + svn_io_file_del_on_pool_cleanup, + scratch_pool, scratch_pool)); + iterpool = svn_pool_create(scratch_pool); + for (i = 0; i < chunk->nelts; i++) + { + svn_stringbuf_t *line = APR_ARRAY_IDX(chunk, i, svn_stringbuf_t *); + apr_size_t bytes_written; + + svn_pool_clear(iterpool); + + SVN_ERR(svn_io_file_write_full(temp_file, line->data, line->len, + &bytes_written, iterpool)); + if (line->len != bytes_written) + return svn_error_create(SVN_ERR_IO_WRITE_ERROR, NULL, + _("Could not write data to temporary file")); + } + SVN_ERR(svn_io_file_flush_to_disk(temp_file, scratch_pool)); + + err = svn_cmdline__edit_file_externally(temp_file_name, editor_cmd, + config, scratch_pool); + if (err && (err->apr_err == SVN_ERR_CL_NO_EXTERNAL_EDITOR)) + { + svn_error_t *root_err = svn_error_root_cause(err); + + SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, "%s\n", + root_err->message ? root_err->message : + _("No editor found."))); + svn_error_clear(err); + *merged_chunk = NULL; + svn_pool_destroy(iterpool); + return SVN_NO_ERROR; + } + else if (err && (err->apr_err == SVN_ERR_EXTERNAL_PROGRAM)) + { + svn_error_t *root_err = svn_error_root_cause(err); + + SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, "%s\n", + root_err->message ? root_err->message : + _("Error running editor."))); + svn_error_clear(err); + *merged_chunk = NULL; + svn_pool_destroy(iterpool); + return SVN_NO_ERROR; + } + else if (err) + return svn_error_trace(err); + + *merged_chunk = apr_array_make(result_pool, 1, sizeof(svn_stringbuf_t *)); + pos = 0; + SVN_ERR(svn_io_file_seek(temp_file, APR_SET, &pos, scratch_pool)); + do + { + svn_stringbuf_t *line; + const char *eol_str; + + svn_pool_clear(iterpool); + + SVN_ERR(svn_io_file_readline(temp_file, &line, &eol_str, &eof, + APR_SIZE_MAX, result_pool, iterpool)); + if (eol_str) + svn_stringbuf_appendcstr(line, eol_str); + + APR_ARRAY_PUSH(*merged_chunk, svn_stringbuf_t *) = line; + } + while (!eof); + svn_pool_destroy(iterpool); + + SVN_ERR(svn_io_file_close(temp_file, scratch_pool)); + + return SVN_NO_ERROR; +} + +/* Create a separator string of the appropriate length. */ +static const char * +get_sep_string(apr_pool_t *result_pool) +{ + int line_width = LINE_DISPLAY_WIDTH; + int i; + svn_stringbuf_t *buf; + + buf = svn_stringbuf_create_empty(result_pool); + for (i = 0; i < line_width; i++) + svn_stringbuf_appendbyte(buf, '-'); + svn_stringbuf_appendbyte(buf, '+'); + for (i = 0; i < line_width; i++) + svn_stringbuf_appendbyte(buf, '-'); + svn_stringbuf_appendbyte(buf, '\n'); + + return buf->data; +} + +/* Merge chunks CHUNK1 and CHUNK2. + * Each lines array contains elements of type svn_stringbuf_t*. + * Return the result in *MERGED_CHUNK, or set *MERGED_CHUNK to NULL in + * case the user chooses to postpone resolution of this chunk. + * If the user wants to abort the merge, set *ABORT_MERGE to TRUE. */ +static svn_error_t * +merge_chunks(apr_array_header_t **merged_chunk, + svn_boolean_t *abort_merge, + apr_array_header_t *chunk1, + apr_array_header_t *chunk2, + svn_linenum_t current_line1, + svn_linenum_t current_line2, + const char *editor_cmd, + apr_hash_t *config, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_stringbuf_t *prompt; + int i; + int max_chunk_lines; + apr_pool_t *iterpool; + + max_chunk_lines = chunk1->nelts > chunk2->nelts ? chunk1->nelts + : chunk2->nelts; + *abort_merge = FALSE; + + /* + * Prepare the selection prompt. + */ + + prompt = svn_stringbuf_create( + apr_psprintf(scratch_pool, "%s\n%s|%s\n%s", + _("Conflicting section found during merge:"), + prepare_line_for_display( + apr_psprintf(scratch_pool, + _("(1) their version (at line %lu)"), + current_line1), + scratch_pool), + prepare_line_for_display( + apr_psprintf(scratch_pool, + _("(2) your version (at line %lu)"), + current_line2), + scratch_pool), + get_sep_string(scratch_pool)), + scratch_pool); + + iterpool = svn_pool_create(scratch_pool); + for (i = 0; i < max_chunk_lines; i++) + { + const char *line1; + const char *line2; + const char *prompt_line; + + svn_pool_clear(iterpool); + + if (i < chunk1->nelts) + { + svn_stringbuf_t *line_utf8; + + SVN_ERR(svn_utf_stringbuf_to_utf8(&line_utf8, + APR_ARRAY_IDX(chunk1, i, + svn_stringbuf_t*), + iterpool)); + line1 = prepare_line_for_display(line_utf8->data, iterpool); + } + else + line1 = prepare_line_for_display("", iterpool); + + if (i < chunk2->nelts) + { + svn_stringbuf_t *line_utf8; + + SVN_ERR(svn_utf_stringbuf_to_utf8(&line_utf8, + APR_ARRAY_IDX(chunk2, i, + svn_stringbuf_t*), + iterpool)); + line2 = prepare_line_for_display(line_utf8->data, iterpool); + } + else + line2 = prepare_line_for_display("", iterpool); + + prompt_line = apr_psprintf(iterpool, "%s|%s\n", line1, line2); + + svn_stringbuf_appendcstr(prompt, prompt_line); + } + + svn_stringbuf_appendcstr(prompt, get_sep_string(scratch_pool)); + svn_stringbuf_appendcstr( + prompt, + _("Select: (1) use their version, (2) use your version,\n" + " (e1) edit their version and use the result,\n" + " (e2) edit your version and use the result,\n" + " (eb) edit both versions and use the result,\n" + " (p) postpone this conflicting section leaving conflict markers,\n" + " (a) abort file merge and return to main menu: ")); + + /* Now let's see what the user wants to do with this conflict. */ + while (TRUE) + { + const char *answer; + + svn_pool_clear(iterpool); + + SVN_ERR(svn_cmdline_prompt_user2(&answer, prompt->data, NULL, iterpool)); + if (strcmp(answer, "1") == 0) + { + *merged_chunk = chunk1; + break; + } + else if (strcmp(answer, "2") == 0) + { + *merged_chunk = chunk2; + break; + } + else if (strcmp(answer, "p") == 0) + { + *merged_chunk = NULL; + break; + } + else if (strcmp(answer, "e1") == 0) + { + SVN_ERR(edit_chunk(merged_chunk, chunk1, editor_cmd, config, + result_pool, iterpool)); + if (*merged_chunk) + break; + } + else if (strcmp(answer, "e2") == 0) + { + SVN_ERR(edit_chunk(merged_chunk, chunk2, editor_cmd, config, + result_pool, iterpool)); + if (*merged_chunk) + break; + } + else if (strcmp(answer, "eb") == 0) + { + apr_array_header_t *conflict_chunk; + + conflict_chunk = merge_chunks_with_conflict_markers(chunk1, chunk2, + scratch_pool); + SVN_ERR(edit_chunk(merged_chunk, conflict_chunk, editor_cmd, config, + result_pool, iterpool)); + if (*merged_chunk) + break; + } + else if (strcmp(answer, "a") == 0) + { + *abort_merge = TRUE; + break; + } + } + svn_pool_destroy(iterpool); + + return SVN_NO_ERROR; +} + +/* Perform a merge of chunks from FILE1 and FILE2, specified by START1/LEN1 + * and START2/LEN2, respectively. Append the result to MERGED_FILE. + * The current line numbers for FILE1 and FILE2 are passed in *CURRENT_LINE1 + * and *CURRENT_LINE2, and will be updated to new values upon return. + * If the user wants to abort the merge, set *ABORT_MERGE to TRUE. */ +static svn_error_t * +merge_file_chunks(svn_boolean_t *remains_in_conflict, + svn_boolean_t *abort_merge, + apr_file_t *merged_file, + apr_file_t *file1, + apr_file_t *file2, + apr_off_t start1, + apr_off_t len1, + apr_off_t start2, + apr_off_t len2, + svn_linenum_t *current_line1, + svn_linenum_t *current_line2, + const char *editor_cmd, + apr_hash_t *config, + apr_pool_t *scratch_pool) +{ + apr_array_header_t *chunk1; + apr_array_header_t *chunk2; + apr_array_header_t *merged_chunk; + apr_pool_t *iterpool; + int i; + + SVN_ERR(read_diff_chunk(&chunk1, current_line1, file1, *current_line1, + start1, len1, scratch_pool, scratch_pool)); + SVN_ERR(read_diff_chunk(&chunk2, current_line2, file2, *current_line2, + start2, len2, scratch_pool, scratch_pool)); + + SVN_ERR(merge_chunks(&merged_chunk, abort_merge, chunk1, chunk2, + *current_line1, *current_line2, + editor_cmd, config, + scratch_pool, scratch_pool)); + + if (*abort_merge) + return SVN_NO_ERROR; + + /* If the user chose 'postpone' put conflict markers and left/right + * versions into the merged file. */ + if (merged_chunk == NULL) + { + *remains_in_conflict = TRUE; + merged_chunk = merge_chunks_with_conflict_markers(chunk1, chunk2, + scratch_pool); + } + + iterpool = svn_pool_create(scratch_pool); + for (i = 0; i < merged_chunk->nelts; i++) + { + apr_size_t bytes_written; + svn_stringbuf_t *line = APR_ARRAY_IDX(merged_chunk, i, + svn_stringbuf_t *); + + svn_pool_clear(iterpool); + + SVN_ERR(svn_io_file_write_full(merged_file, line->data, line->len, + &bytes_written, iterpool)); + if (line->len != bytes_written) + return svn_error_create(SVN_ERR_IO_WRITE_ERROR, NULL, + _("Could not write data to merged file")); + } + svn_pool_destroy(iterpool); + + return SVN_NO_ERROR; +} + +/* Original, modified, and latest all differ from one another. + * This is a conflict and we'll need to ask the user to merge it. */ +static svn_error_t * +file_merge_output_conflict(void *output_baton, + apr_off_t original_start, + apr_off_t original_length, + apr_off_t modified_start, + apr_off_t modified_length, + apr_off_t latest_start, + apr_off_t latest_length, + svn_diff_t *resolved_diff) +{ + struct file_merge_baton *b = output_baton; + + if (b->abort_merge) + return SVN_NO_ERROR; + + SVN_ERR(merge_file_chunks(&b->remains_in_conflict, + &b->abort_merge, + b->merged_file, + b->modified_file, + b->latest_file, + modified_start, + modified_length, + latest_start, + latest_length, + &b->current_line_modified, + &b->current_line_latest, + b->editor_cmd, + b->config, + b->scratch_pool)); + return SVN_NO_ERROR; +} + +/* Our collection of diff output functions that get driven during the merge. */ +static svn_diff_output_fns_t file_merge_diff_output_fns = { + file_merge_output_common, + file_merge_output_diff_modified, + file_merge_output_diff_latest, + file_merge_output_diff_common, + file_merge_output_conflict +}; + +svn_error_t * +svn_cl__merge_file(const char *base_path, + const char *their_path, + const char *my_path, + const char *merged_path, + const char *wc_path, + const char *path_prefix, + const char *editor_cmd, + apr_hash_t *config, + svn_boolean_t *remains_in_conflict, + apr_pool_t *scratch_pool) +{ + svn_diff_t *diff; + svn_diff_file_options_t *diff_options; + apr_file_t *original_file; + apr_file_t *modified_file; + apr_file_t *latest_file; + apr_file_t *merged_file; + const char *merged_file_name; + struct file_merge_baton fmb; + svn_boolean_t executable; + const char *merged_path_local_style; + const char *merged_rel_path; + const char *wc_path_local_style; + const char *wc_rel_path = svn_dirent_skip_ancestor(path_prefix, wc_path); + + /* PATH_PREFIX may not be an ancestor of WC_PATH, just use the + full WC_PATH in that case. */ + if (wc_rel_path) + wc_path_local_style = svn_dirent_local_style(wc_rel_path, scratch_pool); + else + wc_path_local_style = svn_dirent_local_style(wc_path, scratch_pool); + + SVN_ERR(svn_cmdline_printf(scratch_pool, _("Merging '%s'.\n"), + wc_path_local_style)); + + SVN_ERR(svn_io_file_open(&original_file, base_path, + APR_READ | APR_BUFFERED, + APR_OS_DEFAULT, scratch_pool)); + SVN_ERR(svn_io_file_open(&modified_file, their_path, + APR_READ | APR_BUFFERED, + APR_OS_DEFAULT, scratch_pool)); + SVN_ERR(svn_io_file_open(&latest_file, my_path, + APR_READ | APR_BUFFERED, + APR_OS_DEFAULT, scratch_pool)); + SVN_ERR(svn_io_open_unique_file3(&merged_file, &merged_file_name, + NULL, svn_io_file_del_none, + scratch_pool, scratch_pool)); + + diff_options = svn_diff_file_options_create(scratch_pool); + SVN_ERR(svn_diff_file_diff3_2(&diff, base_path, their_path, my_path, + diff_options, scratch_pool)); + + fmb.original_file = original_file; + fmb.modified_file = modified_file; + fmb.latest_file = latest_file; + fmb.current_line_original = 0; + fmb.current_line_modified = 0; + fmb.current_line_latest = 0; + fmb.merged_file = merged_file; + fmb.remains_in_conflict = FALSE; + fmb.editor_cmd = editor_cmd; + fmb.config = config; + fmb.abort_merge = FALSE; + fmb.scratch_pool = scratch_pool; + + SVN_ERR(svn_diff_output(diff, &fmb, &file_merge_diff_output_fns)); + + SVN_ERR(svn_io_file_close(original_file, scratch_pool)); + SVN_ERR(svn_io_file_close(modified_file, scratch_pool)); + SVN_ERR(svn_io_file_close(latest_file, scratch_pool)); + SVN_ERR(svn_io_file_close(merged_file, scratch_pool)); + + /* Start out assuming that conflicts remain. */ + if (remains_in_conflict) + *remains_in_conflict = TRUE; + + if (fmb.abort_merge) + { + SVN_ERR(svn_io_remove_file2(merged_file_name, TRUE, scratch_pool)); + SVN_ERR(svn_cmdline_printf(scratch_pool, _("Merge of '%s' aborted.\n"), + wc_path_local_style)); + return SVN_NO_ERROR; + } + + SVN_ERR(svn_io_is_file_executable(&executable, merged_path, scratch_pool)); + + merged_rel_path = svn_dirent_skip_ancestor(path_prefix, merged_path); + if (merged_rel_path) + merged_path_local_style = svn_dirent_local_style(merged_rel_path, + scratch_pool); + else + merged_path_local_style = svn_dirent_local_style(merged_path, + scratch_pool); + + SVN_ERR_W(svn_io_copy_file(merged_file_name, merged_path, FALSE, + scratch_pool), + apr_psprintf(scratch_pool, + _("Could not write merged result to '%s', saved " + "instead at '%s'.\n'%s' remains in conflict.\n"), + merged_path_local_style, + svn_dirent_local_style(merged_file_name, + scratch_pool), + wc_path_local_style)); + SVN_ERR(svn_io_set_file_executable(merged_path, executable, FALSE, + scratch_pool)); + SVN_ERR(svn_io_remove_file2(merged_file_name, TRUE, scratch_pool)); + + /* The merge was not aborted and we could install the merged result. The + * file remains in conflict unless all conflicting sections were resolved. */ + if (remains_in_conflict) + *remains_in_conflict = fmb.remains_in_conflict; + + if (fmb.remains_in_conflict) + SVN_ERR(svn_cmdline_printf( + scratch_pool, + _("Merge of '%s' completed (remains in conflict).\n"), + wc_path_local_style)); + else + SVN_ERR(svn_cmdline_printf( + scratch_pool, _("Merge of '%s' completed.\n"), + wc_path_local_style)); + + return SVN_NO_ERROR; +} diff --git a/subversion/svn/help-cmd.c b/subversion/svn/help-cmd.c new file mode 100644 index 0000000..93fecc3 --- /dev/null +++ b/subversion/svn/help-cmd.c @@ -0,0 +1,153 @@ +/* + * 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_hash.h" +#include "svn_string.h" +#include "svn_config.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 = NULL; + svn_stringbuf_t *version_footer = NULL; + + /* 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"); + + if (baton) + { + svn_cl__cmd_baton_t *const cmd_baton = baton; +#ifndef SVN_DISABLE_PLAINTEXT_PASSWORD_STORAGE + /* Windows never actually stores plaintext passwords, it + encrypts the contents using CryptoAPI. ... + + ... If CryptoAPI is available ... but it should be on all + versions of Windows that are even remotely interesting two + days before the scheduled end of the world, when this comment + is being written. */ +# ifndef WIN32 + svn_boolean_t store_auth_creds = + SVN_CONFIG_DEFAULT_OPTION_STORE_AUTH_CREDS; + svn_boolean_t store_passwords = + SVN_CONFIG_DEFAULT_OPTION_STORE_PASSWORDS; + svn_boolean_t store_plaintext_passwords = FALSE; + svn_config_t *cfg; + + if (cmd_baton->ctx->config) + { + cfg = svn_hash_gets(cmd_baton->ctx->config, + SVN_CONFIG_CATEGORY_CONFIG); + if (cfg) + { + SVN_ERR(svn_config_get_bool(cfg, &store_auth_creds, + SVN_CONFIG_SECTION_AUTH, + SVN_CONFIG_OPTION_STORE_AUTH_CREDS, + store_auth_creds)); + SVN_ERR(svn_config_get_bool(cfg, &store_passwords, + SVN_CONFIG_SECTION_AUTH, + SVN_CONFIG_OPTION_STORE_PASSWORDS, + store_passwords)); + } + cfg = svn_hash_gets(cmd_baton->ctx->config, + SVN_CONFIG_CATEGORY_SERVERS); + if (cfg) + { + const char *value; + SVN_ERR(svn_config_get_yes_no_ask + (cfg, &value, + SVN_CONFIG_SECTION_GLOBAL, + SVN_CONFIG_OPTION_STORE_PLAINTEXT_PASSWORDS, + SVN_CONFIG_DEFAULT_OPTION_STORE_PLAINTEXT_PASSWORDS)); + if (0 == svn_cstring_casecmp(value, SVN_CONFIG_TRUE)) + store_plaintext_passwords = TRUE; + } + } + + if (store_plaintext_passwords && store_auth_creds && store_passwords) + { + version_footer = svn_stringbuf_create( + _("WARNING: Plaintext password storage is enabled!\n\n"), + pool); + svn_stringbuf_appendcstr(version_footer, ra_desc_start); + } +# endif /* !WIN32 */ +#endif /* !SVN_DISABLE_PLAINTEXT_PASSWORD_STORAGE */ + + opt_state = cmd_baton->opt_state; + } + + if (!version_footer) + version_footer = svn_stringbuf_create(ra_desc_start, pool); + SVN_ERR(svn_ra_print_modules(version_footer, pool)); + + return svn_opt_print_help4(os, + "svn", /* ### erm, derive somehow? */ + opt_state ? opt_state->version : FALSE, + opt_state ? opt_state->quiet : FALSE, + opt_state ? opt_state->verbose : 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..6fe5af6 --- /dev/null +++ b/subversion/svn/import-cmd.c @@ -0,0 +1,132 @@ +/* + * 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_import5(path, + url, + opt_state->depth, + opt_state->no_ignore, + opt_state->no_autoprops, + opt_state->force, + opt_state->revprop_table, + NULL, NULL, /* filter callback / baton */ + (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..56833f6 --- /dev/null +++ b/subversion/svn/info-cmd.c @@ -0,0 +1,683 @@ +/* + * 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 "cl-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_empty(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); + + /* "<url> xx </url>" */ + svn_cl__xml_tagged_cdata(&sb, pool, "url", info->URL); + + if (info->repos_root_URL && info->URL) + { + /* "<relative-url> xx </relative-url>" */ + svn_cl__xml_tagged_cdata(&sb, pool, "relative-url", + apr_pstrcat(pool, "^/", + svn_path_uri_encode( + svn_uri_skip_ancestor( + info->repos_root_URL, + info->URL, pool), + pool), + NULL)); + } + + 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); + + if (info->wc_info->moved_from_abspath) + { + const char *relpath; + + relpath = svn_dirent_skip_ancestor(info->wc_info->wcroot_abspath, + info->wc_info->moved_from_abspath); + + /* <moved-from> xx </moved-from> */ + if (relpath && relpath[0] != '\0') + svn_cl__xml_tagged_cdata(&sb, pool, "moved-from", relpath); + else + svn_cl__xml_tagged_cdata(&sb, pool, "moved-from", + info->wc_info->moved_from_abspath); + } + + if (info->wc_info->moved_to_abspath) + { + const char *relpath; + + relpath = svn_dirent_skip_ancestor(info->wc_info->wcroot_abspath, + info->wc_info->moved_to_abspath); + /* <moved-to> xx </moved-to> */ + if (relpath && relpath[0] != '\0') + svn_cl__xml_tagged_cdata(&sb, pool, "moved-to", relpath); + else + svn_cl__xml_tagged_cdata(&sb, pool, "moved-to", + info->wc_info->moved_to_abspath); + } + + /* "</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 *); + + SVN_ERR(svn_cl__append_conflict_info_xml(sb, conflict, pool)); + } + } + + 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->URL && info->repos_root_URL) + SVN_ERR(svn_cmdline_printf(pool, _("Relative URL: ^/%s\n"), + svn_path_uri_encode( + svn_uri_skip_ancestor(info->repos_root_URL, + info->URL, pool), + pool))); + + 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->wc_info->moved_from_abspath) + { + const char *relpath; + + relpath = svn_dirent_skip_ancestor(info->wc_info->wcroot_abspath, + info->wc_info->moved_from_abspath); + if (relpath && relpath[0] != '\0') + SVN_ERR(svn_cmdline_printf(pool, _("Moved From: %s\n"), relpath)); + else + SVN_ERR(svn_cmdline_printf(pool, _("Moved From: %s\n"), + info->wc_info->moved_from_abspath)); + } + + if (info->wc_info->moved_to_abspath) + { + const char *relpath; + + relpath = svn_dirent_skip_ancestor(info->wc_info->wcroot_abspath, + info->wc_info->moved_to_abspath); + if (relpath && relpath[0] != '\0') + SVN_ERR(svn_cmdline_printf(pool, _("Moved To: %s\n"), relpath)); + else + SVN_ERR(svn_cmdline_printf(pool, _("Moved To: %s\n"), + info->wc_info->moved_to_abspath)); + } + } + + 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) + { + svn_boolean_t printed_prop_conflict_file = FALSE; + 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; + + 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: + if (! printed_prop_conflict_file) + SVN_ERR(svn_cmdline_printf(pool, + _("Conflict Properties File: %s\n"), + svn_dirent_local_style(conflict->their_abspath, + pool))); + printed_prop_conflict_file = TRUE; + break; + + case svn_wc_conflict_kind_tree: + SVN_ERR( + svn_cl__get_human_readable_tree_conflict_description( + &desc, conflict, pool)); + + SVN_ERR(svn_cmdline_printf(pool, "%s: %s\n", + _("Tree conflict"), desc)); + break; + } + } + + /* We only store one left and right version for all conflicts, which is + referenced from all conflicts. + Print it after the conflicts to match the 1.6/1.7 output where it is + only available for tree conflicts */ + { + const char *src_left_version; + const char *src_right_version; + const svn_wc_conflict_description2_t *conflict = + APR_ARRAY_IDX(info->wc_info->conflicts, 0, + const svn_wc_conflict_description2_t *); + + 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); + + 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)); + } + } + } + + 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..efe4279 --- /dev/null +++ b/subversion/svn/list-cmd.c @@ -0,0 +1,424 @@ +/* + * 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; + + /* To keep track of last seen external information. */ + const char *last_external_parent_url; + const char *last_external_target; + svn_boolean_t in_external; +}; + +/* This implements the svn_client_list_func2_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, + const char *external_parent_url, + const char *external_target, + apr_pool_t *scratch_pool) +{ + struct print_baton *pb = baton; + const char *entryname; + static const char *time_format_long = NULL; + static const char *time_format_short = NULL; + + SVN_ERR_ASSERT((external_parent_url == NULL && external_target == NULL) || + (external_parent_url && external_target)); + + if (time_format_long == NULL) + time_format_long = _("%b %d %H:%M"); + if (time_format_short == NULL) + time_format_short = _("%b %d %Y"); + + 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, scratch_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 (external_parent_url && external_target) + { + if ((pb->last_external_parent_url == NULL + && pb->last_external_target == NULL) + || (strcmp(pb->last_external_parent_url, external_parent_url) != 0 + || strcmp(pb->last_external_target, external_target) != 0)) + { + SVN_ERR(svn_cmdline_printf(scratch_pool, + _("Listing external '%s'" + " defined on '%s':\n"), + external_target, + external_parent_url)); + + pb->last_external_parent_url = external_parent_url; + pb->last_external_target = external_target; + } + } + + 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), + time_format_long, &exp_time); + } + else + { + apr_err = apr_strftime(timestr, &size, sizeof(timestr), + time_format_short, &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, scratch_pool)); + + sizestr = apr_psprintf(scratch_pool, "%" SVN_FILESIZE_T_FMT, + dirent->size); + + return svn_cmdline_printf + (scratch_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(scratch_pool, "%s%s\n", entryname, + (dirent->kind == svn_node_dir) + ? "/" : ""); + } +} + + +/* This implements the svn_client_list_func2_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, + const char *external_parent_url, + const char *external_target, + apr_pool_t *scratch_pool) +{ + struct print_baton *pb = baton; + const char *entryname; + svn_stringbuf_t *sb = svn_stringbuf_create_empty(scratch_pool); + + SVN_ERR_ASSERT((external_parent_url == NULL && external_target == NULL) || + (external_parent_url && external_target)); + + if (strcmp(path, "") == 0) + { + if (dirent->kind == svn_node_file) + entryname = svn_dirent_basename(abs_path, scratch_pool); + 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)); + + if (external_parent_url && external_target) + { + if ((pb->last_external_parent_url == NULL + && pb->last_external_target == NULL) + || (strcmp(pb->last_external_parent_url, external_parent_url) != 0 + || strcmp(pb->last_external_target, external_target) != 0)) + { + if (pb->in_external) + { + /* The external item being listed is different from the previous + one, so close the tag. */ + svn_xml_make_close_tag(&sb, scratch_pool, "external"); + pb->in_external = FALSE; + } + + svn_xml_make_open_tag(&sb, scratch_pool, svn_xml_normal, "external", + "parent_url", external_parent_url, + "target", external_target, + NULL); + + pb->last_external_parent_url = external_parent_url; + pb->last_external_target = external_target; + pb->in_external = TRUE; + } + } + + svn_xml_make_open_tag(&sb, scratch_pool, svn_xml_normal, "entry", + "kind", svn_cl__node_kind_str_xml(dirent->kind), + NULL); + + svn_cl__xml_tagged_cdata(&sb, scratch_pool, "name", entryname); + + if (dirent->kind == svn_node_file) + { + svn_cl__xml_tagged_cdata + (&sb, scratch_pool, "size", + apr_psprintf(scratch_pool, "%" SVN_FILESIZE_T_FMT, dirent->size)); + } + + svn_xml_make_open_tag(&sb, scratch_pool, svn_xml_normal, "commit", + "revision", + apr_psprintf(scratch_pool, "%ld", dirent->created_rev), + NULL); + svn_cl__xml_tagged_cdata(&sb, scratch_pool, "author", dirent->last_author); + if (dirent->time) + svn_cl__xml_tagged_cdata(&sb, scratch_pool, "date", + svn_time_to_cstring(dirent->time, scratch_pool)); + svn_xml_make_close_tag(&sb, scratch_pool, "commit"); + + if (lock) + { + svn_xml_make_open_tag(&sb, scratch_pool, svn_xml_normal, "lock", NULL); + svn_cl__xml_tagged_cdata(&sb, scratch_pool, "token", lock->token); + svn_cl__xml_tagged_cdata(&sb, scratch_pool, "owner", lock->owner); + svn_cl__xml_tagged_cdata(&sb, scratch_pool, "comment", lock->comment); + svn_cl__xml_tagged_cdata(&sb, scratch_pool, "created", + svn_time_to_cstring(lock->creation_date, + scratch_pool)); + if (lock->expiration_date != 0) + svn_cl__xml_tagged_cdata(&sb, scratch_pool, "expires", + svn_time_to_cstring + (lock->expiration_date, scratch_pool)); + svn_xml_make_close_tag(&sb, scratch_pool, "lock"); + } + + svn_xml_make_close_tag(&sb, scratch_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_error_t *externals_err = SVN_NO_ERROR; + 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)); + + /* 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; + + if (opt_state->include_externals) + { + 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; + } + + /* 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; + + /* Initialize the following variables for + every list target. */ + pb.last_external_parent_url = NULL; + pb.last_external_target = NULL; + pb.in_external = FALSE; + + 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_empty(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_list3(truepath, &peg_revision, + &(opt_state->start_revision), + opt_state->depth, + dirent_fields, + (opt_state->xml || opt_state->verbose), + opt_state->include_externals, + 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_empty(pool); + + if (pb.in_external) + { + /* close the final external item's tag */ + svn_xml_make_close_tag(&sb, pool, "external"); + pb.in_external = FALSE; + } + + 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->include_externals && nwb.had_externals_error) + { + externals_err = svn_error_create(SVN_ERR_CL_ERROR_PROCESSING_EXTERNALS, + NULL, + _("Failure occurred processing one or " + "more externals definitions")); + } + + if (opt_state->xml && ! opt_state->incremental) + SVN_ERR(svn_cl__xml_print_footer("lists", pool)); + + if (seen_nonexistent_target) + err = svn_error_create(SVN_ERR_ILLEGAL_TARGET, NULL, + _("Could not list all targets because some targets don't exist")); + + return svn_error_compose_create(externals_err, err); +} 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..af57cf4 --- /dev/null +++ b/subversion/svn/log-cmd.c @@ -0,0 +1,875 @@ +/* + * 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. + * ==================================================================== + */ + +#include <apr_fnmatch.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 "private/svn_cmdline_private.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; + + /* Log message search patterns. Log entries will only be shown if the author, + * the log message, or a changed path matches one of these patterns. */ + apr_array_header_t *search_patterns; + + /* Pool for persistent allocations. */ + apr_pool_t *pool; +}; + + +/* The separator between log messages. */ +#define SEP_STRING \ + "------------------------------------------------------------------------\n" + + +/* Display a diff of the subtree TARGET_PATH_OR_URL@TARGET_PEG_REVISION as + * it changed in the revision that LOG_ENTRY describes. + * + * Restrict the diff to depth DEPTH. Pass DIFF_EXTENSIONS along to the diff + * subroutine. + * + * Write the diff to OUTSTREAM and write any stderr output to ERRSTREAM. + * ### How is exit code handled? 0 and 1 -> SVN_NO_ERROR, else an svn error? + * ### Should we get rid of ERRSTREAM and use svn_error_t instead? + */ +static svn_error_t * +display_diff(const svn_log_entry_t *log_entry, + const char *target_path_or_url, + const svn_opt_revision_t *target_peg_revision, + svn_depth_t depth, + const char *diff_extensions, + svn_stream_t *outstream, + svn_stream_t *errstream, + svn_client_ctx_t *ctx, + apr_pool_t *pool) +{ + apr_array_header_t *diff_options; + svn_opt_revision_t start_revision; + svn_opt_revision_t end_revision; + + /* Fall back to "" to get options initialized either way. */ + if (diff_extensions) + diff_options = svn_cstring_split(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_stream_puts(outstream, "\n")); + SVN_ERR(svn_client_diff_peg6(diff_options, + target_path_or_url, + target_peg_revision, + &start_revision, &end_revision, + NULL, + depth, + FALSE /* ignore ancestry */, + FALSE /* no diff added */, + TRUE /* no diff deleted */, + FALSE /* show copies as adds */, + FALSE /* ignore content type */, + FALSE /* ignore prop diff */, + FALSE /* properties only */, + FALSE /* use git diff format */, + svn_cmdline_output_encoding(pool), + outstream, + errstream, + NULL, + ctx, pool)); + SVN_ERR(svn_stream_puts(outstream, _("\n"))); + return SVN_NO_ERROR; +} + + +/* Return TRUE if SEARCH_PATTERN matches the AUTHOR, DATE, LOG_MESSAGE, + * or a path in the set of keys of the CHANGED_PATHS hash. Else, return FALSE. + * Any of AUTHOR, DATE, LOG_MESSAGE, and CHANGED_PATHS may be NULL. */ +static svn_boolean_t +match_search_pattern(const char *search_pattern, + const char *author, + const char *date, + const char *log_message, + apr_hash_t *changed_paths, + apr_pool_t *pool) +{ + /* Match any substring containing the pattern, like UNIX 'grep' does. */ + const char *pattern = apr_psprintf(pool, "*%s*", search_pattern); + int flags = 0; + + /* Does the author match the search pattern? */ + if (author && apr_fnmatch(pattern, author, flags) == APR_SUCCESS) + return TRUE; + + /* Does the date the search pattern? */ + if (date && apr_fnmatch(pattern, date, flags) == APR_SUCCESS) + return TRUE; + + /* Does the log message the search pattern? */ + if (log_message && apr_fnmatch(pattern, log_message, flags) == APR_SUCCESS) + return TRUE; + + if (changed_paths) + { + apr_hash_index_t *hi; + + /* Does a changed path match the search pattern? */ + for (hi = apr_hash_first(pool, changed_paths); + hi; + hi = apr_hash_next(hi)) + { + const char *path = svn__apr_hash_index_key(hi); + svn_log_changed_path2_t *log_item; + + if (apr_fnmatch(pattern, path, flags) == APR_SUCCESS) + return TRUE; + + /* Match copy-from paths, too. */ + log_item = svn__apr_hash_index_val(hi); + if (log_item->copyfrom_path + && SVN_IS_VALID_REVNUM(log_item->copyfrom_rev) + && apr_fnmatch(pattern, + log_item->copyfrom_path, flags) == APR_SUCCESS) + return TRUE; + } + } + + return FALSE; +} + +/* Match all search patterns in SEARCH_PATTERNS against AUTHOR, DATE, MESSAGE, + * and CHANGED_PATHS. Return TRUE if any pattern matches, else FALSE. + * SCRACH_POOL is used for temporary allocations. */ +static svn_boolean_t +match_search_patterns(apr_array_header_t *search_patterns, + const char *author, + const char *date, + const char *message, + apr_hash_t *changed_paths, + apr_pool_t *scratch_pool) +{ + int i; + svn_boolean_t match = FALSE; + apr_pool_t *iterpool = svn_pool_create(scratch_pool); + + for (i = 0; i < search_patterns->nelts; i++) + { + apr_array_header_t *pattern_group; + int j; + + pattern_group = APR_ARRAY_IDX(search_patterns, i, apr_array_header_t *); + + /* All patterns within the group must match. */ + for (j = 0; j < pattern_group->nelts; j++) + { + const char *pattern; + + svn_pool_clear(iterpool); + + pattern = APR_ARRAY_IDX(pattern_group, j, const char *); + match = match_search_pattern(pattern, author, date, message, + changed_paths, iterpool); + if (!match) + break; + } + + match = (match && j == pattern_group->nelts); + if (match) + break; + } + svn_pool_destroy(iterpool); + + return match; +} + +/* 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 = ""; + + if (lb->search_patterns && + ! match_search_patterns(lb->search_patterns, author, date, message, + log_entry->changed_paths2, pool)) + { + if (log_entry->has_children) + APR_ARRAY_PUSH(lb->merge_stack, svn_revnum_t) = log_entry->revision; + + return SVN_NO_ERROR; + } + + 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 = item->value; + 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)); + } + + SVN_ERR(svn_cmdline_fflush(stdout)); + SVN_ERR(svn_cmdline_fflush(stderr)); + + /* Print a diff if requested. */ + if (lb->show_diff) + { + svn_stream_t *outstream; + svn_stream_t *errstream; + + SVN_ERR(svn_stream_for_stdout(&outstream, pool)); + SVN_ERR(svn_stream_for_stderr(&errstream, pool)); + + SVN_ERR(display_diff(log_entry, + lb->target_path_or_url, &lb->target_peg_revision, + lb->depth, lb->diff_extensions, + outstream, errstream, + lb->ctx, pool)); + + SVN_ERR(svn_stream_close(outstream)); + SVN_ERR(svn_stream_close(errstream)); + } + + 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_empty(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 (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; + } + + /* Match search pattern before XML-escaping. */ + if (lb->search_patterns && + ! match_search_patterns(lb->search_patterns, author, date, message, + log_entry->changed_paths2, pool)) + { + if (log_entry->has_children) + APR_ARRAY_PUSH(lb->merge_stack, svn_revnum_t) = log_entry->revision; + + return SVN_NO_ERROR; + } + + 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); + + 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_array_header_t *sorted_paths; + int i; + + /* <paths> */ + svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "paths", + NULL); + + /* Get an array of sorted hash keys. */ + sorted_paths = svn_sort__hash(log_entry->changed_paths2, + svn_sort_compare_items_as_paths, pool); + + 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 = item->value; + 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), + "text-mods", svn_tristate__to_word( + log_item->text_modified), + "prop-mods", svn_tristate__to_word( + log_item->props_modified), + 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_cmdline__print_xml_prop_hash(&sb, log_entry->revprops, + FALSE, /* name_only */ + FALSE, 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.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->diff.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.search_patterns = opt_state->search_patterns; + 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/merge-cmd.c b/subversion/svn/merge-cmd.c new file mode 100644 index 0000000..c14f769 --- /dev/null +++ b/subversion/svn/merge-cmd.c @@ -0,0 +1,467 @@ +/* + * 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 "private/svn_client_private.h" + +#include "svn_private_config.h" + +/* A handy constant */ +static const svn_opt_revision_t unspecified_revision + = { svn_opt_revision_unspecified, { 0 } }; + + +/*** Code. ***/ + +/* Throw an error if PATH_OR_URL is a path and REVISION isn't a repository + * revision. */ +static svn_error_t * +ensure_wc_path_has_repo_revision(const char *path_or_url, + const svn_opt_revision_t *revision, + apr_pool_t *scratch_pool) +{ + if (revision->kind != svn_opt_revision_number + && revision->kind != svn_opt_revision_date + && revision->kind != svn_opt_revision_head + && ! svn_path_is_url(path_or_url)) + return svn_error_createf( + SVN_ERR_CLIENT_BAD_REVISION, NULL, + _("Invalid merge source '%s'; a working copy path can only be " + "used with a repository revision (a number, a date, or head)"), + svn_dirent_local_style(path_or_url, scratch_pool)); + return SVN_NO_ERROR; +} + +/* Run a merge. + * + * (No docs yet, as this code was just hoisted out of svn_cl__merge().) + * + * Having FIRST_RANGE_START/_END params is ugly -- we should be able to use + * PEG_REVISION1/2 and/or RANGES_TO_MERGE instead, maybe adjusting the caller. + */ +static svn_error_t * +run_merge(svn_boolean_t two_sources_specified, + const char *sourcepath1, + svn_opt_revision_t peg_revision1, + const char *sourcepath2, + const char *targetpath, + apr_array_header_t *ranges_to_merge, + svn_opt_revision_t first_range_start, + svn_opt_revision_t first_range_end, + svn_cl__opt_state_t *opt_state, + apr_array_header_t *options, + svn_client_ctx_t *ctx, + apr_pool_t *scratch_pool) +{ + svn_error_t *merge_err; + + if (opt_state->reintegrate) + { + merge_err = svn_cl__deprecated_merge_reintegrate( + sourcepath1, &peg_revision1, targetpath, + opt_state->dry_run, options, ctx, scratch_pool); + } + else if (! two_sources_specified) + { + /* 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)) + { + ranges_to_merge = NULL; + + /* This must be a 'sync' merge so check branch relationship. */ + if (opt_state->verbose) + SVN_ERR(svn_cmdline_printf( + scratch_pool, _("--- Checking branch relationship\n"))); + SVN_ERR_W(svn_cl__check_related_source_and_target( + sourcepath1, &peg_revision1, + targetpath, &unspecified_revision, ctx, scratch_pool), + _("Source and target must be different but related branches")); + } + + if (opt_state->verbose) + SVN_ERR(svn_cmdline_printf(scratch_pool, _("--- Merging\n"))); + merge_err = svn_client_merge_peg5(sourcepath1, + ranges_to_merge, + &peg_revision1, + targetpath, + opt_state->depth, + opt_state->ignore_ancestry, + opt_state->ignore_ancestry, + opt_state->force, /* force_delete */ + opt_state->record_only, + opt_state->dry_run, + opt_state->allow_mixed_rev, + options, + ctx, + scratch_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")); + + if (opt_state->verbose) + SVN_ERR(svn_cmdline_printf(scratch_pool, _("--- Merging\n"))); + merge_err = svn_client_merge5(sourcepath1, + &first_range_start, + sourcepath2, + &first_range_end, + targetpath, + opt_state->depth, + opt_state->ignore_ancestry, + opt_state->ignore_ancestry, + opt_state->force, /* force_delete */ + opt_state->record_only, + opt_state->dry_run, + opt_state->allow_mixed_rev, + options, + ctx, + scratch_pool); + } + + return merge_err; +} + +/* 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 *merge_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; + svn_boolean_t has_explicit_target = FALSE; + + /* 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. */ + /* 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 *); + has_explicit_target = TRUE; + 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. That is, it doesn't read from the WC itself, it + reads from the WC's URL. Forcing the user to specify a repository + revision should avoid any confusion. */ + SVN_ERR(ensure_wc_path_has_repo_revision(sourcepath1, &first_range_start, + pool)); + SVN_ERR(ensure_wc_path_has_repo_revision(sourcepath2, &first_range_end, + pool)); + + /* 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 *); + has_explicit_target = TRUE; + } + } + + /* If no targetpath was specified, see if we can infer it from the + sourcepaths. */ + if (! has_explicit_target + && sourcepath1 && sourcepath2 + && strcmp(targetpath, "") == 0) + { + /* If the sourcepath is a URL, it can only refer to a target in + the current working directory or which is 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) + { + const char *target_url; + const char *target_base; + + SVN_ERR(svn_client_url_from_path2(&target_url, targetpath, ctx, + pool, pool)); + target_base = svn_uri_basename(target_url, pool); + + /* If the basename of the source is the same as the basename of + the cwd assume the cwd is the target. */ + if (strcmp(sp1_basename, target_base) != 0) + { + svn_node_kind_t kind; + + /* If the basename of the source differs from the basename + of the target. We still might assume the cwd is the + target, but first check if there is a file in the cwd + with the same name as the source basename. If there is, + then assume that file is the target. */ + 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; + + SVN_ERR(svn_io_check_path(sourcepath1, &kind, pool)); + if (kind == svn_node_file) + { + targetpath = sourcepath1; + } + } + } + + 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->ignore_ancestry) + return svn_error_create(SVN_ERR_CL_MUTUALLY_EXCLUSIVE_ARGS, NULL, + _("--reintegrate cannot be used with " + "--ignore-ancestry")); + + if (opt_state->record_only) + return svn_error_create(SVN_ERR_CL_MUTUALLY_EXCLUSIVE_ARGS, NULL, + _("--reintegrate cannot be used with " + "--record-only")); + + 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")); + } + + merge_err = run_merge(two_sources_specified, + sourcepath1, peg_revision1, + sourcepath2, + targetpath, + ranges_to_merge, first_range_start, first_range_end, + opt_state, options, ctx, pool); + if (merge_err && merge_err->apr_err + == SVN_ERR_CLIENT_INVALID_MERGEINFO_NO_MERGETRACKING) + { + return svn_error_quick_wrap( + merge_err, + _("Merge tracking not possible, use --ignore-ancestry or\n" + "fix invalid mergeinfo in target with 'svn propset'")); + } + + if (!opt_state->quiet) + { + svn_error_t *err = svn_cl__notifier_print_conflict_stats( + ctx->notify_baton2, pool); + + merge_err = svn_error_compose_create(merge_err, err); + } + + return svn_cl__may_need_force(merge_err); +} diff --git a/subversion/svn/mergeinfo-cmd.c b/subversion/svn/mergeinfo-cmd.c new file mode 100644 index 0000000..a78c42a --- /dev/null +++ b/subversion/svn/mergeinfo-cmd.c @@ -0,0 +1,349 @@ +/* + * 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; +} + +/* Draw a diagram (by printing text to the console) summarizing the state + * of merging between two branches, given the merge description + * indicated by YCA, BASE, RIGHT, TARGET, REINTEGRATE_LIKE. */ +static svn_error_t * +mergeinfo_diagram(const char *yca_url, + const char *base_url, + const char *right_url, + const char *target_url, + svn_revnum_t yca_rev, + svn_revnum_t base_rev, + svn_revnum_t right_rev, + svn_revnum_t target_rev, + const char *repos_root_url, + svn_boolean_t target_is_wc, + svn_boolean_t reintegrate_like, + apr_pool_t *pool) +{ + /* The graph occupies 4 rows of text, and the annotations occupy + * another 2 rows above and 2 rows below. The graph is constructed + * from left to right in discrete sections ("columns"), each of which + * can have a different width (measured in characters). Each element in + * the array is either a text string of the appropriate width, or can + * be NULL to draw a blank cell. */ +#define ROWS 8 +#define COLS 4 + const char *g[ROWS][COLS] = {{0}}; + int col_width[COLS]; + int row, col; + + /* The YCA (that is, the branching point). And an ellipsis, because we + * don't show information about earlier merges */ + g[0][0] = apr_psprintf(pool, " %-8ld ", yca_rev); + g[1][0] = " | "; + if (strcmp(yca_url, right_url) == 0) + { + g[2][0] = "-------| |--"; + g[3][0] = " \\ "; + g[4][0] = " \\ "; + g[5][0] = " --| |--"; + } + else if (strcmp(yca_url, target_url) == 0) + { + g[2][0] = " --| |--"; + g[3][0] = " / "; + g[4][0] = " / "; + g[5][0] = "-------| |--"; + } + else + { + g[2][0] = " --| |--"; + g[3][0] = "... / "; + g[4][0] = " \\ "; + g[5][0] = " --| |--"; + } + + /* The last full merge */ + if ((base_rev > yca_rev) && reintegrate_like) + { + g[2][2] = "---------"; + g[3][2] = " / "; + g[4][2] = " / "; + g[5][2] = "---------"; + g[6][2] = "| "; + g[7][2] = apr_psprintf(pool, "%-8ld ", base_rev); + } + else if (base_rev > yca_rev) + { + g[0][2] = apr_psprintf(pool, "%-8ld ", base_rev); + g[1][2] = "| "; + g[2][2] = "---------"; + g[3][2] = " \\ "; + g[4][2] = " \\ "; + g[5][2] = "---------"; + } + else + { + g[2][2] = "---------"; + g[3][2] = " "; + g[4][2] = " "; + g[5][2] = "---------"; + } + + /* The tips of the branches */ + { + g[0][3] = apr_psprintf(pool, "%-8ld", right_rev); + g[1][3] = "| "; + g[2][3] = "- "; + g[3][3] = " "; + g[4][3] = " "; + g[5][3] = "- "; + g[6][3] = "| "; + g[7][3] = target_is_wc ? "WC " + : apr_psprintf(pool, "%-8ld", target_rev); + } + + /* Find the width of each column, so we know how to print blank cells */ + for (col = 0; col < COLS; col++) + { + col_width[col] = 0; + for (row = 0; row < ROWS; row++) + { + if (g[row][col] && ((int)strlen(g[row][col]) > col_width[col])) + col_width[col] = (int)strlen(g[row][col]); + } + } + + /* Column headings */ + SVN_ERR(svn_cmdline_printf(pool, + " %s\n" + " | %s\n" + " | | %s\n" + " | | | %s\n" + "\n", + _("youngest common ancestor"), _("last full merge"), + _("tip of branch"), _("repository path"))); + + /* Print the diagram, row by row */ + for (row = 0; row < ROWS; row++) + { + SVN_ERR(svn_cmdline_fputs(" ", stdout, pool)); + for (col = 0; col < COLS; col++) + { + if (g[row][col]) + { + SVN_ERR(svn_cmdline_fputs(g[row][col], stdout, pool)); + } + else + { + /* Print <column-width> spaces */ + SVN_ERR(svn_cmdline_printf(pool, "%*s", col_width[col], "")); + } + } + if (row == 2) + SVN_ERR(svn_cmdline_printf(pool, " %s", + svn_uri_skip_ancestor(repos_root_url, right_url, pool))); + if (row == 5) + SVN_ERR(svn_cmdline_printf(pool, " %s", + svn_uri_skip_ancestor(repos_root_url, target_url, pool))); + SVN_ERR(svn_cmdline_fputs("\n", stdout, pool)); + } + + return SVN_NO_ERROR; +} + +/* Display a summary of the state of merging between the two branches + * SOURCE_PATH_OR_URL@SOURCE_REVISION and + * TARGET_PATH_OR_URL@TARGET_REVISION. */ +static svn_error_t * +mergeinfo_summary( + const char *source_path_or_url, + const svn_opt_revision_t *source_revision, + const char *target_path_or_url, + const svn_opt_revision_t *target_revision, + svn_client_ctx_t *ctx, + apr_pool_t *pool) +{ + const char *yca_url, *base_url, *right_url, *target_url; + svn_revnum_t yca_rev, base_rev, right_rev, target_rev; + const char *repos_root_url; + svn_boolean_t target_is_wc, is_reintegration; + + target_is_wc = (! svn_path_is_url(target_path_or_url)) + && (target_revision->kind == svn_opt_revision_unspecified + || target_revision->kind == svn_opt_revision_working); + SVN_ERR(svn_client_get_merging_summary( + &is_reintegration, + &yca_url, &yca_rev, + &base_url, &base_rev, + &right_url, &right_rev, + &target_url, &target_rev, + &repos_root_url, + source_path_or_url, source_revision, + target_path_or_url, target_revision, + ctx, pool, pool)); + + SVN_ERR(mergeinfo_diagram(yca_url, base_url, right_url, target_url, + yca_rev, base_rev, right_rev, target_rev, + repos_root_url, target_is_wc, is_reintegration, + pool)); + + 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; + svn_opt_revision_t *src_start_revision, *src_end_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)); + + /* Parse the arguments: SOURCE[@REV] optionally followed by TARGET[@REV]. */ + 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")); + SVN_ERR(svn_opt_parse_path(&src_peg_revision, &source, + APR_ARRAY_IDX(targets, 0, const char *), pool)); + 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. */ + /* ### But what if SOURCE is a WC path not a URL -- shouldn't we then use + * BASE (but not WORKING: that would be inconsistent with 'svn merge')? */ + 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. */ + /* ### But we would like to be able to examine a working copy with an + uncommitted merge in it, so change this to use WORKING not 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; + } + + SVN_ERR_W(svn_cl__check_related_source_and_target(source, &src_peg_revision, + target, &tgt_peg_revision, + ctx, pool), + _("Source and target must be different but related branches")); + + src_start_revision = &(opt_state->start_revision); + if (opt_state->end_revision.kind == svn_opt_revision_unspecified) + src_end_revision = src_start_revision; + else + src_end_revision = &(opt_state->end_revision); + + /* 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_log2(TRUE, target, &tgt_peg_revision, + source, &src_peg_revision, + src_start_revision, + src_end_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_log2(FALSE, target, &tgt_peg_revision, + source, &src_peg_revision, + src_start_revision, + src_end_revision, + print_log_rev, NULL, + TRUE, depth, NULL, ctx, + pool)); + } + else + { + if ((opt_state->start_revision.kind != svn_opt_revision_unspecified) + || (opt_state->end_revision.kind != svn_opt_revision_unspecified)) + return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, + _("--revision (-r) option valid only with " + "--show-revs option")); + if (opt_state->depth != svn_depth_unknown) + return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, + _("Depth specification options valid only " + "with --show-revs option")); + + SVN_ERR(mergeinfo_summary(source, &src_peg_revision, + target, &tgt_peg_revision, + 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..bb71043 --- /dev/null +++ b/subversion/svn/move-cmd.c @@ -0,0 +1,105 @@ +/* + * 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_move7(targets, dst_path, + TRUE /* move_as_child */, + opt_state->parents /* make_parents */, + opt_state->allow_mixed_rev /* allow_mixed_revisions*/, + FALSE /* metadata_only */, + 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..6498fb1 --- /dev/null +++ b/subversion/svn/notify.c @@ -0,0 +1,1222 @@ +/* + * 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 "svn_sorts.h" +#include "svn_hash.h" +#include "cl.h" +#include "private/svn_subr_private.h" +#include "private/svn_dep_compat.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 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. */ + + svn_cl__conflict_stats_t *conflict_stats; + + /* The cwd, for use in decomposing absolute paths. */ + const char *path_prefix; +}; + +/* Conflict stats for operations such as update and merge. */ +struct svn_cl__conflict_stats_t +{ + apr_pool_t *stats_pool; + apr_hash_t *text_conflicts, *prop_conflicts, *tree_conflicts; + int text_conflicts_resolved, prop_conflicts_resolved, tree_conflicts_resolved; + int skipped_paths; +}; + +svn_cl__conflict_stats_t * +svn_cl__conflict_stats_create(apr_pool_t *pool) +{ + svn_cl__conflict_stats_t *conflict_stats + = apr_palloc(pool, sizeof(*conflict_stats)); + + conflict_stats->stats_pool = pool; + conflict_stats->text_conflicts = apr_hash_make(pool); + conflict_stats->prop_conflicts = apr_hash_make(pool); + conflict_stats->tree_conflicts = apr_hash_make(pool); + conflict_stats->text_conflicts_resolved = 0; + conflict_stats->prop_conflicts_resolved = 0; + conflict_stats->tree_conflicts_resolved = 0; + conflict_stats->skipped_paths = 0; + return conflict_stats; +} + +/* Add the PATH (as a key, with a meaningless value) into the HASH in NB. */ +static void +store_path(struct notify_baton *nb, apr_hash_t *hash, const char *path) +{ + svn_hash_sets(hash, apr_pstrdup(nb->conflict_stats->stats_pool, path), ""); +} + +void +svn_cl__conflict_stats_resolved(svn_cl__conflict_stats_t *conflict_stats, + const char *path_local, + svn_wc_conflict_kind_t conflict_kind) +{ + switch (conflict_kind) + { + case svn_wc_conflict_kind_text: + if (svn_hash_gets(conflict_stats->text_conflicts, path_local)) + { + svn_hash_sets(conflict_stats->text_conflicts, path_local, NULL); + conflict_stats->text_conflicts_resolved++; + } + break; + case svn_wc_conflict_kind_property: + if (svn_hash_gets(conflict_stats->prop_conflicts, path_local)) + { + svn_hash_sets(conflict_stats->prop_conflicts, path_local, NULL); + conflict_stats->prop_conflicts_resolved++; + } + break; + case svn_wc_conflict_kind_tree: + if (svn_hash_gets(conflict_stats->tree_conflicts, path_local)) + { + svn_hash_sets(conflict_stats->tree_conflicts, path_local, NULL); + conflict_stats->tree_conflicts_resolved++; + } + break; + } +} + +static const char * +remaining_str(apr_pool_t *pool, int n_remaining) +{ + return apr_psprintf(pool, Q_("%d remaining", + "%d remaining", + n_remaining), + n_remaining); +} + +static const char * +resolved_str(apr_pool_t *pool, int n_resolved) +{ + return apr_psprintf(pool, Q_("and %d already resolved", + "and %d already resolved", + n_resolved), + n_resolved); +} + +svn_error_t * +svn_cl__notifier_print_conflict_stats(void *baton, apr_pool_t *scratch_pool) +{ + struct notify_baton *nb = baton; + int n_text = apr_hash_count(nb->conflict_stats->text_conflicts); + int n_prop = apr_hash_count(nb->conflict_stats->prop_conflicts); + int n_tree = apr_hash_count(nb->conflict_stats->tree_conflicts); + int n_text_r = nb->conflict_stats->text_conflicts_resolved; + int n_prop_r = nb->conflict_stats->prop_conflicts_resolved; + int n_tree_r = nb->conflict_stats->tree_conflicts_resolved; + + if (n_text > 0 || n_text_r > 0 + || n_prop > 0 || n_prop_r > 0 + || n_tree > 0 || n_tree_r > 0 + || nb->conflict_stats->skipped_paths > 0) + SVN_ERR(svn_cmdline_printf(scratch_pool, + _("Summary of conflicts:\n"))); + + if (n_text_r == 0 && n_prop_r == 0 && n_tree_r == 0) + { + if (n_text > 0) + SVN_ERR(svn_cmdline_printf(scratch_pool, + _(" Text conflicts: %d\n"), + n_text)); + if (n_prop > 0) + SVN_ERR(svn_cmdline_printf(scratch_pool, + _(" Property conflicts: %d\n"), + n_prop)); + if (n_tree > 0) + SVN_ERR(svn_cmdline_printf(scratch_pool, + _(" Tree conflicts: %d\n"), + n_tree)); + } + else + { + if (n_text > 0 || n_text_r > 0) + SVN_ERR(svn_cmdline_printf(scratch_pool, + _(" Text conflicts: %s (%s)\n"), + remaining_str(scratch_pool, n_text), + resolved_str(scratch_pool, n_text_r))); + if (n_prop > 0 || n_prop_r > 0) + SVN_ERR(svn_cmdline_printf(scratch_pool, + _(" Property conflicts: %s (%s)\n"), + remaining_str(scratch_pool, n_prop), + resolved_str(scratch_pool, n_prop_r))); + if (n_tree > 0 || n_tree_r > 0) + SVN_ERR(svn_cmdline_printf(scratch_pool, + _(" Tree conflicts: %s (%s)\n"), + remaining_str(scratch_pool, n_tree), + resolved_str(scratch_pool, n_tree_r))); + } + if (nb->conflict_stats->skipped_paths > 0) + SVN_ERR(svn_cmdline_printf(scratch_pool, + _(" Skipped paths: %d\n"), + nb->conflict_stats->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->conflict_stats->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->conflict_stats->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->conflict_stats->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->conflict_stats->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->conflict_stats->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_broken_lock: + if ((err = svn_cmdline_printf(pool, "B %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_left_local_modifications: + if ((err = svn_cmdline_printf(pool, "Left local modifications as '%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) + { + store_path(nb, nb->conflict_stats->text_conflicts, path_local); + 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) + { + store_path(nb, nb->conflict_stats->text_conflicts, path_local); + statchar_buf[0] = 'C'; + } + else + statchar_buf[0] = 'E'; + + if (n->prop_state == svn_wc_notify_state_conflicted) + { + store_path(nb, nb->conflict_stats->prop_conflicts, path_local); + 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) + { + store_path(nb, nb->conflict_stats->text_conflicts, path_local); + 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) + { + store_path(nb, nb->conflict_stats->prop_conflicts, path_local); + 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) + { + /* If we are patching from the start of an empty file, + it is nicer to show offset 0 */ + if (n->hunk_original_start == 0 && n->hunk_matched_line == 1) + off = 0; /* No offset, just adding */ + else + 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) + { + store_path(nb, nb->conflict_stats->text_conflicts, path_local); + 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) + { + store_path(nb, nb->conflict_stats->prop_conflicts, path_local); + 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->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 (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: + store_path(nb, nb->conflict_stats->tree_conflicts, path_local); + 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\n", + apr_psprintf(pool, _("'%s' is not under version control"), + path_local)); + if (err) + goto print_error; + break; + + case svn_wc_notify_conflict_resolver_starting: + /* Once all operations invoke the interactive conflict resolution after + * they've completed, we can run svn_cl__notifier_print_conflict_stats() + * here. */ + break; + + case svn_wc_notify_conflict_resolver_done: + break; + + case svn_wc_notify_foreign_copy_begin: + if (n->merge_range == NULL) + { + err = svn_cmdline_printf( + pool, + _("--- Copying from foreign repository URL '%s':\n"), + n->url); + if (err) + goto print_error; + } + break; + + case svn_wc_notify_move_broken: + err = svn_cmdline_printf(pool, + _("Breaking move with source path '%s'\n"), + 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_cl__conflict_stats_t *conflict_stats, + 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->in_external = FALSE; + nb->had_print_error = FALSE; + nb->conflict_stats = conflict_stats; + 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..83707c6 --- /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__notifier_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..520fe6c --- /dev/null +++ b/subversion/svn/propedit-cmd.c @@ -0,0 +1,356 @@ +/* + * 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_hash.h" +#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_cmdline_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->force) + SVN_ERR(svn_cl__check_svn_prop_name(pname_utf8, opt_state->revprop, + svn_cl__prop_use_edit, pool)); + + 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_empty(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_cmdline__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 *abspath_or_url; + 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(&abspath_or_url, target, subpool)); + else + abspath_or_url = target; + + /* 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_propget5(&props, NULL, pname_utf8, abspath_or_url, + &peg_revision, + &(opt_state->start_revision), + &base_rev, svn_depth_empty, + NULL, ctx, subpool, subpool)); + + /* Get the property value. */ + propval = svn_hash_gets(props, abspath_or_url); + if (! propval) + propval = svn_string_create_empty(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_kind2(&kind, ctx->wc_ctx, abspath_or_url, + FALSE, 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_cmdline__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; + + SVN_ERR(svn_cl__propset_print_binary_mime_type_warning( + targs, pname_utf8, propval, subpool)); + + 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 a 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..e291911 --- /dev/null +++ b/subversion/svn/propget-cmd.c @@ -0,0 +1,493 @@ +/* + * 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_hash.h" +#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_array_header_t *inherited_props, + apr_pool_t *pool) +{ + apr_array_header_t *sorted_props; + int i; + apr_pool_t *iterpool = NULL; + svn_stringbuf_t *sb; + + if (inherited_props && inherited_props->nelts) + { + iterpool = svn_pool_create(pool); + + for (i = 0; i < inherited_props->nelts; i++) + { + const char *name_local; + svn_prop_inherited_item_t *iprop = + APR_ARRAY_IDX(inherited_props, i, svn_prop_inherited_item_t *); + svn_string_t *propval = svn__apr_hash_index_val( + apr_hash_first(pool, iprop->prop_hash)); + + sb = NULL; + svn_pool_clear(iterpool); + + if (svn_path_is_url(iprop->path_or_url)) + name_local = iprop->path_or_url; + else + name_local = svn_dirent_local_style(iprop->path_or_url, iterpool); + + svn_xml_make_open_tag(&sb, iterpool, svn_xml_normal, "target", + "path", name_local, NULL); + + svn_cmdline__print_xml_prop(&sb, pname, propval, TRUE, iterpool); + svn_xml_make_close_tag(&sb, iterpool, "target"); + + SVN_ERR(svn_cl__error_checked_fputs(sb->data, stdout)); + } + } + + if (iterpool == NULL) + iterpool = svn_pool_create(iterpool); + + 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; + + 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, FALSE, iterpool); + svn_xml_make_close_tag(&sb, iterpool, "target"); + + SVN_ERR(svn_cl__error_checked_fputs(sb->data, stdout)); + } + + if (iterpool) + svn_pool_destroy(iterpool); + + return SVN_NO_ERROR; +} + +/* Print the property PNAME_UTF with the value PROPVAL set on ABSPATH_OR_URL + to the stream OUT. + + If INHERITED_PROPERTY is true then the property described is inherited, + otherwise it is explicit. + + WC_PATH_PREFIX is the absolute path of the current working directory (and + is ignored if ABSPATH_OR_URL is a URL). + + All other arguments are as per print_properties. */ +static svn_error_t * +print_single_prop(svn_string_t *propval, + const char *target_abspath_or_url, + const char *abspath_or_URL, + const char *wc_path_prefix, + svn_stream_t *out, + const char *pname_utf8, + svn_boolean_t print_filenames, + svn_boolean_t omit_newline, + svn_boolean_t like_proplist, + svn_boolean_t inherited_property, + apr_pool_t *scratch_pool) +{ + if (print_filenames) + { + const char *header; + + /* Print the file name. */ + + if (! svn_path_is_url(abspath_or_URL)) + abspath_or_URL = svn_cl__local_style_skip_ancestor(wc_path_prefix, + abspath_or_URL, + scratch_pool); + + /* In verbose mode, print exactly same as "proplist" does; + * otherwise, print a brief header. */ + if (inherited_property) + { + if (like_proplist) + { + if (! svn_path_is_url(target_abspath_or_url)) + target_abspath_or_url = + svn_cl__local_style_skip_ancestor(wc_path_prefix, + target_abspath_or_url, + scratch_pool); + header = apr_psprintf( + scratch_pool, + _("Inherited properties on '%s',\nfrom '%s':\n"), + target_abspath_or_url, abspath_or_URL); + } + else + { + header = apr_psprintf(scratch_pool, "%s - ", abspath_or_URL); + } + } + else + header = apr_psprintf(scratch_pool, like_proplist + ? _("Properties on '%s':\n") + : "%s - ", abspath_or_URL); + SVN_ERR(svn_cmdline_cstring_from_utf8(&header, header, scratch_pool)); + SVN_ERR(svn_subst_translate_cstring2(header, &header, + APR_EOL_STR, /* 'native' eol */ + FALSE, /* no repair */ + NULL, /* no keywords */ + FALSE, /* no expansion */ + scratch_pool)); + 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(scratch_pool); + + svn_hash_sets(hash, pname_utf8, propval); + SVN_ERR(svn_cmdline__print_prop_hash(out, hash, FALSE, scratch_pool)); + } + 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, scratch_pool)); + + SVN_ERR(stream_write(out, propval->data, propval->len)); + + if (! omit_newline) + SVN_ERR(stream_write(out, APR_EOL_STR, + strlen(APR_EOL_STR))); + } + return SVN_NO_ERROR; +} + +/* Print the properties in PROPS and/or *INHERITED_PROPS to the stream OUT. + PROPS is a hash mapping (const char *) path to (svn_string_t) property + value. INHERITED_PROPS is a depth-first ordered array of + svn_prop_inherited_item_t * structures. + + TARGET_ABSPATH_OR_URL is the path which inherits INHERITED_PROPS. + + PROPS may be an empty hash, but is never null. INHERITED_PROPS may be + null. + + If IS_URL is true, all paths in PROPS 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, + const char *target_abspath_or_url, + const char *pname_utf8, + apr_hash_t *props, + apr_array_header_t *inherited_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)); + + if (inherited_props) + { + svn_pool_clear(iterpool); + + for (i = 0; i < inherited_props->nelts; i++) + { + svn_prop_inherited_item_t *iprop = + APR_ARRAY_IDX(inherited_props, i, svn_prop_inherited_item_t *); + svn_string_t *propval = svn__apr_hash_index_val(apr_hash_first(pool, + iprop->prop_hash)); + SVN_ERR(print_single_prop(propval, target_abspath_or_url, + iprop->path_or_url, + path_prefix, out, pname_utf8, + print_filenames, omit_newline, + like_proplist, TRUE, iterpool)); + } + } + + 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); + + SVN_ERR(print_single_prop(propval, target_abspath_or_url, filename, + path_prefix, out, pname_utf8, print_filenames, + omit_newline, like_proplist, FALSE, + iterpool)); + } + + 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; + + if (opt_state->show_inherited_props) + return svn_error_create( + SVN_ERR_CL_ARG_PARSING_ERROR, NULL, + _("--show-inherited-props can't be used with --revprop")); + + 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, FALSE, + 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; + apr_array_header_t *inherited_props; + + 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_propget5( + &props, + opt_state->show_inherited_props ? &inherited_props : NULL, + 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->show_inherited_props) + && (! 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, + opt_state->show_inherited_props ? inherited_props : NULL, + subpool)); + else + SVN_ERR(print_properties( + out, truepath, pname_utf8, + props, + opt_state->show_inherited_props ? inherited_props : NULL, + 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..fe23a67 --- /dev/null +++ b/subversion/svn/proplist-cmd.c @@ -0,0 +1,336 @@ +/* + * 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 "svn_props.h" +#include "cl.h" + +#include "private/svn_cmdline_private.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_receiver2_t interface, printing XML to + stdout. */ +static svn_error_t * +proplist_receiver_xml(void *baton, + const char *path, + apr_hash_t *prop_hash, + apr_array_header_t *inherited_props, + 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; + const char *name_local; + + if (inherited_props && inherited_props->nelts) + { + int i; + apr_pool_t *iterpool = svn_pool_create(pool); + + for (i = 0; i < inherited_props->nelts; i++) + { + svn_prop_inherited_item_t *iprop = + APR_ARRAY_IDX(inherited_props, i, svn_prop_inherited_item_t *); + + sb = NULL; + + if (svn_path_is_url(iprop->path_or_url)) + name_local = iprop->path_or_url; + else + name_local = svn_dirent_local_style(iprop->path_or_url, iterpool); + + svn_xml_make_open_tag(&sb, iterpool, svn_xml_normal, "target", + "path", name_local, NULL); + SVN_ERR(svn_cmdline__print_xml_prop_hash(&sb, iprop->prop_hash, + (! opt_state->verbose), + TRUE, iterpool)); + svn_xml_make_close_tag(&sb, iterpool, "target"); + SVN_ERR(svn_cl__error_checked_fputs(sb->data, stdout)); + } + svn_pool_destroy(iterpool); + } + + if (! is_url) + name_local = svn_dirent_local_style(path, pool); + else + name_local = path; + + sb = NULL; + + + if (prop_hash) + { + /* "<target ...>" */ + svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "target", + "path", name_local, NULL); + + SVN_ERR(svn_cmdline__print_xml_prop_hash(&sb, prop_hash, + (! opt_state->verbose), + FALSE, pool)); + + /* "</target>" */ + svn_xml_make_close_tag(&sb, pool, "target"); + SVN_ERR(svn_cl__error_checked_fputs(sb->data, stdout)); + } + + return SVN_NO_ERROR; +} + + +/* This implements the svn_proplist_receiver2_t interface. */ +static svn_error_t * +proplist_receiver(void *baton, + const char *path, + apr_hash_t *prop_hash, + apr_array_header_t *inherited_props, + 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 (inherited_props) + { + int i; + apr_pool_t *iterpool = svn_pool_create(pool); + + for (i = 0; i < inherited_props->nelts; i++) + { + svn_prop_inherited_item_t *iprop = + APR_ARRAY_IDX(inherited_props, i, svn_prop_inherited_item_t *); + + svn_pool_clear(iterpool); + + if (!opt_state->quiet) + { + if (svn_path_is_url(iprop->path_or_url)) + SVN_ERR(svn_cmdline_printf( + iterpool, _("Inherited properties on '%s',\nfrom '%s':\n"), + name_local, iprop->path_or_url)); + else + SVN_ERR(svn_cmdline_printf( + iterpool, _("Inherited properties on '%s',\nfrom '%s':\n"), + name_local, svn_dirent_local_style(iprop->path_or_url, + iterpool))); + } + + SVN_ERR(svn_cmdline__print_prop_hash(NULL, iprop->prop_hash, + (! opt_state->verbose), + iterpool)); + } + svn_pool_destroy(iterpool); + } + + if (prop_hash && apr_hash_count(prop_hash)) + { + if (!opt_state->quiet) + SVN_ERR(svn_cmdline_printf(pool, _("Properties on '%s':\n"), + name_local)); + SVN_ERR(svn_cmdline__print_prop_hash(NULL, prop_hash, + (! opt_state->verbose), pool)); + } + + return SVN_NO_ERROR; +} + + +/* 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; + + if (opt_state->show_inherited_props) + return svn_error_create( + SVN_ERR_CL_ARG_PARSING_ERROR, NULL, + _("--show-inherited-props can't be used with --revprop")); + + 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_cmdline__print_xml_prop_hash(&sb, proplist, + (! opt_state->verbose), + FALSE, 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_cmdline__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_receiver2_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_proplist4(truepath, &peg_revision, + &(opt_state->start_revision), + opt_state->depth, + opt_state->changelists, + opt_state->show_inherited_props, + 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..2a41ac8 --- /dev/null +++ b/subversion/svn/props.c @@ -0,0 +1,356 @@ +/* + * 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 <stdlib.h> + +#include <apr_hash.h> +#include "svn_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_string_private.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; +} + +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' + || svn_cstring_casecmp(propbuf->data, "0") == 0 + || svn_cstring_casecmp(propbuf->data, "no") == 0 + || svn_cstring_casecmp(propbuf->data, "off") == 0 + || svn_cstring_casecmp(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); + } +} + + +/* Context for sorting property names */ +struct simprop_context_t +{ + svn_string_t name; /* The name of the property we're comparing with */ + svn_membuf_t buffer; /* Buffer for similarity testing */ +}; + +struct simprop_t +{ + const char *propname; /* The original svn: property name */ + svn_string_t name; /* The property name without the svn: prefix */ + unsigned int score; /* The similarity score */ + apr_size_t diff; /* Number of chars different from context.name */ + struct simprop_context_t *context; /* Sorting context for qsort() */ +}; + +/* Similarity test between two property names */ +static APR_INLINE unsigned int +simprop_key_diff(const svn_string_t *key, const svn_string_t *ctx, + svn_membuf_t *buffer, apr_size_t *diff) +{ + apr_size_t lcs; + const unsigned int score = svn_string__similarity(key, ctx, buffer, &lcs); + if (key->len > ctx->len) + *diff = key->len - lcs; + else + *diff = ctx->len - lcs; + return score; +} + +/* Key comparator for qsort for simprop_t */ +static int +simprop_compare(const void *pkeya, const void *pkeyb) +{ + struct simprop_t *const keya = *(struct simprop_t *const *)pkeya; + struct simprop_t *const keyb = *(struct simprop_t *const *)pkeyb; + struct simprop_context_t *const context = keya->context; + + if (keya->score == -1) + keya->score = simprop_key_diff(&keya->name, &context->name, + &context->buffer, &keya->diff); + if (keyb->score == -1) + keyb->score = simprop_key_diff(&keyb->name, &context->name, + &context->buffer, &keyb->diff); + + return (keya->score < keyb->score ? 1 + : (keya->score > keyb->score ? -1 + : (keya->diff > keyb->diff ? 1 + : (keya->diff < keyb->diff ? -1 : 0)))); +} + + +static const char* +force_prop_option_message(svn_cl__prop_use_t prop_use, const char *prop_name, + apr_pool_t *scratch_pool) +{ + switch (prop_use) + { + case svn_cl__prop_use_set: + return apr_psprintf( + scratch_pool, + _("(To set the '%s' property, re-run with '--force'.)"), + prop_name); + case svn_cl__prop_use_edit: + return apr_psprintf( + scratch_pool, + _("(To edit the '%s' property, re-run with '--force'.)"), + prop_name); + case svn_cl__prop_use_use: + default: + return apr_psprintf( + scratch_pool, + _("(To use the '%s' property, re-run with '--force'.)"), + prop_name); + } +} + +static const char* +wrong_prop_error_message(svn_cl__prop_use_t prop_use, const char *prop_name, + apr_pool_t *scratch_pool) +{ + switch (prop_use) + { + case svn_cl__prop_use_set: + return apr_psprintf( + scratch_pool, + _("'%s' is not a valid %s property name;" + " re-run with '--force' to set it"), + prop_name, SVN_PROP_PREFIX); + case svn_cl__prop_use_edit: + return apr_psprintf( + scratch_pool, + _("'%s' is not a valid %s property name;" + " re-run with '--force' to edit it"), + prop_name, SVN_PROP_PREFIX); + case svn_cl__prop_use_use: + default: + return apr_psprintf( + scratch_pool, + _("'%s' is not a valid %s property name;" + " re-run with '--force' to use it"), + prop_name, SVN_PROP_PREFIX); + } +} + +svn_error_t * +svn_cl__check_svn_prop_name(const char *propname, + svn_boolean_t revprop, + svn_cl__prop_use_t prop_use, + apr_pool_t *scratch_pool) +{ + static const char *const nodeprops[] = + { + SVN_PROP_NODE_ALL_PROPS + }; + static const apr_size_t nodeprops_len = sizeof(nodeprops)/sizeof(*nodeprops); + + static const char *const revprops[] = + { + SVN_PROP_REVISION_ALL_PROPS + }; + static const apr_size_t revprops_len = sizeof(revprops)/sizeof(*revprops); + + const char *const *const proplist = (revprop ? revprops : nodeprops); + const apr_size_t numprops = (revprop ? revprops_len : nodeprops_len); + + struct simprop_t **propkeys; + struct simprop_t *propbuf; + apr_size_t i; + + struct simprop_context_t context; + svn_string_t prefix; + + context.name.data = propname; + context.name.len = strlen(propname); + prefix.data = SVN_PROP_PREFIX; + prefix.len = strlen(SVN_PROP_PREFIX); + + svn_membuf__create(&context.buffer, 0, scratch_pool); + + /* First, check if the name is even close to being in the svn: namespace. + It must contain a colon in the right place, and we only allow + one-char typos or a single transposition. */ + if (context.name.len < prefix.len + || context.name.data[prefix.len - 1] != prefix.data[prefix.len - 1]) + return SVN_NO_ERROR; /* Wrong prefix, ignore */ + else + { + apr_size_t lcs; + const apr_size_t name_len = context.name.len; + context.name.len = prefix.len; /* Only check up to the prefix length */ + svn_string__similarity(&context.name, &prefix, &context.buffer, &lcs); + context.name.len = name_len; /* Restore the original propname length */ + if (lcs < prefix.len - 1) + return SVN_NO_ERROR; /* Wrong prefix, ignore */ + + /* If the prefix is slightly different, the rest must be + identical in order to trigger the error. */ + if (lcs == prefix.len - 1) + { + for (i = 0; i < numprops; ++i) + { + if (0 == strcmp(proplist[i] + prefix.len, propname + prefix.len)) + return svn_error_createf( + SVN_ERR_CLIENT_PROPERTY_NAME, NULL, + _("'%s' is not a valid %s property name;" + " did you mean '%s'?\n%s"), + propname, SVN_PROP_PREFIX, proplist[i], + force_prop_option_message(prop_use, propname, scratch_pool)); + } + return SVN_NO_ERROR; + } + } + + /* Now find the closest match from amongst the set of reserved + node or revision property names. Skip the prefix while matching, + we already know that it's the same and looking at it would only + skew the results. */ + propkeys = apr_palloc(scratch_pool, + numprops * sizeof(struct simprop_t*)); + propbuf = apr_palloc(scratch_pool, + numprops * sizeof(struct simprop_t)); + context.name.data += prefix.len; + context.name.len -= prefix.len; + for (i = 0; i < numprops; ++i) + { + propkeys[i] = &propbuf[i]; + propbuf[i].propname = proplist[i]; + propbuf[i].name.data = proplist[i] + prefix.len; + propbuf[i].name.len = strlen(propbuf[i].name.data); + propbuf[i].score = (unsigned int)-1; + propbuf[i].context = &context; + } + + qsort(propkeys, numprops, sizeof(*propkeys), simprop_compare); + + if (0 == propkeys[0]->diff) + return SVN_NO_ERROR; /* We found an exact match. */ + + /* See if we can suggest a sane alternative spelling */ + for (i = 0; i < numprops; ++i) + if (propkeys[i]->score < 666) /* 2/3 similarity required */ + break; + + switch (i) + { + case 0: + /* The best alternative isn't good enough */ + return svn_error_create( + SVN_ERR_CLIENT_PROPERTY_NAME, NULL, + wrong_prop_error_message(prop_use, propname, scratch_pool)); + + case 1: + /* There is only one good candidate */ + return svn_error_createf( + SVN_ERR_CLIENT_PROPERTY_NAME, NULL, + _("'%s' is not a valid %s property name; did you mean '%s'?\n%s"), + propname, SVN_PROP_PREFIX, propkeys[0]->propname, + force_prop_option_message(prop_use, propname, scratch_pool)); + + case 2: + /* Suggest a list of the most likely candidates */ + return svn_error_createf( + SVN_ERR_CLIENT_PROPERTY_NAME, NULL, + _("'%s' is not a valid %s property name\n" + "Did you mean '%s' or '%s'?\n%s"), + propname, SVN_PROP_PREFIX, + propkeys[0]->propname, propkeys[1]->propname, + force_prop_option_message(prop_use, propname, scratch_pool)); + + default: + /* Never suggest more than three candidates */ + return svn_error_createf( + SVN_ERR_CLIENT_PROPERTY_NAME, NULL, + _("'%s' is not a valid %s property name\n" + "Did you mean '%s', '%s' or '%s'?\n%s"), + propname, SVN_PROP_PREFIX, + propkeys[0]->propname, propkeys[1]->propname, propkeys[2]->propname, + force_prop_option_message(prop_use, propname, scratch_pool)); + } +} diff --git a/subversion/svn/propset-cmd.c b/subversion/svn/propset-cmd.c new file mode 100644 index 0000000..07b9bbd --- /dev/null +++ b/subversion/svn/propset-cmd.c @@ -0,0 +1,191 @@ +/* + * 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); + if (!opt_state->force) + SVN_ERR(svn_cl__check_svn_prop_name(pname_utf8, opt_state->revprop, + svn_cl__prop_use_set, scratch_pool)); + + /* 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_cl__propset_print_binary_mime_type_warning(targets, + pname_utf8, + propval, + scratch_pool)); + + 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..fe50f66 --- /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, *to, *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..ce4818e --- /dev/null +++ b/subversion/svn/resolve-cmd.c @@ -0,0 +1,131 @@ +/* + * 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. ***/ +#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; + svn_boolean_t had_error = FALSE; + + 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: + if (opt_state->non_interactive) + return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, + _("missing --accept option")); + conflict_choice = svn_wc_conflict_choose_unspecified; + break; + 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) + svn_opt_push_implicit_dot_target(targets, scratch_pool); + + if (opt_state->depth == svn_depth_unknown) + { + if (opt_state->accept_which == svn_cl__accept_unspecified) + opt_state->depth = svn_depth_infinity; + else + 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); + had_error = TRUE; + } + } + svn_pool_destroy(iterpool); + + if (had_error) + return svn_error_create(SVN_ERR_CL_ERROR_PROCESSING_EXTERNALS, NULL, + _("Failure occurred resolving one or more " + "conflicts")); + + return SVN_NO_ERROR; +} diff --git a/subversion/svn/resolved-cmd.c b/subversion/svn/resolved-cmd.c new file mode 100644 index 0000000..51e2da17 --- /dev/null +++ b/subversion/svn/resolved-cmd.c @@ -0,0 +1,88 @@ +/* + * 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. ***/ +#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..ab89b81 --- /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 consistency with 'svn status'. + 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..3dc43f6 --- /dev/null +++ b/subversion/svn/schema/info.rnc @@ -0,0 +1,134 @@ +# 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?, relative-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 } + +## Repository relative URL (^/...) of this item in the repository. +relative-url = element relative-url { string } + +## 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?, + moved-from?, + moved-to? + } + +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 } + +moved-from = element moved-from { string } + +moved-to = element moved-to { string } + +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" | "replace" }, + ## Local reason for the conflict. + attribute reason { "edit" | "obstruction" | "delete" | "add" | + "missing" | "unversioned" | "replace" | + "moved-away" | "moved-here" } 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..73d0ca0 --- /dev/null +++ b/subversion/svn/schema/status.rnc @@ -0,0 +1,92 @@ +# 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" }?, + ## If root of a move-here, the local path to the move source. + attribute moved-from { text }?, + ## If root of a move-away, the local path to the move destination. + attribute moved-to { text }? + +## 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..0a73dac --- /dev/null +++ b/subversion/svn/status-cmd.c @@ -0,0 +1,416 @@ +/* + * 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_hash.h" +#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. */ + const char *cwd_abspath; + svn_boolean_t suppress_externals_placeholders; + 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_empty(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_empty(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(sb->cwd_abspath, path, status, + sb->ctx, pool); + else + return svn_cl__print_status(sb->cwd_abspath, path, status, + sb->suppress_externals_placeholders, + 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 = + svn_hash_gets(sb->cached_changelists, cl_key); + if (path_array == NULL) + { + path_array = apr_array_make(sb->cl_pool, 1, + sizeof(struct status_cache *)); + svn_hash_sets(sb->cached_changelists, cl_key, 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")); + } + + SVN_ERR(svn_dirent_get_absolute(&(sb.cwd_abspath), "", scratch_pool)); + sb.suppress_externals_placeholders = (opt_state->quiet + && (! opt_state->verbose)); + 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_ERR_WC_PATH_NOT_FOUND)); + + 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_empty(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_setempty(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_setempty(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..3679bff --- /dev/null +++ b/subversion/svn/status.c @@ -0,0 +1,607 @@ +/* + * 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_hash.h" +#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 "cl-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(); + } +} + +/* Make a relative path containing '..' elements as needed. + RELATIVE_TO_PATH must be the path to a directory (not a file!) and + TARGET_PATH must be the path to any file or directory. Both + RELATIVE_TO_PATH and TARGET_PATH must be based on the same parent path, + i.e. they can either both be absolute or they can both be relative to the + same parent directory. Both paths are expected to be canonical. + + If above conditions are met, a relative path that leads to TARGET_ABSPATH + from RELATIVE_TO_PATH is returned, but there is no error checking involved. + + The returned path is allocated from RESULT_POOL, all other allocations are + made in SCRATCH_POOL. */ +static const char * +make_relpath(const char *relative_to_path, + const char *target_path, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + const char *la; + const char *parent_dir_els = ""; + + /* An example: + * relative_to_path = /a/b/c + * target_path = /a/x/y/z + * result = ../../x/y/z + * + * Another example (Windows specific): + * relative_to_path = F:/wc + * target_path = C:/wc + * result = C:/wc + */ + + /* Skip the common ancestor of both paths, here '/a'. */ + la = svn_dirent_get_longest_ancestor(relative_to_path, target_path, + scratch_pool); + if (*la == '\0') + { + /* Nothing in common: E.g. C:/ vs F:/ on Windows */ + return apr_pstrdup(result_pool, target_path); + } + relative_to_path = svn_dirent_skip_ancestor(la, relative_to_path); + target_path = svn_dirent_skip_ancestor(la, target_path); + + /* In above example, we'd now have: + * relative_to_path = b/c + * target_path = x/y/z */ + + /* Count the elements of relative_to_path and prepend as many '..' elements + * to target_path. */ + while (*relative_to_path) + { + svn_dirent_split(&relative_to_path, NULL, relative_to_path, + scratch_pool); + parent_dir_els = svn_dirent_join(parent_dir_els, "..", scratch_pool); + } + + return svn_dirent_join(parent_dir_els, target_path, result_pool); +} + + +/* Print STATUS and PATH in a format determined by DETAILED and + SHOW_LAST_COMMITTED. */ +static svn_error_t * +print_status(const char *cwd_abspath, 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 = ""; + const char *moved_from_line = ""; + const char *moved_to_line = ""; + + path = make_relpath(cwd_abspath, path, pool, pool); + + /* 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)++; + } + + /* Note that moved-from and moved-to information is only available in STATUS + * for (op-)roots of a move. Those are exactly the nodes we want to show + * move info for in 'svn status'. See also comments in svn_wc_status3_t. */ + if (status->moved_from_abspath && status->moved_to_abspath && + strcmp(status->moved_from_abspath, status->moved_to_abspath) == 0) + { + const char *relpath; + + relpath = make_relpath(cwd_abspath, status->moved_from_abspath, + pool, pool); + relpath = svn_dirent_local_style(relpath, pool); + moved_from_line = apr_pstrcat(pool, "\n > ", + apr_psprintf(pool, + _("swapped places with %s"), + relpath), + (char *)NULL); + } + else if (status->moved_from_abspath || status->moved_to_abspath) + { + const char *relpath; + + if (status->moved_from_abspath) + { + relpath = make_relpath(cwd_abspath, status->moved_from_abspath, + pool, pool); + relpath = svn_dirent_local_style(relpath, pool); + moved_from_line = apr_pstrcat(pool, "\n > ", + apr_psprintf(pool, _("moved from %s"), + relpath), + (char *)NULL); + } + + if (status->moved_to_abspath) + { + relpath = make_relpath(cwd_abspath, status->moved_to_abspath, + pool, pool); + relpath = svn_dirent_local_style(relpath, pool); + moved_to_line = apr_pstrcat(pool, "\n > ", + apr_psprintf(pool, _("moved to %s"), + relpath), + (char *)NULL); + } + } + + 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 %8s %8s %-12s %s%s%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, + moved_to_line, + moved_from_line, + tree_desc_line)); + } + else + SVN_ERR( + svn_cmdline_printf(pool, "%c%c%c%c%c%c%c %c %8s %s%s%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, + moved_to_line, + moved_from_line, + tree_desc_line)); + } + else + SVN_ERR( + svn_cmdline_printf(pool, "%c%c%c%c%c%c%c %s%s%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, + moved_to_line, + moved_from_line, + tree_desc_line)); + + return svn_cmdline_fflush(stdout); +} + + +svn_error_t * +svn_cl__print_status_xml(const char *cwd_abspath, + 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_empty(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)); + + path = make_relpath(cwd_abspath, path, pool, 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); + svn_hash_sets(att_hash, "item", + generate_status_desc(combined_status(status))); + + svn_hash_sets(att_hash, "props", + generate_status_desc( + (status->node_status != svn_wc_status_deleted) + ? status->prop_status + : svn_wc_status_none)); + if (status->wc_is_locked) + svn_hash_sets(att_hash, "wc-locked", "true"); + if (status->copied) + svn_hash_sets(att_hash, "copied", "true"); + if (status->switched) + svn_hash_sets(att_hash, "switched", "true"); + if (status->file_external) + svn_hash_sets(att_hash, "file-external", "true"); + if (status->versioned && ! status->copied) + svn_hash_sets(att_hash, "revision", + apr_psprintf(pool, "%ld", status->revision)); + if (tree_conflicted) + svn_hash_sets(att_hash, "tree-conflicted", "true"); + if (status->moved_from_abspath || status->moved_to_abspath) + { + const char *relpath; + + if (status->moved_from_abspath) + { + relpath = make_relpath(cwd_abspath, status->moved_from_abspath, + pool, pool); + relpath = svn_dirent_local_style(relpath, pool); + svn_hash_sets(att_hash, "moved-from", relpath); + } + if (status->moved_to_abspath) + { + relpath = make_relpath(cwd_abspath, status->moved_to_abspath, + pool, pool); + relpath = svn_dirent_local_style(relpath, pool); + svn_hash_sets(att_hash, "moved-to", relpath); + } + } + 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 *cwd_abspath, + const char *path, + const svn_client_status_t *status, + svn_boolean_t suppress_externals_placeholders, + 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; + + /* If we're trying not to print boring "X /path/to/external" + lines..." */ + if (suppress_externals_placeholders) + { + /* ... skip regular externals unmodified in the repository. */ + if ((status->node_status == svn_wc_status_external) + && (status->repos_node_status == svn_wc_status_none) + && (! status->conflicted)) + return SVN_NO_ERROR; + + /* ... skip file externals that aren't modified locally or + remotely, changelisted, or locked (in either sense of the + word). */ + if ((status->file_external) + && (status->repos_node_status == svn_wc_status_none) + && ((status->node_status == svn_wc_status_normal) + || (status->node_status == svn_wc_status_none)) + && ((status->prop_status == svn_wc_status_normal) + || (status->prop_status == svn_wc_status_none)) + && (! status->changelist) + && (! status->lock) + && (! status->wc_is_locked) + && (! status->conflicted)) + return SVN_NO_ERROR; + } + + return print_status(cwd_abspath, 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/svn.c b/subversion/svn/svn.c new file mode 100644 index 0000000..cbcec87 --- /dev/null +++ b/subversion/svn/svn.c @@ -0,0 +1,2961 @@ +/* + * svn.c: Subversion command line client main 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 <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_opt_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_auth_password = SVN_OPT_FIRST_LONGOPT_ID, + opt_auth_username, + opt_autoprops, + opt_changelist, + opt_config_dir, + opt_config_options, + /* diff options */ + opt_diff_cmd, + opt_internal_diff, + opt_no_diff_added, + opt_no_diff_deleted, + opt_show_copies_as_adds, + opt_notice_ancestry, + opt_summarize, + opt_use_git_diff_format, + opt_ignore_properties, + opt_properties_only, + opt_patch_compatible, + /* end of diff options */ + 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_ignore, + opt_no_unlock, + opt_non_interactive, + opt_force_interactive, + opt_old_cmd, + opt_record_only, + opt_relocate, + opt_remove, + opt_revprop, + opt_stop_on_copy, + opt_strict, + 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_ignore_keywords, + opt_reverse_diff, + opt_ignore_whitespace, + opt_diff, + opt_allow_mixed_revisions, + opt_include_externals, + opt_show_inherited_props, + opt_search, + opt_search_and +} 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\n" + " " + "If ARG is of the form ARG1-ARG2 then this is like\n" + " " + "ARG1:ARG2, where ARG1 is inclusive")}, + {"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_("Specify differencing options for external diff or\n" + " " + "internal diff or blame. Default: '-u'. Options are\n" + " " + "separated by spaces. Internal diff and blame take:\n" + " " + " -u, --unified: Show 3 lines of unified context\n" + " " + " -b, --ignore-space-change: Ignore changes in\n" + " " + " amount of white space\n" + " " + " -w, --ignore-all-space: Ignore all white space\n" + " " + " --ignore-eol-style: Ignore changes in EOL style\n" + " " + " -p, --show-c-function: Show C function name")}, + {"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 and\n" + " " + "svn:global-ignores 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 (default is to prompt\n" + " " + "only if standard input is a terminal device)")}, + {"force-interactive", opt_force_interactive, 0, + N_("do interactive prompting even if standard input\n" + " " + "is not a terminal device")}, + {"dry-run", opt_dry_run, 0, + N_("try operation but make no changes")}, + {"ignore-ancestry", opt_ignore_ancestry, 0, + N_("disable merge tracking; diff nodes as if related")}, + {"ignore-externals", opt_ignore_externals, 0, + N_("ignore externals definitions")}, + {"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")}, + {"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_("deprecated")}, + {"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.")}, + {"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 */ + /* diff options */ + {"diff-cmd", opt_diff_cmd, 1, N_("use ARG as diff command")}, + {"internal-diff", opt_internal_diff, 0, + N_("override diff-cmd specified in config file")}, + {"no-diff-added", opt_no_diff_added, 0, + N_("do not print differences for added files")}, + {"no-diff-deleted", opt_no_diff_deleted, 0, + N_("do not print differences for deleted files")}, + {"show-copies-as-adds", opt_show_copies_as_adds, 0, + N_("don't diff copied or moved files with their source")}, + {"notice-ancestry", opt_notice_ancestry, 0, + N_("diff unrelated nodes as delete and add")}, + {"summarize", opt_summarize, 0, N_("show a summary of the results")}, + {"git", opt_use_git_diff_format, 0, + N_("use git's extended diff format")}, + {"ignore-properties", opt_ignore_properties, 0, + N_("ignore properties during the operation")}, + {"properties-only", opt_properties_only, 0, + N_("show only properties during the operation")}, + {"patch-compatible", opt_patch_compatible, 0, + N_("generate diff suitable for generic third-party\n" + " " + "patch tools; currently the same as\n" + " " + "--show-copies-as-adds --ignore-properties" + )}, + /* end of diff options */ + {"allow-mixed-revisions", opt_allow_mixed_revisions, 0, + N_("Allow operation on mixed-revision working copy.\n" + " " + "Use of this option is not recommended!\n" + " " + "Please run 'svn update' instead.")}, + {"include-externals", opt_include_externals, 0, + N_("Also commit file and dir externals reached by\n" + " " + "recursion. This does not include externals with a\n" + " " + "fixed revision. (See the svn:externals property)")}, + {"show-inherited-props", opt_show_inherited_props, 0, + N_("retrieve target's inherited properties")}, + {"search", opt_search, 1, + N_("use ARG as search pattern (glob syntax)")}, + {"search-and", opt_search_and, 1, + N_("combine ARG with the previous search pattern")}, + + /* 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 a 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_force_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, opt_include_externals} }, + + { "copy", svn_cl__copy, {"cp"}, N_ + ("Copy files and directories in a working copy or repository.\n" + "usage: copy SRC[@REV]... DST\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. When copying multiple sources,\n" + " they will be added as children of DST, which must be a directory.\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 local changes or differences between two revisions or paths.\n" + "usage: 1. diff\n" + " 2. diff [-c M | -r N[:M]] [TARGET[@REV]...]\n" + " 3. diff [-r N[:M]] --old=OLD-TGT[@OLDREV] [--new=NEW-TGT[@NEWREV]] \\\n" + " [PATH...]\n" + " 4. diff OLD-URL[@OLDREV] NEW-URL[@NEWREV]\n" + " 5. diff OLD-URL[@OLDREV] NEW-PATH[@NEWREV]\n" + " 6. diff OLD-PATH[@OLDREV] NEW-URL[@NEWREV]\n" + "\n" + " 1. Use just 'svn diff' to display local modifications in a working copy.\n" + "\n" + " 2. 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" + " 3. 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" + " If OLDREV or NEWREV are not specified, they default to WORKING for\n" + " working copy targets and to HEAD for URL targets.\n" + "\n" + " Either or both OLD-TGT and NEW-TGT may also be paths to unversioned\n" + " targets. Revisions cannot be specified for unversioned targets.\n" + " Both targets must be of the same node kind (file or directory).\n" + " Diffing unversioned targets against URL targets is not supported.\n" + "\n" + " 4. Shorthand for 'svn diff --old=OLD-URL[@OLDREV] --new=NEW-URL[@NEWREV]'\n" + " 5. Shorthand for 'svn diff --old=OLD-URL[@OLDREV] --new=NEW-PATH[@NEWREV]'\n" + " 6. Shorthand for 'svn diff --old=OLD-PATH[@OLDREV] --new=NEW-URL[@NEWREV]'\n"), + {'r', 'c', opt_old_cmd, opt_new_cmd, 'N', opt_depth, opt_diff_cmd, + opt_internal_diff, 'x', opt_no_diff_added, opt_no_diff_deleted, + opt_ignore_properties, opt_properties_only, + opt_show_copies_as_adds, opt_notice_ancestry, opt_summarize, opt_changelist, + opt_force, opt_xml, opt_use_git_diff_format, opt_patch_compatible} }, + { "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, + opt_include_externals }, + {{opt_include_externals, N_("include externals definitions")}} }, + + { "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" + " If the --search option is used, log messages are displayed only if the\n" + " provided search pattern matches any of the author, date, log message\n" + " text (unless --quiet is used), or, if the --verbose option is also\n" + " provided, a changed path.\n" + " The search pattern may include \"glob syntax\" wildcards:\n" + " ? matches any single character\n" + " * matches a sequence of arbitrary characters\n" + " [abc] matches any of the characters listed inside the brackets\n" + " If multiple --search options are provided, a log message is shown if\n" + " it matches any of the provided search patterns. If the --search-and\n" + " option is used, that option's argument is combined with the pattern\n" + " from the previous --search or --search-and option, and a log message\n" + " is shown only if it matches the combined search pattern.\n" + " If --limit is used in combination with --search, --limit restricts the\n" + " number of log messages searched, rather than restricting the output\n" + " to a particular number of matching log messages.\n" + "\n" + " Examples:\n" + "\n" + " Show the latest 5 log messages for the current working copy\n" + " directory and display paths changed in each commit:\n" + " svn log -l 5 -v\n" + "\n" + " Show the log for bar.c as of revision 42:\n" + " svn log bar.c@42\n" + "\n" + " Show log messages and diffs for each commit to foo.c:\n" + " svn log --diff http://www.example.com/repo/project/foo.c\n" + " (Because the above command uses a full URL it does not require\n" + " a working copy.)\n" + "\n" + " Show log messages for the children foo.c and bar.c of the directory\n" + " '/trunk' as it appeared in revision 50, using the ^/ URL shortcut:\n" + " svn log ^/trunk@50 foo.c bar.c\n" + "\n" + " Show the log messages for any incoming changes to foo.c during the\n" + " next 'svn update':\n" + " svn log -r BASE:HEAD foo.c\n" + "\n" + " Show the log message for the revision in which /branches/foo\n" + " was created:\n" + " svn log --stop-on-copy --limit 1 -r0:HEAD ^/branches/foo\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_search, + opt_search_and, }, + {{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 'automatic' merge)\n" +" 2. merge [-c M[,N...] | -r N:M ...] SOURCE[@REV] [TARGET_WCPATH]\n" +" (the 'cherry-pick' merge)\n" +" 3. merge SOURCE1[@REV1] SOURCE2[@REV2] [TARGET_WCPATH]\n" +" (the '2-URL' merge)\n" +"\n" +" 1. This form, with one source path and no revision range, is called\n" +" an 'automatic' merge:\n" +"\n" +" svn merge SOURCE[@REV] [TARGET_WCPATH]\n" +"\n" +" The automatic merge is used for the 'sync' and 'reintegrate' merges\n" +" in the 'feature branch' pattern described below. It finds all the\n" +" changes on the source branch that have not already been merged to the\n" +" target branch, and merges them into the working copy. Merge tracking\n" +" is used to know which changes have already been merged.\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 generally\n" +" assumed. There are some special cases:\n" +"\n" +" - If SOURCE is a URL:\n" +"\n" +" - If the basename of the URL and the basename of '.' are the\n" +" same, then the differences are applied to '.'. Otherwise,\n" +" if a file with the same basename as that of the URL is found\n" +" within '.', then the differences are applied to that file.\n" +" In all other cases, the target defaults to '.'.\n" +"\n" +" - If SOURCE is a working copy path:\n" +"\n" +" - If the source is a file, then differences are applied to that\n" +" file (useful for reverse-merging earlier changes). Otherwise,\n" +" if the source is a directory, then the target defaults to '.'.\n" +"\n" +" In normal usage the working copy should be up to date, at a single\n" +" revision, with no local modifications and no switched subtrees.\n" +"\n" +" - The 'Feature Branch' Merging Pattern -\n" +"\n" +" In this commonly used work flow, known also as the 'development\n" +" branch' pattern, a developer creates a branch and commits a series of\n" +" changes that implement a new feature. The developer periodically\n" +" merges all the latest changes from the parent branch so as to keep the\n" +" development branch up to date with those changes. When the feature is\n" +" complete, the developer performs a merge from the feature branch to\n" +" the parent branch to re-integrate the changes.\n" +"\n" +" parent --+----------o------o-o-------------o--\n" +" \\ \\ \\ /\n" +" \\ merge merge merge\n" +" \\ \\ \\ /\n" +" feature +--o-o-------o----o-o----o-------\n" +"\n" +" A merge from the parent branch to the feature branch is called a\n" +" 'sync' or 'catch-up' merge, and a merge from the feature branch to the\n" +" parent branch is called a 'reintegrate' merge.\n" +"\n" +" - Sync Merge Example -\n" +" ............\n" +" . .\n" +" trunk --+------------L--------------R------\n" +" \\ \\\n" +" \\ |\n" +" \\ v\n" +" feature +------------------------o-----\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 (trunk@100)\n" +" and R marks the right side (trunk@200) of the merge source. 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" +" - Reintegrate Merge Example -\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" +" rW rX\n" +" trunk ------+--------------------L------------------o\n" +" \\ . ^\n" +" \\ ............. /\n" +" \\ . /\n" +" feature +--------------------------------R\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 ^/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" +"\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 generally\n" +" assumed. The special cases noted above in the 'automatic' merge form\n" +" also apply here.\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>'. Undoing changes like this is also known\n" +" as performing a 'reverse merge'.\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 '2-URL merge':\n" +"\n" +" svn merge SOURCE1[@REV1] SOURCE2[@REV2] [TARGET_WCPATH]\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" +" Two source URLs are specified, identifying two trees on the same\n" +" branch or on different branches. The trees are compared and the\n" +" difference from SOURCE1@REV1 to SOURCE2@REV2 is applied to the\n" +" working copy of the target branch at TARGET_WCPATH. The target\n" +" branch may be the same as one or both sources, or different again.\n" +" The three branches involved can be completely unrelated.\n" +"\n" +" TARGET_WCPATH is a working copy path; if omitted, '.' is generally\n" +" assumed. The special cases noted above in the 'automatic' merge form\n" +" also apply here.\n" +"\n" +" SOURCE1 and/or SOURCE2 can also be specified as a working copy path,\n" +" in which case the merge source URL is derived from the working copy.\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, 'v'} }, + + { "mergeinfo", svn_cl__mergeinfo, {0}, N_ + ("Display merge-related information.\n" + "usage: 1. mergeinfo SOURCE[@REV] [TARGET[@REV]]\n" + " 2. mergeinfo --show-revs=WHICH SOURCE[@REV] [TARGET[@REV]]\n" + "\n" + " 1. Summarize the history of merging between SOURCE and TARGET. The graph\n" + " shows, from left to right:\n" + " the youngest common ancestor of the branches;\n" + " the latest full merge in either direction, and thus the common base\n" + " that will be used for the next automatic merge;\n" + " the repository path and revision number of the tip of each branch.\n" + "\n" + " 2. Print the revision numbers on SOURCE that have been merged to TARGET\n" + " (with --show-revs=merged), or that have not been merged to TARGET\n" + " (with --show-revs=eligible). Print only revisions in which there was\n" + " at least one change in SOURCE.\n" + "\n" + " If --revision (-r) is provided, filter the displayed information to\n" + " show only that which is associated with the revisions within the\n" + " specified range. Revision numbers, dates, and the 'HEAD' keyword are\n" + " valid range values.\n" + "\n" + " SOURCE and TARGET are the source and target branch URLs, respectively.\n" + " (If a WC path is given, the corresponding base URL is used.) The default\n" + " TARGET is the current working directory ('.'). REV specifies the revision\n" + " to be considered the tip of the branch; the default for SOURCE is HEAD,\n" + " and the default for TARGET is HEAD for a URL or BASE for a WC path.\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 (rename) an item in a working copy or repository.\n" + "usage: move SRC... DST\n" + "\n" + " SRC and DST can both be working copy (WC) paths or URLs:\n" + " WC -> WC: move an item in a working copy, as a local change to\n" + " be committed later (with or without further changes)\n" + " URL -> URL: move an item in the repository directly, immediately\n" + " creating a new revision in the repository\n" + " All the SRCs must be of the same type. When moving multiple sources,\n" + " they will be added as children of DST, which must be a directory.\n" + "\n" + " SRC and DST of WC -> WC moves must be committed in the same revision.\n" + " Furthermore, WC -> WC moves will refuse to move a mixed-revision subtree.\n" + " To avoid unnecessary conflicts, it is recommended to run 'svn update'\n" + " to update the subtree to a single revision before moving it.\n" + " The --allow-mixed-revisions option is provided for backward compatibility.\n" + "\n" + " The --revision option has no use and is deprecated.\n"), + {'r', 'q', opt_force, opt_parents, opt_allow_mixed_revisions, + 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, except for Subversion\n" + " property diffs as produced by 'svn diff'.\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" + " With --verbose, the target path and the property name are printed on\n" + " separate lines before each value, like 'svn proplist --verbose'.\n" + " Otherwise, if there is more than one TARGET or a depth other than\n" + " 'empty', the target path is printed on the same line before each value.\n" + "\n" + " By default, an extra newline is printed after the property value so that\n" + " the output looks pretty. With a single TARGET and depth 'empty', you can\n" + " use the --strict option to disable this (useful when redirecting a binary\n" + " property value to a file, for example).\n"), + {'v', 'R', opt_depth, 'r', opt_revprop, opt_strict, opt_xml, + opt_changelist, opt_show_inherited_props }, + {{'v', N_("print path, name and value on separate lines")}, + {opt_strict, N_("don't print an extra newline")}} }, + + { "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" + "\n" + " With --verbose, the property values are printed as well, like 'svn propget\n" + " --verbose'. With --quiet, the paths are not printed.\n"), + {'v', 'R', opt_depth, 'r', 'q', opt_revprop, opt_xml, opt_changelist, + opt_show_inherited_props }, + {{'v', N_("print path, name and value on separate lines")}, + {'q', N_("don't print the path")}} }, + + { "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" + " Property names starting with 'svn:' are reserved. Subversion recognizes\n" + " the following special versioned properties on a file:\n" + " svn:keywords - Keywords to be expanded. Valid keywords are:\n" + " URL, HeadURL - The URL for the head version of the file.\n" + " Author, LastChangedBy - The last person to modify the file.\n" + " Date, LastChangedDate - The date/time the file was last modified.\n" + " Rev, Revision, - The last revision the file changed.\n" + " LastChangedRevision\n" + " Id - A compressed summary of the previous four.\n" + " Header - Similar to Id but includes the full URL.\n" + "\n" + " Custom keywords can be defined with a format string separated from\n" + " the keyword name with '='. Valid format substitutions are:\n" + " %a - The author of the revision given by %r.\n" + " %b - The basename of the URL of the file.\n" + " %d - Short format of the date of the revision given by %r.\n" + " %D - Long format of the date of the revision given by %r.\n" + " %P - The file's path, relative to the repository root.\n" + " %r - The number of the revision which last changed the file.\n" + " %R - The URL to the root of the repository.\n" + " %u - The URL of the file.\n" + " %_ - A space (keyword definitions cannot contain a literal space).\n" + " %% - A literal '%'.\n" + " %H - Equivalent to %P%_%r%_%d%_%a.\n" + " %I - Equivalent to %b%_%r%_%d%_%a.\n" + " Example custom keyword definition: MyKeyword=%r%_%a%_%P\n" + " Once a custom keyword has been defined for a file, it can be used\n" + " within the file like any other keyword: $MyKeyword$\n" + "\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: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" + " Subversion recognizes the following special versioned properties on a\n" + " directory:\n" + " svn:ignore - A list of file glob patterns to ignore, one per line.\n" + " svn:global-ignores - Like svn:ignore, but inheritable.\n" + " svn:externals - A list of module specifiers, one per line, in the\n" + " following format similar to the syntax of 'svn checkout':\n" + " [-r REV] URL[@PEG] LOCALPATH\n" + " Example:\n" + " http://example.com/repos/zig foo/bar\n" + " The LOCALPATH is relative to the directory having this property.\n" + " To pin the external to a known revision, specify the optional REV:\n" + " -r25 http://example.com/repos/zig foo/bar\n" + " To unambiguously identify an element at a path which may have been\n" + " subsequently deleted or renamed, specify the optional PEG revision:\n" + " -r25 http://example.com/repos/zig@42 foo/bar\n" + " The URL may be a full URL or a relative URL starting with one of:\n" + " ../ to the parent directory of the extracted external\n" + " ^/ to the repository root\n" + " / to the server root\n" + " // to the URL scheme\n" + " Use of the following format is discouraged but is supported for\n" + " interoperability with Subversion 1.4 and earlier clients:\n" + " LOCALPATH [-r PEG] URL\n" + " The ambiguous format 'relative_path relative_path' is taken as\n" + " 'relative_url relative_path' with peg revision support.\n" + " Lines starting with a '#' character are ignored.\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 [PATH...]\n" + "\n" + " By default, perform interactive conflict resolution on PATH.\n" + " In this mode, the command is recursive by default (depth 'infinity').\n" + "\n" + " The --accept=ARG option prevents interactive prompting and forces\n" + " conflicts on PATH to be resolved in the manner specified by ARG.\n" + " In this mode, the command is not recursive by default (depth 'empty').\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 state (undo local changes).\n" + "usage: revert PATH...\n" + "\n" + " Revert changes in the working copy at or within PATH, and remove\n" + " conflict markers as well, if any.\n" + "\n" + " This subcommand does not revert already committed changes.\n" + " For information about undoing already committed changes, search\n" + " the output of 'svn help merge' for 'undo'.\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}, + {{opt_ignore_ancestry, + N_("allow switching to a node with no common ancestor")}} + }, + + { "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; +} + +/* Add a --search argument to OPT_STATE. + * These options start a new search pattern group. */ +static void +add_search_pattern_group(svn_cl__opt_state_t *opt_state, + const char *pattern, + apr_pool_t *result_pool) +{ + apr_array_header_t *group = NULL; + + if (opt_state->search_patterns == NULL) + opt_state->search_patterns = apr_array_make(result_pool, 1, + sizeof(apr_array_header_t *)); + + group = apr_array_make(result_pool, 1, sizeof(const char *)); + APR_ARRAY_PUSH(group, const char *) = pattern; + APR_ARRAY_PUSH(opt_state->search_patterns, apr_array_header_t *) = group; +} + +/* Add a --search-and argument to OPT_STATE. + * These patterns are added to an existing pattern group, if any. */ +static void +add_search_pattern_to_latest_group(svn_cl__opt_state_t *opt_state, + const char *pattern, + apr_pool_t *result_pool) +{ + apr_array_header_t *group; + + if (opt_state->search_patterns == NULL) + { + add_search_pattern_group(opt_state, pattern, result_pool); + return; + } + + group = APR_ARRAY_IDX(opt_state->search_patterns, + opt_state->search_patterns->nelts - 1, + apr_array_header_t *); + APR_ARRAY_PUSH(group, const char *) = pattern; +} + + +/*** Main. ***/ + +/* Report and clear the error ERR, and return EXIT_FAILURE. Suppress the + * error message if it is SVN_ERR_IO_PIPE_WRITE_ERROR. */ +#define EXIT_ERROR(err) \ + svn_cmdline_handle_exit_error(err, NULL, "svn: ") + +/* A redefinition of the public SVN_INT_ERR macro, that suppresses the + * error message if it is SVN_ERR_IO_PIPE_WRITE_ERROR. */ +#undef SVN_INT_ERR +#define SVN_INT_ERR(expr) \ + do { \ + svn_error_t *svn_err__temp = (expr); \ + if (svn_err__temp) \ + return EXIT_ERROR(svn_err__temp); \ + } while (0) + +static int +sub_main(int argc, const char *argv[], apr_pool_t *pool) +{ + svn_error_t *err; + 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; + svn_boolean_t force_interactive = FALSE; + svn_cl__conflict_stats_t *conflict_stats + = svn_cl__conflict_stats_create(pool); + svn_boolean_t use_notifier = TRUE; + svn_boolean_t reading_file_from_stdin = FALSE; + apr_hash_t *changelists; + apr_hash_t *cfg_hash; + + received_opts = apr_array_make(pool, SVN_OPT_MAX_OPTIONS, sizeof(int)); + + /* Check library versions */ + SVN_INT_ERR(check_lib_versions()); + +#if defined(WIN32) || defined(__CYGWIN__) + /* Set the working copy administrative directory name. */ + if (getenv("SVN_ASP_DOT_NET_HACK")) + { + SVN_INT_ERR(svn_wc_set_adm_dir("_svn", pool)); + } +#endif + + /* Initialize the RA library. */ + SVN_INT_ERR(svn_ra_initialize(pool)); + + /* 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_invalid; + + /* No args? Show usage. */ + if (argc <= 1) + { + SVN_INT_ERR(svn_cl__help(NULL, NULL, pool)); + return EXIT_FAILURE; + } + + /* Else, parse options. */ + SVN_INT_ERR(svn_cmdline__getopt_init(&os, argc, argv, pool)); + + 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_INT_ERR(svn_cl__help(NULL, NULL, 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 EXIT_ERROR(err); + } + if (opt_state.limit <= 0) + { + err = svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL, + _("Argument to --limit must be positive")); + return EXIT_ERROR(err); + } + } + 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 EXIT_ERROR(err); + } + + for (i = 0; i < change_revs->nelts; i++) + { + char *end; + svn_revnum_t changeno, changeno_end; + 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 EXIT_ERROR(err); + } + 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 EXIT_ERROR(err); + } + + if (changeno == 0) + { + err = svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, + _("There is no change 0")); + return EXIT_ERROR(err); + } + + 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; + } + + opt_state.used_change_arg = TRUE; + APR_ARRAY_PUSH(opt_state.revision_ranges, + svn_opt_revision_range_t *) + = svn_opt__revision_range_from_revnums(changeno, changeno_end, + pool); + } + } + break; + case 'r': + opt_state.used_revision_arg = TRUE; + if (svn_opt_parse_revision_to_range(opt_state.revision_ranges, + opt_arg, pool) != 0) + { + SVN_INT_ERR(svn_utf_cstring_to_utf8(&utf8_opt_arg, opt_arg, pool)); + err = svn_error_createf + (SVN_ERR_CL_ARG_PARSING_ERROR, NULL, + _("Syntax error in revision argument '%s'"), + utf8_opt_arg); + return EXIT_ERROR(err); + } + 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': + SVN_INT_ERR(svn_utf_cstring_to_utf8(&utf8_opt_arg, opt_arg, pool)); + SVN_INT_ERR(svn_stringbuf_from_file2(&(opt_state.filedata), + utf8_opt_arg, pool)); + reading_file_from_stdin = (strcmp(utf8_opt_arg, "-") == 0); + 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(). */ + + SVN_INT_ERR(svn_utf_cstring_to_utf8(&utf8_opt_arg, opt_arg, pool)); + SVN_INT_ERR(svn_stringbuf_from_file2(&buffer, utf8_opt_arg, pool)); + SVN_INT_ERR(svn_utf_stringbuf_to_utf8(&buffer_utf8, buffer, pool)); + 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 EXIT_ERROR + (svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, err, + _("Error converting depth " + "from locale to UTF-8"))); + opt_state.depth = svn_depth_from_word(utf8_opt_arg); + if (opt_state.depth == svn_depth_unknown + || opt_state.depth == svn_depth_exclude) + { + return 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)); + } + break; + case opt_set_depth: + err = svn_utf_cstring_to_utf8(&utf8_opt_arg, opt_arg, pool); + if (err) + return EXIT_ERROR + (svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, err, + _("Error converting depth " + "from locale to UTF-8"))); + 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 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)); + } + break; + case opt_version: + opt_state.version = TRUE; + break; + case opt_auth_username: + SVN_INT_ERR(svn_utf_cstring_to_utf8(&opt_state.auth_username, + opt_arg, pool)); + break; + case opt_auth_password: + SVN_INT_ERR(svn_utf_cstring_to_utf8(&opt_state.auth_password, + opt_arg, pool)); + 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_force_interactive: + force_interactive = TRUE; + break; + case opt_trust_server_cert: + opt_state.trust_server_cert = TRUE; + break; + case opt_no_diff_added: + opt_state.diff.no_diff_added = TRUE; + break; + case opt_no_diff_deleted: + opt_state.diff.no_diff_deleted = TRUE; + break; + case opt_ignore_properties: + opt_state.diff.ignore_properties = TRUE; + break; + case opt_show_copies_as_adds: + opt_state.diff.show_copies_as_adds = TRUE; + break; + case opt_notice_ancestry: + opt_state.diff.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': + SVN_INT_ERR(svn_utf_cstring_to_utf8(&opt_state.extensions, + opt_arg, pool)); + break; + case opt_diff_cmd: + opt_state.diff.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 EXIT_ERROR(err); + } + 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; + SVN_INT_ERR(svn_utf_cstring_to_utf8(&path_utf8, opt_arg, pool)); + 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*)); + + SVN_INT_ERR(svn_utf_cstring_to_utf8(&opt_arg, opt_arg, pool)); + SVN_INT_ERR(svn_cmdline__parse_config_option(opt_state.config_options, + opt_arg, pool)); + 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 + { + SVN_INT_ERR(svn_utf_cstring_to_utf8(&utf8_opt_arg, opt_arg, pool)); + err = svn_error_createf + (SVN_ERR_CL_ARG_PARSING_ERROR, NULL, + _("Syntax error in native-eol argument '%s'"), + utf8_opt_arg); + return EXIT_ERROR(err); + } + break; + case opt_no_unlock: + opt_state.no_unlock = TRUE; + break; + case opt_summarize: + opt_state.diff.summarize = TRUE; + break; + case opt_remove: + opt_state.remove = TRUE; + break; + case opt_changelist: + SVN_INT_ERR(svn_utf_cstring_to_utf8(&utf8_opt_arg, opt_arg, pool)); + opt_state.changelist = utf8_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 EXIT_ERROR(err); + } + svn_hash_sets(changelists, opt_state.changelist, (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: + SVN_INT_ERR(svn_opt_parse_revprop(&opt_state.revprop_table, + opt_arg, pool)); + 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 EXIT_ERROR + (svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, + _("'%s' is not a valid --accept value"), + opt_arg)); + 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 EXIT_ERROR + (svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, + _("'%s' is not a valid --show-revs value"), + opt_arg)); + 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 EXIT_ERROR(err); + } + if (opt_state.strip < 0) + { + err = svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL, + _("Argument to --strip must be positive")); + return EXIT_ERROR(err); + } + } + 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.diff.internal_diff = TRUE; + break; + case opt_patch_compatible: + opt_state.diff.patch_compatible = TRUE; + break; + case opt_use_git_diff_format: + opt_state.diff.use_git_diff_format = TRUE; + break; + case opt_allow_mixed_revisions: + opt_state.allow_mixed_rev = TRUE; + break; + case opt_include_externals: + opt_state.include_externals = TRUE; + break; + case opt_show_inherited_props: + opt_state.show_inherited_props = TRUE; + break; + case opt_properties_only: + opt_state.diff.properties_only = TRUE; + break; + case opt_search: + add_search_pattern_group(&opt_state, opt_arg, pool); + break; + case opt_search_and: + add_search_pattern_to_latest_group(&opt_state, opt_arg, pool); + default: + /* Hmmm. Perhaps this would be a good place to squirrel away + opts that commands like svn diff might need. Hmmm indeed. */ + break; + } + } + + /* The --non-interactive and --force-interactive options are mutually + * exclusive. */ + if (opt_state.non_interactive && force_interactive) + { + err = svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, + _("--non-interactive and --force-interactive " + "are mutually exclusive")); + return EXIT_ERROR(err); + } + else + opt_state.non_interactive = !svn_cmdline__be_interactive( + opt_state.non_interactive, + force_interactive); + + /* Turn our hash of changelists into an array of unique ones. */ + SVN_INT_ERR(svn_hash_keys(&(opt_state.changelists), changelists, pool)); + + /* ### 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? */ + SVN_INT_ERR(svn_config_ensure(opt_state.config_dir, pool)); + + /* 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 */ + 'v', /* verbose 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_error_clear(svn_cl__help(NULL, NULL, 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; + SVN_INT_ERR(svn_utf_cstring_to_utf8(&first_arg_utf8, + first_arg, pool)); + svn_error_clear + (svn_cmdline_fprintf(stderr, pool, + _("Unknown subcommand: '%s'\n"), + first_arg_utf8)); + svn_error_clear(svn_cl__help(NULL, NULL, pool)); + + /* Be kind to people who try 'svn undo'. */ + if (strcmp(first_arg_utf8, "undo") == 0) + { + svn_error_clear + (svn_cmdline_fprintf(stderr, pool, + _("Undo is done using either the " + "'svn revert' or the 'svn merge' " + "command.\n"))); + } + + 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_error_clear(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)); + 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 EXIT_ERROR(err); + } + } + + /* 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 EXIT_ERROR(err); + } + + /* 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 EXIT_ERROR(err); + } + + /* 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 EXIT_ERROR(err); + } + + /* 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 EXIT_ERROR(err); + } + + /* --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 EXIT_ERROR(err); + } + + /* Disallow simultaneous use of both --diff-cmd and + --internal-diff. */ + if (opt_state.diff.diff_cmd && opt_state.diff.internal_diff) + { + err = svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, + _("--diff-cmd and --internal-diff " + "are mutually exclusive")); + return EXIT_ERROR(err); + } + + /* 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; + + err = svn_config_get_config(&cfg_hash, 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); + cfg_hash = NULL; + } + else + return EXIT_ERROR(err); + } + + /* 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 EXIT_ERROR(err); + } + if (! descend) + { + err = svn_error_create( + SVN_ERR_CL_MUTUALLY_EXCLUSIVE_ARGS, NULL, + _("--relocate and --non-recursive (-N) are mutually " + "exclusive")); + return EXIT_ERROR(err); + } + } + + /* 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__mergeinfo + && 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 EXIT_ERROR(err); + } + } + + /* -N has a different meaning depending on the command */ + if (!descend) + { + 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; + } + } + + cfg_config = svn_hash_gets(cfg_hash, SVN_CONFIG_CATEGORY_CONFIG); + + /* Update the options in the config */ + if (opt_state.config_options) + { + svn_error_clear( + svn_cmdline__apply_config_options(cfg_hash, + opt_state.config_options, + "svn: ", "--config-option")); + } + +#if !defined(SVN_CL_NO_EXCLUSIVE_LOCK) + { + const char *exclusive_clients_option; + apr_array_header_t *exclusive_clients; + + svn_config_get(cfg_config, &exclusive_clients_option, + SVN_CONFIG_SECTION_WORKING_COPY, + SVN_CONFIG_OPTION_SQLITE_EXCLUSIVE_CLIENTS, + NULL); + exclusive_clients = svn_cstring_split(exclusive_clients_option, + " ,", TRUE, pool); + for (i = 0; i < exclusive_clients->nelts; ++i) + { + const char *exclusive_client = APR_ARRAY_IDX(exclusive_clients, i, + const char *); + + /* This blocks other clients from accessing the wc.db so it must + be explicitly enabled.*/ + if (!strcmp(exclusive_client, "svn")) + svn_config_set(cfg_config, + SVN_CONFIG_SECTION_WORKING_COPY, + SVN_CONFIG_OPTION_SQLITE_EXCLUSIVE, + "true"); + } + } +#endif + + /* Create a client context object. */ + command_baton.opt_state = &opt_state; + SVN_INT_ERR(svn_client_create_context2(&ctx, cfg_hash, pool)); + 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). This may + access the working copy so do it after setting the locking mode. */ + 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_kind2(&kind, ctx->wc_ctx, local_abspath, TRUE, + 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 EXIT_ERROR(err); + } + } + 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 EXIT_ERROR(err); + } + } + } + + /* XXX: Only diff_cmd for now, overlay rest later and stop passing + opt_state altogether? */ + if (opt_state.diff.diff_cmd) + svn_config_set(cfg_config, SVN_CONFIG_SECTION_HELPERS, + SVN_CONFIG_OPTION_DIFF_CMD, opt_state.diff.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.diff.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 EXIT_ERROR(err); + } + + /* 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) + { + SVN_INT_ERR(svn_io_parse_mimetypes_file(&(ctx->mimetypes_map), + mimetypes_file, pool)); + } + + 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. + + In general, we use it any time we aren't in --quiet mode. 'svn + status' is unique, though, in that we don't want it in --quiet mode + unless we're also in --verbose mode. When in --xml mode, + though, we never want it. */ + if (opt_state.quiet) + use_notifier = FALSE; + if ((subcommand->cmd_func == svn_cl__status) && opt_state.verbose) + use_notifier = TRUE; + if (opt_state.xml) + use_notifier = FALSE; + if (use_notifier) + { + SVN_INT_ERR(svn_cl__get_notifier(&ctx->notify_func2, &ctx->notify_baton2, + conflict_stats, pool)); + } + + /* 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. */ + SVN_INT_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)); + + ctx->auth_baton = ab; + + if (opt_state.non_interactive) + { + if (opt_state.accept_which == svn_cl__accept_edit) + return EXIT_ERROR( + svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, + _("--accept=%s incompatible with" + " --non-interactive"), + SVN_CL__ACCEPT_EDIT)); + + if (opt_state.accept_which == svn_cl__accept_launch) + return EXIT_ERROR( + svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, + _("--accept=%s incompatible with" + " --non-interactive"), + SVN_CL__ACCEPT_LAUNCH)); + + /* The default action when we're non-interactive is to postpone + * conflict resolution. */ + if (opt_state.accept_which == svn_cl__accept_unspecified) + opt_state.accept_which = svn_cl__accept_postpone; + } + + /* Check whether interactive conflict resolution is disabled by + * the configuration file. If no --accept option was specified + * we postpone all conflicts in this case. */ + SVN_INT_ERR(svn_config_get_bool(cfg_config, &interactive_conflicts, + SVN_CONFIG_SECTION_MISCELLANY, + SVN_CONFIG_OPTION_INTERACTIVE_CONFLICTS, + TRUE)); + if (!interactive_conflicts) + { + /* Make 'svn resolve' non-interactive. */ + if (subcommand->cmd_func == svn_cl__resolve) + opt_state.non_interactive = TRUE; + + /* We're not resolving conflicts interactively. If no --accept option + * was provided the default behaviour is to postpone all conflicts. */ + if (opt_state.accept_which == svn_cl__accept_unspecified) + opt_state.accept_which = svn_cl__accept_postpone; + } + + /* Install the default conflict handler. */ + { + svn_cl__interactive_conflict_baton_t *b; + + ctx->conflict_func = NULL; + ctx->conflict_baton = NULL; + + ctx->conflict_func2 = svn_cl__conflict_func_interactive; + SVN_INT_ERR(svn_cl__get_conflict_func_interactive_baton( + &b, + opt_state.accept_which, + ctx->config, opt_state.editor_cmd, conflict_stats, + ctx->cancel_func, ctx->cancel_baton, pool)); + ctx->conflict_baton2 = b; + } + + /* 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, apr_psprintf(pool, + _("Try 'svn help %s' for more information"), + subcommand->name)); + } + if (err->apr_err == SVN_ERR_WC_UPGRADE_REQUIRED) + { + err = svn_error_quick_wrap(err, + _("Please see the 'svn upgrade' command")); + } + + if (err->apr_err == SVN_ERR_AUTHN_FAILED && opt_state.non_interactive) + { + err = svn_error_quick_wrap(err, + _("Authentication failed and interactive" + " prompting is disabled; see the" + " --force-interactive option")); + if (reading_file_from_stdin) + err = svn_error_quick_wrap(err, + _("Reading file from standard input " + "because of -F option; this can " + "interfere with interactive " + "prompting")); + } + + /* 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)) + { + err = svn_error_quick_wrap( + err, _("Run 'svn cleanup' to remove locks " + "(type 'svn help cleanup' for details)")); + } + + if (err->apr_err == SVN_ERR_SQLITE_BUSY) + { + err = svn_error_quick_wrap(err, + _("Another process is blocking the " + "working copy database, or the " + "underlying filesystem does not " + "support file locking; if the working " + "copy is on a network filesystem, make " + "sure file locking has been enabled " + "on the file server")); + } + + if (svn_error_find_cause(err, SVN_ERR_RA_CANNOT_CREATE_TUNNEL) && + (opt_state.auth_username || opt_state.auth_password)) + { + err = svn_error_quick_wrap( + err, _("When using svn+ssh:// URLs, keep in mind that the " + "--username and --password options are ignored " + "because authentication is performed by SSH, not " + "Subversion")); + } + + return EXIT_ERROR(err); + } + else + { + /* Ensure that stdout is flushed, so the user will see any write errors. + This makes sure that output is not silently lost. */ + SVN_INT_ERR(svn_cmdline_fflush(stdout)); + + return EXIT_SUCCESS; + } +} + +int +main(int argc, const char *argv[]) +{ + apr_pool_t *pool; + int exit_code; + + /* 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. + */ + pool = apr_allocator_owner_get(svn_pool_create_allocator(FALSE)); + + exit_code = sub_main(argc, argv, pool); + + svn_pool_destroy(pool); + return exit_code; +} diff --git a/subversion/svn/switch-cmd.c b/subversion/svn/switch-cmd.c new file mode 100644 index 0000000..aaef2b5 --- /dev/null +++ b/subversion/svn/switch-cmd.c @@ -0,0 +1,199 @@ +/* + * 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_NO_ERROR; + svn_error_t *externals_err = SVN_NO_ERROR; + 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)); + if (err->apr_err == SVN_ERR_RA_UUID_MISMATCH + || err->apr_err == SVN_ERR_WC_INVALID_SWITCH) + return svn_error_quick_wrap( + err, + _("'svn switch' does not support switching a working copy to " + "a different repository")); + return err; + } + + if (nwb.had_externals_error) + externals_err = svn_error_create(SVN_ERR_CL_ERROR_PROCESSING_EXTERNALS, + NULL, + _("Failure occurred processing one or " + "more externals definitions")); + + if (! opt_state->quiet) + { + err = svn_cl__notifier_print_conflict_stats(nwb.wrapped_baton, scratch_pool); + if (err) + return svn_error_compose_create(externals_err, err); + } + + return svn_error_compose_create(externals_err, err); +} 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..77c28f9 --- /dev/null +++ b/subversion/svn/update-cmd.c @@ -0,0 +1,196 @@ +/* + * 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; + svn_boolean_t printed_header = FALSE; + + if (targets->nelts < 2) + return SVN_NO_ERROR; + + SVN_ERR(svn_dirent_get_absolute(&path_prefix, "", scratch_pool)); + + 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. */ + if (! printed_header) + { + SVN_ERR(svn_cmdline_printf(scratch_pool, + _("Summary of updates:\n"))); + printed_header = TRUE; + } + + 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_error_t *err = SVN_NO_ERROR; + svn_error_t *externals_err = SVN_NO_ERROR; + + 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 (nwb.had_externals_error) + externals_err = svn_error_create(SVN_ERR_CL_ERROR_PROCESSING_EXTERNALS, + NULL, + _("Failure occurred processing one or " + "more externals definitions")); + + if (! opt_state->quiet) + { + err = print_update_summary(targets, result_revs, scratch_pool); + if (err) + return svn_error_compose_create(externals_err, err); + + /* ### 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. */ + err = svn_cl__notifier_print_conflict_stats(nwb.wrapped_baton, + scratch_pool); + if (err) + return svn_error_compose_create(externals_err, err); + } + + return svn_error_compose_create(externals_err, err); +} 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..5d386f8 --- /dev/null +++ b/subversion/svn/util.c @@ -0,0 +1,1109 @@ +/* + * 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_wc.h" +#include "svn_xml.h" +#include "svn_time.h" +#include "svn_props.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_cmdline_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; +} + + +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 ? svn_hash_gets(config, SVN_CONFIG_CATEGORY_CONFIG) : 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; +} + + +/* 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 = svn_hash_gets(config, SVN_CONFIG_CATEGORY_CONFIG); + 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_string_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_empty(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_cmdline__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_empty(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)) != APR_SUCCESS) + { + 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_empty(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_empty(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; +} + +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, *peg; + + SVN_ERR(svn_opt__split_arg_at_peg_revision(&true_target, &peg, + target, pool)); + if (peg[0] && peg[1]) + return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL, + _("'%s': a peg revision is not allowed here"), + target); + 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_create(SVN_ERR_CL_ARG_PARSING_ERROR, err, NULL); + 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); +} + +/* Return a string of the form "PATH_OR_URL@REVISION". */ +static const char * +path_for_display(const char *path_or_url, + const svn_opt_revision_t *revision, + apr_pool_t *pool) +{ + const char *rev_str = svn_opt__revision_to_string(revision, pool); + + if (! svn_path_is_url(path_or_url)) + path_or_url = svn_dirent_local_style(path_or_url, pool); + return apr_psprintf(pool, "%s@%s", path_or_url, rev_str); +} + +svn_error_t * +svn_cl__check_related_source_and_target(const char *path_or_url1, + const svn_opt_revision_t *revision1, + const char *path_or_url2, + const svn_opt_revision_t *revision2, + svn_client_ctx_t *ctx, + apr_pool_t *pool) +{ + const char *ancestor_url; + svn_revnum_t ancestor_rev; + + SVN_ERR(svn_client__youngest_common_ancestor( + &ancestor_url, &ancestor_rev, + path_or_url1, revision1, path_or_url2, revision2, + ctx, pool, pool)); + + if (ancestor_url == NULL) + { + return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, + _("Source and target have no common ancestor: " + "'%s' and '%s'"), + path_for_display(path_or_url1, revision1, pool), + path_for_display(path_or_url2, revision2, pool)); + } + return SVN_NO_ERROR; +} + +svn_error_t * +svn_cl__propset_print_binary_mime_type_warning(apr_array_header_t *targets, + const char *propname, + const svn_string_t *propval, + apr_pool_t *scratch_pool) +{ + if (strcmp(propname, SVN_PROP_MIME_TYPE) == 0) + { + apr_pool_t *iterpool = svn_pool_create(scratch_pool); + int i; + + for (i = 0; i < targets->nelts; i++) + { + const char *detected_mimetype; + const char *target = APR_ARRAY_IDX(targets, i, const char *); + const char *local_abspath; + const svn_string_t *canon_propval; + svn_node_kind_t node_kind; + + svn_pool_clear(iterpool); + + SVN_ERR(svn_dirent_get_absolute(&local_abspath, target, iterpool)); + SVN_ERR(svn_io_check_path(local_abspath, &node_kind, iterpool)); + if (node_kind != svn_node_file) + continue; + + SVN_ERR(svn_wc_canonicalize_svn_prop(&canon_propval, + propname, propval, + local_abspath, + svn_node_file, + FALSE, NULL, NULL, + iterpool)); + + if (svn_mime_type_is_binary(canon_propval->data)) + { + SVN_ERR(svn_io_detect_mimetype2(&detected_mimetype, + local_abspath, NULL, + iterpool)); + if (detected_mimetype == NULL || + !svn_mime_type_is_binary(detected_mimetype)) + svn_error_clear(svn_cmdline_fprintf(stderr, iterpool, + _("svn: warning: '%s' is a binary mime-type but file '%s' " + "looks like text; diff, merge, blame, and other " + "operations will stop working on this file\n"), + canon_propval->data, + svn_dirent_local_style(local_abspath, iterpool))); + + } + } + svn_pool_destroy(iterpool); + } + + return SVN_NO_ERROR; +} + |