blob: aef2c40d70d67242894878e43f429c8530800990 [file] [log] [blame]
/*
* 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 "svn_hash.h"
#include "cl.h"
#include "svn_private_config.h"
/*** Code. ***/
/* Baton for conflict_status_walker */
struct conflict_status_walker_baton
{
svn_client_ctx_t *ctx;
svn_client_conflict_option_id_t option_id;
svn_wc_notify_func2_t notify_func;
void *notify_baton;
svn_boolean_t resolved_one;
apr_hash_t *resolve_later;
svn_cl__accept_t *accept_which;
svn_boolean_t *quit;
svn_boolean_t *external_failed;
svn_boolean_t *printed_summary;
const char *editor_cmd;
apr_hash_t *config;
const char *path_prefix;
svn_cmdline_prompt_baton_t *pb;
svn_cl__conflict_stats_t *conflict_stats;
};
/* Implements svn_wc_notify_func2_t to collect new conflicts caused by
resolving a tree conflict. */
static void
tree_conflict_collector(void *baton,
const svn_wc_notify_t *notify,
apr_pool_t *pool)
{
struct conflict_status_walker_baton *cswb = baton;
if (cswb->notify_func)
cswb->notify_func(cswb->notify_baton, notify, pool);
if (cswb->resolve_later
&& (notify->action == svn_wc_notify_tree_conflict
|| notify->prop_state == svn_wc_notify_state_conflicted
|| notify->content_state == svn_wc_notify_state_conflicted))
{
if (!svn_hash_gets(cswb->resolve_later, notify->path))
{
const char *dup_path;
dup_path = apr_pstrdup(apr_hash_pool_get(cswb->resolve_later),
notify->path);
svn_hash_sets(cswb->resolve_later, dup_path, dup_path);
}
}
}
/*
* Record a tree conflict resolution failure due to error condition ERR
* in the RESOLVE_LATER hash table. If the hash table is not available
* (meaning the caller does not wish to retry resolution later), or if
* the error condition does not indicate circumstances where another
* existing tree conflict is blocking the resolution attempt, then
* return the error ERR itself.
*/
static svn_error_t *
handle_tree_conflict_resolution_failure(const char *local_abspath,
svn_error_t *err,
apr_hash_t *resolve_later)
{
const char *dup_abspath;
if (!resolve_later
|| (err->apr_err != SVN_ERR_WC_OBSTRUCTED_UPDATE
&& err->apr_err != SVN_ERR_WC_FOUND_CONFLICT))
return svn_error_trace(err); /* Give up. Do not retry resolution later. */
svn_error_clear(err);
dup_abspath = apr_pstrdup(apr_hash_pool_get(resolve_later),
local_abspath);
svn_hash_sets(resolve_later, dup_abspath, dup_abspath);
return SVN_NO_ERROR; /* Caller may retry after resolving other conflicts. */
}
/* Implements svn_wc_status4_t to walk all conflicts to resolve.
*/
static svn_error_t *
conflict_status_walker(void *baton,
const char *local_abspath,
const svn_wc_status3_t *status,
apr_pool_t *scratch_pool)
{
struct conflict_status_walker_baton *cswb = baton;
apr_pool_t *iterpool;
svn_boolean_t resolved = FALSE;
svn_client_conflict_t *conflict;
svn_error_t *err;
svn_boolean_t tree_conflicted;
if (!status->conflicted)
return SVN_NO_ERROR;
iterpool = svn_pool_create(scratch_pool);
SVN_ERR(svn_client_conflict_get(&conflict, local_abspath, cswb->ctx,
iterpool, iterpool));
SVN_ERR(svn_client_conflict_get_conflicted(NULL, NULL, &tree_conflicted,
conflict, iterpool, iterpool));
err = svn_cl__resolve_conflict(&resolved, cswb->accept_which,
cswb->quit, cswb->external_failed,
cswb->printed_summary,
conflict, cswb->editor_cmd,
cswb->config, cswb->path_prefix,
cswb->pb, cswb->conflict_stats,
cswb->option_id, cswb->ctx,
scratch_pool);
if (err)
{
if (tree_conflicted)
SVN_ERR(handle_tree_conflict_resolution_failure(local_abspath, err,
cswb->resolve_later));
else
return svn_error_trace(err);
}
if (resolved)
cswb->resolved_one = TRUE;
svn_pool_destroy(iterpool);
/* If the has user decided to quit resolution, cancel the status walk. */
if (*cswb->quit)
return svn_error_create(SVN_ERR_CANCELLED, NULL, NULL);
return SVN_NO_ERROR;
}
static svn_error_t *
walk_conflicts(svn_client_ctx_t *ctx,
const char *local_abspath,
svn_depth_t depth,
svn_client_conflict_option_id_t option_id,
svn_cl__accept_t *accept_which,
svn_boolean_t *quit,
svn_boolean_t *external_failed,
svn_boolean_t *printed_summary,
const char *editor_cmd,
apr_hash_t *config,
const char *path_prefix,
svn_cmdline_prompt_baton_t *pb,
svn_cl__conflict_stats_t *conflict_stats,
apr_pool_t *scratch_pool)
{
struct conflict_status_walker_baton cswb;
apr_pool_t *iterpool = NULL;
svn_error_t *err;
if (depth == svn_depth_unknown)
depth = svn_depth_infinity;
cswb.ctx = ctx;
cswb.option_id = option_id;
cswb.resolved_one = FALSE;
cswb.resolve_later = (depth != svn_depth_empty)
? apr_hash_make(scratch_pool)
: NULL;
cswb.accept_which = accept_which;
cswb.quit = quit;
cswb.external_failed = external_failed;
cswb.printed_summary = printed_summary;
cswb.editor_cmd = editor_cmd;
cswb.config = config;
cswb.path_prefix = path_prefix;
cswb.pb = pb;
cswb.conflict_stats = conflict_stats;
/* ### call notify.c code */
if (ctx->notify_func2)
ctx->notify_func2(ctx->notify_baton2,
svn_wc_create_notify(
local_abspath,
svn_wc_notify_conflict_resolver_starting,
scratch_pool),
scratch_pool);
cswb.notify_func = ctx->notify_func2;
cswb.notify_baton = ctx->notify_baton2;
ctx->notify_func2 = tree_conflict_collector;
ctx->notify_baton2 = &cswb;
err = svn_wc_walk_status(ctx->wc_ctx,
local_abspath,
depth,
FALSE /* get_all */,
FALSE /* no_ignore */,
TRUE /* ignore_text_mods */,
NULL /* ignore_patterns */,
conflict_status_walker, &cswb,
ctx->cancel_func, ctx->cancel_baton,
scratch_pool);
/* If we got new tree conflicts (or delayed conflicts) during the initial
walk, we now walk them one by one as closure. */
while (!err && cswb.resolve_later && apr_hash_count(cswb.resolve_later))
{
apr_hash_index_t *hi;
svn_wc_status3_t *status = NULL;
const char *tc_abspath = NULL;
if (iterpool)
svn_pool_clear(iterpool);
else
iterpool = svn_pool_create(scratch_pool);
hi = apr_hash_first(scratch_pool, cswb.resolve_later);
cswb.resolve_later = apr_hash_make(scratch_pool);
cswb.resolved_one = FALSE;
for (; hi && !err; hi = apr_hash_next(hi))
{
const char *relpath;
svn_pool_clear(iterpool);
tc_abspath = apr_hash_this_key(hi);
if (ctx->cancel_func)
SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
relpath = svn_dirent_skip_ancestor(local_abspath,
tc_abspath);
if (!relpath
|| (depth >= svn_depth_empty
&& depth < svn_depth_infinity
&& strchr(relpath, '/')))
{
continue;
}
SVN_ERR(svn_wc_status3(&status, ctx->wc_ctx, tc_abspath,
iterpool, iterpool));
if (depth == svn_depth_files
&& status->kind == svn_node_dir)
continue;
err = svn_error_trace(conflict_status_walker(&cswb, tc_abspath,
status, scratch_pool));
}
/* None of the remaining conflicts got resolved, and non did provide
an error...
We can fix that if we disable the 'resolve_later' option...
*/
if (!cswb.resolved_one && !err && tc_abspath
&& apr_hash_count(cswb.resolve_later))
{
/* Run the last resolve operation again. We still have status
and tc_abspath for that one. */
cswb.resolve_later = NULL; /* Produce proper error! */
/* Recreate the error */
err = svn_error_trace(conflict_status_walker(&cswb, tc_abspath,
status, scratch_pool));
SVN_ERR_ASSERT(err != NULL);
err = svn_error_createf(
SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, err,
_("Unable to resolve pending conflict on '%s'"),
svn_dirent_local_style(tc_abspath, scratch_pool));
break;
}
}
if (iterpool)
svn_pool_destroy(iterpool);
if (err)
{
if (err->apr_err == SVN_ERR_CANCELLED)
{
/* If QUIT is set, the user has selected the 'q' option at
* the conflict prompt and the status walk was aborted.
* This is not an error condition. */
if (quit)
{
svn_error_clear(err);
err = SVN_NO_ERROR;
}
}
else if (err->apr_err != SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE)
err = svn_error_createf(
SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, err,
_("Unable to resolve conflicts on '%s'"),
svn_dirent_local_style(local_abspath, scratch_pool));
SVN_ERR(err);
}
ctx->notify_func2 = cswb.notify_func;
ctx->notify_baton2 = cswb.notify_baton;
/* ### call notify.c code */
if (ctx->notify_func2)
ctx->notify_func2(ctx->notify_baton2,
svn_wc_create_notify(local_abspath,
svn_wc_notify_conflict_resolver_done,
scratch_pool),
scratch_pool);
return SVN_NO_ERROR;
}
svn_error_t *
svn_cl__walk_conflicts(apr_array_header_t *targets,
svn_cl__conflict_stats_t *conflict_stats,
svn_boolean_t is_resolve_cmd,
svn_cl__opt_state_t *opt_state,
svn_client_ctx_t *ctx,
apr_pool_t *scratch_pool)
{
svn_boolean_t had_error = FALSE;
svn_boolean_t quit = FALSE;
svn_boolean_t external_failed = FALSE;
svn_boolean_t printed_summary = FALSE;
svn_client_conflict_option_id_t option_id;
svn_cmdline_prompt_baton_t *pb = apr_palloc(scratch_pool, sizeof(*pb));
const char *path_prefix;
svn_error_t *err;
int i;
apr_pool_t *iterpool;
SVN_ERR(svn_dirent_get_absolute(&path_prefix, "", scratch_pool));
pb->cancel_func = ctx->cancel_func;
pb->cancel_baton = ctx->cancel_baton;
switch (opt_state->accept_which)
{
case svn_cl__accept_working:
option_id = svn_client_conflict_option_merged_text;
break;
case svn_cl__accept_base:
option_id = svn_client_conflict_option_base_text;
break;
case svn_cl__accept_theirs_conflict:
option_id = svn_client_conflict_option_incoming_text_where_conflicted;
break;
case svn_cl__accept_mine_conflict:
option_id = svn_client_conflict_option_working_text_where_conflicted;
break;
case svn_cl__accept_theirs_full:
option_id = svn_client_conflict_option_incoming_text;
break;
case svn_cl__accept_mine_full:
option_id = svn_client_conflict_option_working_text;
break;
case svn_cl__accept_unspecified:
if (is_resolve_cmd && opt_state->non_interactive)
return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
_("missing --accept option"));
option_id = svn_client_conflict_option_unspecified;
break;
case svn_cl__accept_postpone:
if (is_resolve_cmd)
return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
_("invalid 'accept' ARG"));
option_id = svn_client_conflict_option_postpone;
break;
case svn_cl__accept_edit:
case svn_cl__accept_launch:
if (is_resolve_cmd)
return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
_("invalid 'accept' ARG"));
option_id = svn_client_conflict_option_unspecified;
break;
default:
return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
_("invalid 'accept' ARG"));
}
iterpool = svn_pool_create(scratch_pool);
for (i = 0; i < targets->nelts; i++)
{
const char *target = APR_ARRAY_IDX(targets, i, const char *);
const char *local_abspath;
svn_client_conflict_t *conflict;
svn_pool_clear(iterpool);
SVN_ERR(svn_cl__check_cancel(ctx->cancel_baton));
SVN_ERR(svn_dirent_get_absolute(&local_abspath, target, iterpool));
if (opt_state->depth == svn_depth_empty)
{
svn_boolean_t resolved;
SVN_ERR(svn_client_conflict_get(&conflict, local_abspath, ctx,
iterpool, iterpool));
err = svn_cl__resolve_conflict(&resolved,
&opt_state->accept_which,
&quit, &external_failed,
&printed_summary,
conflict, opt_state->editor_cmd,
ctx->config, path_prefix,
pb, conflict_stats,
option_id, ctx,
iterpool);
}
else
{
err = walk_conflicts(ctx, local_abspath, opt_state->depth,
option_id, &opt_state->accept_which,
&quit, &external_failed, &printed_summary,
opt_state->editor_cmd, ctx->config,
path_prefix, pb, conflict_stats, iterpool);
}
if (err)
{
svn_handle_warning2(stderr, svn_error_root_cause(err), "svn: ");
svn_error_clear(err);
had_error = TRUE;
}
}
svn_pool_destroy(iterpool);
if (had_error)
return svn_error_create(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
_("Failure occurred resolving one or more "
"conflicts"));
return SVN_NO_ERROR;
}
/* 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_cl__conflict_stats_t *conflict_stats =
((svn_cl__cmd_baton_t *) baton)->conflict_stats;
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));
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));
SVN_ERR(svn_cl__walk_conflicts(targets, conflict_stats, TRUE,
opt_state, ctx, scratch_pool));
return SVN_NO_ERROR;
}