You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@subversion.apache.org by ne...@apache.org on 2010/07/01 19:51:37 UTC
svn commit: r959735 [1/2] - in /subversion/trunk: notes/tree-conflicts/
subversion/libsvn_client/ subversion/libsvn_wc/ subversion/tests/cmdline/
Author: neels
Date: Thu Jul 1 17:51:36 2010
New Revision: 959735
URL: http://svn.apache.org/viewvc?rev=959735&view=rev
Log:
Improve selected add-vs.-add tree conflict situations -- add as in unversioned
obstruction, add, copy-here, move-here, upon update, switch and checkout.
Take advantage of the wc-ng we already have to teach update_editor:add_file()
and close_file() to treat many add-vs-add conflicts much better. Note that
this is not changing *_directory() yet.
A very detailed description of this patch can be found in
notes/tree-conflicts/all-add-vs-add-tree-conflicts.txt
, which is being added in this revision.
* subversion/libsvn_wc/update_editor.c
(complete_directory):
Don't remove not_present nodes that were created to mark an add-vs.-add
conflict against unversioned.
(struct file_baton):
New flag ADDING_BASE_UNDER_LOCAL_ADD.
(create_tree_conflict): New function.
(check_tree_conflict):
Out-source the conflict struct creation to create_tree_conflict() above.
Fix the comment.
(build_write_revert_base_props): New function.
(add_file):
Remove some obsolete logic, but, in essence, just streamline and flag a
few more tree conflicts. Also use the COPYFROM_* args to flag conflicts
where local add hits an incoming copy, and to omit conflicts where local
and incoming are identical copies.
(close_file):
Detect whether incoming adds are symlinks, and flag tree conflicts if
symlink-ness mismatches local. Query file_baton's new parameter
ADDING_BASE_UNDER_LOCAL_ADD, and pull the incoming add directly into the
revert-base slots, if appropriate, not changing the WORKING/ACTUAL data.
* subversion/libsvn_client/info.c
(info_found_node_callback):
Make sure that unversioned nodes can show a tree conflict on 'svn info'.
* notes/tree-conflicts/all-add-vs-add-tree-conflicts.txt:
New file.
* subversion/tests/cmdline/checkout_tests.py
(co_with_obstructing_local_adds),
* subversion/tests/cmdline/svntest/actions.py
(run_and_validate_lock),
* subversion/tests/cmdline/update_tests.py
(forced_update_failures, update_with_obstructing_additions):
* subversion/tests/cmdline/switch_tests.py
(obstructed_switch, forced_switch_failures,
switch_with_obstructing_local_adds):
Change these tests to match the new tree conflicts behavior. Some of the
tests are rewritten from scratch (using svntest.factory).
* subversion/tests/cmdline/tree_conflict_tests.py
(incoming_paths, localmod_paths, modify, set_up_repos, ensure_tree_conflict):
Add 'F3' and a flag to the test suite so that the incoming copy always
mismatches the local copy (for files only, so far).
Added:
subversion/trunk/notes/tree-conflicts/all-add-vs-add-tree-conflicts.txt
Modified:
subversion/trunk/subversion/libsvn_client/info.c
subversion/trunk/subversion/libsvn_wc/update_editor.c
subversion/trunk/subversion/tests/cmdline/checkout_tests.py
subversion/trunk/subversion/tests/cmdline/switch_tests.py
subversion/trunk/subversion/tests/cmdline/tree_conflict_tests.py
subversion/trunk/subversion/tests/cmdline/update_tests.py
Added: subversion/trunk/notes/tree-conflicts/all-add-vs-add-tree-conflicts.txt
URL: http://svn.apache.org/viewvc/subversion/trunk/notes/tree-conflicts/all-add-vs-add-tree-conflicts.txt?rev=959735&view=auto
==============================================================================
--- subversion/trunk/notes/tree-conflicts/all-add-vs-add-tree-conflicts.txt (added)
+++ subversion/trunk/notes/tree-conflicts/all-add-vs-add-tree-conflicts.txt Thu Jul 1 17:51:36 2010
@@ -0,0 +1,114 @@
+This file describes handling of all add-vs.-add tree conflict situations
+on 2010-jul-01. Maybe someone will update it and put another date here some
+day, maybe not.
+
+Add-vs.-add tree conflicts handling introduces a new meaning for
+svn_wc__db_status_not_present. Apart from the usual "node deleted and
+committed and parent not updated yet", a not_present BASE_NODE can also mean:
+This node was intended to be added during update/switch/checkout, but there
+was an unversioned obstruction in the local working copy at that path. A tree
+conflict with reason == svn_wc_conflict_reason_unversioned is flagged on such
+nodes. If such a tree conflict with _reason_unversioned is found on a
+not_present node during update/checkout/switch, and if furthermore there is no
+unversioned obstruction in the WC anymore (the user moved it away), then
+complete_directory() automatically removes the tree conflict, after which the
+running update/switch/checkout will pull that node in.
+
+
+All add vs. add cases:
+----------------------
+(see a legend below)
+
+all add-vs.-add | incoming ADD of a | incoming COPY of a |
+tree conflict | file | symlink | dir (2) | file | symlink | dir (2) |
+situations |co up sw |co up sw |co up sw |co up sw |co up sw |co up sw |
+------------------+---------+---------+---------+---------+---------+---------+
+locally file |?C*?C*?C*|?C*?C*?C*|:( :( :( |?C*?C*?C*|?C*?C*?C*|:( :( :( |
+UNVER- symlink |?C*?C*?C*|?C*?C*?C*|:( :( :( |?C*?C*?C*|?C*?C*?C*|:( :( :( |
+SIONED dir |?C*?C*?C*|?C*?C*?C*|:( :( :( |?C*?C*?C*|?C*?C*?C*|:( :( :( |
+------------------+---------+---------+---------+---------+---------+---------+
+locally file |Ex Ex RC*|RC*RC*RC*|:( :( :( |Ex RC*RC*|RC*RC*RC*|:( :( :( |
+ADDED symlink |RC*RC*RC*|Ex Ex RC*|:( :( :( |RC*RC*RC*|Ex RC*RC*|:( :( :( |
+ (3) dir |:( :( :( |:( :( :( |Ex Ex Ex |:( :( :( |:( :( :( |Ex Ex Ex |
+------------------+---------+---------+---------+---------+---------+---------+
+DIFFERENT file |RC*RC*RC*|RC*RC*RC*|:( :( :( |RC*RC*RC*|RC*RC*RC*|:( :( :( |
+COPY symlink |RC*RC*RC*|RC*RC*RC*|:( :( :( |RC*RC*RC*|RC*RC*RC*|:( :( :( |
+of a (3) dir |:( :( :( |:( :( :( |AC AC AC |:( :( :( |:( :( :( |AC AC AC |
+------------------+---------+---------+---------+---------+---------+---------+
+IDENTICAL file | |RC*Ex*RC*| | |
+COPY of symlink | (see above) | |RC*Ex*RC*| |
+a (1) (3) dir | | | |AC AC AC |
+
+
+"*" The cases most recently changed are marked with a * on their right.
+
+(1) Where both local and incoming are copies and both copies are from the
+exact same copyfrom URL@REV, the node kinds must also match. The colums
+against incoming simple-add are identical to "different copy" and omitted.
+
+(2) Since this patch only modifies add_file() and close_file() for starters,
+these columns are not affected by this patch.
+
+(3) Since we can only deal with replace-by-different-kind properly after we
+moved to single-db, these rows are not affected by this patch.
+
+
+In the table above, each cell shows three letter pairs for,
+checkout, update, switch operations, respectively.
+
+The letter pairs are:
+ RC = tree conflict where local add/copy becomes a schedule-replace,
+ i.e. status reports:
+ 'R C' -> that's why this is called 'RC'
+ ' > local add, incoming add upon update|switch'
+ Resolving is easy. 'svn revert' gives theirs, 'svn commit' replaces
+ theirs with mine. (this is new!)
+
+ ?C = tree conflict against locally unversioned, adds a not_present BASE
+ node. Status reports
+ '? C'
+ ' > local unversioned, incoming add upon update|switch'
+ Resolving is easy. Just move away the obstruction and run 'update'.
+ The tree conflict is removed automagically. (this is new!)
+
+ Ex = "Existed": local add and incoming add are seen as identical, there
+ will be a text/prop merge and possibly a text conflict.
+
+ :( = svn bails with an error, interrupting the operation. The parent is
+ left '!' == incomplete. Removing the obstruction and running the
+ operation again fixes the node as expected.
+
+ AC = Though a tree conflict is flagged, the node is skipped and the working
+ copy is left in a state where it doesn't notice that the incoming add
+ is still missing. After a revert, it is necessary to update back to an
+ older revision and then again back to HEAD to restore the file.
+ This is the old behaviour for all caught add-vs-add tree conflicts.
+ i.e. status reports
+ 'A C'
+ or 'A + C',
+ ' > local add, incoming add upon update|switch'
+
+So "Ex Ex RC*" would mean:
+ "During checkout and update, the local add is merged with the incoming add.
+ During switch, an easily resolvable tree conflict is flagged.
+ This patch only changes the 'switch' behavior."
+
+A note on checkout: It *is* possible to checkout a URL onto a working copy of
+the same URL, which can have local adds scheduled. Then, checkout presumably
+is like an update -- almost. Note the incoming copy columns during checkout
+saying 'Ex' and 'RC' in some wrong places. That's because during checkout,
+add_file() gets no copy_from info and the incoming action always looks like a
+simple add. #TODO: we should discuss this non-feature.
+Note that tree conflicts from a checkout wrongly say "upon update".
+
+Notably, earlier servers provide no copy_from info to add_file() either, so
+'update' then acts the same way as 'checkout' -- fails to flag same-kind tree
+conflicts with local-add vs. incoming-copy, and fails to not flag conflicts on
+matching copies. #TODO: we should discuss this, too.
+
+The reasoning with 'switch' is that when 'switch' merges a local add
+with an incoming add, it is "lost" in the sense that switching back wants to
+simply delete that node, not re-schedule it for addition. So switch should
+*always* flag tree conflicts, never merge local adds away, so that the user is
+(hopefully not annoyed but) made aware of the situation.
+
Modified: subversion/trunk/subversion/libsvn_client/info.c
URL: http://svn.apache.org/viewvc/subversion/trunk/subversion/libsvn_client/info.c?rev=959735&r1=959734&r2=959735&view=diff
==============================================================================
--- subversion/trunk/subversion/libsvn_client/info.c (original)
+++ subversion/trunk/subversion/libsvn_client/info.c Thu Jul 1 17:51:36 2010
@@ -335,7 +335,8 @@ info_found_node_callback(const char *loc
err = build_info_for_entry(&info, fe_baton->wc_ctx, local_abspath,
pool);
- if (err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
+ if (err && (err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND
+ || err->apr_err == SVN_ERR_ENTRY_NOT_FOUND))
{
/* Check for a tree conflict, and if there is one, send a minimal
info struct. */
Modified: subversion/trunk/subversion/libsvn_wc/update_editor.c
URL: http://svn.apache.org/viewvc/subversion/trunk/subversion/libsvn_wc/update_editor.c?rev=959735&r1=959734&r2=959735&view=diff
==============================================================================
--- subversion/trunk/subversion/libsvn_wc/update_editor.c (original)
+++ subversion/trunk/subversion/libsvn_wc/update_editor.c Thu Jul 1 17:51:36 2010
@@ -886,9 +886,30 @@ complete_directory(struct edit_baton *eb
number different from the target revision of the update means the
update never mentioned the item, so the entry should be
removed. */
- if (status == svn_wc__db_status_not_present
- || (status == svn_wc__db_status_absent
- && revnum != *eb->target_revision))
+ if (status == svn_wc__db_status_not_present)
+ {
+ /* "Usually", not_present nodes indicate that an 'svn delete' was
+ * committed and its parent has not been updated yet. We have just
+ * updated the parent and so the not_present BASE_NODE should go
+ * away.
+ * However, not_present can also mean that 'update' wanted to add a
+ * node and found an unversioned obstruction at that path. We don't
+ * want to remove such not_present state, so check if there is a
+ * tree conflict flagged against an unversioned node and leave the
+ * BASE_NODE alone if so.
+ * Note that add_file() automatically fixes such an
+ * added-not_present node when it finds the obstruction gone. */
+ const svn_wc_conflict_description2_t *tree_conflict;
+ SVN_ERR(svn_wc__get_tree_conflict(&tree_conflict,
+ eb->wc_ctx,
+ node_abspath,
+ iterpool, iterpool));
+ if (!tree_conflict
+ || tree_conflict->reason != svn_wc_conflict_reason_unversioned)
+ SVN_ERR(svn_wc__db_base_remove(eb->db, node_abspath, iterpool));
+ }
+ else if (status == svn_wc__db_status_absent
+ && revnum != *eb->target_revision)
{
SVN_ERR(svn_wc__db_base_remove(eb->db, node_abspath, iterpool));
}
@@ -1042,6 +1063,11 @@ struct file_baton
/* Bump information for the directory this file lives in */
struct bump_dir_info *bump_info;
+
+ /* This is set when there is an incoming add of a file/symlink node onto a
+ * locally added node of different identity (add-vs-add tree conflict). */
+ svn_boolean_t adding_base_under_local_add;
+
};
@@ -1548,6 +1574,200 @@ tree_has_local_mods(svn_boolean_t *modif
/* Indicates an unset svn_wc_conflict_reason_t. */
#define SVN_WC_CONFLICT_REASON_NONE (svn_wc_conflict_reason_t)(-1)
+/* Create a tree conflict struct.
+ *
+ * The REASON is stored directly in the tree conflict info.
+ *
+ * All temporary allocactions are be made in SCRATCH_POOL, while allocations
+ * needed for the returned conflict struct are made in RESULT_POOL.
+ *
+ * All other parameters are identical to and described by
+ * check_tree_conflict(), with the slight modification that this function
+ * relies on the reason passed in REASON instead of actively looking for one. */
+static svn_error_t *
+create_tree_conflict(svn_wc_conflict_description2_t **pconflict,
+ struct edit_baton *eb,
+ const char *local_abspath,
+ svn_wc_conflict_reason_t reason,
+ svn_wc_conflict_action_t action,
+ svn_node_kind_t their_node_kind,
+ const char *their_relpath,
+ apr_pool_t *result_pool, apr_pool_t *scratch_pool)
+{
+ const char *repos_root_url = NULL;
+ const char *left_repos_relpath;
+ svn_revnum_t left_revision;
+ svn_node_kind_t left_kind;
+ const char *right_repos_relpath;
+ const char *added_repos_relpath = NULL;
+ svn_node_kind_t conflict_node_kind;
+ svn_wc_conflict_version_t *src_left_version;
+ svn_wc_conflict_version_t *src_right_version;
+
+ *pconflict = NULL;
+
+ SVN_ERR_ASSERT(reason != SVN_WC_CONFLICT_REASON_NONE);
+
+ /* Get the source-left information, i.e. the local state of the node
+ * before any changes were made to the working copy, i.e. the state the
+ * node would have if it was reverted. */
+ if (reason == svn_wc_conflict_reason_added)
+ {
+ svn_wc__db_status_t added_status;
+
+ /* ###TODO: It would be nice to tell the user at which URL and
+ * ### revision source-left was empty, which could be quite difficult
+ * ### to code, and is a slight theoretical leap of the svn mind.
+ * ### Update should show
+ * ### URL: svn_wc__db_scan_addition( &repos_relpath )
+ * ### REV: The base revision of the parent of before this update
+ * ### started
+ * ### ### BUT what if parent was updated/switched away with
+ * ### ### depth=empty after this node was added?
+ * ### Switch should show
+ * ### URL: scan_addition URL of before this switch started
+ * ### REV: same as above */
+
+ /* In case of a local addition, source-left is non-existent / empty. */
+ left_kind = svn_node_none;
+ left_revision = SVN_INVALID_REVNUM;
+ left_repos_relpath = NULL;
+
+ /* Still get the repository root needed by both 'update' and 'switch',
+ * and the would-be repos_relpath needed to construct the source-right
+ * in case of an 'update'. Check sanity while we're at it. */
+ SVN_ERR(svn_wc__db_scan_addition(&added_status, NULL,
+ &added_repos_relpath,
+ &repos_root_url,
+ NULL, NULL, NULL, NULL, NULL,
+ eb->db, local_abspath,
+ result_pool, scratch_pool));
+
+ /* This better really be an added status. */
+ SVN_ERR_ASSERT(added_status == svn_wc__db_status_added
+ || added_status == svn_wc__db_status_obstructed_add
+ || added_status == svn_wc__db_status_copied
+ || added_status == svn_wc__db_status_moved_here);
+ }
+ else if (reason == svn_wc_conflict_reason_unversioned)
+ {
+ /* Obstructed by an unversioned node. Source-left is
+ * non-existent/empty. */
+ left_kind = svn_node_none;
+ left_revision = SVN_INVALID_REVNUM;
+ left_repos_relpath = NULL;
+ repos_root_url = eb->repos_root;
+ }
+ else
+ {
+ /* A BASE node should exist. */
+ svn_wc__db_kind_t base_kind;
+
+ /* If anything else shows up, then this assertion is probably naive
+ * and that other case should also be handled. */
+ SVN_ERR_ASSERT(reason == svn_wc_conflict_reason_edited
+ || reason == svn_wc_conflict_reason_deleted
+ || reason == svn_wc_conflict_reason_replaced
+ || reason == svn_wc_conflict_reason_obstructed);
+
+ SVN_ERR(svn_wc__db_base_get_info(NULL, &base_kind,
+ &left_revision,
+ &left_repos_relpath,
+ &repos_root_url,
+ NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL,
+ eb->db,
+ local_abspath,
+ result_pool,
+ scratch_pool));
+ /* Translate the node kind. */
+ if (base_kind == svn_wc__db_kind_file
+ || base_kind == svn_wc__db_kind_symlink)
+ left_kind = svn_node_file;
+ else if (base_kind == svn_wc__db_kind_dir)
+ left_kind = svn_node_dir;
+ else
+ SVN_ERR_MALFUNCTION();
+ }
+
+ SVN_ERR_ASSERT(strcmp(repos_root_url, eb->repos_root) == 0);
+
+ /* Find the source-right information, i.e. the state in the repository
+ * to which we would like to update. */
+ if (eb->switch_relpath)
+ {
+ /* If this is a 'switch' operation, try to construct the switch
+ * target's REPOS_RELPATH. */
+ if (their_relpath != NULL)
+ right_repos_relpath = their_relpath;
+ else
+ {
+ /* The complete source-right URL is not available, but it
+ * is somewhere below the SWITCH_URL. For now, just go
+ * without it.
+ * ### TODO: Construct a proper THEIR_URL in some of the
+ * delete cases that still pass NULL for THEIR_URL when
+ * calling this function. Do that on the caller's side. */
+ right_repos_relpath = eb->switch_relpath;
+ right_repos_relpath = apr_pstrcat(result_pool, right_repos_relpath,
+ "_THIS_IS_INCOMPLETE", NULL);
+ }
+ }
+ else
+ {
+ /* This is an 'update', so REPOS_RELPATH would be the same as for
+ * source-left. However, we don't have a source-left for locally
+ * added files. */
+ right_repos_relpath = (reason == svn_wc_conflict_reason_added ?
+ added_repos_relpath : left_repos_relpath);
+ if (! right_repos_relpath)
+ right_repos_relpath = their_relpath;
+ }
+
+ SVN_ERR_ASSERT(right_repos_relpath != NULL);
+
+ /* Determine PCONFLICT's overall node kind, which is not allowed to be
+ * svn_node_none. We give it the source-right revision (THEIR_NODE_KIND)
+ * -- unless source-right is deleted and hence == svn_node_none, in which
+ * case we take it from source-left, which has to be the node kind that
+ * was deleted. */
+ conflict_node_kind = (action == svn_wc_conflict_action_delete ?
+ left_kind : their_node_kind);
+ SVN_ERR_ASSERT(conflict_node_kind == svn_node_file
+ || conflict_node_kind == svn_node_dir);
+
+
+ /* Construct the tree conflict info structs. */
+
+ if (left_repos_relpath == NULL)
+ /* A locally added or unversioned path in conflict with an incoming add.
+ * Send an 'empty' left revision. */
+ src_left_version = NULL;
+ else
+ src_left_version = svn_wc_conflict_version_create(repos_root_url,
+ left_repos_relpath,
+ left_revision,
+ left_kind,
+ result_pool);
+
+ src_right_version = svn_wc_conflict_version_create(repos_root_url,
+ right_repos_relpath,
+ *eb->target_revision,
+ their_node_kind,
+ result_pool);
+
+ *pconflict = svn_wc_conflict_description_create_tree2(
+ local_abspath, conflict_node_kind,
+ eb->switch_relpath ?
+ svn_wc_operation_switch : svn_wc_operation_update,
+ src_left_version, src_right_version, result_pool);
+ (*pconflict)->action = action;
+ (*pconflict)->reason = reason;
+
+ return SVN_NO_ERROR;
+}
+
+
/* Check whether the incoming change ACTION on FULL_PATH would conflict with
* LOCAL_ABSPATH's scheduled change. If so, then raise a tree conflict with
* LOCAL_ABSPATH as the victim.
@@ -1555,24 +1775,20 @@ tree_has_local_mods(svn_boolean_t *modif
* The edit baton EB gives information including whether the operation is
* an update or a switch.
*
- * If PCONFLICT is not null, set *PCONFLICT to the conflict description if
- * there is one or else to null.
+ * If a tree conflict reason was found for the incoming action, the resulting
+ * tree conflict info is returned in *PCONFLICT. PCONFLICT must be non-NULL,
+ * while *PCONFLICT is always overwritten.
*
- * THEIR_NODE_KIND is the node kind reflected by the incoming edit
+ * THEIR_NODE_KIND should be the node kind reflected by the incoming edit
* function. E.g. dir_opened() should pass svn_node_dir, etc.
* In some cases of delete, svn_node_none may be used here.
*
- * THEIR_RELPATH is the involved node's repository relative path on the
- * source-right side, the side that the target should become after the
- * update. Simply put, that's the URL obtained from the node's
- * dir_baton->new_relpath or file_baton->new_relpath (but it's more
- * complex for a delete).
- *
- * ACCEPT_DELETED is true if one of the ancestors got tree conflicted, but
- * the operation continued updating a deleted base tree.
+ * THEIR_RELPATH should be the involved node's repository-relative path on the
+ * source-right side, the side that the target should become after the update.
+ * Simply put, that's the URL obtained from the node's dir_baton->new_relpath
+ * or file_baton->new_relpath (but it's more complex for a delete).
*
- * Tree conflict use cases are described in issue #2282 and in
- * notes/tree-conflicts/detection.txt.
+ * All allocations are made in POOL.
*/
static svn_error_t *
check_tree_conflict(svn_wc_conflict_description2_t **pconflict,
@@ -1754,162 +1970,12 @@ check_tree_conflict(svn_wc_conflict_desc
/* A conflict was detected. Append log commands to the log accumulator
* to record it. */
- {
- const char *repos_root_url = NULL;
- const char *left_repos_relpath;
- svn_revnum_t left_revision;
- svn_node_kind_t left_kind;
- const char *right_repos_relpath;
- const char *added_repos_relpath = NULL;
- svn_node_kind_t conflict_node_kind;
- svn_wc_conflict_version_t *src_left_version;
- svn_wc_conflict_version_t *src_right_version;
-
- /* Get the source-left information, i.e. the local state of the node
- * before any changes were made to the working copy, i.e. the state the
- * node would have if it was reverted. */
- if (reason == svn_wc_conflict_reason_added)
- {
- svn_wc__db_status_t added_status;
-
- /* ###TODO: It would be nice to tell the user at which URL and
- * ### revision source-left was empty, which could be quite difficult
- * ### to code, and is a slight theoretical leap of the svn mind.
- * ### Update should show
- * ### URL: svn_wc__db_scan_addition( &repos_relpath )
- * ### REV: The base revision of the parent of before this update
- * ### started
- * ### ### BUT what if parent was updated/switched away with
- * ### ### depth=empty after this node was added?
- * ### Switch should show
- * ### URL: scan_addition URL of before this switch started
- * ### REV: same as above */
-
- /* In case of a local addition, source-left is non-existent / empty. */
- left_kind = svn_node_none;
- left_revision = SVN_INVALID_REVNUM;
- left_repos_relpath = NULL;
-
- /* Still get the repository root needed by both 'update' and 'switch',
- * and the would-be repos_relpath needed to construct the source-right
- * in case of an 'update'. Check sanity while we're at it. */
- SVN_ERR(svn_wc__db_scan_addition(&added_status, NULL,
- &added_repos_relpath,
- &repos_root_url,
- NULL, NULL, NULL, NULL, NULL,
- eb->db, local_abspath, pool, pool));
-
- /* This better really be an added status. */
- SVN_ERR_ASSERT(added_status == svn_wc__db_status_added
- || added_status == svn_wc__db_status_obstructed_add
- || added_status == svn_wc__db_status_copied
- || added_status == svn_wc__db_status_moved_here);
- }
- else
- {
- /* A BASE node should exist. */
- svn_wc__db_kind_t base_kind;
-
- /* If anything else shows up, then this assertion is probably naive
- * and that other case should also be handled. */
- SVN_ERR_ASSERT(reason == svn_wc_conflict_reason_edited
- || reason == svn_wc_conflict_reason_deleted
- || reason == svn_wc_conflict_reason_replaced
- || reason == svn_wc_conflict_reason_obstructed);
-
- SVN_ERR(svn_wc__db_base_get_info(NULL, &base_kind,
- &left_revision,
- &left_repos_relpath,
- &repos_root_url,
- NULL, NULL, NULL, NULL, NULL, NULL,
- NULL, NULL, NULL, NULL,
- eb->db,
- local_abspath,
- pool,
- pool));
- /* Translate the node kind. */
- if (base_kind == svn_wc__db_kind_file
- || base_kind == svn_wc__db_kind_symlink)
- left_kind = svn_node_file;
- else if (base_kind == svn_wc__db_kind_dir)
- left_kind = svn_node_dir;
- else
- SVN_ERR_MALFUNCTION();
- }
-
-
- /* Find the source-right information, i.e. the state in the repository
- * to which we would like to update. */
- if (eb->switch_relpath)
- {
- /* If this is a 'switch' operation, try to construct the switch
- * target's REPOS_RELPATH. */
- if (their_relpath != NULL)
- right_repos_relpath = their_relpath;
- else
- {
- /* The complete source-right URL is not available, but it
- * is somewhere below the SWITCH_URL. For now, just go
- * without it.
- * ### TODO: Construct a proper THEIR_URL in some of the
- * delete cases that still pass NULL for THEIR_URL when
- * calling this function. Do that on the caller's side. */
- right_repos_relpath = eb->switch_relpath;
- right_repos_relpath = apr_pstrcat(pool, right_repos_relpath,
- "_THIS_IS_INCOMPLETE", NULL);
- }
- }
- else
- {
- /* This is an 'update', so REPOS_RELPATH would be the same as for
- * source-left. However, we don't have a source-left for locally
- * added files. */
- right_repos_relpath = (reason == svn_wc_conflict_reason_added ?
- added_repos_relpath : left_repos_relpath);
- }
-
- /* Determine PCONFLICT's overall node kind, which is not allowed to be
- * svn_node_none. We give it the source-right revision (THEIR_NODE_KIND)
- * -- unless source-right is deleted and hence == svn_node_none, in which
- * case we take it from source-left, which has to be the node kind that
- * was deleted. */
- conflict_node_kind = (action == svn_wc_conflict_action_delete ?
- left_kind : their_node_kind);
- SVN_ERR_ASSERT(conflict_node_kind == svn_node_file
- || conflict_node_kind == svn_node_dir);
-
-
- /* Construct the tree conflict info structs. */
-
- if (left_repos_relpath == NULL)
- /* A locally added path in conflict with an incoming add.
- * Send an 'empty' left revision. */
- src_left_version = NULL;
- else
- src_left_version = svn_wc_conflict_version_create(repos_root_url,
- left_repos_relpath,
- left_revision,
- left_kind,
- pool);
-
- src_right_version = svn_wc_conflict_version_create(repos_root_url,
- right_repos_relpath,
- *eb->target_revision,
- their_node_kind,
- pool);
-
- *pconflict = svn_wc_conflict_description_create_tree2(
- local_abspath, conflict_node_kind,
- eb->switch_relpath ?
- svn_wc_operation_switch : svn_wc_operation_update,
- src_left_version, src_right_version, pool);
- (*pconflict)->action = action;
- (*pconflict)->reason = reason;
- }
-
- return SVN_NO_ERROR;
+ return svn_error_return(create_tree_conflict(pconflict, eb, local_abspath,
+ reason, action, their_node_kind,
+ their_relpath, pool, pool));
}
+
/* If LOCAL_ABSPATH is inside a conflicted tree, set *CONFLICTED to TRUE,
* Otherwise set *CONFLICTED to FALSE. Use SCRATCH_POOL for temporary
* allocations.
@@ -2870,6 +2936,23 @@ build_write_base_props(svn_skel_t **work
scratch_pool));
}
+static svn_error_t *
+build_write_revert_base_props(svn_skel_t **work_item,
+ const char *local_abspath,
+ svn_wc__db_kind_t kind,
+ apr_hash_t *revert_base_props,
+ apr_pool_t *scratch_pool)
+{
+ const char *props_abspath;
+
+ SVN_ERR(svn_wc__prop_path(&props_abspath, local_abspath, kind,
+ svn_wc__props_revert, scratch_pool));
+ return svn_error_return(svn_wc__wq_build_write_old_props(work_item,
+ props_abspath,
+ revert_base_props,
+ scratch_pool));
+}
+
/* Set *WORK_ITEM to a new work item to write an old-style propsfile for the
specified ACTUAL properties. If ACTUAL_PROPS is NULL, then the old-style
@@ -3687,7 +3770,13 @@ add_file(const char *path,
svn_wc__db_status_t status;
apr_pool_t *subpool;
svn_boolean_t already_conflicted;
+ svn_boolean_t versioned_locally_and_present;
svn_error_t *err;
+ svn_wc_conflict_description2_t *tree_conflict = NULL;
+
+ /* Skip the initial '/' */
+ const char *copyfrom_relpath = (copyfrom_path && copyfrom_path[0] ?
+ copyfrom_path+1 : copyfrom_path);
/* Semantic check. Either both "copyfrom" args are valid, or they're
NULL and SVN_INVALID_REVNUM. A mixture is illegal semantics. */
@@ -3718,24 +3807,6 @@ add_file(const char *path,
subpool = svn_pool_create(pool);
- /* Is this path a conflict victim? */
- SVN_ERR(node_already_conflicted(&already_conflicted, eb->db,
- fb->local_abspath, subpool));
- if (already_conflicted)
- {
- SVN_ERR(remember_skipped_tree(eb, fb->local_abspath));
-
- fb->skip_this = TRUE;
- fb->already_notified = TRUE;
-
- do_notification(eb, fb->local_abspath, svn_node_unknown,
- svn_wc_notify_skip, subpool);
-
- svn_pool_destroy(subpool);
-
- return SVN_NO_ERROR;
- }
-
/* It may not be named the same as the administrative directory. */
if (svn_wc_is_adm_dir(fb->name, pool))
return svn_error_createf(
@@ -3760,48 +3831,111 @@ add_file(const char *path,
svn_error_clear(err);
wc_kind = svn_wc__db_kind_unknown;
status = svn_wc__db_status_normal;
+
+ versioned_locally_and_present = FALSE;
}
+ else
+ versioned_locally_and_present = IS_NODE_PRESENT(status);
- /* An obstructing dir (or unknown, just to be paranoid) is an error. */
- if (kind == svn_node_dir || kind == svn_node_unknown ||
- (wc_kind != svn_wc__db_kind_unknown &&
- wc_kind != svn_wc__db_kind_file &&
- wc_kind != svn_wc__db_kind_symlink && IS_NODE_PRESENT(status)))
+
+ /* Is this path a conflict victim? */
+ SVN_ERR(node_already_conflicted(&already_conflicted, eb->db,
+ fb->local_abspath, subpool));
+ if (already_conflicted)
{
- fb->already_notified = TRUE;
- do_notification(eb, fb->local_abspath, svn_node_file,
- svn_wc_notify_update_obstruction, pool);
+ svn_boolean_t do_skip = TRUE;
- return svn_error_createf(
- SVN_ERR_WC_OBSTRUCTED_UPDATE, NULL,
- _("Failed to add file '%s': a non-file object of the "
- "same name already exists"),
- svn_dirent_local_style(fb->local_abspath, subpool));
- }
+ /* A conflict is flagged. Now let's do some user convenience.
+ * When we flagged a tree conflict for a local unversioned node
+ * vs. an incoming add, and we find that this unversioned node is
+ * no longer in the way, automatically pull in the versioned node
+ * and remove the conflict marker. */
+ if (status == svn_wc__db_status_not_present
+ && kind == svn_node_none)
+ {
+ /* Right, the node status matches (not_present) and there is no
+ * unversioned obstruction in the file system (anymore?). If it
+ * has a tree conflict with reason 'unversioned', remove that. */
+ const svn_wc_conflict_description2_t *previous_tc;
+ SVN_ERR(svn_wc__get_tree_conflict(&previous_tc,
+ eb->wc_ctx,
+ fb->local_abspath,
+ subpool, subpool));
+ if (previous_tc
+ && previous_tc->reason == svn_wc_conflict_reason_unversioned)
+ {
+ do_skip = FALSE;
- /* An unversioned, obstructing file may be OK. */
- if (kind == svn_node_file &&
- (wc_kind == svn_wc__db_kind_unknown || !IS_NODE_PRESENT(status)))
- {
- fb->obstruction_found = TRUE;
+ /* Remove tree conflict. */
+ SVN_ERR(svn_wc__db_op_set_tree_conflict(eb->db,
+ fb->local_abspath,
+ NULL, subpool));
+ }
+ }
- if (!eb->allow_unver_obstructions)
+ if (do_skip)
{
- do_notification(eb, fb->local_abspath, svn_node_file,
- svn_wc_notify_update_obstruction, pool);
+ SVN_ERR(remember_skipped_tree(eb, fb->local_abspath));
- return svn_error_createf(
- SVN_ERR_WC_OBSTRUCTED_UPDATE, NULL,
- _("Failed to add file '%s': an unversioned "
- "file of the same name already exists"),
- svn_dirent_local_style(fb->local_abspath, subpool));
+ fb->skip_this = TRUE;
+ fb->already_notified = TRUE;
+
+ do_notification(eb, fb->local_abspath, svn_node_unknown,
+ svn_wc_notify_skip, subpool);
+
+ svn_pool_destroy(subpool);
+
+ return SVN_NO_ERROR;
}
}
- else if (wc_kind == svn_wc__db_kind_file ||
- wc_kind == svn_wc__db_kind_symlink)
+
+
+ if (versioned_locally_and_present)
{
- if (IS_NODE_PRESENT(status) &&
- status != svn_wc__db_status_deleted)
+ /* What to do with a versioned or schedule-add file:
+
+ If the UUID doesn't match the parent's, or the URL isn't a child of
+ the parent dir's URL, it's an error.
+
+ A file with matching history is OK. Set add_existed so that
+ user notification is delayed until after any text or prop conflicts
+ have been found.
+
+ Whether the incoming add is a symlink or a file will only be known in
+ close_file(), when the props are known. So with a locally added file
+ or symlink, let close_file() check for a tree conflict.
+
+ We will never see missing files here, because these would be
+ re-added during the crawler phase. */
+ svn_boolean_t local_is_file;
+ svn_boolean_t local_is_non_file;
+ svn_boolean_t is_file_external;
+ const char *local_copyfrom_repos_relpath = NULL;
+ svn_revnum_t local_copyfrom_rev = SVN_INVALID_REVNUM;
+
+ /* Is the local add a copy, and where from? */
+ if (status == svn_wc__db_status_added)
+ SVN_ERR(svn_wc__node_get_copyfrom_info(NULL,
+ &local_copyfrom_repos_relpath,
+ NULL,
+ &local_copyfrom_rev,
+ NULL,
+ eb->wc_ctx,
+ fb->local_abspath,
+ subpool, subpool));
+
+
+ /* Is there something that is a file? */
+ local_is_file = ((wc_kind == svn_wc__db_kind_file
+ || wc_kind == svn_wc__db_kind_symlink)
+ && status != svn_wc__db_status_deleted);
+
+ /* Is there *something* that is not a file? */
+ local_is_non_file = ((wc_kind == svn_wc__db_kind_dir
+ || wc_kind == svn_wc__db_kind_unknown)
+ && status != svn_wc__db_status_deleted);
+
+ if (local_is_file)
{
svn_boolean_t wc_root, switched;
@@ -3841,96 +3975,153 @@ add_file(const char *path,
}
}
- /* What to do with a versioned or schedule-add file:
-
- If the UUID doesn't match the parent's, or the URL isn't a child of
- the parent dir's URL, it's an error.
-
- A file already added without history is OK. Set add_existed so that
- user notification is delayed until after any text or prop conflicts
- have been found.
-
- A file added with history is a tree conflict.
-
- We will never see missing files here, because these would be
- re-added during the crawler phase. */
-
- /* Specialize the added case to added, copied, moved */
- if (status == svn_wc__db_status_added)
+ /* We can't properly handle add vs. add with mismatching
+ * node kinds before single db. */
+ if (local_is_non_file)
{
- SVN_ERR(svn_wc__db_scan_addition(&status, NULL, NULL, NULL, NULL,
- NULL, NULL, NULL, NULL,
- eb->db, fb->local_abspath,
- subpool, subpool));
+ return svn_error_createf(
+ SVN_ERR_WC_OBSTRUCTED_UPDATE, NULL,
+ _("Failed to add file '%s': a non-file object "
+ "of the same name already exists"),
+ svn_dirent_local_style(fb->local_abspath,
+ pool));
}
- switch (status)
+ /* Find out if this is a file external, because we want to allow pulling
+ * in a file external onto an existing node -- because that's how
+ * externals are currently implemented. :( */
+ err = svn_wc__node_is_file_external(&is_file_external, eb->wc_ctx,
+ fb->local_abspath, subpool);
+ if (err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
{
- case svn_wc__db_status_absent:
- case svn_wc__db_status_excluded:
- /* Ignore these hidden states. Allow pulling them (back) in. */
- break;
- case svn_wc__db_status_not_present:
- break;
- case svn_wc__db_status_added:
- /* ### BH: I think this case should be conditional with something
- like allow_unver_obstructions, as this changes the
- base of locally added files.
- ### BH: Always generate tree conflict? */
- fb->add_existed = TRUE;
- break;
- default:
- /* svn_wc__db_status_normal,
- * svn_wc__db_status_moved_here,
- * svn_wc__db_status_copied,
- * svn_wc__db_status_deleted,
- * svn_wc__db_status_incomplete,
- * svn_wc__db_status_base_deleted
- */
- {
- svn_wc_conflict_description2_t *tree_conflict = NULL;
-
- /* Check for conflicts only when we haven't already recorded
- * a tree-conflict on a parent node. */
- if (!pb->in_deleted_and_tree_conflicted_subtree)
- SVN_ERR(check_tree_conflict(&tree_conflict, eb,
- fb->local_abspath,
- svn_wc_conflict_action_add,
- svn_node_file, fb->new_relpath,
- subpool));
+ svn_error_clear(err);
+ is_file_external = FALSE;
+ }
+ else
+ SVN_ERR(err);
- if (tree_conflict != NULL)
- {
- svn_skel_t *work_item;
+ /* Do tree conflict checking if
+ * - if a copy is involved on either side, except if both are copies
+ * from the same URL and revnum.
+ * - if this is a switch operation
+ * - if we are not busy fetching externals
+ * - the node kinds mismatch (when single db is here)
+ * IOW, do no tree conflict checking if during update both sides'
+ * histories match (both simple adds or both copies from the same
+ * URL@REV); don't flag tree conflicts on externals, being handled
+ * elsewhere.
+ *
+ * During switch, local adds at the same path as incoming adds get
+ * "lost" in that switching back to the original will no longer have the
+ * local add. So switch always alerts the user with a tree conflict.
+ *
+ * Allow pulling absent/exluded/not_present nodes back in.
+ *
+ * ### This code is already gearing up to single db with respect to
+ * add-vs.-add conflicts with mismatching node kinds. But before single
+ * db, we cannot deal with mismatching node kinds properly.
+ *
+ * ### Checking the copyfrom_path is bogus during checkout and when
+ * contacting older servers, because then, there is no incoming copy
+ * information. In those cases svn still acts like 1.6 did, like it used
+ * to -- fails to flag same-kind tree conflicts with local-add vs.
+ * incoming-copy, and fails to not flag conflicts on matching copies. */
+ if (! pb->in_deleted_and_tree_conflicted_subtree
+ && ! is_file_external
+ && (eb->switch_relpath != NULL
+ || local_is_non_file
+ || ((copyfrom_path != NULL
+ || local_copyfrom_repos_relpath != NULL)
+ &&
+ ! (copyfrom_path != NULL
+ && local_copyfrom_repos_relpath != NULL
+ && strcmp(local_copyfrom_repos_relpath,
+ copyfrom_relpath) == 0
+ && local_copyfrom_rev == copyfrom_rev)
+ )
+ )
+ )
+ {
+ SVN_ERR(check_tree_conflict(&tree_conflict, eb,
+ fb->local_abspath,
+ svn_wc_conflict_action_add,
+ svn_node_file, fb->new_relpath,
+ subpool));
+ }
- /* Record the conflict so that the file is skipped silently
- by the other callbacks. */
- SVN_ERR(svn_wc__loggy_add_tree_conflict(&work_item,
- eb->db,
- pb->local_abspath,
- tree_conflict,
- subpool));
- SVN_ERR(svn_wc__db_wq_add(eb->db, pb->local_abspath,
- work_item, pool));
- SVN_ERR(remember_skipped_tree(eb, fb->local_abspath));
- fb->skip_this = TRUE;
- fb->already_notified = TRUE;
+ if (tree_conflict == NULL)
+ /* We have a node in WORKING and we've decided not to flag a
+ * conflict, so merge it with the incoming add. */
+ fb->add_existed = TRUE;
+ else
+ /* We have a tree conflict of a local add vs. an incoming add.
+ * We want to update BASE only, scheduling WORKING as a replace
+ * of BASE so that WORKING/ACTUAL stay unchanged. */
+ fb->adding_base_under_local_add = TRUE;
- do_notification(eb, fb->local_abspath, svn_node_unknown,
- svn_wc_notify_tree_conflict, subpool);
+ }
+ else if (kind != svn_node_none)
+ {
+ /* There's an unversioned node at this path. */
+ fb->obstruction_found = TRUE;
- return SVN_NO_ERROR;
- }
- }
- break;
+ /* Unversioned, obstructing files are handled by text merge/conflict,
+ * if unversioned obstructions are allowed. */
+ if (! (kind == svn_node_file && eb->allow_unver_obstructions))
+ {
+ /* ### Instead of skipping, this should bring in the BASE node
+ * and mark some sort of obstruction-conflict. Come, o single-db! */
+ fb->skip_this = TRUE;
+
+ /* If we are skipping an add, we need to tell the WC that
+ * there's a node supposed to be here which we don't have. */
+ SVN_ERR(svn_wc__db_base_add_absent_node(eb->db, fb->local_abspath,
+ fb->new_relpath,
+ eb->repos_root,
+ eb->repos_uuid,
+ (eb->target_revision?
+ *eb->target_revision
+ : SVN_INVALID_REVNUM),
+ svn_wc__db_kind_file,
+ svn_wc__db_status_not_present,
+ NULL, NULL, subpool));
+ SVN_ERR(remember_skipped_tree(eb, fb->local_abspath));
+
+ /* Mark a conflict */
+ SVN_ERR(create_tree_conflict(&tree_conflict, eb,
+ fb->local_abspath,
+ svn_wc_conflict_reason_unversioned,
+ svn_wc_conflict_action_add,
+ svn_node_file,
+ fb->new_relpath, subpool, subpool));
+ SVN_ERR_ASSERT(tree_conflict != NULL);
}
}
+ if (tree_conflict != NULL)
+ {
+ svn_skel_t *work_item;
+
+ fb->obstruction_found = TRUE;
+
+ SVN_ERR(svn_wc__loggy_add_tree_conflict(&work_item,
+ eb->db,
+ pb->local_abspath,
+ tree_conflict,
+ subpool));
+ SVN_ERR(svn_wc__db_wq_add(eb->db, pb->local_abspath,
+ work_item, pool));
+
+ fb->already_notified = TRUE;
+ do_notification(eb, fb->local_abspath, svn_node_unknown,
+ svn_wc_notify_tree_conflict, subpool);
+ }
+
svn_pool_destroy(subpool);
/* Now, if this is an add with history, do the history part. */
- if (copyfrom_path)
+ if (copyfrom_path && !fb->skip_this)
{
SVN_ERR(add_file_with_history(pb, copyfrom_path, copyfrom_rev,
fb, pool));
@@ -4660,9 +4851,10 @@ close_file(void *file_baton,
apr_array_header_t *wc_props;
apr_array_header_t *regular_props;
svn_boolean_t install_pristine;
- const char *install_from;
+ const char *install_from = NULL;
apr_hash_t *current_base_props = NULL;
apr_hash_t *current_actual_props = NULL;
+ apr_hash_t *local_actual_props = NULL;
svn_skel_t *all_work_items;
svn_skel_t *work_item;
svn_revnum_t new_changed_rev;
@@ -4778,6 +4970,14 @@ close_file(void *file_baton,
/* ### some of this feels like voodoo... */
+ if (kind != svn_node_none)
+ SVN_ERR(svn_wc__get_actual_props(&local_actual_props,
+ eb->db, fb->local_abspath,
+ pool, pool));
+ if (local_actual_props == NULL)
+ local_actual_props = apr_hash_make(pool);
+
+
if (fb->copied_base_props)
{
/* The BASE props are given by the source of the copy. We may also
@@ -4788,13 +4988,11 @@ close_file(void *file_baton,
}
else if (kind != svn_node_none)
{
- /* This node already exists. Grab its properties. */
+ /* This node already exists. Grab its properties. */
SVN_ERR(svn_wc__get_pristine_props(¤t_base_props,
eb->db, fb->local_abspath,
pool, pool));
- SVN_ERR(svn_wc__get_actual_props(¤t_actual_props,
- eb->db, fb->local_abspath,
- pool, pool));
+ current_actual_props = local_actual_props;
}
/* Note: even if the node existed before, it may not have
@@ -4806,86 +5004,211 @@ close_file(void *file_baton,
if (current_actual_props == NULL)
current_actual_props = apr_hash_make(pool);
- prop_state = svn_wc_notify_state_unknown;
+ /* Catch symlink-ness change.
+ * add_file() doesn't know whether the incoming added node is a file or
+ * a symlink, because symlink-ness is saved in a prop :(
+ * So add_file() cannot notice when update wants to add a symlink where
+ * locally there already is a file scheduled for addition, or vice versa.
+ * It sees incoming symlinks as simple files and may wrongly try to offer
+ * a text conflict. So flag a tree conflict here. */
+ if (fb->adding_file && fb->add_existed)
+ {
+ int i;
+ svn_boolean_t local_is_link = FALSE;
+ svn_boolean_t incoming_is_link = FALSE;
- /* Merge the 'regular' props into the existing working proplist. */
- /* This will merge the old and new props into a new prop db, and
- write <cp> commands to the logfile to install the merged
- props. */
- SVN_ERR(svn_wc__merge_props(&prop_state,
- &new_base_props,
- &new_actual_props,
- eb->db,
- fb->local_abspath,
- svn_wc__db_kind_file,
- NULL /* left_version */,
- NULL /* right_version */,
- NULL /* server_baseprops (update, not merge) */,
- current_base_props,
- current_actual_props,
- regular_props,
- TRUE /* base_merge */,
- FALSE /* dry_run */,
- eb->conflict_func, eb->conflict_baton,
- eb->cancel_func, eb->cancel_baton,
- pool,
- pool));
- /* We will ALWAYS have properties to save (after a not-dry-run merge). */
- SVN_ERR_ASSERT(new_base_props != NULL && new_actual_props != NULL);
-
- /* We are going to write a new set of BASE properties. Create a work item
- to update the old-style props file. */
- SVN_ERR(build_write_base_props(&all_work_items, fb->local_abspath,
- svn_wc__db_kind_file, new_base_props,
- pool));
+ local_is_link = apr_hash_get(local_actual_props,
+ SVN_PROP_SPECIAL,
+ APR_HASH_KEY_STRING) != NULL;
+
+ /* Jump through hoops to get the proper props in case of
+ * a copy. (see the fb->copied_base_props condition above) */
+ if (fb->copied_base_props)
+ {
+ incoming_is_link = fb->copied_working_props
+ && apr_hash_get(fb->copied_working_props,
+ SVN_PROP_SPECIAL,
+ APR_HASH_KEY_STRING) != NULL;
+ }
+ else
+ {
+ for (i = 0; i < regular_props->nelts; ++i)
+ {
+ const svn_prop_t *prop = &APR_ARRAY_IDX(regular_props, i,
+ svn_prop_t);
- {
- apr_hash_t *props;
- apr_array_header_t *prop_diffs;
+ if (strcmp(prop->name, SVN_PROP_SPECIAL) == 0)
+ {
+ incoming_is_link = TRUE;
+ }
+ }
+ }
- /* ### grr. the BASE/ACTUAL props changes need to occur before the
- ### merge_file() work. we may be able to solve this by shifting
- ### some of the code blocks in this function up to here. in any
- ### case, get the old-style props files written now, before
- ### merge_file. the in-database values are going to occur later
- ### (skew!), but we'll get that fixed before switching over to
- ### db properties. */
- props = new_actual_props;
- SVN_ERR(svn_prop_diffs(&prop_diffs, new_actual_props, new_base_props,
- pool));
- if (prop_diffs->nelts == 0)
- props = NULL;
- SVN_ERR(build_write_actual_props(&work_item, fb->local_abspath,
- svn_wc__db_kind_file,
- props,
+ if (local_is_link != incoming_is_link)
+ {
+ svn_wc_conflict_description2_t *tree_conflict = NULL;
+
+ fb->adding_base_under_local_add = TRUE;
+ fb->obstruction_found = TRUE;
+ fb->add_existed = FALSE;
+
+ SVN_ERR(check_tree_conflict(&tree_conflict, eb, fb->local_abspath,
+ svn_wc_conflict_action_add, svn_node_file,
+ fb->new_relpath, pool));
+ SVN_ERR_ASSERT(tree_conflict != NULL);
+ SVN_ERR(svn_wc__loggy_add_tree_conflict(&work_item,
+ eb->db,
+ fb->dir_baton->local_abspath,
+ tree_conflict,
+ pool));
+ SVN_ERR(svn_wc__db_wq_add(eb->db, fb->dir_baton->local_abspath,
+ work_item, pool));
+
+ fb->already_notified = TRUE;
+ do_notification(eb, fb->local_abspath, svn_node_unknown,
+ svn_wc_notify_tree_conflict, pool);
+ }
+ }
+
+ prop_state = svn_wc_notify_state_unknown;
+
+ if (! fb->adding_base_under_local_add)
+ {
+ /* Merge the 'regular' props into the existing working proplist. */
+ /* This will merge the old and new props into a new prop db, and
+ write <cp> commands to the logfile to install the merged
+ props. */
+ SVN_ERR(svn_wc__merge_props(&prop_state,
+ &new_base_props,
+ &new_actual_props,
+ eb->db,
+ fb->local_abspath,
+ svn_wc__db_kind_file,
+ NULL /* left_version */,
+ NULL /* right_version */,
+ NULL /* server_baseprops (update, not merge) */,
+ current_base_props,
+ current_actual_props,
+ regular_props, /* propchanges */
+ TRUE /* base_merge */,
+ FALSE /* dry_run */,
+ eb->conflict_func, eb->conflict_baton,
+ eb->cancel_func, eb->cancel_baton,
+ pool,
+ pool));
+ /* We will ALWAYS have properties to save (after a not-dry-run merge). */
+ SVN_ERR_ASSERT(new_base_props != NULL && new_actual_props != NULL);
+
+ /* We are going to write a new set of BASE properties. Create a work item
+ to update the old-style props file. */
+ SVN_ERR(build_write_base_props(&all_work_items, fb->local_abspath,
+ svn_wc__db_kind_file, new_base_props,
pool));
+
+ {
+ apr_hash_t *props;
+ apr_array_header_t *prop_diffs;
+
+ /* ### grr. the BASE/ACTUAL props changes need to occur before the
+ ### merge_file() work. we may be able to solve this by shifting
+ ### some of the code blocks in this function up to here. in any
+ ### case, get the old-style props files written now, before
+ ### merge_file. the in-database values are going to occur later
+ ### (skew!), but we'll get that fixed before switching over to
+ ### db properties. */
+ props = new_actual_props;
+ SVN_ERR(svn_prop_diffs(&prop_diffs, new_actual_props, new_base_props,
+ pool));
+ if (prop_diffs->nelts == 0)
+ props = NULL;
+
+ SVN_ERR(build_write_actual_props(&work_item, fb->local_abspath,
+ svn_wc__db_kind_file,
+ props,
+ pool));
+ all_work_items = svn_wc__wq_merge(all_work_items, work_item, pool);
+ }
+
+ /* Do the hard work. This will queue some additional work. */
+ SVN_ERR(merge_file(&work_item, &install_pristine, &install_from,
+ &content_state, fb, new_text_base_abspath, pool));
all_work_items = svn_wc__wq_merge(all_work_items, work_item, pool);
- }
- /* Do the hard work. This will queue some additional work. */
- SVN_ERR(merge_file(&work_item, &install_pristine, &install_from,
- &content_state, fb, new_text_base_abspath, pool));
- all_work_items = svn_wc__wq_merge(all_work_items, work_item, pool);
-
- if (install_pristine)
- {
- svn_boolean_t record_fileinfo;
-
- /* If we are installing from the pristine contents, then go ahead and
- record the fileinfo. That will be the "proper" values. Installing
- from some random file means the fileinfo does NOT correspond to
- the pristine (in which case, the fileinfo will be cleared for
- safety's sake). */
- record_fileinfo = install_from == NULL;
+ if (install_pristine)
+ {
+ svn_boolean_t record_fileinfo;
- SVN_ERR(svn_wc__wq_build_file_install(&work_item,
- eb->db,
- fb->local_abspath,
- install_from,
- eb->use_commit_times,
- record_fileinfo,
- pool, pool));
+ /* If we are installing from the pristine contents, then go ahead and
+ record the fileinfo. That will be the "proper" values. Installing
+ from some random file means the fileinfo does NOT correspond to
+ the pristine (in which case, the fileinfo will be cleared for
+ safety's sake). */
+ record_fileinfo = install_from == NULL;
+
+ SVN_ERR(svn_wc__wq_build_file_install(&work_item,
+ eb->db,
+ fb->local_abspath,
+ install_from,
+ eb->use_commit_times,
+ record_fileinfo,
+ pool, pool));
+ all_work_items = svn_wc__wq_merge(all_work_items, work_item, pool);
+ }
+
+ }
+ else
+ {
+ /* Adding a BASE node under a locally added node.
+ * The incoming add becomes the revert-base! */
+ svn_wc_notify_state_t no_prop_state;
+ apr_hash_t *copied_base_props;
+ apr_hash_t *no_new_actual_props = NULL;
+ apr_hash_t *no_working_props = apr_hash_make(pool);
+ const char *text_revert_base_abspath;
+
+ copied_base_props = fb->copied_base_props;
+ if (! copied_base_props)
+ copied_base_props = apr_hash_make(pool);
+
+
+ /* Store the incoming props (sent as propchanges) in new_base_props.
+ * Keep the actual props unchanged. */
+ SVN_ERR(svn_wc__merge_props(&no_prop_state,
+ &new_base_props,
+ &no_new_actual_props,
+ eb->db,
+ fb->local_abspath,
+ svn_wc__db_kind_file,
+ NULL /* left_version */,
+ NULL /* right_version */,
+ NULL /* server_baseprops (update, not merge) */,
+ copied_base_props,
+ no_working_props,
+ regular_props, /* propchanges */
+ TRUE /* base_merge */,
+ FALSE /* dry_run */,
+ eb->conflict_func, eb->conflict_baton,
+ eb->cancel_func, eb->cancel_baton,
+ pool,
+ pool));
+
+ /* We are going to write a new set of BASE properties. Create a work item
+ to update the old-style props file. */
+ SVN_ERR(build_write_revert_base_props(&all_work_items, fb->local_abspath,
+ svn_wc__db_kind_file,
+ new_base_props, pool));
+
+ prop_state = svn_wc_notify_state_unchanged;
+ new_actual_props = local_actual_props;
+
+ SVN_ERR(svn_wc__text_revert_path(&text_revert_base_abspath, eb->db,
+ fb->local_abspath, pool));
+ SVN_ERR(svn_wc__loggy_move(&work_item, eb->db,
+ fb->dir_baton->local_abspath,
+ new_text_base_abspath,
+ text_revert_base_abspath,
+ pool));
all_work_items = svn_wc__wq_merge(all_work_items, work_item, pool);
}
@@ -5034,32 +5357,33 @@ close_file(void *file_baton,
/* Now we need to update the ACTUAL tree, with the result of the
properties merge. */
- {
- apr_hash_t *props;
- apr_array_header_t *prop_diffs;
+ if (! fb->adding_base_under_local_add)
+ {
+ apr_hash_t *props;
+ apr_array_header_t *prop_diffs;
- SVN_ERR_ASSERT(new_actual_props != NULL);
+ SVN_ERR_ASSERT(new_actual_props != NULL);
- /* If the ACTUAL props are the same as the BASE props, then we
- should "write" a NULL. This will remove the props from the
- ACTUAL_NODE row, and remove the old-style props file, indicating
- "no change". */
- props = new_actual_props;
- SVN_ERR(svn_prop_diffs(&prop_diffs, new_actual_props, new_base_props,
- pool));
- if (prop_diffs->nelts == 0)
- props = NULL;
-
- SVN_ERR(build_write_actual_props(&work_item, fb->local_abspath,
- svn_wc__db_kind_file,
- props,
- pool));
- SVN_ERR(svn_wc__db_op_set_props(eb->db, fb->local_abspath,
- props,
- NULL /* conflict */,
- work_item,
- pool));
- }
+ /* If the ACTUAL props are the same as the BASE props, then we
+ should "write" a NULL. This will remove the props from the
+ ACTUAL_NODE row, and remove the old-style props file, indicating
+ "no change". */
+ props = new_actual_props;
+ SVN_ERR(svn_prop_diffs(&prop_diffs, new_actual_props, new_base_props,
+ pool));
+ if (prop_diffs->nelts == 0)
+ props = NULL;
+
+ SVN_ERR(build_write_actual_props(&work_item, fb->local_abspath,
+ svn_wc__db_kind_file,
+ props,
+ pool));
+ SVN_ERR(svn_wc__db_op_set_props(eb->db, fb->local_abspath,
+ props,
+ NULL /* conflict */,
+ work_item,
+ pool));
+ }
/* ### we may as well run whatever is in the queue right now. this
### starts out with some crap node data via construct_base_node(),
Modified: subversion/trunk/subversion/tests/cmdline/checkout_tests.py
URL: http://svn.apache.org/viewvc/subversion/trunk/subversion/tests/cmdline/checkout_tests.py?rev=959735&r1=959734&r2=959735&view=diff
==============================================================================
--- subversion/trunk/subversion/tests/cmdline/checkout_tests.py (original)
+++ subversion/trunk/subversion/tests/cmdline/checkout_tests.py Thu Jul 1 17:51:36 2010
@@ -30,7 +30,7 @@ import sys, re, os, time, subprocess
# Our testing module
import svntest
-from svntest import wc
+from svntest import wc, actions
# (abbreviation)
Skip = svntest.testcase.Skip
@@ -162,23 +162,49 @@ def checkout_with_obstructions(sbox):
#----------------------------------------------------------------------
def forced_checkout_of_file_with_dir_obstructions(sbox):
- """forced co fails if a dir obstructs a file"""
+ """forced co flags conflict if a dir obstructs a file"""
+ # svntest.factory.make(sbox,
+ # """mkdir $WC_DIR.other/iota
+ # svn co --force url $WC_DIR.other """)
+ sbox.build()
+ url = sbox.repo_url
+ wc_dir_other = sbox.add_wc_path('other')
- make_local_tree(sbox, False, False)
+ other_iota = os.path.join(wc_dir_other, 'iota')
- # Make the "other" working copy
- other_wc = sbox.add_wc_path('other')
- os.mkdir(other_wc)
- os.mkdir(os.path.join(other_wc, "iota"))
+ # mkdir $WC_DIR.other/iota
+ os.makedirs(other_iota)
- # Checkout the standard greek repos into a directory that has a dir named
- # "iota" obstructing the file "iota" in the repos. This should fail.
- exit_code, sout, serr = svntest.actions.run_and_verify_svn(
- "Expected error during co", None, svntest.verify.AnyOutput,
- "co", "--force", sbox.repo_url, other_wc)
+ # svn co --force url $WC_DIR.other
+ expected_output = svntest.wc.State(wc_dir_other, {
+ 'A' : Item(status='A '),
+ 'A/B' : Item(status='A '),
+ 'A/B/E' : Item(status='A '),
+ 'A/B/E/alpha' : Item(status='A '),
+ 'A/B/E/beta' : Item(status='A '),
+ 'A/B/F' : Item(status='A '),
+ 'A/B/lambda' : Item(status='A '),
+ 'A/D' : Item(status='A '),
+ 'A/D/H' : Item(status='A '),
+ 'A/D/H/chi' : Item(status='A '),
+ 'A/D/H/omega' : Item(status='A '),
+ 'A/D/H/psi' : Item(status='A '),
+ 'A/D/G' : Item(status='A '),
+ 'A/D/G/pi' : Item(status='A '),
+ 'A/D/G/rho' : Item(status='A '),
+ 'A/D/G/tau' : Item(status='A '),
+ 'A/D/gamma' : Item(status='A '),
+ 'A/C' : Item(status='A '),
+ 'A/mu' : Item(status='A '),
+ 'iota' : Item(status=' ', treeconflict='C'),
+ })
+
+ expected_disk = svntest.main.greek_state.copy()
+ expected_disk.tweak('iota', contents=None)
+
+ actions.run_and_verify_checkout(url, wc_dir_other, expected_output,
+ expected_disk, None, None, None, None, '--force')
- test_stderr(".*Failed to add file.*a non-file object of the same name " \
- "already exists", serr)
#----------------------------------------------------------------------
@@ -817,7 +843,7 @@ def co_with_obstructing_local_adds(sbox)
None, None, None, None,
'--force')
- expected_status.tweak('A/B/F/omicron', treeconflict='C')
+ expected_status.tweak('A/B/F/omicron', treeconflict='C', status='R ')
expected_status.add({
'A/B/F' : Item(status=' ', wc_rev=4),
})