blob: 92cff09f2a8fd00670817efa1beb1b8b166c59aa [file] [log] [blame]
/*
* delta.c: an editor driver for svn_repos_dir_delta
*
* ====================================================================
* 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 "svn_types.h"
#include "svn_delta.h"
#include "svn_fs.h"
#include "svn_path.h"
#include "apr_hash.h"
#include "svn_repos.h"
#include "svn_pools.h"
/* THINGS TODO: Currently the code herein gives only a slight nod to
fully supporting directory deltas that involve renames, copies, and
such. */
/* Some datatypes and declarations used throughout the file. */
/* Parameters which remain constant throughout a delta traversal.
At the top of the recursion, we initialize one of these structures.
Then, we pass it down, unchanged, to every call. This way,
functions invoked deep in the recursion can get access to this
traversal's global parameters, without using global variables. */
struct context {
const svn_delta_edit_fns_t *editor;
svn_fs_root_t *source_root;
apr_hash_t *source_rev_diffs;
svn_fs_root_t *target_root;
svn_boolean_t text_deltas;
svn_boolean_t recurse;
svn_boolean_t use_copyfrom_args;
int target_is_rev;
};
/* The type of a function that accepts changes to an object's property
list. OBJECT is the object whose properties are being changed.
NAME is the name of the property to change. VALUE is the new value
for the property, or zero if the property should be deleted. */
typedef svn_error_t *proplist_change_fn_t (struct context *c,
void *object,
const char *name,
const svn_string_t *value,
apr_pool_t *pool);
/* Some prototypes for functions used throughout. See each individual
function for information about what it does. */
/* Retrieving the base revision from the path/revision hash. */
static svn_revnum_t get_revision_from_hash (apr_hash_t *hash,
const char *path,
apr_pool_t *pool);
/* proplist_change_fn_t property changing functions. */
static svn_error_t *change_dir_prop (struct context *c,
void *object,
const char *name,
const svn_string_t *value,
apr_pool_t *pool);
static svn_error_t *change_file_prop (struct context *c,
void *object,
const char *name,
const svn_string_t *value,
apr_pool_t *pool);
/* Constructing deltas for properties of files and directories. */
static svn_error_t *delta_proplists (struct context *c,
const char *source_path,
const char *target_path,
proplist_change_fn_t *change_fn,
void *object,
apr_pool_t *pool);
/* Constructing deltas for file constents. */
static svn_error_t *send_text_delta (struct context *c,
void *file_baton,
svn_txdelta_stream_t *delta_stream,
apr_pool_t *pool);
static svn_error_t *delta_files (struct context *c,
void *file_baton,
const char *source_path,
const char *target_path,
apr_pool_t *pool);
/* Generic directory deltafication routines. */
static svn_error_t *delete (struct context *c,
void *dir_baton,
const char *target_entry,
apr_pool_t *pool);
static svn_error_t *add_file_or_dir (struct context *c,
void *dir_baton,
const char *source_parent,
const char *source_entry,
const char *target_parent,
const char *target_entry,
apr_pool_t *pool);
static svn_error_t *replace_file_or_dir (struct context *c,
void *dir_baton,
const char *source_parent,
const char *source_entry,
const char *target_parent,
const char *target_entry,
apr_pool_t *pool);
static svn_error_t *find_nearest_entry (svn_fs_dirent_t **s_entry,
int *distance,
struct context *c,
const char *source_parent,
const char *target_parent,
const svn_fs_dirent_t *t_entry,
apr_pool_t *pool);
static svn_error_t *delta_dirs (struct context *c,
void *dir_baton,
const char *source_path,
const char *target_path,
apr_pool_t *pool);
static svn_error_t *
not_a_dir_error (const char *role,
const char *path,
apr_pool_t *pool)
{
return svn_error_createf
(SVN_ERR_FS_NOT_DIRECTORY, 0, 0, pool,
"not_a_dir_error: invalid %s directory '%s'",
role, path ? path : "(null)");
}
/* Public interface to computing directory deltas. */
svn_error_t *
svn_repos_dir_delta (svn_fs_root_t *src_root,
const char *src_parent_dir,
const char *src_entry,
apr_hash_t *src_revs,
svn_fs_root_t *tgt_root,
const char *tgt_path,
const svn_delta_edit_fns_t *editor,
void *edit_baton,
svn_boolean_t text_deltas,
svn_boolean_t recurse,
svn_boolean_t use_copyfrom_args,
apr_pool_t *pool)
{
void *root_baton;
struct context c;
svn_stringbuf_t *tgt_parent_dir, *tgt_entry;
svn_stringbuf_t *src_fullpath;
svn_fs_id_t *src_id, *tgt_id;
svn_error_t *err;
int distance;
/* ### need to change svn_path_is_empty() */
svn_stringbuf_t *tempbuf;
/* SRC_PARENT_DIR must be valid. */
if (! src_parent_dir)
return not_a_dir_error ("source parent", src_parent_dir, pool);
/* TGT_PATH must be valid. */
if (! tgt_path)
return svn_error_create (SVN_ERR_FS_PATH_SYNTAX, 0, 0, pool,
"svn_repos_dir_delta: invalid target path");
tempbuf = svn_stringbuf_create (tgt_path, pool);
/* Aplit TGT_PATH into TGT_PARENT_DIR and TGT_ENTRY unless SRC_ENTRY
is NULL or TGT_PATH cannot be split. */
if ((! src_entry) || (svn_path_is_empty (tempbuf)))
{
tgt_parent_dir = svn_stringbuf_create (tgt_path, pool);
tgt_entry = NULL;
}
else
{
svn_path_split (tempbuf, &tgt_parent_dir, &tgt_entry, pool);
}
/* Make sure that parent dirs are really directories under both the
source and target roots. This also doubles as an existence
check. Obviously, an empty parent path is the root of the
repository, guaranteed to exist as a directory. */
svn_stringbuf_set (tempbuf, src_parent_dir);
if (! svn_path_is_empty (tempbuf))
{
int s_dir, t_dir;
SVN_ERR (svn_fs_is_dir (&s_dir, src_root, src_parent_dir, pool));
SVN_ERR (svn_fs_is_dir (&t_dir, tgt_root, tgt_parent_dir->data, pool));
if ((! s_dir) || (! t_dir))
return not_a_dir_error ("source parent", src_parent_dir, pool);
}
if (! svn_path_is_empty (tgt_parent_dir))
{
int s_dir, t_dir;
SVN_ERR (svn_fs_is_dir (&s_dir, src_root, tgt_parent_dir->data, pool));
SVN_ERR (svn_fs_is_dir (&t_dir, tgt_root, tgt_parent_dir->data, pool));
if ((! s_dir) || (! t_dir))
return not_a_dir_error ("target parent", tgt_parent_dir->data, pool);
}
/* Setup our pseudo-global structure here. We need these variables
throughout the deltafication process, so pass them around by
reference to all the helper functions. */
c.editor = editor;
c.source_root = src_root;
c.source_rev_diffs = src_revs;
c.target_root = tgt_root;
c.target_is_rev = svn_fs_is_revision_root (tgt_root);
c.recurse = recurse;
c.text_deltas = text_deltas;
#if SVN_REPOS_SUPPORT_COPY_FROM_ARGS
c.use_copyfrom_args = use_copyfrom_args;
#else
c.use_copyfrom_args = FALSE;
#endif
/* Set the global target revision if the target is a revision. */
if (c.target_is_rev)
SVN_ERR (editor->set_target_revision
(edit_baton, svn_fs_revision_root_revision (tgt_root)));
/* Call open_root to get our root_baton... */
SVN_ERR (editor->open_root
(edit_baton,
get_revision_from_hash (src_revs, src_parent_dir, pool),
&root_baton));
/* Construct the full path of the source and target update items. */
src_fullpath = svn_stringbuf_create (src_parent_dir, pool);
if (src_entry && *src_entry != '\0')
svn_path_add_component_nts (src_fullpath, src_entry);
/* Get the node ids for the source and target paths. */
err = svn_fs_node_id (&tgt_id, tgt_root, tgt_path, pool);
if (err)
{
if (err->apr_err == SVN_ERR_FS_NOT_FOUND)
{
/* Caller thinks that target still exists, but it doesn't.
So just delete the target and go home. */
svn_error_clear_all (err);
SVN_ERR (delete (&c, root_baton, src_entry, pool));
goto cleanup;
}
else
{
return err;
}
}
err = svn_fs_node_id (&src_id, src_root, src_fullpath->data, pool);
if (err)
{
if (err->apr_err == SVN_ERR_FS_NOT_FOUND)
{
/* The target has been deleted from our working copy. Add
back a new one. */
svn_error_clear_all (err);
SVN_ERR (add_file_or_dir (&c, root_baton,
NULL,
NULL,
tgt_parent_dir->data,
tgt_entry->data,
pool));
}
else
{
return err;
}
}
else if (src_entry && *src_entry != '\0')
{
/* Use the distance between the node ids to determine the best
way to update the requested entry. */
distance = svn_fs_id_distance (src_id, tgt_id);
if (distance == 0)
{
/* They're the same node! No-op (you gotta love those). */
}
else if (distance == -1)
{
/* The nodes are not related at all. Delete the one, and
add the other. */
SVN_ERR (delete (&c, root_baton, src_entry, pool));
SVN_ERR (add_file_or_dir (&c, root_baton,
NULL, NULL,
tgt_parent_dir->data,
tgt_entry->data,
pool));
}
else
{
/* The nodes are at least related. Just open the one
with the other. */
SVN_ERR (replace_file_or_dir (&c, root_baton,
src_parent_dir,
src_entry,
tgt_parent_dir->data,
tgt_entry->data,
pool));
}
}
else
{
/* There is no entry given, so update the whole parent directory. */
SVN_ERR (delta_dirs (&c, root_baton,
src_fullpath->data, tgt_path,
pool));
}
cleanup:
/* Make sure we close the root directory we opened above. */
SVN_ERR (editor->close_directory (root_baton));
/* Close the edit. */
SVN_ERR (editor->close_edit (edit_baton));
/* All's well that ends well. */
return SVN_NO_ERROR;
}
/* Retrieving the base revision from the path/revision hash. */
/* Look through a HASH (with paths as keys, and pointers to revision
numbers as values) for the revision associated with the given PATH.
Perform all necessary memory allocations in POOL. */
static svn_revnum_t
get_revision_from_hash (apr_hash_t *hash, const char *path,
apr_pool_t *pool)
{
void *val;
svn_stringbuf_t *path_copy;
svn_revnum_t revision = SVN_INVALID_REVNUM;
if (! hash)
return SVN_INVALID_REVNUM;
/* See if this path has a revision assigned in the hash. */
val = apr_hash_get (hash, path, APR_HASH_KEY_STRING);
if (val)
{
revision = *((svn_revnum_t *) val);
if (SVN_IS_VALID_REVNUM(revision))
return revision;
}
/* Make a copy of our path that we can hack on. */
path_copy = svn_stringbuf_create (path, pool);
/* If we haven't found a valid revision yet, and our copy of the
path isn't empty, hack the last component off the path and see if
*that* has a revision entry in our hash. */
while ((! SVN_IS_VALID_REVNUM(revision))
&& (! svn_path_is_empty (path_copy)))
{
svn_path_remove_component (path_copy);
val = apr_hash_get (hash, path_copy->data, APR_HASH_KEY_STRING);
if (val)
revision = *((svn_revnum_t *) val);
}
return revision;
}
/* proplist_change_fn_t property changing functions. */
/* Call the directory property-setting function of C->editor to set
the property NAME to given VALUE on the OBJECT passed to this
function. */
static svn_error_t *
change_dir_prop (struct context *c, void *object,
const char *name, const svn_string_t *value,
apr_pool_t *pool)
{
/* ### fix editor interface */
svn_stringbuf_t *namebuf = svn_stringbuf_create (name, pool);
svn_stringbuf_t *valbuf =
value ? svn_stringbuf_create_from_string (value, pool) : NULL;
return c->editor->change_dir_prop (object, namebuf, valbuf);
}
/* Call the file property-setting function of C->editor to set the
property NAME to given VALUE on the OBJECT passed to this
function. */
static svn_error_t *
change_file_prop (struct context *c, void *object,
const char *name, const svn_string_t *value,
apr_pool_t *pool)
{
/* ### fix editor interface */
svn_stringbuf_t *namebuf = svn_stringbuf_create (name, pool);
svn_stringbuf_t *valbuf =
value ? svn_stringbuf_create_from_string (value, pool) : NULL;
return c->editor->change_file_prop (object, namebuf, valbuf);
}
/* Constructing deltas for properties of files and directories. */
/* Generate the appropriate property editing calls to turn the
properties of SOURCE_PATH into those of TARGET_PATH. If
SOURCE_PATH is NULL, treat it as if it were a file with no
properties. Pass OBJECT on to the editor function wrapper
CHANGE_FN. */
static svn_error_t *
delta_proplists (struct context *c,
const char *source_path,
const char *target_path,
proplist_change_fn_t *change_fn,
void *object,
apr_pool_t *pool)
{
apr_hash_t *s_props = 0;
apr_hash_t *t_props = 0;
apr_hash_index_t *hi;
apr_pool_t *subpool;
/* Make a subpool for local allocations. */
subpool = svn_pool_create (pool);
if (source_path && target_path)
{
int changed;
/* Is this deltification worth our time? */
SVN_ERR (svn_fs_props_changed (&changed,
c->target_root,
target_path,
c->source_root,
source_path,
subpool));
if (! changed)
{
svn_pool_destroy (subpool);
return SVN_NO_ERROR;
}
}
/* Get the source file's properties */
if (source_path)
SVN_ERR (svn_fs_node_proplist
(&s_props, c->source_root, source_path,
subpool));
/* Get the target file's properties */
if (target_path)
SVN_ERR (svn_fs_node_proplist
(&t_props, c->target_root, target_path,
subpool));
for (hi = apr_hash_first (subpool, t_props); hi; hi = apr_hash_next (hi))
{
const svn_string_t *s_value;
const void *key;
void *val;
apr_ssize_t klen;
/* KEY is property name in target, VAL the value */
apr_hash_this (hi, &key, &klen, &val);
/* See if this property existed in the source. If so, and if
the values in source and target differ, open the value in
target with the one in source. */
if (s_props
&& ((s_value = apr_hash_get (s_props, key, klen)) != 0))
{
if (! svn_string_compare (s_value, val))
SVN_ERR (change_fn (c, object, key, val, subpool));
/* Remove the property from source list so we can track
which items have matches in the target list. */
apr_hash_set (s_props, key, klen, NULL);
}
else
{
/* This property didn't exist in the source, so this is just
an add. */
SVN_ERR (change_fn (c, object, key, val, subpool));
}
}
/* All the properties remaining in the source list are not present
in the target, and so must be deleted. */
if (s_props)
{
for (hi = apr_hash_first (subpool, s_props); hi; hi = apr_hash_next (hi))
{
const void *key;
/* KEY is property name in target, VAL the value */
apr_hash_this (hi, &key, NULL, NULL);
SVN_ERR (change_fn (c, object, key, NULL, subpool));
}
}
/* Destroy local subpool. */
svn_pool_destroy (subpool);
return SVN_NO_ERROR;
}
/* Constructing deltas for file constents. */
/* Change the contents of FILE_BATON in C->editor, according to the
text delta from DELTA_STREAM. */
static svn_error_t *
send_text_delta (struct context *c,
void *file_baton,
svn_txdelta_stream_t *delta_stream,
apr_pool_t *pool)
{
svn_txdelta_window_handler_t delta_handler;
void *delta_handler_baton;
/* Get a handler that will apply the delta to the file. */
SVN_ERR (c->editor->apply_textdelta
(file_baton, &delta_handler, &delta_handler_baton));
if (c->text_deltas)
{
/* Deliver the delta stream to the file. */
SVN_ERR (svn_txdelta_send_txstream (delta_stream,
delta_handler,
delta_handler_baton,
pool));
}
else
{
/* The caller doesn't want text delta data. Just send a single
NULL window. */
SVN_ERR (delta_handler (NULL, delta_handler_baton));
}
return SVN_NO_ERROR;
}
/* Make the appropriate edits on FILE_BATON to change its contents and
properties from those in SOURCE_PATH to those in TARGET_PATH. */
static svn_error_t *
delta_files (struct context *c, void *file_baton,
const char *source_path,
const char *target_path,
apr_pool_t *pool)
{
svn_txdelta_stream_t *delta_stream;
apr_pool_t *subpool;
/* Make a subpool for local allocations. */
subpool = svn_pool_create (pool);
/* Compare the files' property lists. */
SVN_ERR (delta_proplists (c, source_path, target_path,
change_file_prop, file_baton, subpool));
/* ### this is too much work if !c->text_deltas. there is no reason to
### ask the FS for a delta stream if we aren't going to use it. */
if (source_path)
{
int changed;
/* Is this deltification worth our time? */
SVN_ERR (svn_fs_contents_changed (&changed,
c->target_root,
target_path,
c->source_root,
source_path,
subpool));
if (! changed)
{
svn_pool_destroy (subpool);
return SVN_NO_ERROR;
}
/* Get a delta stream turning SOURCE_PATH's contents into
TARGET_PATH's contents. */
SVN_ERR (svn_fs_get_file_delta_stream
(&delta_stream,
c->source_root, source_path,
c->target_root, target_path,
subpool));
}
else
{
/* Get a delta stream turning an empty file into one having
TARGET_PATH's contents. */
SVN_ERR (svn_fs_get_file_delta_stream
(&delta_stream, 0, 0,
c->target_root, target_path, subpool));
}
SVN_ERR (send_text_delta (c, file_baton, delta_stream, subpool));
/* Cleanup. */
svn_pool_destroy (subpool);
return 0;
}
/* Generic directory deltafication routines. */
/* Emit a delta to delete the entry named TARGET_ENTRY from DIR_BATON. */
static svn_error_t *
delete (struct context *c,
void *dir_baton,
const char *target_entry,
apr_pool_t *pool)
{
/* ### change the editor prototypes... */
svn_stringbuf_t *entrybuf = svn_stringbuf_create (target_entry, pool);
return c->editor->delete_entry (entrybuf, SVN_INVALID_REVNUM, dir_baton);
}
/* Emit a delta to create the entry named TARGET_ENTRY in the
directory TARGET_PARENT. If SOURCE_PARENT and SOURCE_ENTRY are
valid, use them to determine the copyfrom args in the editor's add
calls. Pass DIR_BATON through to editor functions that require it. */
static svn_error_t *
add_file_or_dir (struct context *c, void *dir_baton,
const char *source_parent,
const char *source_entry,
const char *target_parent,
const char *target_entry,
apr_pool_t *pool)
{
int is_dir;
svn_stringbuf_t *target_full_path;
svn_stringbuf_t *source_full_path;
svn_revnum_t base_revision = SVN_INVALID_REVNUM;
/* ### change the delta interface */
svn_stringbuf_t *namebuf;
if (!target_parent || !target_entry)
abort();
/* Get the target's full path */
target_full_path = svn_stringbuf_create (target_parent, pool);
svn_path_add_component_nts (target_full_path, target_entry);
/* Is the target a file or a directory? */
SVN_ERR (svn_fs_is_dir (&is_dir, c->target_root,
target_full_path->data, pool));
if (source_parent && source_entry)
{
/* Get the source's full path */
source_full_path = svn_stringbuf_create (source_parent, pool);
svn_path_add_component_nts (source_full_path, source_entry);
/* Get the base revision for the entry from the hash. */
base_revision = get_revision_from_hash (c->source_rev_diffs,
source_full_path->data,
pool);
}
else
source_full_path = NULL;
namebuf = svn_stringbuf_create (target_entry, pool);
if (is_dir)
{
void *subdir_baton;
SVN_ERR (c->editor->add_directory
(namebuf, dir_baton,
source_full_path, base_revision, &subdir_baton));
SVN_ERR (delta_dirs (c, subdir_baton,
source_full_path ? source_full_path->data : NULL,
target_full_path->data, pool));
SVN_ERR (c->editor->close_directory (subdir_baton));
}
else
{
void *file_baton;
SVN_ERR (c->editor->add_file
(namebuf, dir_baton,
source_full_path, base_revision, &file_baton));
SVN_ERR (delta_files (c, file_baton,
source_full_path ? source_full_path->data : NULL,
target_full_path->data, pool));
SVN_ERR (c->editor->close_file (file_baton));
}
return SVN_NO_ERROR;
}
/* Modify the directory TARGET_PARENT by replacing its entry named
TARGET_ENTRY with the SOURCE_ENTRY found in SOURCE_PARENT. Pass
DIR_BATON through to editor functions that require it. */
static svn_error_t *
replace_file_or_dir (struct context *c,
void *dir_baton,
const char *target_parent,
const char *target_entry,
const char *source_parent,
const char *source_entry,
apr_pool_t *pool)
{
int is_dir;
svn_stringbuf_t *source_full_path = 0;
svn_stringbuf_t *target_full_path = 0;
svn_revnum_t base_revision = SVN_INVALID_REVNUM;
/* ### change the delta interface */
svn_stringbuf_t *namebuf;
if (!target_parent || !target_entry)
abort();
if (!source_parent || !source_entry)
abort();
/* Get the target's full path */
target_full_path = svn_stringbuf_create (target_parent, pool);
svn_path_add_component_nts (target_full_path, target_entry);
/* Is the target a file or a directory? */
SVN_ERR (svn_fs_is_dir (&is_dir, c->target_root,
target_full_path->data, pool));
/* Get the source's full path */
source_full_path = svn_stringbuf_create (source_parent, pool);
svn_path_add_component_nts (source_full_path, source_entry);
/* Get the base revision for the entry from the hash. */
base_revision = get_revision_from_hash (c->source_rev_diffs,
source_full_path->data,
pool);
namebuf = svn_stringbuf_create (target_entry, pool);
if (is_dir)
{
void *subdir_baton;
SVN_ERR (c->editor->open_directory
(namebuf, dir_baton, base_revision, &subdir_baton));
SVN_ERR (delta_dirs (c, subdir_baton,
source_full_path->data, target_full_path->data,
pool));
SVN_ERR (c->editor->close_directory (subdir_baton));
}
else
{
void *file_baton;
SVN_ERR (c->editor->open_file
(namebuf, dir_baton, base_revision, &file_baton));
SVN_ERR (delta_files (c, file_baton,
source_full_path->data, target_full_path->data,
pool));
SVN_ERR (c->editor->close_file (file_baton));
}
return SVN_NO_ERROR;
}
/* Do a `replace' edit in DIR_BATON, replacing the entry named
T_ENTRY->name in the directory TARGET_PARENT with the closest
related node available in SOURCE_PARENT. If no relative can be
found, simply delete in the entry from TARGET_PARENT, and then
re-add the new one. */
static svn_error_t *
find_nearest_entry (svn_fs_dirent_t **s_entry,
int *distance,
struct context *c,
const char *source_parent,
const char *target_parent,
const svn_fs_dirent_t *t_entry,
apr_pool_t *pool)
{
apr_hash_t *s_entries;
apr_hash_index_t *hi;
int best_distance = -1;
svn_fs_dirent_t *best_entry = NULL;
svn_stringbuf_t *source_full_path;
svn_stringbuf_t *target_full_path;
int t_is_dir;
apr_pool_t *subpool;
/* Make a subpool for local allocations */
subpool = svn_pool_create (pool);
/* If there's no source to search, return a failed ancestor hunt. */
source_full_path = svn_stringbuf_create ("", subpool);
if (! source_parent)
{
*s_entry = 0;
*distance = -1;
svn_pool_destroy (subpool);
return SVN_NO_ERROR;
}
/* Get the list of entries in source. Note that we are using the
pool that was passed in instead of the subpool...we're returning
a reference to an item in this hash, and it would suck to blow it
away before our caller gets a chance to see it. */
SVN_ERR (svn_fs_dir_entries (&s_entries, c->source_root,
source_parent, pool));
target_full_path = svn_stringbuf_create (target_parent, subpool);
svn_path_add_component_nts (target_full_path, t_entry->name);
/* Is the target a file or a directory? */
SVN_ERR (svn_fs_is_dir (&t_is_dir, c->target_root,
target_full_path->data, subpool));
/* Find the closest relative to TARGET_ENTRY in SOURCE.
In principle, a replace operation can choose the ancestor from
anywhere in the delta's whole source tree. In this
implementation, we only search SOURCE for possible ancestors.
This will need to improve, so we can find the best ancestor, no
matter where it's hidden away in the source tree. */
for (hi = apr_hash_first (subpool, s_entries); hi; hi = apr_hash_next (hi))
{
const void *key;
void *val;
apr_ssize_t klen;
int this_distance;
svn_fs_dirent_t *this_entry;
int s_is_dir;
/* KEY will be the entry name in source, VAL the dirent */
apr_hash_this (hi, &key, &klen, &val);
this_entry = val;
svn_stringbuf_set (source_full_path, source_parent);
svn_path_add_component_nts (source_full_path, this_entry->name);
/* Is this entry a file or a directory? */
SVN_ERR (svn_fs_is_dir (&s_is_dir, c->source_root,
source_full_path->data, subpool));
/* If we aren't looking at the same node type, skip this
entry. */
if ((s_is_dir && (! t_is_dir)) || ((! s_is_dir) && t_is_dir))
continue;
/* Find the distance between the target entry and this source
entry. This returns -1 if they're completely unrelated.
Here we're using ID distance as an approximation for delta
size. */
this_distance = svn_fs_id_distance (t_entry->id, this_entry->id);
/* If these nodes are completely unrelated, move along. */
if (this_distance == -1)
continue;
/* If this is the first related node we've found, or just a
closer node than previously discovered, update our
best_distance tracker. */
if ((best_distance == -1) || (this_distance < best_distance))
{
best_distance = this_distance;
best_entry = this_entry;
}
}
/* If our best distance is still reflects no ancestry, return a NULL
entry to the caller, else return the best entry we found. */
*s_entry = ((*distance = best_distance) == -1) ? 0 : best_entry;
/* Destroy local allocation subpool. */
svn_pool_destroy (subpool);
return SVN_NO_ERROR;
}
/* Emit deltas to turn SOURCE_PATH into TARGET_PATH. Assume that
DIR_BATON represents the directory we're constructing to the editor
in the context C. */
static svn_error_t *
delta_dirs (struct context *c,
void *dir_baton,
const char *source_path,
const char *target_path,
apr_pool_t *pool)
{
apr_hash_t *s_entries = 0, *t_entries = 0;
apr_hash_index_t *hi;
apr_pool_t *subpool;
/* Compare the property lists. */
SVN_ERR (delta_proplists (c, source_path, target_path,
change_dir_prop, dir_baton, pool));
/* Get the list of entries in each of source and target. */
if (target_path)
{
SVN_ERR (svn_fs_dir_entries (&t_entries, c->target_root,
target_path, pool));
}
else
{
/* Return a viscious error. */
abort();
}
if (source_path)
{
SVN_ERR (svn_fs_dir_entries (&s_entries, c->source_root,
source_path, pool));
}
/* Make a subpool for local allocations. */
subpool = svn_pool_create (pool);
/* Loop over the hash of entries in the target, searching for its
partner in the source. If we find the matching partner entry,
use editor calls to replace the one in target with a new version
if necessary, then remove that entry from the source entries
hash. If we can't find a related node in the source, we use
editor calls to add the entry as a new item in the target.
Having handled all the entries that exist in target, any entries
still remaining the source entries hash represent entries that no
longer exist in target. Use editor calls to delete those entries
from the target tree. */
for (hi = apr_hash_first (pool, t_entries); hi; hi = apr_hash_next (hi))
{
const svn_fs_dirent_t *s_entry, *t_entry;
const void *key;
void *val;
apr_ssize_t klen;
svn_stringbuf_t *target_fullpath =
svn_stringbuf_create (target_path, subpool);
/* KEY is the entry name in target, VAL the dirent */
apr_hash_this (hi, &key, &klen, &val);
t_entry = val;
svn_path_add_component_nts (target_fullpath, t_entry->name);
/* Can we find something with the same name in the source
entries hash? */
if (s_entries
&& ((s_entry = apr_hash_get (s_entries, key, klen)) != 0))
{
int distance;
int is_dir;
SVN_ERR (svn_fs_is_dir (&is_dir,
c->target_root, target_fullpath->data,
subpool));
if (c->recurse || !is_dir)
{
/* Check the distance between the ids.
0 means they are the same id, and this is a noop.
-1 means they are unrelated, so try to find an ancestor
elsewhere in the directory. Theoretically, using an
ancestor as a baseline will reduce the size of the deltas.
Any other positive value means the nodes are related
through ancestry, so go ahead and do the replace
directly. */
distance = svn_fs_id_distance (s_entry->id, t_entry->id);
if (distance == 0)
{
/* no-op */
}
else if (distance == -1)
{
svn_fs_dirent_t *best_entry;
int best_distance = distance;
/* If we are allowed to use copyfrom args, try to
find a related entry that we might use as an
optimization over simply deleting the old thing
and add the new one. */
if (c->use_copyfrom_args)
{
SVN_ERR (find_nearest_entry (&best_entry, &best_distance,
c, source_path,
target_path, t_entry,
subpool));
}
/* If we did't find a related node (or simply didn't
look), just delete this entry and create a new
one from scratch. Else, replace this entry with
the related node we found (sending any changes
that might exist between the two). */
if (best_distance == -1)
{
SVN_ERR (delete (c, dir_baton, t_entry->name, subpool));
SVN_ERR (add_file_or_dir
(c, dir_baton, NULL, NULL,
target_path, t_entry->name, subpool));
}
else
{
SVN_ERR (replace_file_or_dir
(c, dir_baton,
source_path,
best_entry->name,
target_path,
t_entry->name,
subpool));
}
}
else
{
SVN_ERR (replace_file_or_dir
(c, dir_baton,
source_path,
s_entry->name,
target_path,
t_entry->name,
subpool));
}
}
/* Remove the entry from the source_hash. */
apr_hash_set (s_entries, key, APR_HASH_KEY_STRING, NULL);
}
else
{
int is_dir;
SVN_ERR (svn_fs_is_dir (&is_dir, c->target_root,
target_fullpath->data, subpool));
if (c->recurse || !is_dir)
{
/* We didn't find an entry with this name in the source
entries hash. This must be something new that needs to
be added. */
svn_fs_dirent_t *best_entry;
int best_distance = -1;
/* If we're allowed to do so, let's first check to see
if we can find an ancestor from which to copy this
new entry. */
if (c->use_copyfrom_args)
{
SVN_ERR (find_nearest_entry (&best_entry, &best_distance,
c, source_path,
target_path, t_entry, subpool));
}
/* Add (with history if we found an ancestor) this new
entry. */
if (best_distance == -1)
SVN_ERR (add_file_or_dir
(c, dir_baton, NULL, NULL,
target_path, t_entry->name,
subpool));
else
SVN_ERR (add_file_or_dir
(c, dir_baton,
source_path,
best_entry->name,
target_path,
t_entry->name,
subpool));
}
}
/* Clear out our subpool for the next iteration... */
svn_pool_clear (subpool);
}
/* All that is left in the source entries hash are things that need
to be deleted. Delete them. */
if (s_entries)
{
for (hi = apr_hash_first (pool, s_entries); hi; hi = apr_hash_next (hi))
{
const svn_fs_dirent_t *s_entry;
const void *key;
void *val;
apr_ssize_t klen;
svn_stringbuf_t *source_fullpath = svn_stringbuf_create (source_path,
subpool);
int is_dir;
/* KEY is the entry name in source, VAL the dirent */
apr_hash_this (hi, &key, &klen, &val);
s_entry = val;
svn_path_add_component_nts (source_fullpath, s_entry->name);
/* Do we actually want to delete the dir if we're non-recursive? */
SVN_ERR (svn_fs_is_dir (&is_dir,
c->source_root,
source_fullpath->data,
subpool));
if (c->recurse || !is_dir)
{
SVN_ERR (delete (c, dir_baton, s_entry->name, subpool));
}
}
}
/* Destroy local allocation subpool. */
svn_pool_destroy (subpool);
return SVN_NO_ERROR;
}
/*
* local variables:
* eval: (load-file "../../tools/dev/svn-dev.el")
* end:
*/