Author: stsp
Date: Wed Nov 23 18:24:33 2011
New Revision: 1205528
URL: http://svn.apache.org/viewvc?rev=1205528&view=rev
Log:
On the moves-scan-log branch, hook the moves-log scanning logic into to the
interactive conflict resolution callback system.
For now, this is triggered only on delete vs. delete conflicts.
This commit also changes some aspects of auto-resolution behaviour of
tree conflict handling back to the level of 1.7. This applies to any
conflict involving an incoming delete. This is because auto-resolution
strategies will be based on feedback we get from the conflict callback.
However, this feedback is not evaluated completely at this point.
This is mostly a checkpoint commit to get these changes off my hard drive.
* subversion/include/svn_wc.h
(svn_wc_repos_move_info_t): Move defintion futher up in this file.
(svn_wc_conflict_description2_t): New fields suggested_move and
server_sends_moves_as_copy_plus_delete. See docstrings for details.
(svn_wc_conflict_choice_t): Add new choices scan_log_for_moves,
delete_is_delete, copy_is_copy, incoming_move, new_local_move_target.
Not all of these are used yet.
(svn_wc_conflict_result_t): Expand to cover responses to above choices
by adding new fields incoming_move and new_local_move_target.
* subversion/svn/cl.h
(svn_cl__conflict_handler): Update to use svn_wc_conflict_description2_t.
* subversion/svn/main.c
(main): Configure an conflict_func2 in the client context, instead of the
old conflict_func that uses svn_wc_conflict_description_t.
* subversion/svn/conflict-callbacks.c
(pick_move): New helper function.
(svn_cl__accept_from_word, show_conflicts, open_editor): Change to
svn_wc_conflict_description2_t.
(svn_cl__conflict_handler): As previous. Also handle 'incoming delete vs.
local delete' conflicts by presenting the user with the following options:
-------------------------------------------------------
(p) postpone, (s) suggest-move,
(a) ask-move, (d) is-delete, (h) help: h
(p) postpone - resolve the conflict later
(s) suggest-move - suggest moves found in revision log
(a) ask-move - specify server-side move yourself
(d) is-delete - treat incoming delete as delete
(h) help - show this help
-------------------------------------------------------
Not all of these options are implemented yet. The 's' option invokes the
log history scanner and presents the list of moves found, if any.
The UI control flow is still a bit crude and will be improved later.
* subversion/libsvn_wc/update_editor.c
(create_tree_conflict): Always set server_sends_moves_as_copy_plus_delete
in the created conflict description. This flag will be useful later when
servers start sending proper moves instead of copy+delete.
(check_tree_conflict): Always consider delete vs. delete a conflict. Move
the code which auto-resolves certain cases from here into delete_entry().
Remove the moved_to_abspath parameter as it is now unneeded.
(compare_revision_items_ascending): New helper.
(find_applicable_move): Select a move from a list of server side moves
which applies to a node being updated. This is half-baked and needs
some additional work, especially for updates into past. It works for simple
forward-update tests, however.
(delete_entry): Do not scan the log for moves by default anymore. Invoke
the interactive conflict resolution callback upon delete vs. delete tree
conflicts.
(add_directory, open_directory, add_file, open_file): Stop passing the
moved_to_abspath paramer to check_tree_conflict(). This breaks some
tree conflict auto-resolution behaviour which will be restored later.
Modified:
subversion/branches/moves-scan-log/subversion/include/svn_wc.h
subversion/branches/moves-scan-log/subversion/libsvn_wc/update_editor.c
subversion/branches/moves-scan-log/subversion/svn/cl.h
subversion/branches/moves-scan-log/subversion/svn/conflict-callbacks.c
subversion/branches/moves-scan-log/subversion/svn/main.c
Modified: subversion/branches/moves-scan-log/subversion/include/svn_wc.h
URL:
http://svn.apache.org/viewvc/subversion/branches/moves-scan-log/subversion/include/svn_wc.h?rev=1205528&r1=1205527&r2=1205528&view=diff
==============================================================================
--- subversion/branches/moves-scan-log/subversion/include/svn_wc.h (original)
+++ subversion/branches/moves-scan-log/subversion/include/svn_wc.h Wed Nov 23
18:24:33 2011
@@ -1695,6 +1695,30 @@ svn_wc_conflict_version_t *
svn_wc_conflict_version_dup(const svn_wc_conflict_version_t *version,
apr_pool_t *pool);
+
+/** Describes a server-side move (really a copy+delete within the same
+ * revision) which has been identified by scanning the revision log.
+ * @since New in 1.8. */
+typedef struct svn_wc_repos_move_info_t {
+ /* The repository relpath the node was moved from. */
+ const char *moved_from_repos_relpath;
+
+ /* The repository relpath the node was moved to. */
+ const char *moved_to_repos_relpath;
+
+ /* The revision in which the move happened. */
+ svn_revnum_t revision;
+
+ /* The copyfrom revision of the moved-to path. */
+ svn_revnum_t copyfrom_rev;
+
+ /* Pointers to previous or subsequent moves of the same node
+ * within interesting history. */
+ struct svn_wc_repos_move_info_t *prev;
+ struct svn_wc_repos_move_info_t *next;
+} svn_wc_repos_move_info_t;
+
+
/** A struct that describes a conflict that has occurred in the
* working copy.
*
@@ -1794,6 +1818,24 @@ typedef struct svn_wc_conflict_descripti
/** Info on the "merge-right source" or "their" version of incoming change.
*/
const svn_wc_conflict_version_t *src_right_version;
+ /** Whether the server expresses server-side moves as copy+delete.
+ * This is only set for tree conflicts. If TRUE, a tree conflict
+ * involving an incoming delete might actually be a tree conflict
+ * involving an incoming move. Conflict resolution callbacks can
+ * trigger a scan of the revision log for detailed information about
+ * moves.
+ * @see svn_wc_conflict_choice_t
+ * @since New in 1.8. */
+ svn_boolean_t server_sends_moves_as_copy_plus_delete;
+
+ /** A chain of one or more suggested moves in case the server sends moves
+ * as copy+delete and the revision log was scanned for server-side moves
+ * at the user's request.
+ * @see svn_wc_conflict_choice_t
+ * @see svn_wc_repos_move_info_t
+ * @since New in 1.8. */
+ svn_wc_repos_move_info_t *suggested_move;
+
/* Remember to adjust svn_wc__conflict_description2_dup()
* if you add new fields to this struct. */
} svn_wc_conflict_description2_t;
@@ -2030,7 +2072,47 @@ typedef enum svn_wc_conflict_choice_t
svn_wc_conflict_choose_mine_full, /**< own version */
svn_wc_conflict_choose_theirs_conflict, /**< incoming (for conflicted hunks)
*/
svn_wc_conflict_choose_mine_conflict, /**< own (for conflicted hunks) */
- svn_wc_conflict_choose_merged /**< merged version */
+ svn_wc_conflict_choose_merged, /**< merged version */
+
+ /** Scan the log for moves. If the server sends server-side moves as
+ * delete+copy (@see svn_wc_conflict_description2_t), users can choose
+ * this option to scan the revision log for server-side moves during
+ * an 'incoming delete vs. local delete' tree conflict or an 'incoming
+ * add vs. local add' the conflict. The callback will then be invoked
+ * again with zero or more suggested server-side moves. The user can then
+ * decide to treat the incoming delete as a true delete, an incoming add
+ * as a tree add, or decide to treat it as part of an incoming move by
+ * picking an incoming move source/target. Conflict callback implementations
+ * may allow users to pick a move source/target even if the list of
+ * suggested server-side moves is empty.
+ * @since New in 1.8. */
+ svn_wc_conflict_choose_scan_log_for_moves,
+
+ /** The user has determined that an incoming delete event is not part
+ * of a server-side move, but really a delete event.
+ * @since New in 1.8. */
+ svn_wc_conflict_choose_delete_is_delete,
+
+ /** The user has determined that an incoming copy event is not part
+ * of a server-side move, but really a copy event.
+ * @since New in 1.8. */
+ svn_wc_conflict_choose_copy_is_copy,
+
+ /** The user has chosen to map an incoming delete or copy to a
+ * server-side move. This move is described by svn_wc_conflict_result_t.
+ * This allows users to divert incoming moves to a different target
+ * location during "incoming move vs. local move" tree conflicts.
+ * @see svn_wc_conflict_result_t
+ * @since New in 1.8. */
+ svn_wc_conflict_choose_incoming_move,
+
+ /** The user has chosen a new target for a local move to resolve an
+ * "incoming move vs. local move" tree conflict. This allows users to
+ * make space at the target location of an incoming move during "incoming
+ * move vs. local move" tree conflicts.
+ * @see svn_wc_conflict_result_t
+ * @since New in 1.8. */
+ svn_wc_conflict_choose_new_local_move_target,
} svn_wc_conflict_choice_t;
@@ -2062,6 +2144,25 @@ typedef struct svn_wc_conflict_result_t
NULL) in the user's working copy. */
svn_boolean_t save_merged;
+ /** The server-side move a delete or copy event should be mapped to,
+ * in case the choice is svn_wc_conflict_choose_incoming_move.
+ * In this structure, the moved_from_repos_relpath and
+ * moved_to_repos_relpath fields must both be set.
+ * The revision fields may be @c SVN_INVALID_REVNUM in case they are
+ * unknown, and the prev/next pointers may both be NULL.
+ * The idea is to allow users to describe an arbitrary incoming move
+ * by naming the source and the target. If the user's chosen incoming
+ * move is not applicable, the conflict callback will be invoked again.
+ * @see svn_wc_conflict_choice_t
+ * @since New in 1.8. */
+ svn_wc_repos_move_info_t *incoming_move;
+
+ /** The absolute path of a new target of a local move in case the
+ * choice is svn_wc_conflict_choose_incoming_move_target.
+ * @see svn_wc_conflict_choice_t
+ * @since New in 1.8. */
+ const char *new_local_move_target;
+
} svn_wc_conflict_result_t;
@@ -5505,28 +5606,6 @@ typedef svn_error_t *(*svn_wc_dirents_fu
apr_pool_t *scratch_pool);
-/** Describes a server-side move (really a copy+delete within the same
- * revision) which has been identified by scanning the revision log.
- * @since New in 1.8. */
-typedef struct svn_wc_repos_move_info_t {
- /* The repository relpath the node was moved from. */
- const char *moved_from_repos_relpath;
-
- /* The repository relpath the node was moved to. */
- const char *moved_to_repos_relpath;
-
- /* The revision in which the move happened. */
- svn_revnum_t revision;
-
- /* The copyfrom revision of the moved-to path. */
- svn_revnum_t copyfrom_rev;
-
- /* Pointers to previous or subsequent moves of the same node
- * within interesting history. */
- struct svn_wc_repos_move_info_t *prev;
- struct svn_wc_repos_move_info_t *next;
-} svn_wc_repos_move_info_t;
-
/* ### TODO docco
* @since New in 1.8. */
typedef svn_error_t *(*svn_wc_get_repos_moves_func_t)(void *baton,
Modified:
subversion/branches/moves-scan-log/subversion/libsvn_wc/update_editor.c
URL:
http://svn.apache.org/viewvc/subversion/branches/moves-scan-log/subversion/libsvn_wc/update_editor.c?rev=1205528&r1=1205527&r2=1205528&view=diff
==============================================================================
--- subversion/branches/moves-scan-log/subversion/libsvn_wc/update_editor.c
(original)
+++ subversion/branches/moves-scan-log/subversion/libsvn_wc/update_editor.c Wed
Nov 23 18:24:33 2011
@@ -36,6 +36,7 @@
#include "svn_pools.h"
#include "svn_delta.h"
#include "svn_hash.h"
+#include "svn_sorts.h"
#include "svn_string.h"
#include "svn_dirent_uri.h"
#include "svn_path.h"
@@ -1464,6 +1465,10 @@ create_tree_conflict(svn_wc_conflict_des
src_left_version, src_right_version, result_pool);
(*pconflict)->action = action;
(*pconflict)->reason = reason;
+
+ /* Until we have editor-v2 or some other way of sending moves from the
+ * server to the client, this must always be set. */
+ (*pconflict)->server_sends_moves_as_copy_plus_delete = TRUE;
return SVN_NO_ERROR;
}
@@ -1506,7 +1511,6 @@ check_tree_conflict(svn_wc_conflict_desc
svn_wc_conflict_action_t action,
svn_node_kind_t their_node_kind,
const char *their_relpath,
- const char *moved_to_abspath,
apr_pool_t *result_pool,
apr_pool_t *scratch_pool)
{
@@ -1574,28 +1578,10 @@ check_tree_conflict(svn_wc_conflict_desc
case svn_wc__db_status_deleted:
- if (!moved_to_abspath)
- reason = svn_wc_conflict_reason_deleted;
- else if (action == svn_wc_conflict_action_delete)
- {
- svn_boolean_t all_edits_are_deletes = FALSE;
-
- /* The update wants to delete a node which was locally moved
- * away. We allow this only if the node wasn't modified post-move.
- * If the only post-move changes within a subtree are deletions,
- * allow the update to delete the entire subtree. */
- if (working_kind == svn_kind_dir)
- SVN_ERR(node_has_local_mods(&modified, &all_edits_are_deletes,
- TRUE, eb->db, moved_to_abspath,
- eb->cancel_func, eb->cancel_baton,
- scratch_pool));
- else
- SVN_ERR(svn_wc__internal_file_modified_p(&modified, eb->db,
- moved_to_abspath,
- FALSE, scratch_pool));
- if (modified && !all_edits_are_deletes)
- reason = svn_wc_conflict_reason_moved_away_and_edited;
- }
+ /* Flag a delete vs. delete conflict for now.
+ * This might get auto-resolved once we've learned whether or
+ * not this incoming delete is really part of an incoming move. */
+ reason = svn_wc_conflict_reason_deleted;
break;
case svn_wc__db_status_incomplete:
@@ -1807,6 +1793,155 @@ get_repos_moves(struct edit_baton *eb, a
return SVN_NO_ERROR;
}
+static int
+compare_revision_items_ascending(const svn_sort__item_t *a,
+ const svn_sort__item_t *b)
+{
+ svn_revnum_t a_rev = *((svn_revnum_t *)a->key);
+ svn_revnum_t b_rev = *((svn_revnum_t *)b->key);
+
+ if (a_rev == b_rev)
+ return 0;
+
+ return a_rev < b_rev ? -1 : 1;
+}
+
+static svn_error_t *
+find_applicable_move(svn_wc_repos_move_info_t **move,
+ struct edit_baton *eb,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ const char *repos_relpath;
+ svn_revnum_t base_revision;
+ apr_array_header_t *sorted;
+ svn_boolean_t update_into_past;
+ int i;
+
+ *move = NULL;
+
+ if (! eb->repos_moves || apr_hash_count(eb->repos_moves) == 0)
+ return SVN_NO_ERROR;
+
+ SVN_ERR(svn_wc__db_base_get_info(NULL, NULL, &base_revision,
+ &repos_relpath, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL,
+ eb->db, local_abspath,
+ scratch_pool, scratch_pool));
+
+ /* Sanity check: If the base revision already matches the target
+ * revision the update is a no-op so this node cannot be a tree
+ * conflict victim. ### Should not happen. */
+ if (base_revision == *eb->target_revision)
+ return SVN_NO_ERROR;
+
+ update_into_past = (base_revision > *eb->target_revision);
+
+ /* Make sure moves are sorted by revisions in the right order. */
+ sorted = svn_sort__hash(eb->repos_moves,
+ compare_revision_items_ascending,
+ scratch_pool);
+
+ /* Try to find a server-side move that applies to this node. */
+ if (!update_into_past)
+ {
+ for (i = 0; i < sorted->nelts; i++)
+ {
+ int j;
+ svn_sort__item_t elt = APR_ARRAY_IDX(sorted, i,
+ svn_sort__item_t);
+ const svn_revnum_t *rev = elt.key;
+ apr_array_header_t *moves = elt.value;
+
+ /* When updating into the future, a move applies if it
+ * happened after the base rev of the node. */
+ if (*rev <= base_revision)
+ continue;
+
+ /* Generally, we have a chain of moves which happened in
+ * distinct revisions, such as:
+ * rA: mv a->b
+ * rB: mv b->c
+ * rC: mv c->d
+ * and so on. Find the first applicable move in the chain. */
+ for (j = 0; j < moves->nelts; j++)
+ {
+ svn_wc_repos_move_info_t *this_move;
+
+ this_move = APR_ARRAY_IDX(moves, j,
+ svn_wc_repos_move_info_t *);
+ if (strcmp(this_move->moved_from_repos_relpath,
+ repos_relpath) == 0)
+ {
+ /* Move forward to the last applicable move in the chain,
+ * collapsing the move chain (e.g. a->b->c->d) into the
+ * move which is applied by the update (e.g. b->d). */
+ while (this_move->next)
+ {
+ if (this_move->next->revision > *eb->target_revision)
+ break;
+ this_move = this_move->next;
+ }
+
+ *move = this_move;
+ break;
+ }
+ }
+ }
+ }
+ else
+ {
+ /* If updating into the past all moves apply in reverse.
+ * ### TODO construct moves list usable for conflict callback */
+ for (i = sorted->nelts - 1; i >= 0; i--)
+ {
+ int j;
+ svn_sort__item_t elt = APR_ARRAY_IDX(sorted, i,
+ svn_sort__item_t);
+ const svn_revnum_t *rev = elt.key;
+ apr_array_header_t *moves = elt.value;
+
+ /* When updating into the past, a move applies if it
+ * happened before the base rev of the node. */
+ if (*rev >= base_revision)
+ continue;
+
+ /* Generally, we have a chain of reversed moves which happened
+ * in distinct revisions, such as:
+ * rC: mv d->c (actually c->d for history in forward direction)
+ * rB: mv c->b (actually b->c)
+ * rA: mv b->a (actually a->b)
+ * and so on. Find the first applicable move in the chain. */
+ for (j = 0; j < moves->nelts; j++)
+ {
+ svn_wc_repos_move_info_t *this_move;
+
+ this_move = APR_ARRAY_IDX(moves, j,
+ svn_wc_repos_move_info_t *);
+ if (strcmp(this_move->moved_to_repos_relpath,
+ repos_relpath) == 0)
+ {
+ /* Move backwards to the last applicable move in the chain,
+ * collapsing the move chain (e.g. d->c->b->a) into the
+ * move which is applied by the update (e.g. d->b). */
+ while (this_move->prev)
+ {
+ if (this_move->prev->revision < *eb->target_revision)
+ break;
+ this_move = this_move->prev;
+ }
+ *move = this_move;
+ break;
+ }
+ }
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
/* An svn_delta_editor_t function. */
static svn_error_t *
delete_entry(const char *path,
@@ -1841,8 +1976,6 @@ delete_entry(const char *path,
SVN_ERR(path_join_under_root(&local_abspath, pb->local_abspath, base,
scratch_pool));
- SVN_ERR(get_repos_moves(eb, scratch_pool));
-
deleting_target = (strcmp(local_abspath, eb->target_abspath) == 0);
/* Detect obstructing working copies */
@@ -1940,8 +2073,83 @@ delete_entry(const char *path,
SVN_ERR(check_tree_conflict(&tree_conflict, eb, local_abspath,
status, kind, TRUE,
svn_wc_conflict_action_delete, svn_node_none,
- repos_relpath, moved_to_abspath,
- pb->pool, scratch_pool));
+ repos_relpath, pb->pool, scratch_pool));
+ }
+
+ /* If this is an incoming delete vs. local delete/move conflict
+ * try the conflict callback to resolve the conflict. */
+ if (tree_conflict != NULL && eb->conflict_func &&
+ status == svn_wc__db_status_deleted)
+ {
+ svn_wc_conflict_result_t *result;
+
+ do
+ {
+ SVN_ERR(eb->conflict_func(&result, tree_conflict,
+ eb->conflict_baton,
+ scratch_pool, scratch_pool));
+ if (result == NULL)
+ return svn_error_create(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE,
+ NULL, _("Conflict callback violated API:"
+ " returned no results"));
+ if (result->choice == svn_wc_conflict_choose_scan_log_for_moves)
+ {
+ svn_wc_repos_move_info_t *move;
+
+ /* Scan revision log for server-side moves */
+ SVN_ERR(get_repos_moves(eb, scratch_pool));
+
+ /* Find a server-side move which applies to the deleted node. */
+ if (apr_hash_count(eb->repos_moves) > 0)
+ {
+ SVN_ERR(find_applicable_move(&move, eb, local_abspath,
+ scratch_pool, scratch_pool));
+ tree_conflict->suggested_move = move;
+ }
+ continue;
+ }
+
+ if (result->choice == svn_wc_conflict_choose_incoming_move)
+ {
+ /* ### TODO */
+ }
+
+ if (result->choice == svn_wc_conflict_choose_new_local_move_target)
+ {
+ /* ### TODO */
+ }
+
+ if (moved_to_abspath &&
+ result->choice == svn_wc_conflict_choose_delete_is_delete)
+ {
+ svn_boolean_t all_edits_are_deletes = FALSE;
+ svn_boolean_t modified;
+
+ /* The update wants to delete a node which was locally moved
+ * away. We allow this only if the node wasn't modified
post-move.
+ * If the only post-move changes within a subtree are deletions,
+ * allow the update to delete the entire subtree. */
+ if (kind == svn_kind_dir)
+ SVN_ERR(node_has_local_mods(&modified, &all_edits_are_deletes,
+ TRUE, eb->db, moved_to_abspath,
+ eb->cancel_func, eb->cancel_baton,
+ scratch_pool));
+ else
+ SVN_ERR(svn_wc__internal_file_modified_p(&modified, eb->db,
+ moved_to_abspath,
+ FALSE, scratch_pool));
+ if (modified && !all_edits_are_deletes)
+ tree_conflict->reason =
+ svn_wc_conflict_reason_moved_away_and_edited;
+ else
+ tree_conflict = NULL; /* discard conflict */
+ }
+ }
+ while (tree_conflict &&
+ result->choice != svn_wc_conflict_choose_incoming_move &&
+ result->choice != svn_wc_conflict_choose_new_local_move_target &&
+ result->choice != svn_wc_conflict_choose_delete_is_delete &&
+ result->choice != svn_wc_conflict_choose_postpone);
}
if (tree_conflict != NULL)
@@ -2319,7 +2527,7 @@ add_directory(const char *path,
status, wc_kind, FALSE,
svn_wc_conflict_action_add,
svn_node_dir, db->new_relpath,
- NULL, pool, pool));
+ pool, pool));
}
if (tree_conflict == NULL)
@@ -2544,8 +2752,7 @@ open_directory(const char *path,
SVN_ERR(check_tree_conflict(&tree_conflict, eb, db->local_abspath,
status, wc_kind, TRUE,
svn_wc_conflict_action_edit, svn_node_dir,
- db->new_relpath, db->moved_to_abspath,
- db->pool, pool));
+ db->new_relpath, db->pool, pool));
/* Remember the roots of any locally deleted trees. */
if (tree_conflict != NULL)
@@ -3415,7 +3622,7 @@ add_file(const char *path,
fb->local_abspath,
status, wc_kind, FALSE,
svn_wc_conflict_action_add,
- svn_node_file, fb->new_relpath, NULL,
+ svn_node_file, fb->new_relpath,
scratch_pool, scratch_pool));
}
@@ -3590,8 +3797,7 @@ open_file(const char *path,
SVN_ERR(check_tree_conflict(&tree_conflict, eb, fb->local_abspath,
status, wc_kind, TRUE,
svn_wc_conflict_action_edit, svn_node_file,
- fb->new_relpath, fb->moved_to_abspath,
- fb->pool, scratch_pool));
+ fb->new_relpath, fb->pool, scratch_pool));
/* Is this path the victim of a newly-discovered tree conflict? */
if (tree_conflict != NULL)
Modified: subversion/branches/moves-scan-log/subversion/svn/cl.h
URL:
http://svn.apache.org/viewvc/subversion/branches/moves-scan-log/subversion/svn/cl.h?rev=1205528&r1=1205527&r2=1205528&view=diff
==============================================================================
--- subversion/branches/moves-scan-log/subversion/svn/cl.h (original)
+++ subversion/branches/moves-scan-log/subversion/svn/cl.h Wed Nov 23 18:24:33
2011
@@ -347,10 +347,10 @@ svn_cl__conflict_baton_make(svn_cl__acce
Implements @c svn_wc_conflict_resolver_func_t. */
svn_error_t *
svn_cl__conflict_handler(svn_wc_conflict_result_t **result,
- const svn_wc_conflict_description_t *desc,
+ const svn_wc_conflict_description2_t *desc,
void *baton,
- apr_pool_t *pool);
-
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
/*** Command-line output functions -- printing to the user. ***/
Modified: subversion/branches/moves-scan-log/subversion/svn/conflict-callbacks.c
URL:
http://svn.apache.org/viewvc/subversion/branches/moves-scan-log/subversion/svn/conflict-callbacks.c?rev=1205528&r1=1205527&r2=1205528&view=diff
==============================================================================
--- subversion/branches/moves-scan-log/subversion/svn/conflict-callbacks.c
(original)
+++ subversion/branches/moves-scan-log/subversion/svn/conflict-callbacks.c Wed
Nov 23 18:24:33 2011
@@ -30,6 +30,7 @@
#include "svn_cmdline.h"
#include "svn_client.h"
+#include "svn_dirent_uri.h"
#include "svn_types.h"
#include "svn_pools.h"
@@ -95,7 +96,7 @@ svn_cl__accept_from_word(const char *wor
/* Print on stdout a diff between the 'base' and 'merged' files, if both of
* those are available, else between 'their' and 'my' files, of DESC. */
static svn_error_t *
-show_diff(const svn_wc_conflict_description_t *desc,
+show_diff(const svn_wc_conflict_description2_t *desc,
apr_pool_t *pool)
{
const char *path1, *path2;
@@ -103,18 +104,18 @@ show_diff(const svn_wc_conflict_descript
svn_stream_t *output;
svn_diff_file_options_t *options;
- if (desc->merged_file && desc->base_file)
+ if (desc->merged_file && desc->base_abspath)
{
/* Show the conflict markers to the user */
- path1 = desc->base_file;
+ path1 = desc->base_abspath;
path2 = desc->merged_file;
}
else
{
/* There's no base file, but we can show the
difference between mine and theirs. */
- path1 = desc->their_file;
- path2 = desc->my_file;
+ path1 = desc->their_abspath;
+ path2 = desc->my_abspath;
}
options = svn_diff_file_options_create(pool);
@@ -134,7 +135,7 @@ show_diff(const svn_wc_conflict_descript
/* Print on stdout just the conflict hunks of a diff among the 'base', 'their'
* and 'my' files of DESC. */
static svn_error_t *
-show_conflicts(const svn_wc_conflict_description_t *desc,
+show_conflicts(const svn_wc_conflict_description2_t *desc,
apr_pool_t *pool)
{
svn_diff_t *diff;
@@ -145,16 +146,16 @@ show_conflicts(const svn_wc_conflict_des
options->ignore_eol_style = TRUE;
SVN_ERR(svn_stream_for_stdout(&output, pool));
SVN_ERR(svn_diff_file_diff3_2(&diff,
- desc->base_file,
- desc->my_file,
- desc->their_file,
+ 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_file,
- desc->my_file,
- desc->their_file,
+ desc->base_abspath,
+ desc->my_abspath,
+ desc->their_abspath,
_("||||||| ORIGINAL"),
_("<<<<<<< MINE (select with 'mc')"),
_(">>>>>>> THEIRS (select with 'tc')"),
@@ -175,7 +176,7 @@ show_conflicts(const svn_wc_conflict_des
* return that error. */
static svn_error_t *
open_editor(svn_boolean_t *performed_edit,
- const svn_wc_conflict_description_t *desc,
+ const svn_wc_conflict_description2_t *desc,
svn_cl__conflict_baton_t *b,
apr_pool_t *pool)
{
@@ -223,15 +224,15 @@ open_editor(svn_boolean_t *performed_edi
* return that error. */
static svn_error_t *
launch_resolver(svn_boolean_t *performed_edit,
- const svn_wc_conflict_description_t *desc,
+ const svn_wc_conflict_description2_t *desc,
svn_cl__conflict_baton_t *b,
apr_pool_t *pool)
{
svn_error_t *err;
- err = svn_cl__merge_file_externally(desc->base_file, desc->their_file,
- desc->my_file, desc->merged_file,
- desc->path, b->config, NULL, pool);
+ 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",
@@ -255,13 +256,96 @@ launch_resolver(svn_boolean_t *performed
}
-/* Implement svn_wc_conflict_resolver_func_t; resolves based on
+static svn_error_t *
+pick_move(svn_wc_repos_move_info_t **move,
+ const svn_wc_conflict_description2_t *desc,
+ svn_cmdline_prompt_baton_t *pb,
+ apr_pool_t *scratch_pool)
+{
+ const char *prompt;
+ svn_wc_repos_move_info_t *this_move = desc->suggested_move;
+ int i = 0;
+ int m;
+ apr_pool_t *iterpool;
+
+ prompt = _("Moves found in revision log:\n");
+ do
+ {
+ prompt = apr_pstrcat(scratch_pool, prompt,
+ apr_psprintf(scratch_pool,
+ _(" (%i) %s@%ld -> %s@%ld\n"),
+ i, this_move->moved_from_repos_relpath,
+ this_move->copyfrom_rev,
+ this_move->moved_to_repos_relpath,
+ this_move->revision),
+ (char *)NULL);
+ i++;
+ this_move = this_move->next;
+ }
+ while (this_move);
+
+ prompt = apr_pstrcat(scratch_pool, prompt,
+ _("Enter number to choose incoming move or hit enter "
+ "for previous menu: "),
+ (char *)NULL);
+
+ iterpool = svn_pool_create(scratch_pool);
+ *move = NULL;
+ while (*move == NULL)
+ {
+ const char *answer;
+ svn_error_t *err;
+
+ svn_pool_clear(iterpool);
+
+ SVN_ERR(svn_cmdline_prompt_user2(&answer, prompt, pb, iterpool));
+
+ if (answer[0] == '\0')
+ break;
+
+ err = svn_cstring_atoi(&m, answer);
+ if (err)
+ {
+ if (err->apr_err == SVN_ERR_INCORRECT_PARAMS)
+ {
+ svn_error_clear(err);
+ SVN_ERR(svn_cmdline_fprintf(stderr, iterpool,
+ "'%s' is not a number\n",
+ answer));
+ continue;
+ }
+ else
+ return svn_error_trace(err);
+ }
+
+ if (m < 0 || m >= i)
+ {
+ SVN_ERR(svn_cmdline_fprintf(stderr, iterpool,
+ "Invalid choice (%i)\n", m));
+ continue;
+ }
+
+ *move = desc->suggested_move;
+ i = 0;
+ while (m < i && (*move)->next)
+ {
+ *move = (*move)->next;
+ m++;
+ }
+ }
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+/* Implement svn_wc_conflict_resolver_func2_t; resolves based on
--accept option if given, else by prompting. */
svn_error_t *
svn_cl__conflict_handler(svn_wc_conflict_result_t **result,
- const svn_wc_conflict_description_t *desc,
+ const svn_wc_conflict_description2_t *desc,
void *baton,
- apr_pool_t *pool)
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
{
svn_cl__conflict_baton_t *b = baton;
svn_error_t *err;
@@ -269,7 +353,7 @@ svn_cl__conflict_handler(svn_wc_conflict
/* Start out assuming we're going to postpone the conflict. */
*result = svn_wc_create_conflict_result(svn_wc_conflict_choose_postpone,
- NULL, pool);
+ NULL, scratch_pool);
switch (b->accept_which)
{
@@ -308,10 +392,10 @@ svn_cl__conflict_handler(svn_wc_conflict
}
err = svn_cl__edit_file_externally(desc->merged_file,
- b->editor_cmd, b->config, pool);
+ b->editor_cmd, b->config,
scratch_pool);
if (err && (err->apr_err == SVN_ERR_CL_NO_EXTERNAL_EDITOR))
{
- SVN_ERR(svn_cmdline_fprintf(stderr, pool, "%s\n",
+ SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, "%s\n",
err->message ? err->message :
_("No editor found;"
" leaving all conflicts.")));
@@ -320,7 +404,7 @@ svn_cl__conflict_handler(svn_wc_conflict
}
else if (err && (err->apr_err == SVN_ERR_EXTERNAL_PROGRAM))
{
- SVN_ERR(svn_cmdline_fprintf(stderr, pool, "%s\n",
+ SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, "%s\n",
err->message ? err->message :
_("Error running editor;"
" leaving all conflicts.")));
@@ -335,8 +419,8 @@ svn_cl__conflict_handler(svn_wc_conflict
/* else, fall through to prompting. */
break;
case svn_cl__accept_launch:
- if (desc->base_file && desc->their_file
- && desc->my_file && desc->merged_file)
+ if (desc->base_abspath && desc->their_abspath
+ && desc->my_abspath && desc->merged_file)
{
svn_boolean_t remains_in_conflict;
@@ -346,17 +430,17 @@ svn_cl__conflict_handler(svn_wc_conflict
return SVN_NO_ERROR;
}
- err = svn_cl__merge_file_externally(desc->base_file,
- desc->their_file,
- desc->my_file,
+ err = svn_cl__merge_file_externally(desc->base_abspath,
+ desc->their_abspath,
+ desc->my_abspath,
desc->merged_file,
- desc->path,
+ desc->local_abspath,
b->config,
&remains_in_conflict,
- pool);
+ scratch_pool);
if (err && err->apr_err == SVN_ERR_CL_NO_EXTERNAL_MERGE_TOOL)
{
- SVN_ERR(svn_cmdline_fprintf(stderr, pool, "%s\n",
+ SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, "%s\n",
err->message ? err->message :
_("No merge tool found;"
" leaving all conflicts.")));
@@ -365,7 +449,7 @@ svn_cl__conflict_handler(svn_wc_conflict
}
else if (err && err->apr_err == SVN_ERR_EXTERNAL_PROGRAM)
{
- SVN_ERR(svn_cmdline_fprintf(stderr, pool, "%s\n",
+ SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, "%s\n",
err->message ? err->message :
_("Error running merge tool;"
" leaving all conflicts.")));
@@ -387,7 +471,7 @@ svn_cl__conflict_handler(svn_wc_conflict
/* We're in interactive mode and either the user gave no --accept
option or the option did not apply; let's prompt. */
- subpool = svn_pool_create(pool);
+ subpool = svn_pool_create(scratch_pool);
/* Handle the most common cases, which is either:
@@ -412,25 +496,28 @@ svn_cl__conflict_handler(svn_wc_conflict
if (desc->kind == svn_wc_conflict_kind_text)
SVN_ERR(svn_cmdline_fprintf(stderr, subpool,
_("Conflict discovered in '%s'.\n"),
- desc->path));
+ svn_dirent_local_style(
+ desc->local_abspath, subpool)));
else if (desc->kind == svn_wc_conflict_kind_property)
{
SVN_ERR(svn_cmdline_fprintf(stderr, subpool,
_("Conflict for property '%s' discovered"
" on '%s'.\n"),
- desc->property_name, desc->path));
+ desc->property_name,
+ svn_dirent_local_style(
+ desc->local_abspath, subpool)));
- if ((!desc->my_file && desc->their_file)
- || (desc->my_file && !desc->their_file))
+ if ((!desc->my_abspath && desc->their_abspath)
+ || (desc->my_abspath && !desc->their_abspath))
{
/* One agent wants to change the property, one wants to
delete it. This is not something we can diff, so we
just tell the user. */
svn_stringbuf_t *myval = NULL, *theirval = NULL;
- if (desc->my_file)
+ if (desc->my_abspath)
{
- SVN_ERR(svn_stringbuf_from_file2(&myval, desc->my_file,
+ SVN_ERR(svn_stringbuf_from_file2(&myval, desc->my_abspath,
subpool));
SVN_ERR(svn_cmdline_fprintf(stderr, subpool,
_("They want to delete the property, "
@@ -439,7 +526,7 @@ svn_cl__conflict_handler(svn_wc_conflict
}
else
{
- SVN_ERR(svn_stringbuf_from_file2(&theirval, desc->their_file,
+ SVN_ERR(svn_stringbuf_from_file2(&theirval,
desc->their_abspath,
subpool));
SVN_ERR(svn_cmdline_fprintf(stderr, subpool,
_("They want to change the property value to '%s', "
@@ -456,8 +543,8 @@ svn_cl__conflict_handler(svn_wc_conflict
markers to the user (this is the typical 3-way merge
scenario), or if no base is available, we can show a diff
between mine and theirs. */
- if ((desc->merged_file && desc->base_file)
- || (!desc->base_file && desc->my_file && desc->their_file))
+ if ((desc->merged_file && desc->base_abspath)
+ || (!desc->base_abspath && desc->my_abspath && desc->their_abspath))
diff_allowed = TRUE;
while (TRUE)
@@ -614,7 +701,7 @@ svn_cl__conflict_handler(svn_wc_conflict
"properties.\n\n")));
continue;
}
- else if (! (desc->my_file && desc->base_file &&
desc->their_file))
+ else if (! (desc->my_abspath && desc->base_abspath &&
desc->their_abspath))
{
SVN_ERR(svn_cmdline_fprintf(stderr, subpool,
_("Invalid option; original "
@@ -654,7 +741,7 @@ svn_cl__conflict_handler(svn_wc_conflict
"\n\n")));
continue;
}
- if (desc->base_file && desc->their_file && desc->my_file
+ if (desc->base_abspath && desc->their_abspath && desc->my_abspath
&& desc->merged_file)
{
SVN_ERR(launch_resolver(&performed_edit, desc, b, subpool));
@@ -709,7 +796,7 @@ svn_cl__conflict_handler(svn_wc_conflict
stderr, subpool,
_("Conflict discovered when trying to add '%s'.\n"
"An object of the same name already exists.\n"),
- desc->path));
+ svn_dirent_local_style(desc->local_abspath, subpool)));
prompt = _("Select: (p) postpone, (mf) mine-full, "
"(tf) theirs-full, (h) help:");
@@ -747,6 +834,86 @@ svn_cl__conflict_handler(svn_wc_conflict
}
}
+ else if (desc->kind == svn_wc_conflict_kind_tree &&
+ desc->action == svn_wc_conflict_action_delete &&
+ desc->server_sends_moves_as_copy_plus_delete &&
+ (desc->reason == svn_wc_conflict_reason_moved_away ||
+ desc->reason == svn_wc_conflict_reason_moved_here ||
+ desc->reason == svn_wc_conflict_reason_deleted))
+ {
+ const char *answer;
+ const char *prompt;
+
+ if (!desc->suggested_move)
+ SVN_ERR(svn_cmdline_fprintf(
+ stderr, subpool,
+ _("Tree conflict discovered when trying to delete\n'%s'\n"
+ "Server is sending moves as copy+delete.\n"
+ "Maybe this incoming delete is part of a move?\n"),
+ svn_dirent_local_style(desc->local_abspath, subpool)));
+
+ prompt = _("Select: (p) postpone, (s) suggest-move,\n"
+ " (a) ask-move, (d) is-delete, (h) help: ");
+
+ while (1)
+ {
+ svn_pool_clear(subpool);
+
+ if (desc->suggested_move)
+ {
+ svn_wc_repos_move_info_t *move;
+
+ SVN_ERR(pick_move(&move, desc, b->pb, subpool));
+ if (move)
+ {
+ (*result)->choice = svn_wc_conflict_choose_incoming_move;
+ (*result)->incoming_move = move;
+ break;
+ }
+ }
+
+ SVN_ERR(svn_cmdline_prompt_user2(&answer, prompt, b->pb, subpool));
+
+ if (strcmp(answer, "h") == 0 || strcmp(answer, "?") == 0)
+ {
+ SVN_ERR(svn_cmdline_fprintf(stderr, subpool,
+ _(" (p) postpone - resolve the conflict later\n"
+ " (s) suggest-move - suggest moves found in revision log\n"
+ " (a) ask-move - specify server-side move yourself\n"
+ " (d) is-delete - treat incoming delete as delete\n"
+ " (h) help - show this help\n\n")));
+ }
+
+ if (strcmp(answer, "p") == 0 || strcmp(answer, ":-P") == 0)
+ {
+ (*result)->choice = svn_wc_conflict_choose_postpone;
+ break;
+ }
+ if (strcmp(answer, "a") == 0)
+ {
+ SVN_ERR(svn_cmdline_fprintf(stderr, subpool,
+ _("Sorry, 'ask-move' is not implemented yet.\n")));
+ continue;
+ }
+ if (strcmp(answer, "d") == 0)
+ {
+ (*result)->choice = svn_wc_conflict_choose_delete_is_delete;
+ break;
+ }
+
+ if (strcmp(answer, "s") == 0)
+ {
+ if (desc->suggested_move)
+ continue;
+ else
+ {
+ (*result)->choice =
svn_wc_conflict_choose_scan_log_for_moves;
+ break;
+ }
+ }
+ }
+ }
+
else /* other types of conflicts -- do nothing about them. */
{
(*result)->choice = svn_wc_conflict_choose_postpone;
Modified: subversion/branches/moves-scan-log/subversion/svn/main.c
URL:
http://svn.apache.org/viewvc/subversion/branches/moves-scan-log/subversion/svn/main.c?rev=1205528&r1=1205527&r2=1205528&view=diff
==============================================================================
--- subversion/branches/moves-scan-log/subversion/svn/main.c (original)
+++ subversion/branches/moves-scan-log/subversion/svn/main.c Wed Nov 23
18:24:33 2011
@@ -2597,6 +2597,8 @@ main(int argc, const char *argv[])
the user said to postpone. */
ctx->conflict_func = NULL;
ctx->conflict_baton = NULL;
+ ctx->conflict_func2 = NULL;
+ ctx->conflict_baton2 = NULL;
}
else
{
@@ -2621,8 +2623,10 @@ main(int argc, const char *argv[])
pool, "svn: ");
}
- ctx->conflict_func = svn_cl__conflict_handler;
- ctx->conflict_baton = svn_cl__conflict_baton_make(
+ ctx->conflict_func = NULL;
+ ctx->conflict_baton = NULL;
+ ctx->conflict_func2 = svn_cl__conflict_handler;
+ ctx->conflict_baton2 = svn_cl__conflict_baton_make(
opt_state.accept_which,
ctx->config,
opt_state.editor_cmd,