📄 tree.c
字号:
svn_fs_dirent_t *s_entry, *t_entry; const void *key; void *val; apr_ssize_t klen; svn_pool_clear(iterpool); apr_hash_this(hi, &key, &klen, &val); s_entry = val; t_entry = apr_hash_get(t_entries, key, klen); /* If NAME exists in TARGET, declare a conflict. */ if (t_entry) return conflict_err(conflict_p, svn_path_join(target_path, t_entry->name, iterpool)); SVN_ERR(svn_fs_fs__dag_set_entry (target, s_entry->name, s_entry->id, s_entry->kind, txn_id, iterpool)); } svn_pool_destroy(iterpool); SVN_ERR(svn_fs_fs__dag_get_predecessor_count(&pred_count, source, pool)); SVN_ERR(update_ancestry(fs, source_id, target_id, txn_id, target_path, pred_count, pool)); return SVN_NO_ERROR;}/* Merge changes between an ancestor and BATON->source_node into BATON->txn. The ancestor is either BATON->ancestor_node, or if that is null, BATON->txn's base node. If the merge is successful, BATON->txn's base will become BATON->source_node, and its root node will have a new ID, a successor of BATON->source_node. */static svn_error_t *merge_changes(dag_node_t *ancestor_node, dag_node_t *source_node, svn_fs_txn_t *txn, svn_stringbuf_t *conflict, apr_pool_t *pool){ dag_node_t *txn_root_node; const svn_fs_id_t *source_id; svn_fs_t *fs = txn->fs; const char *txn_id = txn->id; source_id = svn_fs_fs__dag_get_id(source_node); SVN_ERR(svn_fs_fs__dag_txn_root(&txn_root_node, fs, txn_id, pool)); if (ancestor_node == NULL) { SVN_ERR(svn_fs_fs__dag_txn_base_root(&ancestor_node, fs, txn_id, pool)); } if (svn_fs_fs__id_eq(svn_fs_fs__dag_get_id(ancestor_node), svn_fs_fs__dag_get_id(txn_root_node))) { /* If no changes have been made in TXN since its current base, then it can't conflict with any changes since that base. So we just set *both* its base and root to source, making TXN in effect a repeat of source. */ /* ### kff todo: this would, of course, be a mighty silly thing for the caller to do, and we might want to consider whether this response is really appropriate. */ abort(); } else SVN_ERR(merge(conflict, "/", txn_root_node, source_node, ancestor_node, txn_id, pool)); return SVN_NO_ERROR;}svn_error_t *svn_fs_fs__commit_txn(const char **conflict_p, svn_revnum_t *new_rev_p, svn_fs_txn_t *txn, apr_pool_t *pool){ /* How do commits work in Subversion? * * When you're ready to commit, here's what you have: * * 1. A transaction, with a mutable tree hanging off it. * 2. A base revision, against which TXN_TREE was made. * 3. A latest revision, which may be newer than the base rev. * * The problem is that if latest != base, then one can't simply * attach the txn root as the root of the new revision, because that * would lose all the changes between base and latest. It is also * not acceptable to insist that base == latest; in a busy * repository, commits happen too fast to insist that everyone keep * their entire tree up-to-date at all times. Non-overlapping * changes should not interfere with each other. * * The solution is to merge the changes between base and latest into * the txn tree [see the function merge()]. The txn tree is the * only one of the three trees that is mutable, so it has to be the * one to adjust. * * You might have to adjust it more than once, if a new latest * revision gets committed while you were merging in the previous * one. For example: * * 1. Jane starts txn T, based at revision 6. * 2. Someone commits (or already committed) revision 7. * 3. Jane's starts merging the changes between 6 and 7 into T. * 4. Meanwhile, someone commits revision 8. * 5. Jane finishes the 6-->7 merge. T could now be committed * against a latest revision of 7, if only that were still the * latest. Unfortunately, 8 is now the latest, so... * 6. Jane starts merging the changes between 7 and 8 into T. * 7. Meanwhile, no one commits any new revisions. Whew. * 8. Jane commits T, creating revision 9, whose tree is exactly * T's tree, except immutable now. * * Lather, rinse, repeat. */ svn_error_t *err; svn_revnum_t new_rev; svn_fs_t *fs = txn->fs; /* Initialize output params. */ new_rev = SVN_INVALID_REVNUM; if (conflict_p) *conflict_p = NULL; while (1729) { svn_revnum_t youngish_rev; svn_fs_root_t *youngish_root; dag_node_t *youngish_root_node; svn_stringbuf_t *conflict = svn_stringbuf_create("", pool); /* Get the *current* youngest revision, in one short-lived Berkeley transaction. (We don't want the revisions table locked while we do the main merge.) We call it "youngish" because new revisions might get committed after we've obtained it. */ SVN_ERR(svn_fs_fs__youngest_rev(&youngish_rev, fs, pool)); SVN_ERR(svn_fs_fs__revision_root(&youngish_root, fs, youngish_rev, pool)); /* Get the dag node for the youngest revision, also in one Berkeley transaction. Later we'll use it as the SOURCE argument to a merge, and if the merge succeeds, this youngest root node will become the new base root for the svn txn that was the target of the merge (but note that the youngest rev may have changed by then -- that's why we're careful to get this root in its own bdb txn here). */ SVN_ERR(get_root(&youngish_root_node, youngish_root, pool)); /* Try to merge. If the merge succeeds, the base root node of TARGET's txn will become the same as youngish_root_node, so any future merges will only be between that node and whatever the root node of the youngest rev is by then. */ err = merge_changes(NULL, youngish_root_node, txn, conflict, pool); if (err) { if ((err->apr_err == SVN_ERR_FS_CONFLICT) && conflict_p) *conflict_p = conflict->data; return err; } txn->base_rev = youngish_rev; /* Try to commit. */ err = svn_fs_fs__commit(&new_rev, fs, txn, pool); if (err && (err->apr_err == SVN_ERR_FS_TXN_OUT_OF_DATE)) { /* Did someone else finish committing a new revision while we were in mid-merge or mid-commit? If so, we'll need to loop again to merge the new changes in, then try to commit again. Or if that's not what happened, then just return the error. */ svn_revnum_t youngest_rev; SVN_ERR(svn_fs_fs__youngest_rev(&youngest_rev, fs, pool)); if (youngest_rev == youngish_rev) return err; else svn_error_clear(err); } else if (err) { return err; } else { /* Set the return value -- our brand spankin' new revision! */ *new_rev_p = new_rev; return SVN_NO_ERROR; } } /* NOTREACHED */}/* Merge changes between two nodes into a third node. Given nodes SOURCE_PATH under SOURCE_ROOT, TARGET_PATH under TARGET_ROOT and ANCESTOR_PATH under ANCESTOR_ROOT, modify target to contain all the changes between the ancestor and source. If there are conflicts, return SVN_ERR_FS_CONFLICT and set *CONFLICT_P to a textual description of the offending changes. Perform any temporary allocations in POOL. */static svn_error_t *fs_merge(const char **conflict_p, svn_fs_root_t *source_root, const char *source_path, svn_fs_root_t *target_root, const char *target_path, svn_fs_root_t *ancestor_root, const char *ancestor_path, apr_pool_t *pool){ dag_node_t *source, *ancestor; svn_fs_txn_t *txn; svn_error_t *err; svn_stringbuf_t *conflict = svn_stringbuf_create("", pool); if (! target_root->is_txn_root) return not_txn(target_root); /* Paranoia. */ if ((source_root->fs != ancestor_root->fs) || (target_root->fs != ancestor_root->fs)) { return svn_error_create (SVN_ERR_FS_CORRUPT, NULL, _("Bad merge; ancestor, source, and target not all in same fs")); } /* ### kff todo: is there any compelling reason to get the nodes in one db transaction? Right now we don't; txn_body_get_root() gets one node at a time. This will probably need to change: Jim Blandy <jimb@zwingli.cygnus.com> writes: > svn_fs_merge needs to be a single transaction, to protect it against > people deleting parents of nodes it's working on, etc. */ /* Get the ancestor node. */ SVN_ERR(get_root(&ancestor, ancestor_root, pool)); /* Get the source node. */ SVN_ERR(get_root(&source, source_root, pool)); /* Open a txn for the txn root into which we're merging. */ SVN_ERR(svn_fs_fs__open_txn(&txn, ancestor_root->fs, target_root->txn, pool)); /* Merge changes between ANCESTOR and SOURCE into TXN. */ err = merge_changes(ancestor, source, txn, conflict, pool); if (err) { if ((err->apr_err == SVN_ERR_FS_CONFLICT) && conflict_p) *conflict_p = conflict->data; return err; } return SVN_NO_ERROR;}svn_error_t *svn_fs_fs__deltify(svn_fs_t *fs, svn_revnum_t revision, apr_pool_t *pool){ /* Deltify is a no-op for fs_fs. */ return SVN_NO_ERROR;}/* Directories. *//* Set *TABLE_P to a newly allocated APR hash table containing the entries of the directory at PATH in ROOT. The keys of the table are entry names, as byte strings, excluding the final null character; the table's values are pointers to svn_fs_dirent_t structures. Allocate the table and its contents in POOL. */static svn_error_t *fs_dir_entries(apr_hash_t **table_p, svn_fs_root_t *root, const char *path, apr_pool_t *pool){ dag_node_t *node; apr_hash_t *entries; /* Get the entries for this path and copy them into the callers's pool. */ SVN_ERR(get_dag(&node, root, path, pool)); SVN_ERR(svn_fs_fs__dag_dir_entries(&entries, node, pool)); *table_p = svn_fs_fs__copy_dir_entries(entries, pool); return SVN_NO_ERROR;}/* Create a new directory named PATH in ROOT. The new directory has no entries, and no properties. ROOT must be the root of a transaction, not a revision. Do any necessary temporary allocation in POOL. */static svn_error_t *fs_make_dir(svn_fs_root_t *root, const char *path, apr_pool_t *pool){ parent_path_t *parent_path; dag_node_t *sub_dir; const char *txn_id = root->txn; SVN_ERR(open_path(&parent_path, root, path, open_path_last_optional, txn_id, pool)); /* Check (recursively) to see if some lock is 'reserving' a path at that location, or even some child-path; if so, check that we can use it. */ if (root->txn_flags & SVN_FS_TXN_CHECK_LOCKS) SVN_ERR(svn_fs_fs__allow_locked_operation(path, root->fs, TRUE, FALSE, pool)); /* If there's already a sub-directory by that name, complain. This also catches the case of trying to make a subdirectory named `/'. */ if (parent_path->node) return already_exists(root, path); /* Create the subdirectory. */ SVN_ERR(make_path_mutable(root, parent_path->parent, path, pool)); SVN_ERR(svn_fs_fs__dag_make_dir(&sub_dir, parent_path->parent->node, parent_path_path(parent_path->parent, pool), parent_path->entry, txn_id, pool)); /* Add this directory to the path cache. */ dag_node_cache_set(root, parent_path_path(parent_path, pool), sub_dir); /* Make a record of this modification in the changes table. */ SVN_ERR(add_change(root->fs, txn_id, path, svn_fs_fs__dag_get_id(sub_dir), svn_fs_path_change_add, 0, 0, SVN_INVALID_REVNUM, NULL, pool)); return SVN_NO_ERROR;} /* Delete the node at PATH under ROOT. ROOT must be a transaction root. Perform temporary allocations in POOL. */static svn_error_t *fs_delete_node(svn_fs_root_t *root, const char *path, apr_pool_t *pool){ parent_path_t *parent_path; const char *txn_id = root->txn; if (! root->is_txn_root) return not_txn(root); SVN_ERR(open_path(&parent_path, root, path, 0, txn_id, pool)); /* We can't remove the root of the filesystem. */ if (! parent_path->parent) return svn_error_create(SVN_ERR_FS_ROOT_DIR, NULL,
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -