| /* deltify.c --- deltification and undeltification of nodes. |
| * |
| * ==================================================================== |
| * Copyright (c) 2000-2002 CollabNet. All rights reserved. |
| * |
| * This software is licensed as described in the file COPYING, which |
| * you should have received as part of this distribution. The terms |
| * are also available at http://subversion.tigris.org/license-1.html. |
| * If newer versions of this license are posted there, you may use a |
| * newer version instead, at your option. |
| * |
| * This software consists of voluntary contributions made by many |
| * individuals. For exact contribution history, see the revision |
| * history and logs, available at http://subversion.tigris.org/. |
| * ==================================================================== |
| */ |
| |
| #include <string.h> |
| #include <db.h> |
| |
| #include "svn_fs.h" |
| #include "svn_pools.h" |
| #include "svn_path.h" |
| |
| #include "fs.h" |
| #include "nodes-table.h" |
| #include "node-rev.h" |
| #include "reps-strings.h" |
| #include "dag.h" |
| #include "id.h" |
| |
| |
| /* Stable nodes and deltification. */ |
| |
| /* In FS, change TARGET's representation to be a delta against SOURCE, |
| as part of TRAIL. If TARGET or SOURCE does not exist, do nothing |
| and return success. */ |
| static svn_error_t * |
| deltify (svn_fs_id_t *target_id, |
| svn_fs_id_t *source_id, |
| svn_fs_t *fs, |
| int props_only, |
| trail_t *trail) |
| { |
| skel_t |
| *source_nr, /* source node revision */ |
| *target_nr; /* target node revision */ |
| |
| const char |
| *target_pkey, /* target property rep key */ |
| *target_dkey, /* target data rep key */ |
| *source_pkey, /* source property rep key */ |
| *source_dkey; /* source data rep key */ |
| |
| skel_t |
| *target_pkey_skel, /* target property rep key skel */ |
| *target_dkey_skel, /* target data rep key skel */ |
| *source_pkey_skel, /* source property rep key skel */ |
| *source_dkey_skel; /* source data rep key skel */ |
| |
| /* Turn those IDs into skels, so we can get the rep keys. */ |
| SVN_ERR (svn_fs__get_node_revision (&target_nr, fs, target_id, trail)); |
| SVN_ERR (svn_fs__get_node_revision (&source_nr, fs, source_id, trail)); |
| |
| /* Check that target and source exist. It is not an error to |
| attempt to deltify something that does not exist, or deltify |
| against a non-existent base. However, nothing happens. */ |
| if ((target_nr == NULL) || (source_nr == NULL)) |
| return SVN_NO_ERROR; |
| |
| /* We have a target and a source. Get all the rep keys... */ |
| { |
| |
| /* Target property key. */ |
| target_pkey_skel = SVN_FS__NR_PROP_KEY (target_nr); |
| if (target_pkey_skel->len != 0) { |
| target_pkey = apr_pstrndup (trail->pool, |
| target_pkey_skel->data, |
| target_pkey_skel->len); |
| } |
| else |
| target_pkey = NULL; |
| |
| /* Target data key. */ |
| target_dkey_skel = SVN_FS__NR_DATA_KEY (target_nr); |
| if (target_dkey_skel->len != 0) { |
| target_dkey = apr_pstrndup (trail->pool, |
| target_dkey_skel->data, |
| target_dkey_skel->len); |
| } |
| else |
| target_dkey = NULL; |
| |
| /* Source property key. */ |
| source_pkey_skel = SVN_FS__NR_PROP_KEY (source_nr); |
| if (source_pkey_skel->len != 0) { |
| source_pkey = apr_pstrndup (trail->pool, |
| source_pkey_skel->data, |
| source_pkey_skel->len); |
| } |
| else |
| source_pkey = NULL; |
| |
| /* Source data key. */ |
| source_dkey_skel = SVN_FS__NR_DATA_KEY (source_nr); |
| if (source_dkey_skel->len != 0) { |
| source_dkey = apr_pstrndup (trail->pool, |
| source_dkey_skel->data, |
| source_dkey_skel->len); |
| } |
| else |
| source_dkey = NULL; |
| } |
| |
| if ((target_pkey && source_pkey) |
| && (strcmp (target_pkey, source_pkey))) |
| SVN_ERR (svn_fs__rep_deltify (fs, target_pkey, source_pkey, trail)); |
| |
| if ((target_dkey && source_dkey) && (! props_only) |
| && (strcmp (target_dkey, source_dkey))) |
| SVN_ERR (svn_fs__rep_deltify (fs, target_dkey, source_dkey, trail)); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| /* In FS, change ID's representation to be a fulltext representation |
| as part of TRAIL. If ID does not exist, do nothing and return |
| success. */ |
| static svn_error_t * |
| undeltify (svn_fs_id_t *id, |
| svn_fs_t *fs, |
| trail_t *trail) |
| { |
| skel_t *node_rev; |
| const char *prop_key = NULL, *data_key = NULL; |
| skel_t *pkey_skel, *dkey_skel; |
| |
| /* Turn ID into a skel so we can get the rep keys. */ |
| SVN_ERR (svn_fs__get_node_revision (&node_rev, fs, id, trail)); |
| |
| /* Check that target exists. If not, no big deal -- just do |
| nothing. */ |
| if (node_rev == NULL) |
| return SVN_NO_ERROR; |
| |
| /* Get the property key. */ |
| pkey_skel = SVN_FS__NR_PROP_KEY (node_rev); |
| if (pkey_skel->len != 0) |
| prop_key = apr_pstrndup (trail->pool, pkey_skel->data, pkey_skel->len); |
| |
| /* Get the data key. */ |
| dkey_skel = SVN_FS__NR_DATA_KEY (node_rev); |
| if (dkey_skel->len != 0) |
| data_key = apr_pstrndup (trail->pool, dkey_skel->data, dkey_skel->len); |
| |
| /* Undeltify the properties. */ |
| if (prop_key) |
| SVN_ERR (svn_fs__rep_undeltify (fs, prop_key, trail)); |
| |
| /* Undeltify the data (entries list for directories, file contents |
| for files). */ |
| if (data_key) |
| SVN_ERR (svn_fs__rep_undeltify (fs, data_key, trail)); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| |
| /* Deltify TARGET_ID in FS against its immediately successor (also in |
| FS). Pass IS_DIR through to deltify(), and do all of this stuff as |
| part of TRAIL. */ |
| static svn_error_t * |
| deltify_by_id (svn_fs_t *fs, |
| svn_fs_id_t *target_id, |
| int is_dir, |
| trail_t *trail) |
| { |
| svn_fs_id_t *source_id = NULL, *tmp_id; |
| apr_size_t len = svn_fs__id_length (target_id); |
| dag_node_t *node; |
| |
| /* Increment TMP_ID as a regular successor of TARGET_ID, and see if |
| it exists in FS. */ |
| tmp_id = svn_fs__id_copy (target_id, trail->pool); |
| tmp_id[len - 1]++; |
| if (SVN_NO_ERROR == svn_fs__dag_get_node (&node, fs, tmp_id, trail)) |
| { |
| source_id = tmp_id; |
| } |
| else |
| { |
| /* If that doesn't exist, we'll branch TARGET_ID, and see if |
| that exists. */ |
| apr_size_t i; |
| tmp_id = apr_pcalloc (trail->pool, sizeof (*tmp_id) * (len + 3)); |
| for (i = 0; i < len; i++) |
| { |
| tmp_id[i] = target_id[i]; |
| } |
| tmp_id[len] = 1; |
| tmp_id[len + 1] = 1; |
| tmp_id[len + 2] = -1; |
| |
| if (SVN_NO_ERROR == svn_fs__dag_get_node (&node, fs, tmp_id, trail)) |
| { |
| source_id = tmp_id; |
| } |
| } |
| |
| /* If we found a valid source ID, perform the deltification step. */ |
| if (source_id) |
| SVN_ERR (deltify (target_id, source_id, fs, is_dir, trail)); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| /* Deltify or undeltify a directory PATH under ROOT in FS (whose |
| node-id is ID). If DO_DELTIFY is non-zero, drive deltify_by_id(), |
| else drive undeltify(). Use RECURSE to determine if this function |
| should recursively deltify or undeltify directories. */ |
| static svn_error_t * |
| deltify_undeltify (svn_fs_t *fs, |
| svn_fs_root_t *root, |
| const char *path, |
| svn_fs_id_t *id, |
| int do_deltify, |
| int recurse, |
| trail_t *trail) |
| { |
| apr_pool_t *subpool = svn_pool_create (trail->pool); |
| svn_stringbuf_t *full_path = svn_stringbuf_create (path, subpool); |
| dag_node_t *node; |
| int is_dir = 0; |
| |
| /* Get the node from its ID, and figure out if it is a directory. */ |
| SVN_ERR (svn_fs__dag_get_node (&node, fs, id, trail)); |
| is_dir = svn_fs__dag_is_directory (node); |
| |
| /* If this is a directory and we have been asked to recurse, then |
| call this function for each of this ID's entries. */ |
| if (is_dir && recurse) |
| { |
| apr_hash_t *entries; |
| apr_hash_index_t *hi; |
| |
| SVN_ERR (svn_fs__dag_dir_entries_hash (&entries, node, trail)); |
| for (hi = apr_hash_first (subpool, entries); hi; hi = apr_hash_next (hi)) |
| { |
| const void *key; |
| void *val; |
| apr_ssize_t klen; |
| svn_fs_dirent_t *entry; |
| |
| /* KEY will be the entry name in source, VAL the dirent */ |
| apr_hash_this (hi, &key, &klen, &val); |
| entry = val; |
| |
| /* Construct the full path of this entry, and recurse. */ |
| svn_stringbuf_set (full_path, path); |
| svn_path_add_component_nts (full_path, entry->name); |
| SVN_ERR (deltify_undeltify (fs, root, full_path->data, entry->id, |
| do_deltify, recurse, trail)); |
| } |
| } |
| |
| /* Do the real work of ... */ |
| if (do_deltify) |
| { |
| /* ... deltification. */ |
| SVN_ERR (deltify_by_id (fs, id, is_dir, trail)); |
| } |
| else |
| { |
| /* ... un-deltification. */ |
| SVN_ERR (undeltify (id, fs, trail)); |
| } |
| |
| /* Destroy per-iteration subpool. */ |
| svn_pool_destroy (subpool); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| struct deltify_args { |
| svn_fs_t *fs; |
| svn_fs_root_t *root; |
| const char *path; |
| svn_fs_id_t *id; |
| int recursive; |
| }; |
| |
| |
| static svn_error_t * |
| txn_body_deltify (void *baton, trail_t *trail) |
| { |
| struct deltify_args *args = baton; |
| int is_dir = 0; |
| dag_node_t *node; |
| |
| /* Use the ID to determine if the target here is a directory. */ |
| SVN_ERR (svn_fs__dag_get_node (&node, args->fs, args->id, trail)); |
| is_dir = svn_fs__dag_is_directory (node); |
| |
| /* Perform the deltification step. */ |
| SVN_ERR (deltify_undeltify (args->fs, args->root, args->path, args->id, |
| 1, args->recursive, trail)); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| static svn_error_t * |
| txn_body_undeltify (void *baton, trail_t *trail) |
| { |
| struct deltify_args *args = baton; |
| int is_dir = 0; |
| dag_node_t *node; |
| |
| /* Use the ID to determine if the target here is a directory. */ |
| SVN_ERR (svn_fs__dag_get_node (&node, args->fs, args->id, trail)); |
| is_dir = svn_fs__dag_is_directory (node); |
| |
| /* Perform the un-deltification step. */ |
| SVN_ERR (deltify_undeltify (args->fs, args->root, args->path, args->id, |
| 0, args->recursive, trail)); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| |
| /*** Public (or semi-public) interfaces. ***/ |
| |
| svn_error_t * |
| svn_fs_deltify (svn_fs_root_t *root, |
| const char *path, |
| int recursive, |
| apr_pool_t *pool) |
| { |
| struct deltify_args args; |
| |
| if (! svn_fs_is_revision_root (root)) |
| return svn_error_create (SVN_ERR_FS_NOT_REVISION_ROOT, 0, NULL, pool, |
| "svn_fs_deltify: root is not a revision root"); |
| |
| args.fs = svn_fs_root_fs (root); |
| args.root = root; |
| args.recursive = recursive; |
| args.path = path; |
| |
| /* Get the ID of the target, which is the node we're changing. */ |
| SVN_ERR (svn_fs_node_id (&(args.id), root, path, pool)); |
| |
| SVN_ERR (svn_fs__retry_txn (args.fs, txn_body_deltify, &args, pool)); |
| return SVN_NO_ERROR; |
| } |
| |
| |
| |
| |
| svn_error_t * |
| svn_fs_undeltify (svn_fs_root_t *root, |
| const char *path, |
| int recursive, |
| apr_pool_t *pool) |
| { |
| struct deltify_args args; |
| |
| if (! svn_fs_is_revision_root (root)) |
| return svn_error_create (SVN_ERR_FS_NOT_REVISION_ROOT, 0, NULL, pool, |
| "svn_fs_deltify: root is not a revision root"); |
| |
| args.fs = svn_fs_root_fs (root); |
| args.root = root; |
| args.recursive = recursive; |
| args.path = path; |
| |
| /* Get the ID of the target, which is the node we're changing. */ |
| SVN_ERR (svn_fs_node_id (&(args.id), root, path, pool)); |
| |
| SVN_ERR (svn_fs__retry_txn (args.fs, txn_body_undeltify, &args, pool)); |
| return SVN_NO_ERROR; |
| } |
| |
| |
| |
| svn_error_t * |
| svn_fs__stable_node (svn_fs_t *fs, |
| svn_fs_id_t *id, |
| trail_t *trail) |
| { |
| svn_fs_id_t *predecessor_id = svn_fs__id_predecessor (id, trail->pool); |
| int is_dir = 0; |
| dag_node_t *node; |
| |
| SVN_ERR (svn_fs__dag_get_node (&node, fs, id, trail)); |
| is_dir = svn_fs__dag_is_directory (node); |
| |
| if (predecessor_id != NULL) |
| SVN_ERR (deltify (predecessor_id, id, fs, is_dir ? 1 : 0, trail)); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| |
| /* |
| * local variables: |
| * eval: (load-file "../../tools/dev/svn-dev.el") |
| * end: |
| */ |