/* Inspect the data and/or prop reps of revision REVNUM in FS. Store * reference count tallies in passed hashes (allocated in RESULT_POOL). * * If PROP_REPS or DATA_REPS is NULL, the respective kind of reps are not * tallied. * * Print progress report to STDERR unless QUIET is true. * * Use SCRATCH_POOL for temporary allocations. */ static svn_error_t * process_one_revision(svn_fs_t *fs, svn_revnum_t revnum, svn_boolean_t quiet, apr_hash_t *prop_reps, apr_hash_t *data_reps, apr_hash_t *both_reps, apr_pool_t *result_pool, apr_pool_t *scratch_pool) { svn_fs_root_t *rev_root; apr_hash_t *paths_changed; apr_hash_index_t *hi; if (! quiet) SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, "processing r%ld\n", revnum)); /* Get the changed paths. */ SVN_ERR(svn_fs_revision_root(&rev_root, fs, revnum, scratch_pool)); SVN_ERR(svn_fs_paths_changed2(&paths_changed, rev_root, scratch_pool)); /* Iterate them. */ /* ### use iterpool? */ for (hi = apr_hash_first(scratch_pool, paths_changed); hi; hi = apr_hash_next(hi)) { const char *path; const svn_fs_path_change2_t *change; const svn_fs_id_t *node_rev_id1, *node_rev_id2; const svn_fs_id_t *the_id; node_revision_t *node_rev; path = svn__apr_hash_index_key(hi); change = svn__apr_hash_index_val(hi); if (! quiet) SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, "processing r%ld:%s\n", revnum, path)); if (change->change_kind == svn_fs_path_change_delete) /* Can't ask for reps of PATH at REVNUM if the path no longer exists * at that revision! */ continue; /* Okay, we have two node_rev id's for this change: the txn one and * the revision one. We'll use the latter. */ node_rev_id1 = change->node_rev_id; SVN_ERR(svn_fs_node_id(&node_rev_id2, rev_root, path, scratch_pool)); SVN_ERR_ASSERT(svn_fs_fs__id_txn_id(node_rev_id1) != NULL); SVN_ERR_ASSERT(svn_fs_fs__id_rev(node_rev_id2) != SVN_INVALID_REVNUM); the_id = node_rev_id2; /* Get the node_rev using the chosen node_rev_id. */ SVN_ERR(svn_fs_fs__get_node_revision(&node_rev, fs, the_id, scratch_pool)); /* Maybe record the sha1's. */ SVN_ERR(record(prop_reps, node_rev->prop_rep, result_pool)); SVN_ERR(record(data_reps, node_rev->data_rep, result_pool)); SVN_ERR(record(both_reps, node_rev->prop_rep, result_pool)); SVN_ERR(record(both_reps, node_rev->data_rep, result_pool)); } return SVN_NO_ERROR; }
/* The caller wants to modify REVISION of FSPATH. Is that allowed? */ static svn_error_t * can_modify(svn_fs_root_t *txn_root, const char *fspath, svn_revnum_t revision, apr_pool_t *scratch_pool) { svn_revnum_t created_rev; /* Out-of-dateness check: compare the created-rev of the node in the txn against the created-rev of FSPATH. */ SVN_ERR(svn_fs_node_created_rev(&created_rev, txn_root, fspath, scratch_pool)); /* Uncommitted nodes (eg. a descendent of a copy/move/rotate destination) have no (committed) revision number. Let the caller go ahead and modify these nodes. Note: strictly speaking, they might be performing an "illegal" edit in certain cases, but let's just assume they're Good Little Boys. If CREATED_REV is invalid, that means it's already mutable in the txn, which means it has already passed this out-of-dateness check. (Usually, this happens when looking at a parent directory of an already-modified node) */ if (!SVN_IS_VALID_REVNUM(created_rev)) return SVN_NO_ERROR; /* If the node is immutable (has a revision), then the caller should have supplied a valid revision number [that they expect to change]. The checks further below will determine the out-of-dateness of the specified revision. */ /* ### ugh. descendents of copy/move/rotate destinations carry along ### their original immutable state and (thus) a valid CREATED_REV. ### but they are logically uncommitted, so the caller will pass ### SVN_INVALID_REVNUM. (technically, the caller could provide ### ORIGINAL_REV, but that is semantically incorrect for the Ev2 ### API). ### ### for now, we will assume the caller knows what they are doing ### and an invalid revision implies such a descendent. in the ### future, we could examine the ancestor chain looking for a ### copy/move/rotate-here node and allow the modification (and the ### converse: if no such ancestor, the caller must specify the ### correct/intended revision to modify). */ #if 1 if (!SVN_IS_VALID_REVNUM(revision)) return SVN_NO_ERROR; #else if (!SVN_IS_VALID_REVNUM(revision)) /* ### use a custom error code? */ return svn_error_createf(SVN_ERR_INCORRECT_PARAMS, NULL, _("Revision for modifying '%s' is required"), fspath); #endif if (revision < created_rev) { /* We asked to change a node that is *older* than what we found in the transaction. The client is out of date. */ return svn_error_createf(SVN_ERR_FS_OUT_OF_DATE, NULL, _("'%s' is out of date; try updating"), fspath); } if (revision > created_rev) { /* We asked to change a node that is *newer* than what we found in the transaction. Given that the transaction was based off of 'youngest', then either: - the caller asked to modify a future node - the caller has committed more revisions since this txn was constructed, and is asking to modify a node in one of those new revisions. In either case, the node may not have changed in those new revisions; use the node's ID to determine this case. */ const svn_fs_id_t *txn_noderev_id; svn_fs_root_t *rev_root; const svn_fs_id_t *new_noderev_id; /* The ID of the node that we would be modifying in the txn */ SVN_ERR(svn_fs_node_id(&txn_noderev_id, txn_root, fspath, scratch_pool)); /* Get the ID from the future/new revision. */ SVN_ERR(svn_fs_revision_root(&rev_root, svn_fs_root_fs(txn_root), revision, scratch_pool)); SVN_ERR(svn_fs_node_id(&new_noderev_id, rev_root, fspath, scratch_pool)); svn_fs_close_root(rev_root); /* Has the target node changed in the future? */ if (svn_fs_compare_ids(txn_noderev_id, new_noderev_id) != 0) { /* Restarting the commit will base the txn on the future/new revision, allowing the modification at REVISION. */ /* ### use a custom error code */ return svn_error_createf(SVN_ERR_FS_CONFLICT, NULL, _("'%s' has been modified since the " "commit began (restart the commit)"), fspath); } } return SVN_NO_ERROR; }