You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@subversion.apache.org by ju...@apache.org on 2014/09/25 09:54:38 UTC
svn commit: r1627475 -
/subversion/branches/move-tracking-2/subversion/svnmover/svnmover.c
Author: julianfoad
Date: Thu Sep 25 07:54:38 2014
New Revision: 1627475
URL: http://svn.apache.org/r1627475
Log:
On the 'move-tracking-2' branch: Implement basic branching operations in the
'svnmover' program.
This program tracks branching and moving info in a rev-prop on revision 0.
It implements operations including 'move', 'branch', 'branchify' and 'list
branches'.
This is a demo, a proof of concept. It is not intended for 'production' use.
It is incomplete and has had very little testing. It would be unwise to use
it on an important repository.
* subversion/svnmover/svnmover.c
(pathrev): Remove.
(txn_path_join,
txn_path_str,
txn_path_to_relpath,
txn_path_dirname): New utility functions.
(SVN_ERR_BRANCHING): New, temporary error code.
(svn_branch_repos_t,
svn_branch_family_t,
svn_branch_definition_t,
svn_branch_instance_t): New types.
(svn_branch_repos_create,
svn_branch_family_create,
svn_branch_definition_create,
svn_branch_instance_create,
branch_set_eid_to_path,
branch_mapping_remove,
branch_get_eid_by_path,
branch_get_path_by_eid,
branch_get_root_path,
branch_get_sub_branches,
family_add_new_subfamily,
family_add_new_branch,
family_add_new_element,
family_get_subfamily_by_id,
repos_get_family_by_id,
default_repos_info,
parse_branch_info,
parse_family_info,
parse_repos_info,
fetch_repos_info,
write_branch_info,
write_family_info,
write_repos_info,
store_repos_info,
branch_find_subbranch_element_by_location,
family_find_element_by_path,
family_get_branch_by_path,
repos_get_branch_by_path,
same_branch,
verify_source_in_branch,
verify_target_in_branch,
svn_branch_walk,
family_list_branch_instances,
svn_branch_mkdir,
svn_branch_mv,
svn_branch_cp,
svn_branch_branch,
svn_branch_branchify,
svn_branch_rm): New functions.
(mtcc_commit): Don't destroy the pool here -- that belongs to the caller,
and the caller needs it for a bit longer.
(action_code_t,
execute,
usage,
sub_main): Update to include branching operations.
Modified:
subversion/branches/move-tracking-2/subversion/svnmover/svnmover.c
Modified: subversion/branches/move-tracking-2/subversion/svnmover/svnmover.c
URL: http://svn.apache.org/viewvc/subversion/branches/move-tracking-2/subversion/svnmover/svnmover.c?rev=1627475&r1=1627474&r2=1627475&view=diff
==============================================================================
--- subversion/branches/move-tracking-2/subversion/svnmover/svnmover.c (original)
+++ subversion/branches/move-tracking-2/subversion/svnmover/svnmover.c Thu Sep 25 07:54:38 2014
@@ -59,6 +59,7 @@
#include "private/svn_subr_private.h"
#include "private/svn_editor3.h"
#include "private/svn_ra_private.h"
+#include "private/svn_string_private.h"
/* Version compatibility check */
static svn_error_t *
@@ -76,18 +77,10 @@ check_lib_versions(void)
return svn_ver_check_list2(&my_version, checklist, svn_ver_equal);
}
-/* Construct a peg-path-rev */
-static svn_editor3_peg_path_t
-pathrev(const char *repos_relpath, svn_revnum_t revision)
-{
- svn_editor3_peg_path_t p;
-
- p.rev = revision;
- p.relpath = repos_relpath;
- return p;
-}
+/* ====================================================================== */
-/* Construct a txn-path-rev */
+/* Construct a txn-path.
+ */
static svn_editor3_txn_path_t
txn_path(const char *repos_relpath, svn_revnum_t revision,
const char *created_relpath)
@@ -100,6 +93,65 @@ txn_path(const char *repos_relpath, svn_
return p;
}
+/* Return a txn-path constructed by extending TXN_PATH with RELPATH.
+ */
+static svn_editor3_txn_path_t
+txn_path_join(svn_editor3_txn_path_t txn_path,
+ const char *relpath,
+ apr_pool_t *result_pool)
+{
+ svn_editor3_txn_path_t p = txn_path;
+
+ p.relpath = svn_relpath_join(p.relpath, relpath, result_pool);
+ return p;
+}
+
+/* Return a human-readable string representation of LOC.
+ */
+static const char *
+txn_path_str(svn_editor3_txn_path_t loc,
+ apr_pool_t *result_pool)
+{
+ return apr_psprintf(result_pool, "%s@%ld//%s",
+ loc.peg.relpath, loc.peg.rev, loc.relpath);
+}
+
+/* Return the full relpath of LOC,
+ * ### assuming the peg-path part doesn't need translation between its
+ * peg-revision and the transaction in which we interpret it.
+ */
+static const char *
+txn_path_to_relpath(svn_editor3_txn_path_t loc,
+ apr_pool_t *result_pool)
+{
+ return svn_relpath_join(loc.peg.relpath, loc.relpath, result_pool);
+}
+
+/* Return the txn-path of the parent directory of LOC.
+ */
+static svn_editor3_txn_path_t
+txn_path_dirname(svn_editor3_txn_path_t loc,
+ apr_pool_t *result_pool)
+{
+ svn_editor3_txn_path_t parent_loc;
+
+ parent_loc.peg.rev = loc.peg.rev;
+ if (*loc.relpath)
+ {
+ parent_loc.peg.relpath = apr_pstrdup(result_pool, loc.peg.relpath);
+ parent_loc.relpath = svn_relpath_dirname(loc.relpath, result_pool);
+ }
+ else
+ {
+ parent_loc.peg.relpath = svn_relpath_dirname(loc.peg.relpath,
+ result_pool);
+ parent_loc.relpath = apr_pstrdup(result_pool, loc.relpath);
+ }
+ return parent_loc;
+}
+
+/* ====================================================================== */
+
typedef struct mtcc_t
{
apr_pool_t *pool;
@@ -195,8 +247,6 @@ mtcc_commit(mtcc_t *mtcc,
err = svn_editor3_complete(mtcc->editor);
- svn_pool_destroy(mtcc->pool);
-
return svn_error_trace(err);
}
@@ -214,32 +264,1259 @@ commit_callback(const svn_commit_info_t
}
typedef enum action_code_t {
+ ACTION_LIST_BRANCHES,
+ ACTION_LIST_BRANCHES_R,
+ ACTION_BRANCH,
+ ACTION_BRANCHIFY,
+ ACTION_DISSOLVE,
ACTION_MV,
ACTION_MKDIR,
ACTION_CP,
- ACTION_PUT,
ACTION_RM
} action_code_t;
struct action {
action_code_t action;
- /* revision (copy-from-rev of path[0] for cp; base-rev for put) */
+ /* revision (copy-from-rev of path[0] for cp) */
svn_revnum_t rev;
- /* action path[0] path[1]
- * ------ ------- -------
- * mv source target
- * mkdir target (null)
- * cp source target
- * put target source
- * rm target (null)
+ /* action path[0] path[1]
+ * ------ ------- -------
+ * list_br path
+ * branch source target
+ * branchify path
+ * dissolve path
+ * mv source target
+ * mkdir target (null)
+ * cp source target
+ * rm target (null)
*/
const char *path[2];
};
+/* ====================================================================== */
+
+/* ### */
+#define SVN_ERR_BRANCHING 123456
+
+struct svn_branch_family_t;
+
+/* Per-repository branching info.
+ */
+typedef struct svn_branch_repos_t
+{
+ svn_ra_session_t *ra_session;
+ svn_editor3_t *editor;
+
+ /* The root family in this repository. */
+ struct svn_branch_family_t *root_family;
+
+ /* The range of family ids assigned within this repos (starts at 0). */
+ int next_fid;
+
+ /* The pool in which this object lives. */
+ apr_pool_t *pool;
+} svn_branch_repos_t;
+
+/* A branch family.
+ */
+typedef struct svn_branch_family_t
+{
+ /* --- Identity of this object --- */
+
+ /* The repository in which this family exists. */
+ svn_branch_repos_t *repos;
+
+ /* The outer family of which this is a sub-family. NULL if this is the
+ root family. */
+ /*struct svn_branch_family_t *outer_family;*/
+
+ /* The FID of this family within its repository. */
+ int fid;
+
+ /* --- Contents of this object --- */
+
+ /* The branch definitions in this family. */
+ apr_array_header_t *branch_definitions;
+
+ /* The branch instances in this family. */
+ apr_array_header_t *branch_instances;
+
+ /* The range of branch ids assigned within this family. */
+ int first_bid, next_bid;
+
+ /* The range of element ids assigned within this family. */
+ int first_eid, next_eid;
+
+ /* The immediate sub-families of this family. */
+ apr_array_header_t *sub_families;
+
+ /* The pool in which this object lives. */
+ apr_pool_t *pool;
+} svn_branch_family_t;
+
+/* A branch.
+ *
+ * A branch definition object describes the characteristics common to all
+ * instances of a branch (with a given BID) in its family. (There is one
+ * instance of this branch within each branch of its outer families.)
+ */
+typedef struct svn_branch_definition_t
+{
+ /* --- Identity of this object --- */
+
+ /* The family of which this branch is a member. */
+ svn_branch_family_t *family;
+
+ /* The BID of this branch within its family. */
+ int bid;
+
+ /* The EID, within the outer family, of the branch root element. */
+ /*int outer_family_eid_of_branch_root;*/
+
+ /* --- Contents of this object --- */
+
+ /* The EID within its family of its pathwise root element. Typically,
+ all branches in a family have the same root element. However, the
+ root element of one branch may correspond to a non-root element of
+ another branch; such a branch could be called a "subtree branch". */
+ int root_eid;
+} svn_branch_definition_t;
+
+/* A branch instance.
+ *
+ * A branch instance object describes one branch in this family. (There is
+ * one instance of this branch within each branch of its outer families.)
+ */
+typedef struct svn_branch_instance_t
+{
+ /* --- Identity of this object --- */
+
+ svn_branch_definition_t *definition;
+
+ /* The branch (instance?), within the outer family, that contains the
+ root element of this branch. */
+ /*svn_branch_instance_t *outer_family_branch_instance;*/
+
+ /* --- Contents of this object --- */
+
+ /* EID <-> path mapping in the current revision. Path is relative to
+ repos root. There is always an entry for root_eid. */
+ apr_hash_t *eid_to_path, *path_to_eid;
+} svn_branch_instance_t;
+
+/* element */
+/*
+typedef struct svn_branch_element_t
+{
+ int eid;
+ svn_branch_family_t *family;
+} svn_branch_element_t;
+*/
+
+/* Create a new branching metadata object */
+static svn_branch_repos_t *
+svn_branch_repos_create(svn_ra_session_t *ra_session,
+ svn_editor3_t *editor,
+ apr_pool_t *result_pool)
+{
+ svn_branch_repos_t *repos = apr_pcalloc(result_pool, sizeof(*repos));
+
+ repos->ra_session = ra_session;
+ repos->editor = editor;
+ repos->pool = result_pool;
+ return repos;
+}
+
+/* Create a new branch family object */
+static svn_branch_family_t *
+svn_branch_family_create(svn_branch_repos_t *repos,
+ int fid,
+ int first_bid,
+ int next_bid,
+ int first_eid,
+ int next_eid,
+ apr_pool_t *result_pool)
+{
+ svn_branch_family_t *f = apr_pcalloc(result_pool, sizeof(*f));
+
+ f->fid = fid;
+ f->repos = repos;
+ f->branch_definitions = apr_array_make(result_pool, 1, sizeof(void *));
+ f->branch_instances = apr_array_make(result_pool, 1, sizeof(void *));
+ f->sub_families = apr_array_make(result_pool, 1, sizeof(void *));
+ f->first_bid = first_bid;
+ f->next_bid = next_bid;
+ f->first_eid = first_eid;
+ f->next_eid = next_eid;
+ f->pool = result_pool;
+ return f;
+}
+
+/* Create a new branch definition object */
+static svn_branch_definition_t *
+svn_branch_definition_create(svn_branch_family_t *family,
+ int bid,
+ int root_eid,
+ apr_pool_t *result_pool)
+{
+ svn_branch_definition_t *b = apr_pcalloc(result_pool, sizeof(*b));
+
+ b->family = family;
+ b->bid = bid;
+ b->root_eid = root_eid;
+ return b;
+}
+
+/* Create a new branch instance object */
+static svn_branch_instance_t *
+svn_branch_instance_create(svn_branch_definition_t *branch_definition,
+ apr_pool_t *result_pool)
+{
+ svn_branch_instance_t *b = apr_pcalloc(result_pool, sizeof(*b));
+
+ b->definition = branch_definition;
+ b->eid_to_path = apr_hash_make(result_pool);
+ b->path_to_eid = apr_hash_make(result_pool);
+ return b;
+}
+
+/* */
+static void
+branch_set_eid_to_path(svn_branch_instance_t *branch,
+ int eid,
+ const char *path)
+{
+ apr_pool_t *pool = apr_hash_pool_get(branch->eid_to_path);
+ int *eid_stored = apr_pmemdup(pool, &eid, sizeof(eid));
+
+ path = apr_pstrdup(pool, path);
+ apr_hash_set(branch->eid_to_path, eid_stored, sizeof(eid), path);
+ svn_hash_sets(branch->path_to_eid, path, eid_stored);
+}
+
+/* */
+static void
+branch_mapping_remove(svn_branch_instance_t *branch,
+ int eid,
+ const char *path)
+{
+ apr_hash_set(branch->eid_to_path, &eid, sizeof(eid), NULL);
+ svn_hash_sets(branch->path_to_eid, path, NULL);
+}
+
+/* Return the EID in BRANCH of the element at repos-relpath PATH,
+ * or -1 if PATH is not currently present in BRANCH. */
+static int
+branch_get_eid_by_path(const svn_branch_instance_t *branch,
+ const char *path)
+{
+ int *eid_p = svn_hash_gets(branch->path_to_eid, path);
+
+ if (! eid_p)
+ return -1;
+ return *eid_p;
+}
+
+/* Return the repos-relpath for element EID of BRANCH, or NULL if EID
+ * is not currently present in BRANCH. */
+static const char *
+branch_get_path_by_eid(const svn_branch_instance_t *branch,
+ int eid)
+{
+ const char *path = apr_hash_get(branch->eid_to_path, &eid, sizeof(eid));
+
+ return path;
+}
+
+/* Return the root repos-relpath of BRANCH. This is always available. */
+static const char *
+branch_get_root_path(const svn_branch_instance_t *branch)
+{
+ return branch_get_path_by_eid(branch, branch->definition->root_eid);
+}
+
+/* Return an array of pointers to the branch instances that are sub-branches
+ * of BRANCH. */
+static apr_array_header_t *
+branch_get_sub_branches(const svn_branch_instance_t *branch,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ const char *branch_root_rrpath = branch_get_root_path(branch);
+ apr_array_header_t *sub_branches = apr_array_make(result_pool, 0, sizeof(void *));
+ int i;
+
+ for (i = 0; i < branch->definition->family->sub_families->nelts; i++)
+ {
+ svn_branch_family_t *family
+ = APR_ARRAY_IDX(branch->definition->family->sub_families, i, void *);
+ int b;
+
+ for (b = 0; b < family->branch_instances->nelts; b++)
+ {
+ svn_branch_instance_t *sub_branch
+ = APR_ARRAY_IDX(family->branch_instances, b, void *);
+ const char *sub_branch_root_rrpath = branch_get_root_path(sub_branch);
+
+ if (svn_relpath_skip_ancestor(branch_root_rrpath, sub_branch_root_rrpath))
+ {
+ APR_ARRAY_PUSH(sub_branches, void *) = sub_branch;
+ }
+ }
+ }
+ return sub_branches;
+}
+
+/* Create a new, empty family in OUTER_FAMILY.
+ */
+static svn_branch_family_t *
+family_add_new_subfamily(svn_branch_family_t *outer_family)
+{
+ svn_branch_repos_t *repos = outer_family->repos;
+ int fid = repos->next_fid++;
+ svn_branch_family_t *family
+ = svn_branch_family_create(repos, fid,
+ fid * 10, fid * 10,
+ fid * 100, fid * 100,
+ outer_family->pool);
+
+ /* Register the family */
+ APR_ARRAY_PUSH(outer_family->sub_families, void *) = family;
+
+ return family;
+}
+
+/* Create a new branch definition in FAMILY, with root EID ROOT_EID.
+ * Create a new, empty branch instance in FAMILY, empty except for the
+ * root element.
+ */
+static svn_branch_instance_t *
+family_add_new_branch(svn_branch_family_t *family,
+ int root_eid,
+ const char *root_rrpath)
+{
+ int bid = family->next_bid++;
+ svn_branch_definition_t *branch_definition
+ = svn_branch_definition_create(family, bid, root_eid, family->pool);
+ svn_branch_instance_t *branch_instance
+ = svn_branch_instance_create(branch_definition, family->pool);
+
+ /* Register the branch */
+ APR_ARRAY_PUSH(family->branch_definitions, void *) = branch_definition;
+ /* ### Should create multiple instances, one per branch of parent family. */
+ APR_ARRAY_PUSH(family->branch_instances, void *) = branch_instance;
+
+ /* Initialize the root element */
+ branch_set_eid_to_path(branch_instance, root_eid, root_rrpath);
+ return branch_instance;
+}
+
+/* Assign a new element id in FAMILY.
+ */
+static int
+family_add_new_element(svn_branch_family_t *family)
+{
+ int eid = family->next_eid++;
+
+ return eid;
+}
+
+/* Find the existing family with id FID in FAMILY (recursively, excluding
+ * FAMILY itself). Assume FID is unique among all sub-families.
+ *
+ * Return NULL if not found.
+ */
+static svn_branch_family_t *
+family_get_subfamily_by_id(const svn_branch_family_t *family,
+ int fid)
+{
+ int i;
+
+ for (i = 0; i < family->sub_families->nelts; i++)
+ {
+ svn_branch_family_t *f
+ = APR_ARRAY_IDX(family->sub_families, i, svn_branch_family_t *);
+
+ if (f->fid == fid)
+ return f;
+ f = family_get_subfamily_by_id(f, fid);
+ if (f)
+ return f;
+ }
+
+ return NULL;
+}
+
+/* Find the existing family with id FID in REPOS.
+ *
+ * Return NULL if not found.
+ */
+static svn_branch_family_t *
+repos_get_family_by_id(const svn_branch_repos_t *repos,
+ int fid)
+{
+ svn_branch_family_t *f;
+
+ if (repos->root_family->fid == fid)
+ {
+ f = repos->root_family;
+ }
+ else
+ {
+ f = family_get_subfamily_by_id(repos->root_family, fid);
+ }
+
+ return f;
+}
+
+/* The default branching metadata for a new repository. */
+static const char *default_repos_info
+ = "r: fids 0 1 root-fid 0\n"
+ "f0: bids 0 1 eids 0 1 parent-fid 0\n"
+ "f0b0: root-eid 0\n"
+ "f0b0e0: \n";
+
+/* Create a new branch *NEW_BRANCH that belongs to FAMILY, initialized
+ * with info parsed from STREAM, allocated in RESULT_POOL.
+ */
+static svn_error_t *
+parse_branch_info(svn_branch_instance_t **new_branch,
+ svn_branch_family_t *family,
+ svn_stream_t *stream,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_branch_definition_t *branch_definition;
+ svn_branch_instance_t *branch_instance;
+ svn_stringbuf_t *line;
+ svn_boolean_t eof;
+ int n;
+ int fid, bid, root_eid;
+ int eid;
+
+ SVN_ERR(svn_stream_readline(stream, &line, "\n", &eof, scratch_pool));
+ SVN_ERR_ASSERT(! eof);
+ n = sscanf(line->data, "f%db%d: root-eid %d\n",
+ &fid, &bid, &root_eid);
+ SVN_ERR_ASSERT(n == 3);
+
+ SVN_ERR_ASSERT(fid == family->fid);
+ branch_definition = svn_branch_definition_create(family, bid, root_eid,
+ result_pool);
+ branch_instance = svn_branch_instance_create(branch_definition, result_pool);
+
+ for (eid = family->first_eid; eid < family->next_eid; eid++)
+ {
+ int this_fid, this_bid, this_eid;
+ int this_path_start_pos;
+ char *this_path;
+
+ SVN_ERR(svn_stream_readline(stream, &line, "\n", &eof, scratch_pool));
+ SVN_ERR_ASSERT(! eof);
+ n = sscanf(line->data, "f%db%de%d: %n\n",
+ &this_fid, &this_bid, &this_eid, &this_path_start_pos);
+ SVN_ERR_ASSERT(n == 3);
+ this_path = line->data + this_path_start_pos;
+ if (strcmp(this_path, "(null)") != 0)
+ {
+ branch_set_eid_to_path(branch_instance, this_eid, this_path);
+ }
+ else
+ {
+ SVN_DBG(("oops! path is 'null' in this line: '%s'", line->data));
+ }
+
+ }
+
+ *new_branch = branch_instance;
+ return SVN_NO_ERROR;
+}
+
+/* Create a new family *NEW_FAMILY as a sub-family of FAMILY, initialized
+ * with info parsed from STREAM, allocated in RESULT_POOL.
+ */
+static svn_error_t *
+parse_family_info(svn_branch_family_t **new_family,
+ int *parent_fid,
+ svn_branch_repos_t *repos,
+ svn_stream_t *stream,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_stringbuf_t *line;
+ svn_boolean_t eof;
+ int n;
+ int fid, first_bid, next_bid, first_eid, next_eid;
+
+ SVN_ERR(svn_stream_readline(stream, &line, "\n", &eof, scratch_pool));
+ SVN_ERR_ASSERT(!eof);
+ n = sscanf(line->data, "f%d: bids %d %d eids %d %d parent-fid %d\n",
+ &fid,
+ &first_bid, &next_bid, &first_eid, &next_eid,
+ parent_fid);
+ SVN_ERR_ASSERT(n == 6);
+
+ *new_family = svn_branch_family_create(repos, fid,
+ first_bid, next_bid,
+ first_eid, next_eid,
+ result_pool);
+
+ return SVN_NO_ERROR;
+}
+
+/* Initialize REPOS with info parsed from STREAM, allocated in REPOS->pool.
+ */
+static svn_error_t *
+parse_repos_info(svn_branch_repos_t *repos,
+ svn_stream_t *stream,
+ apr_pool_t *scratch_pool)
+{
+ int root_fid;
+ svn_stringbuf_t *line;
+ svn_boolean_t eof;
+ int n, i;
+
+ SVN_ERR(svn_stream_readline(stream, &line, "\n", &eof, scratch_pool));
+ SVN_ERR_ASSERT(! eof);
+ n = sscanf(line->data, "r: fids %*d %d root-fid %d",
+ &repos->next_fid, &root_fid);
+ SVN_ERR_ASSERT(n == 2);
+
+ /* parse the families */
+ for (i = 0; i < repos->next_fid; i++)
+ {
+ svn_branch_family_t *family;
+ int bid, parent_fid;
+
+ SVN_ERR(parse_family_info(&family, &parent_fid, repos, stream,
+ repos->pool, scratch_pool));
+
+ if (family->fid == root_fid)
+ {
+ repos->root_family = family;
+ }
+ else
+ {
+ svn_branch_family_t *parent_family
+ = repos_get_family_by_id(repos, parent_fid);
+
+ SVN_ERR_ASSERT(parent_family);
+ APR_ARRAY_PUSH(parent_family->sub_families, void *) = family;
+ }
+
+ /* parse the branches */
+ for (bid = family->first_bid; bid < family->next_bid; ++bid)
+ {
+ svn_branch_instance_t *branch;
+
+ SVN_ERR(parse_branch_info(&branch, family, stream,
+ family->pool, scratch_pool));
+ APR_ARRAY_PUSH(family->branch_instances, void *) = branch;
+ }
+ }
+ return SVN_NO_ERROR;
+}
+
+/* Create a new repository object and read the move-tracking /
+ * branch-tracking metadata from the repository into it.
+ */
+static svn_error_t *
+fetch_repos_info(svn_branch_repos_t **repos_p,
+ svn_ra_session_t *ra_session,
+ svn_editor3_t *editor,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_branch_repos_t *repos
+ = svn_branch_repos_create(ra_session, editor, result_pool);
+ svn_string_t *value;
+ svn_stream_t *stream;
+
+ /* Read initial state from repository */
+ SVN_ERR(svn_ra_rev_prop(ra_session, 0, "svn-br-info", &value, scratch_pool));
+ if (! value)
+ {
+ value = svn_string_create(default_repos_info, scratch_pool);
+ SVN_DBG(("fetch_repos_info: LOADED DEFAULT INFO:\n%s", value->data));
+ }
+ stream = svn_stream_from_string(value, scratch_pool);
+
+ SVN_ERR(parse_repos_info(repos, stream, scratch_pool));
+
+ *repos_p = repos;
+ return SVN_NO_ERROR;
+}
+
+/* Write to STREAM a parseable representation of BRANCH.
+ */
+static svn_error_t *
+write_branch_info(svn_stream_t *stream,
+ svn_branch_instance_t *branch,
+ apr_pool_t *scratch_pool)
+{
+ svn_branch_family_t *family = branch->definition->family;
+ int eid;
+
+ SVN_ERR(svn_stream_printf(stream, scratch_pool,
+ "f%db%d: root-eid %d\n",
+ family->fid, branch->definition->bid,
+ branch->definition->root_eid));
+ for (eid = family->first_eid; eid < family->next_eid; eid++)
+ {
+ const char *path = apr_hash_get(branch->eid_to_path, &eid, sizeof (eid));
+ if (path)
+ {
+ SVN_ERR(svn_stream_printf(stream, scratch_pool,
+ "f%db%de%d: %s\n",
+ family->fid, branch->definition->bid, eid,
+ path));
+ }
+ else
+ {
+ /* ### TODO: instead of writing this out, write nothing; but the
+ parser can't currently handle that. */
+ SVN_ERR(svn_stream_printf(stream, scratch_pool,
+ "f%db%de%d: %s\n",
+ family->fid, branch->definition->bid, eid,
+ "(null)"));
+ }
+ }
+ return SVN_NO_ERROR;
+}
+
+/* Write to STREAM a parseable representation of FAMILY whose parent
+ * family id is PARENT_FID.
+ */
+static svn_error_t *
+write_family_info(svn_stream_t *stream,
+ svn_branch_family_t *family,
+ int parent_fid,
+ apr_pool_t *scratch_pool)
+{
+ int i;
+
+ SVN_ERR(svn_stream_printf(stream, scratch_pool,
+ "f%d: bids %d %d eids %d %d parent-fid %d\n",
+ family->fid,
+ family->first_bid, family->next_bid,
+ family->first_eid, family->next_eid,
+ parent_fid));
+
+ for (i = 0; i < family->branch_instances->nelts; i++)
+ {
+ svn_branch_instance_t *branch
+ = APR_ARRAY_IDX(family->branch_instances, i, void *);
+
+ SVN_ERR(write_branch_info(stream, branch, scratch_pool));
+ }
+
+ if (family->sub_families)
+ {
+ for (i = 0; i < family->sub_families->nelts; i++)
+ {
+ svn_branch_family_t *f
+ = APR_ARRAY_IDX(family->sub_families, i, void *);
+
+ SVN_ERR(write_family_info(stream, f, family->fid, scratch_pool));
+ }
+ }
+ return SVN_NO_ERROR;
+}
+
+/* Write to STREAM a parseable representation of REPOS.
+ */
+static svn_error_t *
+write_repos_info(svn_stream_t *stream,
+ svn_branch_repos_t *repos,
+ apr_pool_t *scratch_pool)
+{
+ SVN_ERR(svn_stream_printf(stream, scratch_pool,
+ "r: fids %d %d root-fid %d\n",
+ 0, repos->next_fid,
+ repos->root_family->fid));
+
+ SVN_ERR(write_family_info(stream, repos->root_family, 0, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* Store the move-tracking / branch-tracking metadata from REPOS into the
+ * repository.
+ */
+static svn_error_t *
+store_repos_info(svn_branch_repos_t *repos,
+ apr_pool_t *scratch_pool)
+{
+ svn_stringbuf_t *buf = svn_stringbuf_create_empty(scratch_pool);
+ svn_stream_t *stream = svn_stream_from_stringbuf(buf, scratch_pool);
+
+ SVN_ERR(write_repos_info(stream, repos, scratch_pool));
+
+ SVN_ERR(svn_stream_close(stream));
+ /*SVN_DBG(("store_repos_info: %s", buf->data));*/
+ SVN_ERR(svn_ra_change_rev_prop2(repos->ra_session, 0, "svn-br-info",
+ NULL, svn_stringbuf__morph_into_string(buf),
+ scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* Set *INNER_BRANCH_P and *INNER_EID_P to the branch and element located
+ * at LOC.
+ *
+ * LOC should be a member of an immediate sub-branch of OUTER_BRANCH,
+ * including the root of such a sub-branch, but not the root of a
+ * sub-sub-branch.
+ *
+ * If not found, set *INNER_BRANCH_P and *INNER_EID_P respectively to NULL
+ * and -1.
+ */
+static svn_error_t *
+branch_find_subbranch_element_by_location(svn_branch_instance_t **inner_branch_p,
+ int *inner_eid_p,
+ svn_branch_instance_t *outer_branch,
+ svn_editor3_txn_path_t loc,
+ apr_pool_t *scratch_pool)
+{
+ const char *outer_branch_root_rrpath = branch_get_root_path(outer_branch);
+ const char *loc_rrpath = txn_path_to_relpath(loc, scratch_pool);
+ apr_array_header_t *branch_instances;
+ int i;
+
+ if (! svn_relpath_skip_ancestor(outer_branch_root_rrpath, loc_rrpath))
+ {
+ return svn_error_createf(SVN_ERR_BRANCHING, NULL,
+ _("location %s is not within branch %d (root path '%s')"),
+ loc_rrpath, outer_branch->definition->bid,
+ outer_branch_root_rrpath);
+ }
+
+ branch_instances = branch_get_sub_branches(outer_branch,
+ scratch_pool, scratch_pool);
+ /* Find the sub-branch that encloses LOC_RRPATH */
+ for (i = 0; i < branch_instances->nelts; i++)
+ {
+ svn_branch_instance_t *sub_branch
+ = APR_ARRAY_IDX(branch_instances, i, void *);
+ const char *sub_branch_root_path = branch_get_root_path(sub_branch);
+
+ if (svn_relpath_skip_ancestor(sub_branch_root_path, loc_rrpath))
+ {
+ /* The path we're looking for is path-wise in this sub-branch. */
+ /* ### TODO: Check it's not a sub-sub-branch. */
+ *inner_branch_p = sub_branch;
+ *inner_eid_p = branch_get_eid_by_path(sub_branch, loc_rrpath);
+ return SVN_NO_ERROR;
+ }
+ }
+
+ *inner_branch_p = NULL;
+ *inner_eid_p = -1;
+ return SVN_NO_ERROR;
+}
+
+/* Set *BRANCH_P to the branch of FAMILY that contains the path RRPATH.
+ * (RRPATH might also be within a sub-branch of *BRANCH_P; we don't
+ * check.) If an element of *BRANCH_P exists at RRPATH, then set *EID_P
+ * to its element id in *BRANCH_P; otherwise set *EID_P to -1.
+ *
+ * If RRPATH is not within any branch in FAMILY, set *BRANCH_P to NULL
+ * and *EID_P to -1.
+ */
+static void
+family_find_element_by_path(svn_branch_instance_t **branch_p,
+ int *eid_p,
+ svn_branch_family_t *family,
+ const char *rrpath,
+ apr_pool_t *scratch_pool)
+{
+ apr_array_header_t *branch_instances = family->branch_instances;
+ int i;
+
+ /* Find the nearest branch-root */
+ for (i = 0; i < branch_instances->nelts; i++)
+ {
+ svn_branch_instance_t *branch
+ = APR_ARRAY_IDX(branch_instances, i, void *);
+ const char *branch_root_path
+ = branch_get_root_path(branch);
+
+ if (svn_relpath_skip_ancestor(branch_root_path, rrpath))
+ {
+ /* The path we're looking for is (path-wise) in this branch.
+ (It might be in a sub-branch -- we don't check.) */
+ *branch_p = branch;
+ *eid_p = branch_get_eid_by_path(branch, rrpath);
+ return;
+ }
+ }
+
+ *branch_p = NULL;
+ *eid_p = -1;
+}
+
+/* Find the deepest branch in FAMILY (recursively) of which RRPATH is
+ * either the root element or a normal, non-sub-branch element.
+ *
+ * An element need not exist at RRPATH.
+ *
+ * Return NULL if not found.
+ */
+static svn_branch_instance_t *
+family_get_branch_by_path(svn_branch_family_t *family,
+ const char *rrpath,
+ apr_pool_t *scratch_pool)
+{
+ svn_branch_instance_t *branch;
+ int eid;
+
+ family_find_element_by_path(&branch, &eid, family, rrpath, scratch_pool);
+
+ if (! branch)
+ return NULL;
+
+ /* The path is within this branch, but perhaps in a sub-branch. */
+ if (family->sub_families)
+ {
+ int f;
+
+ for (f = 0; f < family->sub_families->nelts; f++)
+ {
+ svn_branch_family_t *sub_family
+ = APR_ARRAY_IDX(family->sub_families, f, svn_branch_family_t *);
+ svn_branch_instance_t *sub_branch
+ = family_get_branch_by_path(sub_family, rrpath, scratch_pool);
+
+ if (sub_branch)
+ {
+ return sub_branch;
+ }
+ }
+ }
+ return branch;
+}
+
+/* Find the deepest branch in REPOS (recursively) of which RRPATH is
+ * either the root element or a normal, non-sub-branch element.
+ *
+ * An element need not exist at RRPATH.
+ *
+ * The result will never be NULL.
+ */
+static svn_branch_instance_t *
+repos_get_branch_by_path(svn_branch_repos_t *repos,
+ const char *rrpath,
+ apr_pool_t *scratch_pool)
+{
+ svn_branch_instance_t *branch
+ = family_get_branch_by_path(repos->root_family, rrpath, scratch_pool);
+
+ SVN_ERR_ASSERT_NO_RETURN(branch);
+ return branch;
+}
+
+/* */
+static svn_boolean_t
+same_branch(const svn_branch_instance_t *branch1,
+ const svn_branch_instance_t *branch2)
+{
+ return (branch1 == branch2);
+}
+
+/* If the location LOC is not in the branch BRANCH,
+ * throw an error (branch nesting violation).
+ */
+static svn_error_t *
+verify_source_in_branch(const svn_branch_instance_t *branch,
+ svn_editor3_txn_path_t loc,
+ apr_pool_t *scratch_pool)
+{
+ const char *rrpath = txn_path_to_relpath(loc, scratch_pool);
+ svn_branch_instance_t *target_branch
+ = repos_get_branch_by_path(branch->definition->family->repos, rrpath,
+ scratch_pool);
+
+ if (! same_branch(target_branch, branch))
+ return svn_error_createf(SVN_ERR_BRANCHING, NULL,
+ _("source path '%s' is in branch '%s', "
+ "not in this branch '%s'"),
+ txn_path_str(loc, scratch_pool),
+ branch_get_root_path(target_branch),
+ branch_get_root_path(branch));
+ return SVN_NO_ERROR;
+}
+
+/* If the location TGT_PARENT_LOC is not in the branch BRANCH,
+ * throw an error (branch nesting violation).
+ */
+static svn_error_t *
+verify_target_in_branch(const svn_branch_instance_t *branch,
+ svn_editor3_txn_path_t tgt_parent_loc,
+ apr_pool_t *scratch_pool)
+{
+ const char *rrpath = txn_path_to_relpath(tgt_parent_loc, scratch_pool);
+ svn_branch_instance_t *target_branch
+ = repos_get_branch_by_path(branch->definition->family->repos, rrpath,
+ scratch_pool);
+
+ if (! same_branch(target_branch, branch))
+ return svn_error_createf(SVN_ERR_BRANCHING, NULL,
+ _("target parent path '%s' is in branch '%s', "
+ "not in this branch '%s'"),
+ txn_path_str(tgt_parent_loc, scratch_pool),
+ branch_get_root_path(target_branch),
+ branch_get_root_path(branch));
+ return SVN_NO_ERROR;
+}
+
+/* Set *TXN_PATHS_P to an array of (const char *) repos-relative paths
+ * of all the children (recursively) of BRANCH:LOC, not including itself.
+ */
static svn_error_t *
-execute(const apr_array_header_t *actions,
+svn_branch_walk(apr_array_header_t **paths_p,
+ svn_branch_instance_t *branch,
+ svn_editor3_txn_path_t loc,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ const char *loc_rrpath = txn_path_to_relpath(loc, scratch_pool);
+ apr_hash_index_t *hi;
+
+ SVN_ERR_ASSERT(svn_relpath_skip_ancestor(branch_get_root_path(branch),
+ loc_rrpath) != NULL);
+
+ *paths_p = apr_array_make(result_pool, 0, sizeof(char *));
+ for (hi = apr_hash_first(scratch_pool, branch->eid_to_path);
+ hi; hi = apr_hash_next(hi))
+ {
+ const char *path = apr_hash_this_val(hi);
+ const char *remainder = svn_relpath_skip_ancestor(loc_rrpath, path);
+
+ if (remainder && remainder[0])
+ {
+ APR_ARRAY_PUSH(*paths_p, const char *) = path;
+ }
+ }
+ return SVN_NO_ERROR;
+}
+
+/* List all branch instances in FAMILY.
+ *
+ * If RECURSIVE is true, include branches in nested families.
+ */
+static svn_error_t *
+family_list_branch_instances(svn_branch_family_t *family,
+ svn_boolean_t recursive,
+ apr_pool_t *scratch_pool)
+{
+ int b;
+
+ printf("family %d (BIDs %d:%d, EIDs %d:%d)\n",
+ family->fid,
+ family->first_bid, family->next_bid,
+ family->first_eid, family->next_eid);
+
+ for (b = 0; b < family->branch_instances->nelts; b++)
+ {
+ svn_branch_instance_t *branch
+ = APR_ARRAY_IDX(family->branch_instances, b, svn_branch_instance_t *);
+ int eid;
+
+ printf(" branch %d (root element %d -> '%s')\n",
+ branch->definition->bid, branch->definition->root_eid,
+ branch_get_root_path(branch));
+ for (eid = family->first_eid; eid < family->next_eid; eid++)
+ {
+ printf(" e%d -> %s\n",
+ eid,
+ (char *)apr_hash_get(branch->eid_to_path, &eid, sizeof(eid)));
+ }
+ }
+
+ if (recursive && family->sub_families)
+ {
+ int f;
+
+ for (f = 0; f < family->sub_families->nelts; f++)
+ {
+ svn_branch_family_t *sub_family
+ = APR_ARRAY_IDX(family->sub_families, f, svn_branch_family_t *);
+
+ SVN_ERR(family_list_branch_instances(sub_family, recursive,
+ scratch_pool));
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* In BRANCH, make a new directory at PARENT_LOC:NEW_NAME. */
+static svn_error_t *
+svn_branch_mkdir(svn_branch_instance_t *branch,
+ svn_editor3_txn_path_t parent_loc,
+ const char *new_name,
+ apr_pool_t *scratch_pool)
+{
+ /* ### TODO: First check PARENT_LOC:NEW_NAME is within BRANCH. */
+
+ svn_editor3_t *editor = branch->definition->family->repos->editor;
+ int eid = family_add_new_element(branch->definition->family);
+ svn_editor3_txn_path_t loc = txn_path_join(parent_loc, new_name, scratch_pool);
+ const char *loc_rrpath = txn_path_to_relpath(loc, scratch_pool);
+
+ SVN_ERR(svn_editor3_mk(editor, svn_node_dir, parent_loc, new_name));
+ branch_set_eid_to_path(branch, eid, loc_rrpath);
+ return SVN_NO_ERROR;
+}
+
+/* In BRANCH, move the subtree at FROM_LOC to PARENT_LOC:NEW_NAME.
+ */
+static svn_error_t *
+svn_branch_mv(svn_branch_instance_t *branch,
+ svn_editor3_txn_path_t from_loc,
+ svn_editor3_txn_path_t parent_loc,
+ const char *new_name,
+ apr_pool_t *scratch_pool)
+{
+ svn_editor3_t *editor = branch->definition->family->repos->editor;
+ svn_editor3_txn_path_t to_loc = txn_path_join(parent_loc, new_name, scratch_pool);
+ const char *from_path = txn_path_to_relpath(from_loc, scratch_pool);
+ const char *to_path = txn_path_to_relpath(to_loc, scratch_pool);
+ int eid;
+
+ SVN_ERR(verify_source_in_branch(branch, from_loc, scratch_pool));
+ SVN_ERR(verify_target_in_branch(branch, parent_loc, scratch_pool));
+
+ eid = branch_get_eid_by_path(branch, from_path);
+ SVN_ERR(svn_editor3_mv(editor, from_loc.peg, parent_loc, new_name));
+ branch_set_eid_to_path(branch, eid, to_path);
+ return SVN_NO_ERROR;
+}
+
+/* In BRANCH, copy the subtree at FROM_LOC to PARENT_LOC:NEW_NAME.
+ */
+static svn_error_t *
+svn_branch_cp(svn_branch_instance_t *branch,
+ svn_editor3_txn_path_t from_loc,
+ svn_editor3_txn_path_t parent_loc,
+ const char *new_name,
+ apr_pool_t *scratch_pool)
+{
+ svn_editor3_t *editor = branch->definition->family->repos->editor;
+ const char *from_rrpath = txn_path_to_relpath(from_loc, scratch_pool);
+ svn_editor3_txn_path_t to_loc;
+ const char *to_rrpath;
+ apr_array_header_t *paths;
+ int i;
+
+ SVN_ERR(verify_source_in_branch(branch, from_loc, scratch_pool));
+ SVN_ERR(verify_target_in_branch(branch, parent_loc, scratch_pool));
+
+ SVN_ERR(svn_editor3_cp(editor, from_loc.peg, parent_loc, new_name));
+
+ /* assign new eids to all elements in the copy */
+ to_loc = txn_path_join(parent_loc, new_name, scratch_pool);
+ to_rrpath = txn_path_to_relpath(to_loc, scratch_pool);
+ branch_set_eid_to_path(branch,
+ family_add_new_element(branch->definition->family),
+ to_rrpath);
+ /* Ideally we'd now walk the target loc, but as svn_editor3_t doesn't
+ support walking a created location, we walk the source and convert
+ source paths to target paths. */
+ SVN_ERR(svn_branch_walk(&paths, branch, from_loc,
+ scratch_pool, scratch_pool));
+ for (i = 0; i < paths->nelts; i++)
+ {
+ const char *this_from_path = APR_ARRAY_IDX(paths, i, const char *);
+ const char *this_relpath = svn_relpath_skip_ancestor(from_rrpath,
+ this_from_path);
+ const char *this_to_path = svn_relpath_join(to_rrpath, this_relpath,
+ scratch_pool);
+ int eid = family_add_new_element(branch->definition->family);
+
+ branch_set_eid_to_path(branch, eid, this_to_path);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* In OUTER_BRANCH, branch the existing sub-branch at FROM_LOC to create
+ * a new branch at PARENT_LOC:NEW_NAME.
+ *
+ * FROM_LOC must address a sub-branch root.
+ * TODO: Allow branching from a non-root element of the sub-branch.
+ *
+ * copy
+ * assign new eid (in outer branch) to root node
+ * assign new bid to new (inner) branch
+ * leave eids of inner br unchanged
+ */
+static svn_error_t *
+svn_branch_branch(svn_branch_instance_t *outer_branch,
+ svn_editor3_txn_path_t from_loc,
+ svn_editor3_txn_path_t parent_loc,
+ const char *new_name,
+ apr_pool_t *scratch_pool)
+{
+ svn_editor3_t *editor = outer_branch->definition->family->repos->editor;
+ const char *from_rrpath = txn_path_to_relpath(from_loc, scratch_pool);
+ svn_editor3_txn_path_t to_loc
+ = txn_path_join(parent_loc, new_name, scratch_pool);
+ const char *to_rrpath = txn_path_to_relpath(to_loc, scratch_pool);
+ svn_branch_instance_t *from_inner_branch;
+ int inner_eid;
+ int to_outer_eid;
+ svn_branch_instance_t *to_inner_branch;
+ apr_array_header_t *paths;
+ int i;
+
+ SVN_ERR(branch_find_subbranch_element_by_location(
+ &from_inner_branch, &inner_eid,
+ outer_branch, from_loc, scratch_pool));
+ if (! from_inner_branch)
+ {
+ return svn_error_createf(SVN_ERR_BRANCHING, NULL,
+ _("cannot branch from '%s': "
+ "is not a sub-branch of '%s'"),
+ txn_path_str(from_loc, scratch_pool),
+ branch_get_root_path(outer_branch));
+ }
+ if (inner_eid < 0)
+ {
+ return svn_error_createf(SVN_ERR_BRANCHING, NULL,
+ _("cannot branch from '%s': "
+ "does not exist"),
+ txn_path_str(from_loc, scratch_pool));
+ }
+
+ SVN_ERR(verify_target_in_branch(outer_branch, parent_loc, scratch_pool));
+
+ /* copy */
+ SVN_ERR(svn_editor3_cp(editor, from_loc.peg, parent_loc, new_name));
+
+ /* assign new eid to root node (outer branch) */
+ to_outer_eid = family_add_new_element(outer_branch->definition->family);
+ branch_set_eid_to_path(outer_branch, to_outer_eid, to_rrpath);
+
+ /* create new inner branch definition & instance */
+ to_inner_branch = family_add_new_branch(from_inner_branch->definition->family,
+ inner_eid, to_rrpath);
+
+ /* Populate new branch instance with eid-path mappings */
+ SVN_ERR(svn_branch_walk(&paths, from_inner_branch, from_loc,
+ scratch_pool, scratch_pool));
+ for (i = 0; i < paths->nelts; i++)
+ {
+ const char *this_from_path = APR_ARRAY_IDX(paths, i, const char *);
+ const char *this_rrpath = svn_relpath_skip_ancestor(from_rrpath,
+ this_from_path);
+ const char *this_to_path = svn_relpath_join(to_rrpath, this_rrpath,
+ scratch_pool);
+ int eid;
+
+ eid = branch_get_eid_by_path(from_inner_branch, this_from_path);
+ branch_set_eid_to_path(to_inner_branch, eid, this_to_path);
+
+#ifdef WITH_OVERLAPPING_EIDS
+ /* Also assign new EIDs in outer branch */
+ eid = family_add_new_element(outer_branch->definition->family);
+ branch_set_eid_to_path(outer_branch, eid, this_to_path);
+#endif
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Change the existing simple sub-tree at LOC into a sub-branch in a
+ * new branch family.
+ *
+ * ### TODO: Also we must (in order to maintain correctness) branchify
+ * the corresponding subtrees in all other branches in this family.
+ *
+ * TODO: Allow adding to an existing family, by specifying a mapping.
+ *
+ * create a new family
+ * create a new branch-def and branch-instance
+ * for each node in subtree:
+ * ?[unassign eid in outer branch (except root node)]
+ * assign a new eid in inner branch
+ */
+static svn_error_t *
+svn_branch_branchify(svn_branch_instance_t *outer_branch,
+ svn_editor3_txn_path_t new_root_loc,
+ apr_pool_t *scratch_pool)
+{
+ /* ### TODO: First check ROOT_LOC is not already a branch root
+ and the subtree at ROOT_LOC does not contain any branch roots. */
+
+ svn_branch_family_t *family
+ = family_add_new_subfamily(outer_branch->definition->family);
+ int new_root_eid = family_add_new_element(family);
+ const char *new_root_rrpath = txn_path_to_relpath(new_root_loc, scratch_pool);
+ svn_branch_instance_t *new_branch
+ = family_add_new_branch(family, new_root_eid, new_root_rrpath);
+ apr_array_header_t *paths;
+ int i;
+
+ SVN_DBG(("branchify(%s): new fid=%d, bid=%d",
+ txn_path_str(new_root_loc, scratch_pool), family->fid, new_branch->definition->bid));
+
+ /* Walk the subtree paths in the outer branch, (re-)assigning them to
+ the new branch. */
+ SVN_ERR(svn_branch_walk(&paths, outer_branch, new_root_loc,
+ scratch_pool, scratch_pool));
+ for (i = 0; i < paths->nelts; i++)
+ {
+ const char *this_path = APR_ARRAY_IDX(paths, i, const char *);
+ int eid = family_add_new_element(new_branch->definition->family);
+
+ branch_set_eid_to_path(new_branch, eid, this_path);
+
+#ifndef WITH_OVERLAPPING_EIDS
+ /* Remove old EID in outer branch */
+ eid = branch_get_eid_by_path(outer_branch, this_path);
+ branch_mapping_remove(outer_branch, eid, this_path);
+#endif
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* */
+static svn_error_t *
+svn_branch_rm(svn_branch_instance_t *branch,
+ svn_editor3_txn_path_t loc,
+ apr_pool_t *scratch_pool)
+{
+ svn_editor3_t *editor = branch->definition->family->repos->editor;
+ const char *rrpath = txn_path_to_relpath(loc, scratch_pool);
+ int eid = branch_get_eid_by_path(branch, rrpath);
+ apr_array_header_t *paths;
+ int i;
+
+ branch_mapping_remove(branch, eid, rrpath);
+
+ SVN_ERR(svn_branch_walk(&paths, branch, loc,
+ scratch_pool, scratch_pool));
+ for (i = 0; i < paths->nelts; i++)
+ {
+ const char *this_path = APR_ARRAY_IDX(paths, i, const char *);
+ int this_eid;
+
+ /* Remove old EID */
+ this_eid = branch_get_eid_by_path(branch, this_path);
+ branch_mapping_remove(branch, this_eid, this_path);
+ }
+
+ SVN_ERR(svn_editor3_rm(editor, loc));
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+execute(const char *branch_rrpath,
+ const apr_array_header_t *actions,
const char *anchor_url,
const char *log_msg,
apr_hash_t *revprops,
@@ -250,7 +1527,10 @@ execute(const apr_array_header_t *action
mtcc_t *mtcc;
svn_editor3_t *editor;
const char *repos_root_url;
+ svn_branch_repos_t *repos;
+ svn_branch_instance_t *branch;
apr_pool_t *iterpool = svn_pool_create(pool);
+ svn_boolean_t made_changes = FALSE;
int i;
svn_error_t *err;
@@ -270,11 +1550,16 @@ execute(const apr_array_header_t *action
editor = mtcc->editor;
repos_root_url = mtcc->repos_root_url;
base_revision = mtcc->base_revision;
+ SVN_ERR(fetch_repos_info(&repos, mtcc->ra_session, editor, pool, pool));
+ branch = repos_get_branch_by_path(repos, branch_rrpath, pool);
+ SVN_DBG(("look up path '%s': found branch f%db%de%d at path '%s'",
+ branch_rrpath, branch->definition->family->fid,
+ branch->definition->bid, branch->definition->root_eid,
+ branch_get_root_path(branch)));
for (i = 0; i < actions->nelts; ++i)
{
struct action *action = APR_ARRAY_IDX(actions, i, struct action *);
- svn_editor3_peg_path_t path1_loc = {0};
svn_editor3_txn_path_t path1_txn_loc = {{0},0};
svn_editor3_txn_path_t path1_parent = {{0},0};
const char *path1_name = NULL;
@@ -287,15 +1572,13 @@ execute(const apr_array_header_t *action
{
const char *rrpath1
= svn_uri_skip_ancestor(repos_root_url, action->path[0], pool);
- path1_loc = pathrev(rrpath1, base_revision);
path1_txn_loc = txn_path(rrpath1, base_revision, ""); /* ### need to
find which part of given path was pre-existing and which was created */
path1_parent = txn_path(svn_relpath_dirname(rrpath1, pool), base_revision, ""); /* ### need to
find which part of given path was pre-existing and which was created */
path1_name = svn_relpath_basename(rrpath1, NULL);
}
- if (action->path[1]
- && action->action != ACTION_PUT /* for which path2 is a local path */)
+ if (action->path[1])
{
const char *rrpath2
= svn_uri_skip_ancestor(repos_root_url, action->path[1], pool);
@@ -305,46 +1588,73 @@ execute(const apr_array_header_t *action
}
switch (action->action)
{
+ case ACTION_LIST_BRANCHES:
+ SVN_ERR(family_list_branch_instances(branch->definition->family,
+ FALSE, iterpool));
+ break;
+ case ACTION_LIST_BRANCHES_R:
+ SVN_ERR(family_list_branch_instances(branch->definition->family,
+ TRUE, iterpool));
+ break;
+ case ACTION_BRANCH:
+ SVN_ERR(svn_branch_branch(branch,
+ path1_txn_loc, path2_parent, path2_name,
+ iterpool));
+ made_changes = TRUE;
+ break;
+ case ACTION_BRANCHIFY:
+ SVN_ERR(svn_branch_branchify(branch,
+ path1_txn_loc,
+ iterpool));
+ made_changes = TRUE;
+ break;
+ case ACTION_DISSOLVE:
+ return svn_error_create(SVN_ERR_BRANCHING, NULL,
+ _("'dissolve' operation not implemented"));
+ made_changes = TRUE;
+ break;
case ACTION_MV:
- SVN_ERR(svn_editor3_mv(editor, path1_loc, path2_parent, path2_name));
+ SVN_ERR(svn_branch_mv(branch,
+ path1_txn_loc, path2_parent, path2_name,
+ iterpool));
+ made_changes = TRUE;
break;
case ACTION_CP:
- path1_loc.rev = action->rev;
- SVN_ERR(svn_editor3_cp(editor, path1_loc, path2_parent, path2_name));
+ path1_txn_loc.peg.rev = action->rev;
+ SVN_ERR(svn_branch_cp(branch,
+ path1_txn_loc, path2_parent, path2_name,
+ iterpool));
+ made_changes = TRUE;
break;
case ACTION_RM:
- SVN_ERR(svn_editor3_rm(editor, path1_txn_loc));
+ SVN_ERR(svn_branch_rm(branch,
+ path1_txn_loc,
+ iterpool));
+ made_changes = TRUE;
break;
case ACTION_MKDIR:
- SVN_ERR(svn_editor3_mk(editor, svn_node_dir, path1_parent, path1_name));
- break;
- case ACTION_PUT:
- /* Unlike svnmucc, here we always (try to) create a new file node,
- without overwriting anything. */
- {
- svn_stream_t *src;
- svn_stringbuf_t *text;
- svn_editor3_node_content_t *new_content;
-
- if (strcmp(action->path[1], "-") != 0)
- SVN_ERR(svn_stream_open_readonly(&src, action->path[1],
- pool, iterpool));
- else
- SVN_ERR(svn_stream_for_stdin(&src, pool));
-
- SVN_ERR(svn_stringbuf_from_stream(&text, src, 0, pool));
- new_content = svn_editor3_node_content_create_file(
- NULL, text, iterpool);
- SVN_ERR(svn_editor3_mk(editor, svn_node_file, path1_parent, path1_name));
- SVN_ERR(svn_editor3_put(editor, path1_txn_loc, new_content));
- }
+ SVN_ERR(svn_branch_mkdir(branch,
+ path1_parent, path1_name,
+ iterpool));
+ made_changes = TRUE;
break;
default:
SVN_ERR_MALFUNCTION();
}
}
- err = mtcc_commit(mtcc, pool);
+ if (made_changes)
+ {
+ err = mtcc_commit(mtcc, pool);
+ if (!err)
+ err = store_repos_info(repos, pool);
+ }
+ else
+ {
+ err = svn_editor3_abort(mtcc->editor);
+ }
+
+ svn_pool_destroy(mtcc->pool);
svn_pool_destroy(iterpool);
return svn_error_trace(err);
@@ -375,15 +1685,21 @@ usage(FILE *stream, apr_pool_t *pool)
" the result as a (single) new revision.\n"
"\n"
"Actions:\n"
+ " ls-br : list all branches\n"
+ " branch SRC DST : branch the (sub)branch at SRC to make a new branch\n"
+ " at DST (presently, SRC must be a branch root)\n"
+ " branchify BR-ROOT : change the existing simple sub-tree at SRC into\n"
+ " a sub-branch (presently, in a new branch family)\n"
+ " dissolve BR-ROOT : change the existing sub-branch at SRC into a\n"
+ " simple sub-tree of its parent branch\n"
" cp REV SRC-URL DST-URL : copy SRC-URL@REV to DST-URL\n"
- " mkdir URL : create new directory URL\n"
" mv SRC-URL DST-URL : move SRC-URL to DST-URL\n"
" rm URL : delete URL\n"
- " put SRC-FILE URL : add or modify file URL with contents copied from\n"
- " SRC-FILE (use \"-\" to read from standard input)\n"
+ " mkdir URL : create new directory URL\n"
"\n"
"Valid options:\n"
" -h, -? [--help] : display this text\n"
+ " -b [--branch] ARG : work in branch of path ARG (default: root)\n"
" -m [--message] ARG : use ARG as a log message\n"
" -F [--file] ARG : read log message from file ARG\n"
" -u [--username] ARG : commit the changes as username ARG\n"
@@ -553,6 +1869,7 @@ sub_main(int *exit_code, int argc, const
trust_server_cert_opt
};
static const apr_getopt_option_t options[] = {
+ {"branch", 'b', 1, ""},
{"message", 'm', 1, ""},
{"file", 'F', 1, ""},
{"username", 'u', 1, ""},
@@ -572,6 +1889,7 @@ sub_main(int *exit_code, int argc, const
{"version", version_opt, 0, ""},
{NULL, 0, 0, NULL}
};
+ const char *branch_rrpath = "";
const char *message = "";
svn_stringbuf_t *filedata = NULL;
const char *username = NULL, *password = NULL;
@@ -612,6 +1930,9 @@ sub_main(int *exit_code, int argc, const
return svn_error_wrap_apr(status, "getopt failure");
switch(opt)
{
+ case 'b':
+ SVN_ERR(svn_utf_cstring_to_utf8(&branch_rrpath, arg, pool));
+ break;
case 'm':
SVN_ERR(svn_utf_cstring_to_utf8(&message, arg, pool));
break;
@@ -634,6 +1955,7 @@ sub_main(int *exit_code, int argc, const
return svn_error_createf(SVN_ERR_INCORRECT_PARAMS, NULL,
"'%s' is not a URL\n", root_url);
root_url = sanitize_url(root_url, pool);
+ anchor = root_url;
break;
case 'r':
{
@@ -780,14 +2102,24 @@ sub_main(int *exit_code, int argc, const
return SVN_NO_ERROR;
/* Now, we iterate over the combined set of arguments -- our actions. */
- for (i = 0; i < action_args->nelts; )
+ for (i = 0; i < action_args->nelts; ++i)
{
int j, num_url_args;
const char *action_string = APR_ARRAY_IDX(action_args, i, const char *);
struct action *action = apr_pcalloc(pool, sizeof(*action));
/* First, parse the action. */
- if (! strcmp(action_string, "mv"))
+ if (! strcmp(action_string, "ls-br"))
+ action->action = ACTION_LIST_BRANCHES;
+ else if (! strcmp(action_string, "ls-br-r"))
+ action->action = ACTION_LIST_BRANCHES_R;
+ else if (! strcmp(action_string, "branch"))
+ action->action = ACTION_BRANCH;
+ else if (! strcmp(action_string, "branchify"))
+ action->action = ACTION_BRANCHIFY;
+ else if (! strcmp(action_string, "dissolve"))
+ action->action = ACTION_DISSOLVE;
+ else if (! strcmp(action_string, "mv"))
action->action = ACTION_MV;
else if (! strcmp(action_string, "cp"))
action->action = ACTION_CP;
@@ -795,8 +2127,6 @@ sub_main(int *exit_code, int argc, const
action->action = ACTION_MKDIR;
else if (! strcmp(action_string, "rm"))
action->action = ACTION_RM;
- else if (! strcmp(action_string, "put"))
- action->action = ACTION_PUT;
else if (! strcmp(action_string, "?") || ! strcmp(action_string, "h")
|| ! strcmp(action_string, "help"))
{
@@ -807,13 +2137,15 @@ sub_main(int *exit_code, int argc, const
return svn_error_createf(SVN_ERR_INCORRECT_PARAMS, NULL,
"'%s' is not an action\n",
action_string);
- if (++i == action_args->nelts)
- return insufficient();
/* For copies, there should be a revision number next. */
if (action->action == ACTION_CP)
{
- const char *rev_str = APR_ARRAY_IDX(action_args, i, const char *);
+ const char *rev_str;
+
+ if (++i == action_args->nelts)
+ return svn_error_trace(insufficient());
+ rev_str = APR_ARRAY_IDX(action_args, i, const char *);
if (strcmp(rev_str, "head") == 0)
action->rev = SVN_INVALID_REVNUM;
else if (strcmp(rev_str, "HEAD") == 0)
@@ -831,36 +2163,32 @@ sub_main(int *exit_code, int argc, const
"'%s' is not a revision\n",
rev_str);
}
- if (++i == action_args->nelts)
- return insufficient();
}
else
{
action->rev = SVN_INVALID_REVNUM;
}
- /* For puts, there should be a local file next. */
- if (action->action == ACTION_PUT)
- {
- action->path[1] =
- svn_dirent_internal_style(APR_ARRAY_IDX(action_args, i,
- const char *), pool);
- if (++i == action_args->nelts)
- return insufficient();
- }
-
/* How many URLs does this action expect? */
if (action->action == ACTION_RM
|| action->action == ACTION_MKDIR
- || action->action == ACTION_PUT)
+ || action->action == ACTION_BRANCHIFY
+ || action->action == ACTION_DISSOLVE)
num_url_args = 1;
+ else if (action->action == ACTION_LIST_BRANCHES
+ || action->action == ACTION_LIST_BRANCHES_R)
+ num_url_args = 0;
else
num_url_args = 2;
/* Parse the required number of URLs. */
for (j = 0; j < num_url_args; ++j)
{
- const char *url = APR_ARRAY_IDX(action_args, i, const char *);
+ const char *url;
+
+ if (++i == action_args->nelts)
+ return svn_error_trace(insufficient());
+ url = APR_ARRAY_IDX(action_args, i, const char *);
/* If there's a ROOT_URL, we expect URL to be a path
relative to ROOT_URL (and we build a full url from the
@@ -895,9 +2223,6 @@ sub_main(int *exit_code, int argc, const
"URLs in the action list do not "
"share a common ancestor");
}
-
- if ((++i == action_args->nelts) && (j + 1 < num_url_args))
- return insufficient();
}
APR_ARRAY_PUSH(actions, struct action *) = action;
@@ -910,7 +2235,8 @@ sub_main(int *exit_code, int argc, const
return SVN_NO_ERROR;
}
- if ((err = execute(actions, anchor, log_msg, revprops, base_revision, ctx, pool)))
+ if ((err = execute(branch_rrpath, actions, anchor, log_msg, revprops,
+ base_revision, ctx, pool)))
{
if (err->apr_err == SVN_ERR_AUTHN_FAILED && non_interactive)
err = svn_error_quick_wrap(err,