blob: 9e068c37fa830729768ec91b2d267eb8f31d5e16 [file] [log] [blame]
/*
* checkout.c : read a repository tree and drive a checkout editor.
*
* ====================================================================
* 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 "ra_local.h"
#include <assert.h>
#include "svn_pools.h"
/* Helper to read data out of a file at ROOT:PATH and push it to
EDITOR via FILE_BATON.
ben sez: whoa. The elegance and level of abstraction going on here
is amazing. What an amazing design. It's like a set of opaque
legos that all perfectly fit together. :) */
static svn_error_t *
send_file_contents (svn_fs_root_t *root,
svn_stringbuf_t *path,
void *file_baton,
const svn_delta_edit_fns_t *editor,
apr_pool_t *pool)
{
svn_stream_t *contents;
svn_txdelta_window_handler_t handler;
void *handler_baton;
/* Get a subpool for local allocations. */
apr_pool_t *subpool = svn_pool_create (pool);
/* Get a readable stream of the file's contents. */
SVN_ERR (svn_fs_file_contents (&contents, root, path->data, subpool));
/* Get an editor func that wants to consume the delta stream. */
SVN_ERR (editor->apply_textdelta (file_baton, &handler, &handler_baton));
/* Send the file's contents to the delta-window handler. */
SVN_ERR (svn_txdelta_send_stream (contents,
handler,
handler_baton,
subpool));
/* Cleanup our subpool */
svn_pool_destroy (subpool);
return SVN_NO_ERROR;
}
/* Helper to push any properties attached to ROOT:PATH at EDITOR,
using OBJECT_BATON. IS_DIR indicates which editor func to call. */
static svn_error_t *
set_any_props (svn_fs_root_t *root,
const svn_string_t *path,
void *object_baton,
const svn_delta_edit_fns_t *editor,
int is_dir,
apr_pool_t *pool)
{
apr_hash_index_t *hi;
svn_revnum_t committed_rev;
svn_string_t *last_author, *committed_date;
char *revision_str = NULL;
apr_hash_t *props = NULL;
/* Get all user properties attached to PATH. */
SVN_ERR (svn_fs_node_proplist (&props, root, path->data, pool));
/* Query the fs for three 'entry' props: specifically, the
last-changed-rev of the file or dir ("created rev"), and the
associated date & author of said revision. Add these three props
to the proplist hash, as a means of getting them into the working
copy's 'entries' file. The working copy Update Editor will
recognize them. */
if ((props == NULL) || (apr_hash_count (props) == 0))
props = apr_hash_make (pool);
SVN_ERR (svn_repos_get_committed_info (&committed_rev,
&committed_date,
&last_author,
root, path, pool));
revision_str = apr_psprintf (pool, "%ld", committed_rev);
apr_hash_set (props, SVN_PROP_ENTRY_COMMITTED_REV,
strlen(SVN_PROP_ENTRY_COMMITTED_REV),
svn_stringbuf_create (revision_str, pool));
apr_hash_set (props, SVN_PROP_ENTRY_COMMITTED_DATE,
strlen(SVN_PROP_ENTRY_COMMITTED_DATE), committed_date);
apr_hash_set (props, SVN_PROP_ENTRY_LAST_AUTHOR,
strlen(SVN_PROP_ENTRY_LAST_AUTHOR), last_author);
/* Loop over properties, send them through the editor. */
for (hi = apr_hash_first (pool, props); hi; hi = apr_hash_next (hi))
{
const void *key;
void *val;
apr_ssize_t klen;
svn_stringbuf_t *name, *value;
apr_hash_this (hi, &key, &klen, &val);
name = svn_stringbuf_ncreate (key, klen, pool);
value = svn_stringbuf_create_from_string ((svn_string_t *) val, pool);
if (is_dir)
SVN_ERR (editor->change_dir_prop (object_baton, name, value));
else
SVN_ERR (editor->change_file_prop (object_baton, name, value));
}
return SVN_NO_ERROR;
}
/* A depth-first recursive walk of DIR_PATH under a fs ROOT that adds
dirs and files via EDITOR and DIR_BATON. URL represents the
current repos location, and is stored in DIR_BATON's working copy.
Note: we're conspicuously creating a subpool in POOL and freeing it
at each level of subdir recursion; this is a safety measure that
protects us when checking out outrageously large or deep trees.
Also, we have a per-iteration subpool which is clear after being
used for each directory entry.
Note: we aren't driving EDITOR with "postfix" text deltas; that
style only exists to recognize skeletal conflicts as early as
possible (during a commit). There are no conflicts in a checkout,
however. :) */
static svn_error_t *
walk_tree (svn_fs_root_t *root,
const svn_string_t *dir_path,
void *dir_baton,
const svn_delta_edit_fns_t *editor,
void *edit_baton,
svn_stringbuf_t *URL,
svn_boolean_t recurse,
apr_pool_t *pool)
{
apr_hash_t *dirents;
apr_hash_index_t *hi;
apr_pool_t *subpool = svn_pool_create (pool);
apr_pool_t *iter_pool = svn_pool_create (subpool);
SVN_ERR (svn_fs_dir_entries (&dirents, root, dir_path->data, subpool));
/* Loop over this directory's dirents: */
for (hi = apr_hash_first (subpool, dirents); hi; hi = apr_hash_next (hi))
{
int is_dir, is_file;
const void *key;
void *val;
apr_ssize_t klen;
svn_fs_dirent_t *dirent;
svn_stringbuf_t *dirent_name;
svn_stringbuf_t *URL_path = svn_stringbuf_dup (URL, iter_pool);
svn_stringbuf_t *dirent_path;
svn_string_t dirent_str;
dirent_path = svn_stringbuf_create_from_string (dir_path, iter_pool);
apr_hash_this (hi, &key, &klen, &val);
dirent = (svn_fs_dirent_t *) val;
dirent_name = svn_stringbuf_create (dirent->name, iter_pool);
svn_path_add_component (dirent_path, dirent_name);
svn_path_add_component (URL_path, dirent_name);
/* What is dirent? */
SVN_ERR (svn_fs_is_dir (&is_dir, root, dirent_path->data, iter_pool));
SVN_ERR (svn_fs_is_file (&is_file, root, dirent_path->data, iter_pool));
dirent_str.data = dirent_path->data;
dirent_str.len = dirent_path->len;
if (is_dir && recurse)
{
void *new_dir_baton;
/* We pass 2 invalid ancestry args, which allows the editor
to infer them via inheritance. We do *not* pass real
args, since we're not referencing any existing working
copy paths. We don't want the editor to "copy" anything. */
SVN_ERR (editor->add_directory (dirent_name, dir_baton,
NULL,
SVN_INVALID_REVNUM,
&new_dir_baton));
SVN_ERR (set_any_props (root, &dirent_str, new_dir_baton,
editor, 1, iter_pool));
/* Recurse */
SVN_ERR (walk_tree (root, &dirent_str, new_dir_baton, editor,
edit_baton, URL_path, recurse, iter_pool));
}
else if (is_file)
{
void *file_baton;
SVN_ERR (editor->add_file (dirent_name, dir_baton,
URL_path, SVN_INVALID_REVNUM,
&file_baton));
SVN_ERR (set_any_props (root, &dirent_str, file_baton,
editor, 0, iter_pool));
SVN_ERR (send_file_contents (root, dirent_path, file_baton,
editor, iter_pool));
SVN_ERR (editor->close_file (file_baton));
}
else
{
/* It's not a file or dir. What the heck? Instead of
returning an error, let's just ignore the thing. */
}
/* Clear out our per-iteration pool. */
svn_pool_clear (iter_pool);
}
/* Close the dir and remove the subpool we used at this level. */
SVN_ERR (editor->close_directory (dir_baton));
/* Destory our subpool (which also destroys its child, the
per-iteration pool. */
svn_pool_destroy (subpool);
return SVN_NO_ERROR;
}
/* The main editor driver. Short and elegant! */
svn_error_t *
svn_ra_local__checkout (svn_fs_t *fs,
svn_revnum_t revnum,
svn_boolean_t recurse,
svn_stringbuf_t *URL,
const svn_string_t *fs_path,
const svn_delta_edit_fns_t *editor,
void *edit_baton,
apr_pool_t *pool)
{
svn_fs_root_t *root;
void *root_dir_baton;
SVN_ERR (svn_fs_revision_root (&root, fs, revnum, pool));
SVN_ERR (editor->set_target_revision (edit_baton, revnum));
SVN_ERR (editor->open_root (edit_baton, SVN_INVALID_REVNUM,
&root_dir_baton));
SVN_ERR (set_any_props (root, fs_path, root_dir_baton,
editor, 1, pool));
SVN_ERR (walk_tree (root, fs_path, root_dir_baton,
editor, edit_baton, URL, recurse, pool));
SVN_ERR (editor->close_edit (edit_baton));
return SVN_NO_ERROR;
}
/* ----------------------------------------------------------------
* local variables:
* eval: (load-file "../../tools/dev/svn-dev.el")
* end: */