You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@subversion.apache.org by pb...@apache.org on 2012/03/21 03:39:00 UTC
svn commit: r1303257 [2/4] - in /subversion/branches/inheritable-props: ./
build/ac-macros/ notes/ notes/directory-index/ notes/wc-ng/
subversion/bindings/swig/ruby/test/ subversion/include/
subversion/include/private/ subversion/libsvn_client/ subvers...
Modified: subversion/branches/inheritable-props/subversion/libsvn_client/merge.c
URL: http://svn.apache.org/viewvc/subversion/branches/inheritable-props/subversion/libsvn_client/merge.c?rev=1303257&r1=1303256&r2=1303257&view=diff
==============================================================================
--- subversion/branches/inheritable-props/subversion/libsvn_client/merge.c (original)
+++ subversion/branches/inheritable-props/subversion/libsvn_client/merge.c Wed Mar 21 02:38:58 2012
@@ -165,6 +165,14 @@ typedef struct url_uuid_t
const char *uuid;
} url_uuid_t;
+/* A location in a repository. */
+typedef struct repo_location_t
+{
+ url_uuid_t *repo;
+ svn_revnum_t rev;
+ const char *url;
+} repo_location_t;
+
/* */
typedef struct merge_source_t
{
@@ -187,14 +195,10 @@ typedef struct merge_target_t
/* Node kind of the WC node (at the start of the merge) */
svn_node_kind_t kind;
- /* URL of the node, or NULL if node is locally added */
- const char *url;
-
- /* Revision of the node, or SVN_INVALID_REVNUM if node is locally added */
- svn_revnum_t rev;
-
- /* Repository root URL and UUID, even if node is locally added */
- url_uuid_t repos_root;
+ /* The repository location of the base node of the target WC. If the node
+ * is locally added, then URL & REV are NULL & SVN_INVALID_REVNUM. REPO
+ * is always valid. */
+ repo_location_t loc;
} merge_target_t;
@@ -332,12 +336,12 @@ check_repos_match(merge_cmd_baton_t *mer
const char *url,
apr_pool_t *scratch_pool)
{
- if (!svn_uri__is_ancestor(merge_b->target->repos_root.url, url))
+ if (!svn_uri__is_ancestor(merge_b->target->loc.repo->url, url))
return svn_error_createf(
SVN_ERR_UNSUPPORTED_FEATURE, NULL,
_("Url '%s' of '%s' is not in repository '%s'"),
url, svn_dirent_local_style(local_abspath, scratch_pool),
- merge_b->target->repos_root.url);
+ merge_b->target->loc.repo->url);
return SVN_NO_ERROR;
}
@@ -1734,13 +1738,13 @@ merge_file_added(svn_wc_notify_state_t *
merge-from-foreign-repository scenario) or wrong place in the
right repository (in the same-repos scenario). So we'll
strip them. (Is this a layering violation?) */
- if (svn_property_kind(NULL, prop->name) == svn_prop_wc_kind)
+ if (svn_property_kind2(prop->name) == svn_prop_wc_kind)
continue;
/* And in the foreign repository merge case, we only want
regular properties. */
if ((! merge_b->same_repos)
- && (svn_property_kind(NULL, prop->name) != svn_prop_regular_kind))
+ && (svn_property_kind2(prop->name) != svn_prop_regular_kind))
continue;
/* Issue #3383: We don't want mergeinfo from a foreign repository. */
@@ -7765,7 +7769,7 @@ record_mergeinfo_for_dir_merge(svn_merge
svn_mergeinfo_t subtree_history_as_mergeinfo;
apr_array_header_t *child_merge_src_rangelist;
const char *subtree_mergeinfo_url =
- svn_path_url_add_component2(merge_b->target->repos_root.url,
+ svn_path_url_add_component2(merge_b->target->loc.repo->url,
child_merge_src_fspath + 1,
iterpool);
@@ -7964,7 +7968,7 @@ record_mergeinfo_for_added_subtrees(
take the intersection of the naive mergeinfo with
MERGEINFO_PATH/rel_added_path's history. */
added_path_mergeinfo_url =
- svn_path_url_add_component2(merge_b->target->repos_root.url,
+ svn_path_url_add_component2(merge_b->target->loc.repo->url,
added_path_mergeinfo_fspath + 1,
iterpool);
SVN_ERR(svn_client__get_history_as_mergeinfo(
@@ -8294,11 +8298,11 @@ remove_noop_subtree_ranges(const merge_s
SVN_ERR(svn_client__path_relative_to_root(
&(log_gap_baton.target_fspath), merge_b->ctx->wc_ctx,
merge_b->target->abspath,
- merge_b->target->repos_root.url, TRUE, NULL,
+ merge_b->target->loc.repo->url, TRUE, NULL,
result_pool, scratch_pool));
SVN_ERR(svn_client__path_relative_to_root(
&(log_gap_baton.source_fspath), merge_b->ctx->wc_ctx,
- source->url2, merge_b->target->repos_root.url, TRUE, NULL,
+ source->url2, merge_b->target->loc.repo->url, TRUE, NULL,
result_pool, scratch_pool));
log_gap_baton.merged_ranges = apr_array_make(scratch_pool, 0,
sizeof(svn_revnum_t *));
@@ -9248,29 +9252,82 @@ ensure_wc_path_has_repo_revision(const c
return SVN_NO_ERROR;
}
-/* Set *TARGET to a new, fully initialized, target description structure. */
+/* "Open" the target WC for a merge. That means:
+ * - find out its node kind
+ * - find out its exact repository location
+ * - check the WC for suitability (throw an error if unsuitable)
+ *
+ * Set *TARGET_P to a new, fully initialized, target description structure.
+ *
+ * ALLOW_MIXED_REV, ALLOW_LOCAL_MODS, ALLOW_SWITCHED_SUBTREES determine
+ * whether the WC is deemed suitable; see ensure_wc_is_suitable_merge_target()
+ * for details.
+ *
+ * If the node is locally added, the rev and URL will be null/invalid. Some
+ * kinds of merge can use such a target; others can't.
+ */
static svn_error_t *
-target_node_location(merge_target_t **target_p,
- const char *wc_abspath,
- svn_client_ctx_t *ctx,
- apr_pool_t *result_pool,
- apr_pool_t *scratch_pool)
+open_target_wc(merge_target_t **target_p,
+ const char *wc_abspath,
+ svn_boolean_t allow_mixed_rev,
+ svn_boolean_t allow_local_mods,
+ svn_boolean_t allow_switched_subtrees,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
{
merge_target_t *target = apr_palloc(result_pool, sizeof(*target));
target->abspath = apr_pstrdup(result_pool, wc_abspath);
+
SVN_ERR(svn_wc_read_kind(&target->kind, ctx->wc_ctx, wc_abspath, FALSE,
scratch_pool));
- SVN_ERR(svn_client__wc_node_get_origin(&target->repos_root.url,
- &target->repos_root.uuid,
- &target->rev, &target->url,
+
+ target->loc.repo = apr_palloc(result_pool, sizeof(*target->loc.repo));
+ SVN_ERR(svn_client__wc_node_get_origin(&target->loc.repo->url,
+ &target->loc.repo->uuid,
+ &target->loc.rev, &target->loc.url,
wc_abspath, ctx,
result_pool, scratch_pool));
+ SVN_ERR(ensure_wc_is_suitable_merge_target(
+ wc_abspath, ctx,
+ allow_mixed_rev, allow_local_mods, allow_switched_subtrees,
+ scratch_pool));
+
*target_p = target;
return SVN_NO_ERROR;
}
+/* Open an RA session to PATH_OR_URL at PEG_REVISION. Set *RA_SESSION_P to
+ * the session and set *LOCATION_P to the resolved revision, URL and
+ * repository root. Allocate the results in RESULT_POOL. */
+static svn_error_t *
+open_source_session(repo_location_t **location_p,
+ svn_ra_session_t **ra_session_p,
+ const char *path_or_url,
+ const svn_opt_revision_t *peg_revision,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ repo_location_t *location = apr_palloc(result_pool, sizeof(*location));
+ svn_ra_session_t *ra_session;
+
+ SVN_ERR(svn_client__ra_session_from_path(
+ &ra_session, &location->rev, &location->url,
+ path_or_url, NULL, peg_revision, peg_revision,
+ ctx, result_pool));
+ location->repo = apr_palloc(result_pool, sizeof(*location->repo));
+ SVN_ERR(svn_ra_get_repos_root2(ra_session, &location->repo->url,
+ result_pool));
+ SVN_ERR(svn_ra_get_uuid2(ra_session, &location->repo->uuid, result_pool));
+
+ *location_p = location;
+ *ra_session_p = ra_session;
+ return SVN_NO_ERROR;
+}
+
/*-----------------------------------------------------------------------*/
@@ -9294,9 +9351,9 @@ merge_locked(const char *source1,
apr_pool_t *scratch_pool)
{
merge_target_t *target;
+ repo_location_t *source1_loc, *source2_loc;
merge_source_t source;
svn_boolean_t related = FALSE, ancestral = FALSE;
- url_uuid_t source_repos_root, source_repos_root2;
svn_ra_session_t *ra_session1, *ra_session2;
apr_array_header_t *merge_sources;
svn_error_t *err;
@@ -9311,46 +9368,33 @@ merge_locked(const char *source1,
ancestor of the other -- just call svn_client_merge_peg3() with
the appropriate args. */
- SVN_ERR(target_node_location(&target, target_abspath,
- ctx, scratch_pool, scratch_pool));
-
- /* Do not allow merges into mixed-revision working copies. */
- SVN_ERR(ensure_wc_is_suitable_merge_target(target->abspath, ctx,
- allow_mixed_rev, TRUE, TRUE,
- scratch_pool));
+ SVN_ERR(open_target_wc(&target, target_abspath,
+ allow_mixed_rev, TRUE, TRUE,
+ ctx, scratch_pool, scratch_pool));
/* Open RA sessions to both sides of our merge source, and resolve URLs
* and revisions. */
sesspool = svn_pool_create(scratch_pool);
- SVN_ERR(svn_client__ra_session_from_path(&ra_session1,
- &source.rev1, &source.url1,
- source1, NULL, revision1, revision1,
- ctx, sesspool));
- SVN_ERR(svn_client__ra_session_from_path(&ra_session2,
- &source.rev2, &source.url2,
- source2, NULL, revision2, revision2,
- ctx, sesspool));
-
- SVN_ERR(svn_ra_get_repos_root2(ra_session1, &source_repos_root.url,
- sesspool));
- SVN_ERR(svn_ra_get_uuid2(ra_session1, &source_repos_root.uuid,
- scratch_pool));
- SVN_ERR(svn_ra_get_repos_root2(ra_session2, &source_repos_root2.url,
- sesspool));
- SVN_ERR(svn_ra_get_uuid2(ra_session2, &source_repos_root2.uuid,
- scratch_pool));
+ SVN_ERR(open_source_session(&source1_loc, &ra_session1, source1, revision1,
+ ctx, sesspool, scratch_pool));
+ source.url1 = source1_loc->url;
+ source.rev1 = source1_loc->rev;
+ SVN_ERR(open_source_session(&source2_loc, &ra_session2, source2, revision2,
+ ctx, sesspool, scratch_pool));
+ source.url2 = source2_loc->url;
+ source.rev2 = source2_loc->rev;
/* We can't do a diff between different repositories. */
/* ### We should also insist that the root URLs of the two sources match,
* as we are only carrying around a single source-repos-root from now
* on, and URL calculations will go wrong if they differ.
* Alternatively, teach the code to cope with differing root URLs. */
- SVN_ERR(check_same_repos(&source_repos_root, source.url1,
- &source_repos_root2, source.url2,
+ SVN_ERR(check_same_repos(source1_loc->repo, source.url1,
+ source2_loc->repo, source.url2,
FALSE /* strict_urls */, scratch_pool));
/* Do our working copy and sources come from the same repository? */
- same_repos = is_same_repos(&target->repos_root, &source_repos_root,
+ same_repos = is_same_repos(target->loc.repo, source1_loc->repo,
TRUE /* strict_urls */);
/* Unless we're ignoring ancestry, see if the two sources are related. */
@@ -9928,15 +9972,12 @@ find_unmerged_mergeinfo_subroutine(svn_m
reintegrate source and the reintegrate target.
SOURCE_REPOS_REL_PATH is the path of the reintegrate source relative to
- the root of the repository. TARGET_REPOS_REL_PATH is the path of the
- reintegrate target relative to the root of the repository.
+ the root of the repository.
- TARGET_REV is the working revision the entire WC tree rooted at
- TARGET_REPOS_REL_PATH is at. SOURCE_REV is the peg revision of the
- reintegrate source.
+ SOURCE_REV is the peg revision of the reintegrate source.
SOURCE_RA_SESSION is a session opened to the SOURCE_REPOS_REL_PATH
- and TARGET_RA_SESSION is open to TARGET_REPOS_REL_PATH.
+ and TARGET_RA_SESSION is open to TARGET->loc.url.
For each entry in TARGET_HISTORY_HASH check that the history it
represents is contained in either the explicit mergeinfo for the
@@ -9964,8 +10005,7 @@ find_unmerged_mergeinfo(svn_mergeinfo_ca
svn_mergeinfo_catalog_t source_catalog,
apr_hash_t *target_history_hash,
const char *source_repos_rel_path,
- const char *target_repos_rel_path,
- svn_revnum_t target_rev,
+ const merge_target_t *target,
svn_revnum_t source_rev,
svn_ra_session_t *source_ra_session,
svn_ra_session_t *target_ra_session,
@@ -9973,16 +10013,19 @@ find_unmerged_mergeinfo(svn_mergeinfo_ca
apr_pool_t *result_pool,
apr_pool_t *scratch_pool)
{
+ const char *target_repos_rel_path;
const char *source_session_url;
- const char *target_session_url;
apr_hash_index_t *hi;
svn_mergeinfo_catalog_t new_catalog = apr_hash_make(result_pool);
apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+ SVN_ERR(svn_client__path_relative_to_root(&target_repos_rel_path,
+ ctx->wc_ctx, target->abspath,
+ NULL, FALSE, NULL,
+ scratch_pool, scratch_pool));
+
*youngest_merged_rev = SVN_INVALID_REVNUM;
- SVN_ERR(svn_ra_get_session_url(target_ra_session, &target_session_url,
- scratch_pool));
SVN_ERR(svn_ra_get_session_url(source_ra_session, &source_session_url,
scratch_pool));
@@ -10106,12 +10149,12 @@ find_unmerged_mergeinfo(svn_mergeinfo_ca
source_url = svn_path_url_add_component2(source_session_url,
path_rel_to_session, iterpool);
- target_url = svn_path_url_add_component2(target_session_url,
+ target_url = svn_path_url_add_component2(target->loc.url,
path_rel_to_session, iterpool);
err = svn_client__get_history_as_mergeinfo(&target_history_as_mergeinfo,
NULL /* has_rev_zero_history */,
target_url,
- target_rev, target_rev,
+ target->loc.rev, target->loc.rev,
SVN_INVALID_REVNUM,
target_ra_session,
ctx, iterpool);
@@ -10143,7 +10186,7 @@ find_unmerged_mergeinfo(svn_mergeinfo_ca
/* ### Why looking at SOURCE_url at TARGET_rev? */
SVN_ERR(find_unmerged_mergeinfo_subroutine(
&filtered_mergeinfo, target_history_as_mergeinfo,
- source_mergeinfo, source_url, target_rev,
+ source_mergeinfo, source_url, target->loc.rev,
source_ra_session, ctx, scratch_pool, iterpool));
if (apr_hash_count(filtered_mergeinfo))
apr_hash_set(new_catalog,
@@ -10176,30 +10219,26 @@ find_unmerged_mergeinfo(svn_mergeinfo_ca
merge actually performs. If no merge should be performed, set
*URL_LEFT to NULL and *REV_LEFT to SVN_INVALID_REVNUM.
- TARGET_ABSPATH is the absolute working copy path of the reintegrate
+ TARGET->abspath is the absolute working copy path of the reintegrate
merge.
- TARGET_REPOS_REL_PATH is the path of TARGET_ABSPATH relative to
- the root of the repository. SOURCE_REPOS_REL_PATH is the path of the
- reintegrate source relative to the root of the repository.
+ SOURCE_LOC is the reintegrate source.
SUBTREES_WITH_MERGEINFO is a hash of (const char *) absolute paths mapped
to (svn_mergeinfo_t *) mergeinfo values for each working copy path with
- explicit mergeinfo in TARGET_ABSPATH. Actually we only need to know the
+ explicit mergeinfo in TARGET->abspath. Actually we only need to know the
paths, not the mergeinfo.
- TARGET_REV is the working revision the entire WC tree rooted at
- TARGET_REPOS_REL_PATH is at. SOURCE_REV is the peg revision of the
- reintegrate source.
+ TARGET->loc.rev is the working revision the entire WC tree rooted at
+ TARGET is at.
Populate *UNMERGED_TO_SOURCE_CATALOG with the mergeinfo describing what
- parts of TARGET_REPOS_REL_PATH@TARGET_REV have not been merged to
- SOURCE_REPOS_REL_PATH@SOURCE_REV, up to the youngest revision ever merged
- from the TARGET_ABSPATH to the source if such exists, see doc string for
- find_unmerged_mergeinfo().
+ parts of TARGET->loc have not been merged to SOURCE_LOC, up to the
+ youngest revision ever merged from the TARGET->abspath to the source if
+ such exists, see doc string for find_unmerged_mergeinfo().
- SOURCE_RA_SESSION is a session opened to the SOURCE_REPOS_REL_PATH
- and TARGET_RA_SESSION is open to TARGET_REPOS_REL_PATH.
+ SOURCE_RA_SESSION is a session opened to the SOURCE_LOC
+ and TARGET_RA_SESSION is open to TARGET->loc.url.
*URL_LEFT, *MERGED_TO_SOURCE_CATALOG , and *UNMERGED_TO_SOURCE_CATALOG are
allocated in RESULT_POOL. SCRATCH_POOL is used for all temporary
@@ -10209,20 +10248,15 @@ calculate_left_hand_side(const char **ur
svn_revnum_t *rev_left,
svn_mergeinfo_t *merged_to_source_catalog,
svn_mergeinfo_t *unmerged_to_source_catalog,
- const char *target_abspath,
- const char *target_repos_rel_path,
+ const merge_target_t *target,
apr_hash_t *subtrees_with_mergeinfo,
- svn_revnum_t target_rev,
- const char *source_repos_rel_path,
- const char *source_repos_root,
- svn_revnum_t source_rev,
+ const repo_location_t *source_loc,
svn_ra_session_t *source_ra_session,
svn_ra_session_t *target_ra_session,
svn_client_ctx_t *ctx,
apr_pool_t *result_pool,
apr_pool_t *scratch_pool)
{
- const char *target_repos_root_url = source_repos_root; /* necessarily */
svn_mergeinfo_catalog_t mergeinfo_catalog, unmerged_catalog;
apr_array_header_t *source_repos_rel_path_as_array
= apr_array_make(scratch_pool, 1, sizeof(const char *));
@@ -10233,8 +10267,7 @@ calculate_left_hand_side(const char **ur
svn_revnum_t youngest_merged_rev;
const char *yc_ancestor_url;
svn_revnum_t yc_ancestor_rev;
- const char *source_url;
- const char *target_url;
+ const char *source_repos_rel_path;
/* Initialize our return variables. */
*url_left = NULL;
@@ -10244,9 +10277,9 @@ calculate_left_hand_side(const char **ur
contained within SUBTREES_WITH_MERGEINFO. If this is the case then
add a dummy item for TARGET_ABSPATH so we get its history (i.e. implicit
mergeinfo) below. */
- if (!apr_hash_get(subtrees_with_mergeinfo, target_abspath,
+ if (!apr_hash_get(subtrees_with_mergeinfo, target->abspath,
APR_HASH_KEY_STRING))
- apr_hash_set(subtrees_with_mergeinfo, target_abspath,
+ apr_hash_set(subtrees_with_mergeinfo, target->abspath,
APR_HASH_KEY_STRING, apr_hash_make(result_pool));
/* Get the history segments (as mergeinfo) for TARGET_ABSPATH and any of
@@ -10269,12 +10302,12 @@ calculate_left_hand_side(const char **ur
NULL, FALSE,
NULL, scratch_pool,
iterpool));
- url = svn_path_url_add_component2(target_repos_root_url,
+ url = svn_path_url_add_component2(target->loc.repo->url,
path_rel_to_root, iterpool);
SVN_ERR(svn_client__get_history_as_mergeinfo(&target_history_as_mergeinfo,
NULL /* has_rev_zero_hist */,
url,
- target_rev, target_rev,
+ target->loc.rev, target->loc.rev,
SVN_INVALID_REVNUM,
target_ra_session,
ctx, scratch_pool));
@@ -10287,27 +10320,21 @@ calculate_left_hand_side(const char **ur
/* Check that SOURCE_URL@SOURCE_REV and TARGET_URL@TARGET_REV are
actually related, we can't reintegrate if they are not. Also
get an initial value for YC_ANCESTOR_REV. */
- source_url = svn_path_url_add_component2(source_repos_root,
- source_repos_rel_path,
- iterpool);
- target_url = svn_path_url_add_component2(source_repos_root,
- target_repos_rel_path,
- iterpool);
SVN_ERR(svn_client__get_youngest_common_ancestor(NULL, &yc_ancestor_url,
&yc_ancestor_rev,
- source_url, source_rev,
- target_url, target_rev,
+ source_loc->url, source_loc->rev,
+ target->loc.url, target->loc.rev,
ctx, iterpool));
if (!(yc_ancestor_url && SVN_IS_VALID_REVNUM(yc_ancestor_rev)))
return svn_error_createf(SVN_ERR_CLIENT_NOT_READY_TO_MERGE, NULL,
_("'%s@%ld' must be ancestrally related to "
- "'%s@%ld'"), source_url, source_rev,
- target_url, target_rev);
+ "'%s@%ld'"), source_loc->url, source_loc->rev,
+ target->loc.url, target->loc.rev);
/* If the source revision is the same as the youngest common
revision, then there can't possibly be any unmerged revisions
that we need to apply to target. */
- if (source_rev == yc_ancestor_rev)
+ if (source_loc->rev == yc_ancestor_rev)
{
svn_pool_destroy(iterpool);
return SVN_NO_ERROR;
@@ -10317,10 +10344,12 @@ calculate_left_hand_side(const char **ur
with differing explicit mergeinfo. */
APR_ARRAY_PUSH(source_repos_rel_path_as_array, const char *) = "";
SVN_ERR(svn_ra_get_mergeinfo(source_ra_session, &mergeinfo_catalog,
- source_repos_rel_path_as_array, source_rev,
+ source_repos_rel_path_as_array, source_loc->rev,
svn_mergeinfo_inherited,
TRUE, iterpool));
+ source_repos_rel_path = svn_uri_skip_ancestor(source_loc->repo->url,
+ source_loc->url, scratch_pool);
if (mergeinfo_catalog)
SVN_ERR(svn_mergeinfo__add_prefix_to_catalog(&mergeinfo_catalog,
mergeinfo_catalog,
@@ -10342,9 +10371,8 @@ calculate_left_hand_side(const char **ur
mergeinfo_catalog,
target_history_hash,
source_repos_rel_path,
- target_repos_rel_path,
- target_rev,
- source_rev,
+ target,
+ source_loc->rev,
source_ra_session,
target_ra_session,
ctx,
@@ -10369,7 +10397,7 @@ calculate_left_hand_side(const char **ur
*URL_LEFT and *REV_LEFT to cover the youngest part of this range. */
*rev_left = youngest_merged_rev;
SVN_ERR(svn_client__repos_location(url_left, target_ra_session,
- target_url, target_rev,
+ target->loc.url, target->loc.rev,
youngest_merged_rev,
ctx, result_pool, iterpool));
}
@@ -10382,8 +10410,10 @@ calculate_left_hand_side(const char **ur
* from SOURCE_PATH_OR_URL at SOURCE_PEG_REVISION into the working
* copy at TARGET.
*
- * Set *TARGET_RA_SESSION_P and *SOURCE_RA_SESSION_P to new RA sessions
- * opened to the target and source branches respectively. Set *SOURCE_P to
+ * SOURCE_RA_SESSION and TARGET_RA_SESSION are RA sessions opened to the
+ * source and target branches respectively.
+ *
+ * Set *SOURCE_P to
* the source-left and source-right locations of the required merge. Set
* *YC_ANCESTOR_REV_P to the revision number of the youngest ancestor.
* Any of these output pointers may be NULL if not wanted.
@@ -10391,21 +10421,16 @@ calculate_left_hand_side(const char **ur
* See svn_client_find_reintegrate_merge() for other details.
*/
static svn_error_t *
-find_reintegrate_merge(svn_ra_session_t **target_ra_session_p,
- svn_ra_session_t **source_ra_session_p,
- merge_source_t **source_p,
+find_reintegrate_merge(merge_source_t **source_p,
svn_revnum_t *yc_ancestor_rev_p,
- const char *source_path_or_url,
- const svn_opt_revision_t *source_peg_revision,
+ svn_ra_session_t *source_ra_session,
+ const repo_location_t *source_loc,
+ svn_ra_session_t *target_ra_session,
const merge_target_t *target,
svn_client_ctx_t *ctx,
apr_pool_t *result_pool,
apr_pool_t *scratch_pool)
{
- url_uuid_t source_repos_root;
- svn_ra_session_t *target_ra_session;
- svn_ra_session_t *source_ra_session;
- const char *source_url, *source_repos_rel_path, *target_repos_rel_path;
const char *yc_ancestor_relpath;
svn_revnum_t yc_ancestor_rev;
merge_source_t source;
@@ -10414,60 +10439,15 @@ find_reintegrate_merge(svn_ra_session_t
svn_error_t *err;
apr_hash_t *subtrees_with_mergeinfo;
- if (! target->url)
- return svn_error_createf(SVN_ERR_CLIENT_UNRELATED_RESOURCES, NULL,
- _("Can't reintegrate into '%s' because it is "
- "locally added and therefore not related to "
- "the merge source"),
- svn_dirent_local_style(target->abspath,
- scratch_pool));
-
- /* Make sure we're dealing with a real URL. */
- SVN_ERR(svn_client_url_from_path2(&source_url, source_path_or_url, ctx,
- scratch_pool, scratch_pool));
- if (! source_url)
- return svn_error_createf(SVN_ERR_ENTRY_MISSING_URL, NULL,
- _("'%s' has no URL"),
- svn_dirent_local_style(source_path_or_url,
- scratch_pool));
-
- /* Determine the source's repository root URL. */
- SVN_ERR(svn_client_get_repos_root(&source_repos_root.url,
- &source_repos_root.uuid, source_url,
- ctx, scratch_pool, scratch_pool));
-
- /* source_repos_root and target->repos_root are required to be the same,
- as mergeinfo doesn't come into play for cross-repository merging. */
- SVN_ERR(check_same_repos(&source_repos_root,
- svn_dirent_local_style(source_path_or_url,
- scratch_pool),
- &target->repos_root,
- svn_dirent_local_style(target->abspath,
- scratch_pool),
- TRUE /* strict_urls */, scratch_pool));
-
- /* A reintegrate merge requires the merge target to reflect a subtree
- * of the repository as found at a single revision. */
- SVN_ERR(ensure_wc_is_suitable_merge_target(target->abspath, ctx,
- FALSE, FALSE, FALSE,
- scratch_pool));
-
/* As the WC tree is "pure", use its last-updated-to revision as
the default revision for the left side of our merge, since that's
what the repository sub-tree is required to be up to date with
(with regard to the WC). */
/* ### Bogus/obsolete comment? */
- source_repos_rel_path = svn_uri_skip_ancestor(target->repos_root.url, source_url,
- scratch_pool);
- SVN_ERR(svn_client__path_relative_to_root(&target_repos_rel_path,
- ctx->wc_ctx, target->abspath,
- NULL, FALSE, NULL,
- scratch_pool, scratch_pool));
-
/* Can't reintegrate to or from the root of the repository. */
- if (svn_path_is_empty(source_repos_rel_path)
- || svn_path_is_empty(target_repos_rel_path))
+ if (strcmp(source_loc->url, source_loc->repo->url) == 0
+ || strcmp(target->loc.url, target->loc.repo->url) == 0)
return svn_error_createf(SVN_ERR_CLIENT_NOT_READY_TO_MERGE, NULL,
_("Neither the reintegrate source nor target "
"can be the root of the repository"));
@@ -10482,26 +10462,12 @@ find_reintegrate_merge(svn_ra_session_t
err = svn_error_quick_wrap(err, _("Reintegrate merge not possible"));
SVN_ERR(err);
- /* Open two RA sessions, one to our source and one to our target. */
- SVN_ERR(svn_client__ra_session_from_path(&source_ra_session, &source.rev2, &source.url2,
- source_url, NULL, source_peg_revision,
- source_peg_revision,
- ctx, result_pool));
- SVN_ERR(svn_client__open_ra_session_internal(&target_ra_session, NULL,
- target->url,
- NULL, NULL, FALSE, FALSE,
- ctx, result_pool));
-
SVN_ERR(calculate_left_hand_side(&source.url1, &source.rev1,
&merged_to_source_mergeinfo_catalog,
&unmerged_to_source_mergeinfo_catalog,
- target->abspath,
- target_repos_rel_path,
+ target,
subtrees_with_mergeinfo,
- target->rev,
- source_repos_rel_path,
- source_repos_root.url,
- source.rev2,
+ source_loc,
source_ra_session,
target_ra_session,
ctx,
@@ -10511,10 +10477,6 @@ find_reintegrate_merge(svn_ra_session_t
be performed here? */
if (! source.url1)
{
- if (target_ra_session_p)
- *target_ra_session_p = NULL;
- if (source_ra_session_p)
- *source_ra_session_p = NULL;
if (source_p)
*source_p = NULL;
if (yc_ancestor_rev_p)
@@ -10522,10 +10484,13 @@ find_reintegrate_merge(svn_ra_session_t
return SVN_NO_ERROR;
}
+ source.rev2 = source_loc->rev;
+ source.url2 = source_loc->url;
+
/* If the target was moved after the source was branched from it,
it is possible that the left URL differs from the target's current
URL. If so, then adjust TARGET_RA_SESSION to point to the old URL. */
- if (strcmp(source.url1, target->url))
+ if (strcmp(source.url1, target->loc.url))
SVN_ERR(svn_ra_reparent(target_ra_session, source.url1, scratch_pool));
SVN_ERR(svn_client__get_youngest_common_ancestor(&yc_ancestor_relpath, NULL,
@@ -10547,6 +10512,9 @@ find_reintegrate_merge(svn_ra_session_t
target? If so, make sure we've merged a contiguous
prefix. */
svn_mergeinfo_t final_unmerged_catalog = apr_hash_make(scratch_pool);
+ const char *source_repos_rel_path
+ = svn_uri_skip_ancestor(source_loc->repo->url, source_loc->url,
+ scratch_pool);
SVN_ERR(find_unsynced_ranges(source_repos_rel_path,
yc_ancestor_relpath,
@@ -10572,17 +10540,13 @@ find_reintegrate_merge(svn_ra_session_t
"reintegrate source, but this is "
"not the case:\n%s"),
yc_ancestor_rev + 1, source.rev2,
- target->url,
+ target->loc.url,
source_mergeinfo_cat_string->data);
}
}
/* Left side: trunk@youngest-trunk-rev-merged-to-branch-at-specified-peg-rev
* Right side: branch@specified-peg-revision */
- if (target_ra_session_p)
- *target_ra_session_p = target_ra_session;
- if (source_ra_session_p)
- *source_ra_session_p = source_ra_session;
if (source_p)
*source_p = apr_pmemdup(result_pool, &source, sizeof(source));
if (yc_ancestor_rev_p)
@@ -10590,6 +10554,73 @@ find_reintegrate_merge(svn_ra_session_t
return SVN_NO_ERROR;
}
+/* Resolve the source and target locations and open RA sessions to them, and
+ * perform some checks appropriate for a reintegrate merge.
+ *
+ * Set *SOURCE_RA_SESSION_P and *SOURCE_LOC_P to a new session and the
+ * repository location of SOURCE_PATH_OR_URL at SOURCE_PEG_REVISION. Set
+ * *TARGET_RA_SESSION_P and *TARGET_P to a new session and the repository
+ * location of the WC at TARGET_ABSPATH.
+ *
+ * Throw a SVN_ERR_CLIENT_UNRELATED_RESOURCES error if the target WC node is
+ * a locally added node or if the source and target are not in the same
+ * repository. Throw a SVN_ERR_CLIENT_NOT_READY_TO_MERGE error if the
+ * target WC is not at a single revision without switched subtrees and
+ * without local mods.
+ *
+ * Allocate all the outputs in RESULT_POOL.
+ */
+static svn_error_t *
+open_reintegrate_source_and_target(svn_ra_session_t **source_ra_session_p,
+ repo_location_t **source_loc_p,
+ svn_ra_session_t **target_ra_session_p,
+ merge_target_t **target_p,
+ const char *source_path_or_url,
+ const svn_opt_revision_t *source_peg_revision,
+ const char *target_abspath,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ repo_location_t *source_loc;
+ merge_target_t *target;
+
+ /* Open the target WC. A reintegrate merge requires the merge target to
+ * reflect a subtree of the repository as found at a single revision. */
+ SVN_ERR(open_target_wc(&target, target_abspath,
+ FALSE, FALSE, FALSE,
+ ctx, scratch_pool, scratch_pool));
+ SVN_ERR(svn_client__open_ra_session_internal(target_ra_session_p, NULL,
+ target->loc.url,
+ NULL, NULL, FALSE, FALSE,
+ ctx, scratch_pool));
+ if (! target->loc.url)
+ return svn_error_createf(SVN_ERR_CLIENT_UNRELATED_RESOURCES, NULL,
+ _("Can't reintegrate into '%s' because it is "
+ "locally added and therefore not related to "
+ "the merge source"),
+ svn_dirent_local_style(target->abspath,
+ scratch_pool));
+
+ SVN_ERR(open_source_session(&source_loc, source_ra_session_p,
+ source_path_or_url, source_peg_revision,
+ ctx, result_pool, scratch_pool));
+
+ /* source_loc->repo and target->loc.repo are required to be the same,
+ as mergeinfo doesn't come into play for cross-repository merging. */
+ SVN_ERR(check_same_repos(source_loc->repo,
+ svn_dirent_local_style(source_path_or_url,
+ scratch_pool),
+ target->loc.repo,
+ svn_dirent_local_style(target->abspath,
+ scratch_pool),
+ TRUE /* strict_urls */, scratch_pool));
+
+ *source_loc_p = source_loc;
+ *target_p = target;
+ return SVN_NO_ERROR;
+}
+
svn_error_t *
svn_client_find_reintegrate_merge(const char **url1_p,
svn_revnum_t *rev1_p,
@@ -10603,16 +10634,23 @@ svn_client_find_reintegrate_merge(const
apr_pool_t *scratch_pool)
{
const char *target_abspath;
+ svn_ra_session_t *source_ra_session;
+ repo_location_t *source_loc;
+ svn_ra_session_t *target_ra_session;
merge_target_t *target;
merge_source_t *source;
SVN_ERR(svn_dirent_get_absolute(&target_abspath, target_wcpath,
scratch_pool));
- SVN_ERR(target_node_location(&target, target_abspath,
- ctx, scratch_pool, scratch_pool));
- SVN_ERR(find_reintegrate_merge(NULL, NULL, &source, NULL,
- source_path_or_url, source_peg_revision,
- target,
+
+ SVN_ERR(open_reintegrate_source_and_target(
+ &source_ra_session, &source_loc, &target_ra_session, &target,
+ source_path_or_url, source_peg_revision, target_abspath,
+ ctx, scratch_pool, scratch_pool));
+
+ SVN_ERR(find_reintegrate_merge(&source, NULL,
+ source_ra_session, source_loc,
+ target_ra_session, target,
ctx, result_pool, scratch_pool));
if (source)
{
@@ -10643,18 +10681,20 @@ merge_reintegrate_locked(const char *sou
{
svn_ra_session_t *target_ra_session, *source_ra_session;
merge_target_t *target;
+ repo_location_t *source_loc;
merge_source_t *source;
svn_revnum_t yc_ancestor_rev;
svn_boolean_t use_sleep;
svn_error_t *err;
- SVN_ERR(target_node_location(&target, target_abspath,
- ctx, scratch_pool, scratch_pool));
-
- SVN_ERR(find_reintegrate_merge(&target_ra_session, &source_ra_session,
- &source, &yc_ancestor_rev,
- source_path_or_url, source_peg_revision,
- target,
+ SVN_ERR(open_reintegrate_source_and_target(
+ &source_ra_session, &source_loc, &target_ra_session, &target,
+ source_path_or_url, source_peg_revision, target_abspath,
+ ctx, scratch_pool, scratch_pool));
+
+ SVN_ERR(find_reintegrate_merge(&source, &yc_ancestor_rev,
+ source_ra_session, source_loc,
+ target_ra_session, target,
ctx, scratch_pool, scratch_pool));
if (! source)
@@ -10734,10 +10774,8 @@ merge_peg_locked(const char *source_path
apr_pool_t *scratch_pool)
{
merge_target_t *target;
- const char *source_url;
- svn_revnum_t source_peg_revnum;
+ repo_location_t *source_loc;
apr_array_header_t *merge_sources;
- url_uuid_t source_repos_root;
svn_ra_session_t *ra_session;
apr_pool_t *sesspool;
svn_boolean_t use_sleep = FALSE;
@@ -10746,30 +10784,24 @@ merge_peg_locked(const char *source_path
SVN_ERR_ASSERT(svn_dirent_is_absolute(target_abspath));
- SVN_ERR(target_node_location(&target, target_abspath,
- ctx, scratch_pool, scratch_pool));
- SVN_ERR(ensure_wc_is_suitable_merge_target(target_abspath, ctx,
- allow_mixed_rev, TRUE, TRUE,
- scratch_pool));
+ SVN_ERR(open_target_wc(&target, target_abspath,
+ allow_mixed_rev, TRUE, TRUE,
+ ctx, scratch_pool, scratch_pool));
/* Open an RA session to our source URL, and determine its root URL. */
sesspool = svn_pool_create(scratch_pool);
- SVN_ERR(svn_client__ra_session_from_path(
- &ra_session, &source_peg_revnum, &source_url,
- source_path_or_url, NULL, source_peg_revision, source_peg_revision,
- ctx, sesspool));
- SVN_ERR(svn_ra_get_repos_root2(ra_session, &source_repos_root.url,
- scratch_pool));
- SVN_ERR(svn_ra_get_uuid2(ra_session, &source_repos_root.uuid, scratch_pool));
+ SVN_ERR(open_source_session(&source_loc, &ra_session,
+ source_path_or_url, source_peg_revision,
+ ctx, sesspool, scratch_pool));
/* Normalize our merge sources. */
SVN_ERR(normalize_merge_sources(&merge_sources, source_path_or_url,
- source_url, source_peg_revnum,
+ source_loc->url, source_loc->rev,
ranges_to_merge, ra_session, ctx,
scratch_pool, scratch_pool));
/* Check for same_repos. */
- same_repos = is_same_repos(&target->repos_root, &source_repos_root,
+ same_repos = is_same_repos(target->loc.repo, source_loc->repo,
TRUE /* strict_urls */);
/* We're done with our little RA session. */
@@ -10832,3 +10864,382 @@ svn_client_merge_peg4(const char *source
return SVN_NO_ERROR;
}
+
+#ifdef SVN_WITH_SYMMETRIC_MERGE
+
+/* Details of a symmetric merge. */
+struct svn_client__symmetric_merge_t
+{
+ repo_location_t *yca, *base, *mid, *right;
+};
+
+/* */
+typedef struct source_and_target_t
+{
+ repo_location_t *source;
+ svn_ra_session_t *source_ra_session;
+ merge_target_t *target;
+ svn_ra_session_t *target_ra_session;
+} source_and_target_t;
+
+/* "Open" the source and target branches of a merge. That means:
+ * - find out their exact repository locations (resolve WC paths and
+ * non-numeric revision numbers),
+ * - check the branches are suitably related,
+ * - establish RA session(s) to the repo,
+ * - check the WC for suitability (throw an error if unsuitable)
+ *
+ * Record this information and return it in a new "merge context" object.
+ */
+static svn_error_t *
+open_source_and_target(source_and_target_t **source_and_target,
+ const char *source_path_or_url,
+ const svn_opt_revision_t *source_peg_revision,
+ const char *target_abspath,
+ svn_boolean_t allow_mixed_rev,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *session_pool,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ source_and_target_t *s_t = apr_palloc(result_pool, sizeof(*s_t));
+
+ /* Target */
+ SVN_ERR(open_target_wc(&s_t->target, target_abspath,
+ allow_mixed_rev, TRUE, TRUE,
+ ctx, result_pool, scratch_pool));
+ SVN_ERR(svn_client_open_ra_session(&s_t->target_ra_session,
+ s_t->target->loc.url,
+ ctx, session_pool));
+
+ /* Source */
+ SVN_ERR(open_source_session(&s_t->source, &s_t->source_ra_session,
+ source_path_or_url, source_peg_revision,
+ ctx, result_pool, scratch_pool));
+
+ *source_and_target = s_t;
+ return SVN_NO_ERROR;
+}
+
+/* "Close" any resources that were acquired in the S_T structure. */
+static svn_error_t *
+close_source_and_target(source_and_target_t *s_t,
+ apr_pool_t *scratch_pool)
+{
+ /* close s_t->source_/target_ra_session */
+ return SVN_NO_ERROR;
+}
+
+/* Find a merge base location on the target branch, like in a sync
+ * merge.
+ *
+ * (Source-left) (Source-right)
+ * BASE RIGHT
+ * o-------o-----------o---
+ * / \ \
+ * -----o prev. \ \ this
+ * YCA \ merge \ \ merge
+ * o-----------o-----------o
+ * TARGET
+ *
+ */
+static svn_error_t *
+find_base_on_source(repo_location_t **base_p,
+ source_and_target_t *s_t,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ *base_p = NULL;
+ return SVN_NO_ERROR;
+}
+
+/* Find a merge base location on the target branch, like in a reintegrate
+ * merge.
+ *
+ * MID RIGHT
+ * o-----------o-------o---
+ * / prev. / \
+ * -----o merge / \ this
+ * YCA \ / \ merge
+ * o-------o---------------o
+ * BASE TARGET
+ *
+ * Set *BASE_P to the latest location on the history of S_T->target at
+ * which all revisions up to *BASE_P are recorded as merged into RIGHT
+ * (which is S_T->source).
+ *
+ * ### TODO: Set *MID_P to the first location on the history of
+ * S_T->source at which all revisions up to BASE_P are recorded as merged.
+ */
+static svn_error_t *
+find_base_on_target(repo_location_t **base_p,
+ repo_location_t **mid_p,
+ source_and_target_t *s_t,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ repo_location_t *base = apr_palloc(result_pool, sizeof(*base));
+ svn_mergeinfo_t unmerged_to_source_mergeinfo_catalog;
+ svn_mergeinfo_t merged_to_source_mergeinfo_catalog;
+ apr_hash_t *subtrees_with_mergeinfo;
+
+ /* Find all the subtrees in TARGET_WCPATH that have explicit mergeinfo. */
+ SVN_ERR(get_wc_explicit_mergeinfo_catalog(&subtrees_with_mergeinfo,
+ s_t->target->abspath,
+ svn_depth_infinity,
+ ctx, scratch_pool, scratch_pool));
+
+ SVN_ERR(calculate_left_hand_side(&base->url, &base->rev,
+ &merged_to_source_mergeinfo_catalog,
+ &unmerged_to_source_mergeinfo_catalog,
+ s_t->target,
+ subtrees_with_mergeinfo,
+ s_t->source,
+ s_t->source_ra_session,
+ s_t->target_ra_session,
+ ctx, result_pool, scratch_pool));
+
+ if (base->url)
+ {
+ *base_p = base;
+ *mid_p = s_t->source; /* ### WRONG! This is quite difficult. */
+ }
+ else
+ {
+ *base_p = NULL;
+ *mid_p = NULL;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* The body of svn_client__find_symmetric_merge(), which see.
+ */
+static svn_error_t *
+find_symmetric_merge(repo_location_t **yca_p,
+ repo_location_t **base_p,
+ repo_location_t **mid_p,
+ source_and_target_t *s_t,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ repo_location_t *yca, *base_on_source, *base_on_target, *mid;
+
+ yca = apr_palloc(result_pool, sizeof(*yca));
+ SVN_ERR(svn_client__get_youngest_common_ancestor(
+ NULL, &yca->url, &yca->rev,
+ s_t->source->url, s_t->source->rev,
+ s_t->target->loc.url, s_t->target->loc.rev,
+ ctx, result_pool));
+ *yca_p = yca;
+
+ /* Find the latest revision of A synced to B and the latest
+ * revision of B synced to A.
+ *
+ * base_on_source = youngest_complete_synced_point(source, target)
+ * base_on_target = youngest_complete_synced_point(target, source)
+ */
+ SVN_ERR(find_base_on_source(&base_on_source, s_t,
+ ctx, scratch_pool, scratch_pool));
+ SVN_ERR(find_base_on_target(&base_on_target, &mid, s_t,
+ ctx, scratch_pool, scratch_pool));
+
+ if (base_on_source)
+ SVN_DBG(("base on source: %s@%ld\n", base_on_source->url, base_on_source->rev));
+ if (base_on_target)
+ SVN_DBG(("base on target: %s@%ld\n", base_on_target->url, base_on_target->rev));
+
+ /* Choose a base. */
+ if (base_on_source
+ && (! base_on_target || (base_on_source->rev > base_on_target->rev)))
+ {
+ *base_p = base_on_source;
+ *mid_p = NULL;
+ }
+ else if (base_on_target)
+ {
+ *base_p = base_on_target;
+ *mid_p = mid;
+ }
+ else
+ {
+ /* No previous merge was found, so this is the simple case where
+ * the base is the youngest common ancestor of the branches. We'll
+ * set MID=NULL; in theory the end result should be the same if we
+ * set MID=YCA instead. */
+ *base_p = yca;
+ *mid_p = NULL;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_client__find_symmetric_merge(svn_client__symmetric_merge_t **merge_p,
+ const char *source_path_or_url,
+ const svn_opt_revision_t *source_revision,
+ const char *target_wcpath,
+ svn_boolean_t allow_mixed_rev,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ const char *target_abspath;
+ source_and_target_t *s_t;
+ svn_client__symmetric_merge_t *merge = apr_palloc(result_pool, sizeof(*merge));
+
+ SVN_ERR(svn_dirent_get_absolute(&target_abspath, target_wcpath, scratch_pool));
+ SVN_ERR(open_source_and_target(&s_t, source_path_or_url, source_revision,
+ target_abspath, allow_mixed_rev,
+ ctx, result_pool, result_pool, scratch_pool));
+
+ /* Check source is in same repos as target. */
+ SVN_ERR(check_same_repos(s_t->source->repo, source_path_or_url,
+ s_t->target->loc.repo, target_wcpath,
+ TRUE /* strict_urls */, scratch_pool));
+
+ SVN_ERR(find_symmetric_merge(&merge->yca, &merge->base, &merge->mid, s_t,
+ ctx, result_pool, scratch_pool));
+ merge->right = s_t->source;
+
+ *merge_p = merge;
+
+ SVN_ERR(close_source_and_target(s_t, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* The body of svn_client__do_symmetric_merge(), which see.
+ *
+ * Five locations are inputs: YCA, BASE, MID, RIGHT, TARGET, as shown
+ * depending on whether the base is on the source branch or the target
+ * branch of this merge.
+ *
+ * MID RIGHT
+ * o-----------o-------o---
+ * / prev. / \
+ * -----o merge / \ this
+ * YCA \ / \ merge
+ * o-------o---------------o
+ * BASE TARGET
+ *
+ * or
+ *
+ * BASE RIGHT (and MID=NULL)
+ * o-------o-----------o---
+ * / \ \
+ * -----o prev. \ \ this
+ * YCA \ merge \ \ merge
+ * o-----------o-----------o
+ * TARGET
+ *
+ * ### TODO: The reintegrate-type (MID!=NULL) code path does not yet
+ * eliminate already-cherry-picked revisions from the source.
+ */
+static svn_error_t *
+do_symmetric_merge_locked(const svn_client__symmetric_merge_t *merge,
+ const char *target_abspath,
+ svn_depth_t depth,
+ svn_boolean_t ignore_ancestry,
+ svn_boolean_t force,
+ svn_boolean_t record_only,
+ svn_boolean_t dry_run,
+ const apr_array_header_t *merge_options,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *scratch_pool)
+{
+ merge_target_t *target;
+ merge_source_t *source = apr_palloc(scratch_pool, sizeof(*source));
+ svn_boolean_t use_sleep = FALSE;
+ svn_error_t *err;
+
+ SVN_ERR(open_target_wc(&target, target_abspath, TRUE, TRUE, TRUE,
+ ctx, scratch_pool, scratch_pool));
+
+ source->url1 = merge->base->url;
+ source->rev1 = merge->base->rev;
+ source->url2 = merge->right->url;
+ source->rev2 = merge->right->rev;
+ SVN_DBG(("yca %s@%ld\n", merge->yca->url, merge->yca->rev));
+ SVN_DBG(("base %s@%ld\n", merge->base->url, merge->base->rev));
+ if (merge->mid)
+ SVN_DBG(("mid %s@%ld\n", merge->mid->url, merge->mid->rev));
+ SVN_DBG(("right %s@%ld\n", merge->right->url, merge->right->rev));
+
+ if (merge->mid)
+ {
+ svn_ra_session_t *ra_session = NULL;
+
+ SVN_ERR(ensure_ra_session_url(&ra_session, source->url1,
+ ctx, scratch_pool));
+
+ err = merge_cousins_and_supplement_mergeinfo(target,
+ ra_session, ra_session,
+ source, merge->yca->rev,
+ TRUE /* same_repos */,
+ depth, ignore_ancestry,
+ force, record_only,
+ dry_run,
+ merge_options, &use_sleep,
+ ctx, scratch_pool);
+
+ }
+ else
+ {
+ apr_array_header_t *merge_sources;
+
+ merge_sources = apr_array_make(scratch_pool, 1, sizeof(merge_source_t *));
+ APR_ARRAY_PUSH(merge_sources, const merge_source_t *) = source;
+
+ err = do_merge(NULL, NULL, merge_sources, target,
+ TRUE /*sources_ancestral*/, TRUE /*related*/,
+ TRUE /*same_repos*/, ignore_ancestry, force, dry_run,
+ record_only, NULL, FALSE, FALSE, depth, merge_options,
+ &use_sleep, ctx, scratch_pool, scratch_pool);
+ }
+
+ if (use_sleep)
+ svn_io_sleep_for_timestamps(target_abspath, scratch_pool);
+
+ SVN_ERR(err);
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_client__do_symmetric_merge(const svn_client__symmetric_merge_t *merge,
+ const char *target_wcpath,
+ svn_depth_t depth,
+ svn_boolean_t ignore_ancestry,
+ svn_boolean_t force,
+ svn_boolean_t record_only,
+ svn_boolean_t dry_run,
+ const apr_array_header_t *merge_options,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ const char *target_abspath, *lock_abspath;
+
+ SVN_ERR(get_target_and_lock_abspath(&target_abspath, &lock_abspath,
+ target_wcpath, ctx, pool));
+
+ if (!dry_run)
+ SVN_WC__CALL_WITH_WRITE_LOCK(
+ do_symmetric_merge_locked(merge,
+ target_abspath, depth, ignore_ancestry,
+ force, record_only, dry_run,
+ merge_options, ctx, pool),
+ ctx->wc_ctx, lock_abspath, FALSE /* lock_anchor */, pool);
+ else
+ SVN_ERR(do_symmetric_merge_locked(merge,
+ target_abspath, depth, ignore_ancestry,
+ force, record_only, dry_run,
+ merge_options, ctx, pool));
+
+ return SVN_NO_ERROR;
+}
+
+#endif /* SVN_WITH_SYMMETRIC_MERGE */
Modified: subversion/branches/inheritable-props/subversion/libsvn_client/prop_commands.c
URL: http://svn.apache.org/viewvc/subversion/branches/inheritable-props/subversion/libsvn_client/prop_commands.c?rev=1303257&r1=1303256&r2=1303257&view=diff
==============================================================================
--- subversion/branches/inheritable-props/subversion/libsvn_client/prop_commands.c (original)
+++ subversion/branches/inheritable-props/subversion/libsvn_client/prop_commands.c Wed Mar 21 02:38:58 2012
@@ -75,7 +75,7 @@ is_revision_prop_name(const char *name)
static svn_error_t *
error_if_wcprop_name(const char *name)
{
- if (svn_property_kind(NULL, name) == svn_prop_wc_kind)
+ if (svn_property_kind2(name) == svn_prop_wc_kind)
{
return svn_error_createf
(SVN_ERR_CLIENT_PROPERTY_NAME, NULL,
@@ -162,7 +162,7 @@ propset_on_url(const char *propname,
svn_client_ctx_t *ctx,
apr_pool_t *pool)
{
- enum svn_prop_kind prop_kind = svn_property_kind(NULL, propname);
+ enum svn_prop_kind prop_kind = svn_property_kind2(propname);
svn_ra_session_t *ra_session;
svn_node_kind_t node_kind;
const char *message;
@@ -1039,7 +1039,7 @@ remote_proplist(const char *target_prefi
svn_string_t *value = svn__apr_hash_index_val(hi);
svn_prop_kind_t prop_kind;
- prop_kind = svn_property_kind(NULL, name);
+ prop_kind = svn_property_kind2(name);
if (prop_kind == svn_prop_regular_kind)
{
Modified: subversion/branches/inheritable-props/subversion/libsvn_client/util.c
URL: http://svn.apache.org/viewvc/subversion/branches/inheritable-props/subversion/libsvn_client/util.c?rev=1303257&r1=1303256&r2=1303257&view=diff
==============================================================================
--- subversion/branches/inheritable-props/subversion/libsvn_client/util.c (original)
+++ subversion/branches/inheritable-props/subversion/libsvn_client/util.c Wed Mar 21 02:38:58 2012
@@ -90,10 +90,7 @@ svn_client__path_relative_to_root(const
/* If we have a WC path... */
if (! svn_path_is_url(abspath_or_url))
{
- /* ...fetch its entry, and attempt to get both its full URL and
- repository root URL. If we can't get REPOS_ROOT from the WC
- entry, we'll get it from the RA layer.*/
-
+ /* ... query it directly. */
SVN_ERR(svn_wc__node_get_repos_relpath(&repos_relpath,
wc_ctx,
abspath_or_url,
Modified: subversion/branches/inheritable-props/subversion/libsvn_delta/compat.c
URL: http://svn.apache.org/viewvc/subversion/branches/inheritable-props/subversion/libsvn_delta/compat.c?rev=1303257&r1=1303256&r2=1303257&view=diff
==============================================================================
--- subversion/branches/inheritable-props/subversion/libsvn_delta/compat.c (original)
+++ subversion/branches/inheritable-props/subversion/libsvn_delta/compat.c Wed Mar 21 02:38:58 2012
@@ -31,7 +31,7 @@
#include "svn_props.h"
#include "svn_pools.h"
-
+
struct file_rev_handler_wrapper_baton {
void *baton;
svn_file_rev_handler_old_t handler;
@@ -130,6 +130,8 @@ struct ev2_edit_baton
{
svn_editor_t *editor;
+ /* ### need to ensure we understand the proper root for these relpaths */
+ apr_hash_t *changes; /* RELPATH -> struct change_node */
apr_hash_t *paths;
apr_array_header_t *path_order;
int paths_processed;
@@ -179,7 +181,6 @@ enum action_code_t
ACTION_ADD,
ACTION_DELETE,
ACTION_ADD_ABSENT,
- ACTION_SET_TEXT,
ACTION_UNLOCK
};
@@ -189,26 +190,43 @@ struct path_action
void *args;
};
-struct prop_args
-{
- const char *name;
- svn_revnum_t base_revision;
- const svn_string_t *value;
- svn_kind_t kind;
-};
-
struct copy_args
{
const char *copyfrom_path;
svn_revnum_t copyfrom_rev;
};
-struct path_checksum_args
+enum restructure_action_t
{
- const char *path;
+ RESTRUCTURE_NONE = 0,
+ RESTRUCTURE_ADD, /* add the node, maybe replacing. maybe copy */
+ RESTRUCTURE_ADD_ABSENT, /* add an absent node, possibly replacing */
+ RESTRUCTURE_DELETE, /* delete this node */
+};
+
+/* Records everything about how this node is to be changed. */
+struct change_node
+{
+ /* what kind of (tree) restructure is occurring at this node? */
+ enum restructure_action_t action;
+
+ svn_kind_t kind; /* the NEW kind of this node */
+
+ /* The revision we're trying to change. Replace it, modify it, etc. */
svn_revnum_t base_revision;
+
+ apr_hash_t *props; /* new/final set of props to apply */
+
+ const char *contents_abspath; /* file containing new fulltext */
+ svn_checksum_t *checksum; /* checksum of new fulltext */
+
+ /* If COPYFROM_PATH is not NULL, then copy PATH@REV to this node.
+ RESTRUCTURE must be RESTRUCTURE_ADD. */
+ const char *copyfrom_path;
+ svn_revnum_t copyfrom_rev;
};
+
static svn_error_t *
add_action(struct ev2_edit_baton *eb,
const char *path,
@@ -238,6 +256,96 @@ add_action(struct ev2_edit_baton *eb,
return SVN_NO_ERROR;
}
+
+static struct change_node *
+locate_change(struct ev2_edit_baton *eb,
+ const char *relpath)
+{
+ struct change_node *change = apr_hash_get(eb->changes, relpath,
+ APR_HASH_KEY_STRING);
+ apr_array_header_t *action_list;
+
+ if (change != NULL)
+ return change;
+
+ /* Shift RELPATH into the proper pool. */
+ relpath = apr_pstrdup(eb->edit_pool, relpath);
+
+ /* Investigate whether there is an action in PATHS. Any presence there
+ will determine whether we need to update PATH_ORDER. */
+ action_list = apr_hash_get(eb->paths, relpath, APR_HASH_KEY_STRING);
+ if (action_list == NULL)
+ {
+ /* Store an empty ACTION_LIST into PATHS. */
+ action_list = apr_array_make(eb->edit_pool, 1,
+ sizeof(struct path_action *));
+ apr_hash_set(eb->paths, relpath, APR_HASH_KEY_STRING, action_list);
+ APR_ARRAY_PUSH(eb->path_order, const char *) = relpath;
+ }
+
+ /* Return an empty change. Callers will tweak as needed. */
+ change = apr_pcalloc(eb->edit_pool, sizeof(*change));
+ change->base_revision = SVN_INVALID_REVNUM;
+
+ apr_hash_set(eb->changes, relpath, APR_HASH_KEY_STRING, change);
+
+ return change;
+}
+
+
+static svn_error_t *
+apply_propedit(struct ev2_edit_baton *eb,
+ const char *relpath,
+ svn_kind_t kind,
+ svn_revnum_t base_revision,
+ const char *name,
+ const svn_string_t *value,
+ apr_pool_t *scratch_pool)
+{
+ struct change_node *change = locate_change(eb, relpath);
+
+ SVN_ERR_ASSERT(change->kind == svn_kind_unknown || change->kind == kind);
+ change->kind = kind;
+
+ SVN_ERR_ASSERT(!SVN_IS_VALID_REVNUM(change->base_revision)
+ || change->base_revision == base_revision);
+ change->base_revision = base_revision;
+
+ if (change->props == NULL)
+ {
+ /* Fetch the original set of properties. We'll apply edits to create
+ the new/target set of properties.
+
+ If this is a copied/moved now, then the original properties come
+ from there. If the node has been added, it starts with empty props.
+ Otherwise, we get the properties from BASE. */
+
+ if (change->copyfrom_path)
+ SVN_ERR(eb->fetch_props_func(&change->props,
+ eb->fetch_props_baton,
+ change->copyfrom_path,
+ change->copyfrom_rev,
+ eb->edit_pool, scratch_pool));
+ else if (change->action == RESTRUCTURE_ADD)
+ change->props = apr_hash_make(eb->edit_pool);
+ else
+ SVN_ERR(eb->fetch_props_func(&change->props,
+ eb->fetch_props_baton,
+ relpath, base_revision,
+ eb->edit_pool, scratch_pool));
+ }
+
+ if (value == NULL)
+ apr_hash_set(change->props, name, APR_HASH_KEY_STRING, NULL);
+ else
+ apr_hash_set(change->props,
+ apr_pstrdup(eb->edit_pool, name), APR_HASH_KEY_STRING,
+ svn_string_dup(value, eb->edit_pool));
+
+ return SVN_NO_ERROR;
+}
+
+
/* Find all the paths which are immediate children of PATH and return their
basenames in a list. */
static apr_array_header_t *
@@ -275,12 +383,12 @@ get_children(struct ev2_edit_baton *eb,
static svn_error_t *
-process_actions(void *edit_baton,
+process_actions(struct ev2_edit_baton *eb,
const char *path,
apr_array_header_t *actions,
+ const struct change_node *change,
apr_pool_t *scratch_pool)
{
- struct ev2_edit_baton *eb = edit_baton;
apr_hash_t *props = NULL;
svn_boolean_t need_add = FALSE;
svn_boolean_t need_delete = FALSE;
@@ -311,42 +419,6 @@ process_actions(void *edit_baton,
switch (action->action)
{
- case ACTION_PROPSET:
- {
- const struct prop_args *p_args = action->args;
-
- kind = p_args->kind;
-
- if (!SVN_IS_VALID_REVNUM(props_base_revision))
- props_base_revision = p_args->base_revision;
- else
- SVN_ERR_ASSERT(p_args->base_revision == props_base_revision);
-
- if (!props)
- {
- /* Fetch the original props. We can then apply each of
- the modifications to it. */
- if (need_delete && need_add)
- props = apr_hash_make(scratch_pool);
- else if (need_copy)
- SVN_ERR(eb->fetch_props_func(&props,
- eb->fetch_props_baton,
- copyfrom_path,
- copyfrom_rev,
- scratch_pool, scratch_pool));
- else
- SVN_ERR(eb->fetch_props_func(&props,
- eb->fetch_props_baton,
- path, props_base_revision,
- scratch_pool, scratch_pool));
- }
-
- /* Note that p_args->value may be NULL. */
- apr_hash_set(props, p_args->name, APR_HASH_KEY_STRING,
- p_args->value);
- break;
- }
-
case ACTION_DELETE:
{
delete_revnum = *((svn_revnum_t *) action->args);
@@ -373,26 +445,6 @@ process_actions(void *edit_baton,
break;
}
- case ACTION_SET_TEXT:
- {
- struct path_checksum_args *pca = action->args;
-
- /* We can only set text on files. */
- kind = svn_kind_file;
-
- SVN_ERR(svn_io_file_checksum2(&checksum, pca->path,
- svn_checksum_sha1, scratch_pool));
- SVN_ERR(svn_stream_open_readonly(&contents, pca->path,
- scratch_pool, scratch_pool));
-
- if (!SVN_IS_VALID_REVNUM(text_base_revision))
- text_base_revision = pca->base_revision;
- else
- SVN_ERR_ASSERT(pca->base_revision == text_base_revision);
-
- break;
- }
-
case ACTION_COPY:
{
struct copy_args *c_args = action->args;
@@ -422,6 +474,32 @@ process_actions(void *edit_baton,
}
}
+ if (change != NULL)
+ {
+ if (change->contents_abspath != NULL)
+ {
+ /* We can only set text on files. */
+ /* ### validate we aren't overwriting KIND? */
+ kind = svn_kind_file;
+
+ /* ### the checksum might be in CHANGE->CHECKSUM */
+ SVN_ERR(svn_io_file_checksum2(&checksum, change->contents_abspath,
+ svn_checksum_sha1, scratch_pool));
+ SVN_ERR(svn_stream_open_readonly(&contents, change->contents_abspath,
+ scratch_pool, scratch_pool));
+
+ text_base_revision = change->base_revision;
+ }
+
+ if (change->props != NULL)
+ {
+ /* ### validate we aren't overwriting KIND? */
+ kind = change->kind;
+ props = change->props;
+ props_base_revision = change->base_revision;
+ }
+ }
+
/* We've now got a wholistic view of what has happened to this node,
* so we can call our own editor APIs on it. */
@@ -486,10 +564,9 @@ process_actions(void *edit_baton,
}
static svn_error_t *
-run_ev2_actions(void *edit_baton,
+run_ev2_actions(struct ev2_edit_baton *eb,
apr_pool_t *scratch_pool)
{
- struct ev2_edit_baton *eb = edit_baton;
apr_pool_t *iterpool;
iterpool = svn_pool_create(scratch_pool);
@@ -502,9 +579,12 @@ run_ev2_actions(void *edit_baton,
const char *);
apr_array_header_t *actions = apr_hash_get(eb->paths, path,
APR_HASH_KEY_STRING);
+ struct change_node *change = apr_hash_get(eb->changes, path,
+ APR_HASH_KEY_STRING);
svn_pool_clear(iterpool);
- SVN_ERR(process_actions(edit_baton, path, actions, iterpool));
+
+ SVN_ERR(process_actions(eb, path, actions, change, iterpool));
}
svn_pool_destroy(iterpool);
@@ -554,10 +634,29 @@ ev2_delete_entry(const char *path,
{
struct ev2_dir_baton *pb = parent_baton;
svn_revnum_t *revnum = apr_palloc(pb->eb->edit_pool, sizeof(*revnum));
+ struct change_node *change = locate_change(pb->eb, path);
+
+ if (SVN_IS_VALID_REVNUM(revision))
+ *revnum = revision;
+ else
+ *revnum = pb->base_revision;
- *revnum = revision;
SVN_ERR(add_action(pb->eb, path, ACTION_DELETE, revnum));
+ /* ### note: cannot switch to CHANGES just yet. the action loop needs
+ ### to see a delete action, and set NEED_DELETE. that is used for
+ ### the file properties. once fileprops are converted, then we
+ ### can fully switch over. */
+
+ /* ### assert that RESTRUCTURE is NONE? */
+ change->action = RESTRUCTURE_DELETE;
+
+#if 0
+ SVN_ERR_ASSERT(!SVN_IS_VALID_REVNUM(change->base_revision)
+ || change->base_revision == revision);
+ change->base_revision = revision;
+#endif
+
return SVN_NO_ERROR;
}
@@ -571,6 +670,10 @@ ev2_add_directory(const char *path,
{
struct ev2_dir_baton *pb = parent_baton;
struct ev2_dir_baton *cb = apr_pcalloc(result_pool, sizeof(*cb));
+ struct change_node *change = locate_change(pb->eb, path);
+
+ /* ### assert that RESTRUCTURE is NONE or DELETE? */
+ change->action = RESTRUCTURE_ADD;
cb->eb = pb->eb;
cb->path = apr_pstrdup(result_pool, path);
@@ -604,6 +707,9 @@ ev2_add_directory(const char *path,
cb->copyfrom_path = args->copyfrom_path;
cb->copyfrom_rev = args->copyfrom_rev;
+
+ change->copyfrom_path = apr_pstrdup(pb->eb->edit_pool, copyfrom_path);
+ change->copyfrom_rev = copyfrom_revision;
}
return SVN_NO_ERROR;
@@ -644,14 +750,9 @@ ev2_change_dir_prop(void *dir_baton,
apr_pool_t *scratch_pool)
{
struct ev2_dir_baton *db = dir_baton;
- struct prop_args *p_args = apr_palloc(db->eb->edit_pool, sizeof(*p_args));
-
- p_args->name = apr_pstrdup(db->eb->edit_pool, name);
- p_args->value = value ? svn_string_dup(value, db->eb->edit_pool) : NULL;
- p_args->base_revision = db->base_revision;
- p_args->kind = svn_kind_dir;
- SVN_ERR(add_action(db->eb, db->path, ACTION_PROPSET, p_args));
+ SVN_ERR(apply_propedit(db->eb, db->path, svn_kind_dir, db->base_revision,
+ name, value, scratch_pool));
return SVN_NO_ERROR;
}
@@ -687,6 +788,10 @@ ev2_add_file(const char *path,
{
struct ev2_file_baton *fb = apr_pcalloc(result_pool, sizeof(*fb));
struct ev2_dir_baton *pb = parent_baton;
+ struct change_node *change = locate_change(pb->eb, path);
+
+ /* ### assert that RESTRUCTURE is NONE or DELETE? */
+ change->action = RESTRUCTURE_ADD;
fb->eb = pb->eb;
fb->path = apr_pstrdup(result_pool, path);
@@ -717,6 +822,9 @@ ev2_add_file(const char *path,
args->copyfrom_path = apr_pstrdup(pb->eb->edit_pool, copyfrom_path);
args->copyfrom_rev = copyfrom_revision;
SVN_ERR(add_action(pb->eb, path, ACTION_COPY, args));
+
+ change->copyfrom_path = apr_pstrdup(fb->eb->edit_pool, copyfrom_path);
+ change->copyfrom_rev = copyfrom_revision;
}
return SVN_NO_ERROR;
@@ -799,11 +907,14 @@ ev2_apply_textdelta(void *file_baton,
struct ev2_file_baton *fb = file_baton;
apr_pool_t *handler_pool = svn_pool_create(fb->eb->edit_pool);
struct handler_baton *hb = apr_pcalloc(handler_pool, sizeof(*hb));
+ struct change_node *change;
svn_stream_t *target;
- struct path_checksum_args *pca = apr_pcalloc(fb->eb->edit_pool,
- sizeof(*pca));
- pca->base_revision = fb->base_revision;
+ change = locate_change(fb->eb, fb->path);
+ SVN_ERR_ASSERT(change->contents_abspath == NULL);
+ SVN_ERR_ASSERT(!SVN_IS_VALID_REVNUM(change->base_revision)
+ || change->base_revision == fb->base_revision);
+ change->base_revision = fb->base_revision;
if (! fb->delta_base)
hb->source = svn_stream_empty(handler_pool);
@@ -811,7 +922,7 @@ ev2_apply_textdelta(void *file_baton,
SVN_ERR(svn_stream_open_readonly(&hb->source, fb->delta_base, handler_pool,
result_pool));
- SVN_ERR(svn_stream_open_unique(&target, &pca->path, NULL,
+ SVN_ERR(svn_stream_open_unique(&target, &change->contents_abspath, NULL,
svn_io_file_del_on_pool_cleanup,
fb->eb->edit_pool, result_pool));
@@ -825,8 +936,6 @@ ev2_apply_textdelta(void *file_baton,
*handler_baton = hb;
*handler = window_handler;
- SVN_ERR(add_action(fb->eb, fb->path, ACTION_SET_TEXT, pca));
-
return SVN_NO_ERROR;
}
@@ -837,7 +946,6 @@ ev2_change_file_prop(void *file_baton,
apr_pool_t *scratch_pool)
{
struct ev2_file_baton *fb = file_baton;
- struct prop_args *p_args = apr_palloc(fb->eb->edit_pool, sizeof(*p_args));
if (!strcmp(name, SVN_PROP_ENTRY_LOCK_TOKEN) && value == NULL)
{
@@ -846,14 +954,8 @@ ev2_change_file_prop(void *file_baton,
SVN_ERR(add_action(fb->eb, fb->path, ACTION_UNLOCK, NULL));
}
- /* We also pass through the deletion, since there may actually exist such
- a property we want to get rid of. In the worse case, this is a no-op. */
- p_args->name = apr_pstrdup(fb->eb->edit_pool, name);
- p_args->value = value ? svn_string_dup(value, fb->eb->edit_pool) : NULL;
- p_args->base_revision = fb->base_revision;
- p_args->kind = svn_kind_file;
-
- SVN_ERR(add_action(fb->eb, fb->path, ACTION_PROPSET, p_args));
+ SVN_ERR(apply_propedit(fb->eb, fb->path, svn_kind_file, fb->base_revision,
+ name, value, scratch_pool));
return SVN_NO_ERROR;
}
@@ -960,6 +1062,7 @@ delta_from_editor(const svn_delta_editor
struct ev2_edit_baton *eb = apr_pcalloc(pool, sizeof(*eb));
eb->editor = editor;
+ eb->changes = apr_hash_make(pool);
eb->paths = apr_hash_make(pool);
eb->path_order = apr_array_make(pool, 1, sizeof(const char *));
eb->edit_pool = pool;
@@ -982,10 +1085,9 @@ delta_from_editor(const svn_delta_editor
return SVN_NO_ERROR;
}
-
-
-
+/* ### note the similarity to struct change_node. these structures will
+ ### be combined in the future. */
struct operation {
enum {
OP_OPEN,