blob: 66960c60b608729b41d0487978e6f2fcd34d512f [file] [log] [blame]
/*
* entries.c : manipulating the administrative `entries' file.
*
* ====================================================================
* 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 <apr_strings.h>
#include <assert.h>
#include "svn_xml.h"
#include "svn_error.h"
#include "svn_types.h"
#include "svn_time.h"
#include "svn_pools.h"
#include "wc.h"
#include "adm_files.h"
#include "adm_ops.h"
#include "entries.h"
/** Overview **/
/* The administrative `entries' file tracks information about files
and subdirs within a particular directory.
See the section on the `entries' file in libsvn_wc/README, for
concrete information about the XML format.
*/
/*--------------------------------------------------------------- */
/*** XML Attribute names and values ***/
/* Attribute values for 'schedule' */
#define SVN_WC__ENTRY_VALUE_ADD "add"
#define SVN_WC__ENTRY_VALUE_DELETE "delete"
#define SVN_WC__ENTRY_VALUE_REPLACE "replace"
/*** Initialization of the entries file. ***/
svn_error_t *
svn_wc__entries_init (svn_stringbuf_t *path,
svn_stringbuf_t *url,
apr_pool_t *pool)
{
apr_status_t apr_err;
apr_file_t *f = NULL;
svn_stringbuf_t *accum = NULL;
char *initial_revstr = apr_psprintf (pool, "%d", 0);
/* Create the entries file, which must not exist prior to this. */
SVN_ERR (svn_wc__open_adm_file (&f, path, SVN_WC__ADM_ENTRIES,
(APR_WRITE | APR_CREATE | APR_EXCL), pool));
/* Make a the XML standard header, to satisfy bureacracy. */
svn_xml_make_header (&accum, pool);
/* Open the file's top-level form. */
svn_xml_make_open_tag (&accum,
pool,
svn_xml_normal,
SVN_WC__ENTRIES_TOPLEVEL,
"xmlns",
svn_stringbuf_create (SVN_XML_NAMESPACE, pool),
NULL);
/* Add an entry for the dir itself -- name is absent, only the
revision and default ancestry are present as xml attributes. */
svn_xml_make_open_tag
(&accum,
pool,
svn_xml_self_closing,
SVN_WC__ENTRIES_ENTRY,
SVN_WC__ENTRY_ATTR_KIND,
svn_stringbuf_create (SVN_WC__ENTRIES_ATTR_DIR_STR, pool),
SVN_WC__ENTRY_ATTR_REVISION,
svn_stringbuf_create (initial_revstr, pool),
SVN_WC__ENTRY_ATTR_URL,
url,
NULL);
/* Close the top-level form. */
svn_xml_make_close_tag (&accum,
pool,
SVN_WC__ENTRIES_TOPLEVEL);
apr_err = apr_file_write_full (f, accum->data, accum->len, NULL);
if (apr_err)
{
apr_file_close (f);
return svn_error_createf (apr_err, 0, NULL, pool,
"svn_wc__entries_init: "
"error writing %s's entries file",
path->data);
}
/* Now we have a `entries' file with exactly one entry, an entry
for this dir. Close the file and sync it up. */
SVN_ERR (svn_wc__close_adm_file (f, path, SVN_WC__ADM_ENTRIES, 1, pool));
return SVN_NO_ERROR;
}
/*--------------------------------------------------------------- */
/*** reading and writing the entries file ***/
struct entries_accumulator
{
/* Keys are entry names, vals are (struct svn_wc_entry_t *)'s. */
apr_hash_t *entries;
/* The parser that's parsing it, for signal_expat_bailout(). */
svn_xml_parser_t *parser;
/* Don't leave home without one. */
apr_pool_t *pool;
};
static svn_wc_entry_t *
alloc_entry (apr_pool_t *pool)
{
svn_wc_entry_t *entry = apr_pcalloc (pool, sizeof (*entry));
entry->revision = SVN_INVALID_REVNUM;
entry->copyfrom_rev = SVN_INVALID_REVNUM;
entry->cmt_rev = SVN_INVALID_REVNUM;
entry->kind = svn_node_none;
return entry;
}
svn_error_t *
svn_wc__atts_to_entry (svn_wc_entry_t **new_entry,
apr_uint32_t *modify_flags,
apr_hash_t *atts,
apr_pool_t *pool)
{
svn_wc_entry_t *entry = alloc_entry (pool);
svn_stringbuf_t *name;
*modify_flags = 0;
/* Find the name and set up the entry under that name. */
name = apr_hash_get (atts, SVN_WC__ENTRY_ATTR_NAME, APR_HASH_KEY_STRING);
entry->name = name ? name
: svn_stringbuf_create (SVN_WC_ENTRY_THIS_DIR, pool);
/* Attempt to set revision (resolve_to_defaults may do it later, too) */
{
svn_stringbuf_t *revision_str
= apr_hash_get (atts, SVN_WC__ENTRY_ATTR_REVISION, APR_HASH_KEY_STRING);
if (revision_str)
{
entry->revision = SVN_STR_TO_REV (revision_str->data);
*modify_flags |= SVN_WC__ENTRY_MODIFY_REVISION;
}
else
entry->revision = SVN_INVALID_REVNUM;
}
/* Attempt to set up url path (again, see resolve_to_defaults). */
{
entry->url
= apr_hash_get (atts, SVN_WC__ENTRY_ATTR_URL, APR_HASH_KEY_STRING);
if (entry->url)
*modify_flags |= SVN_WC__ENTRY_MODIFY_URL;
}
/* Set up kind. */
{
svn_stringbuf_t *kindstr
= apr_hash_get (atts, SVN_WC__ENTRY_ATTR_KIND, APR_HASH_KEY_STRING);
entry->kind = svn_node_none;
if (kindstr)
{
if (! strcmp (kindstr->data, SVN_WC__ENTRIES_ATTR_FILE_STR))
entry->kind = svn_node_file;
else if (! strcmp (kindstr->data, SVN_WC__ENTRIES_ATTR_DIR_STR))
entry->kind = svn_node_dir;
else
return svn_error_createf
(SVN_ERR_UNKNOWN_NODE_KIND, 0, NULL, pool,
"Entry '%s' has invalid node kind",
(name ? name->data : SVN_WC_ENTRY_THIS_DIR));
*modify_flags |= SVN_WC__ENTRY_MODIFY_KIND;
}
}
/* Look for a schedule attribute on this entry. */
{
svn_stringbuf_t *schedulestr
= apr_hash_get (atts, SVN_WC__ENTRY_ATTR_SCHEDULE, APR_HASH_KEY_STRING);
entry->schedule = svn_wc_schedule_normal;
if (schedulestr)
{
if (! strcmp (schedulestr->data, SVN_WC__ENTRY_VALUE_ADD))
entry->schedule = svn_wc_schedule_add;
else if (! strcmp (schedulestr->data, SVN_WC__ENTRY_VALUE_DELETE))
entry->schedule = svn_wc_schedule_delete;
else if (! strcmp (schedulestr->data, SVN_WC__ENTRY_VALUE_REPLACE))
entry->schedule = svn_wc_schedule_replace;
else if (! strcmp (schedulestr->data, ""))
entry->schedule = svn_wc_schedule_normal;
else
return svn_error_createf
(SVN_ERR_ENTRY_ATTRIBUTE_INVALID, 0, NULL, pool,
"Entry '%s' has invalid '%s' value",
(name ? name->data : SVN_WC_ENTRY_THIS_DIR),
SVN_WC__ENTRY_ATTR_SCHEDULE);
*modify_flags |= SVN_WC__ENTRY_MODIFY_SCHEDULE;
}
}
/* Is this entry in a state of mental torment (conflict)? */
{
if ((entry->prejfile
= apr_hash_get (atts, SVN_WC__ENTRY_ATTR_PREJFILE,
APR_HASH_KEY_STRING)))
*modify_flags |= SVN_WC__ENTRY_MODIFY_PREJFILE;
if ((entry->conflict_old
= apr_hash_get (atts, SVN_WC__ENTRY_ATTR_CONFLICT_OLD,
APR_HASH_KEY_STRING)))
*modify_flags |= SVN_WC__ENTRY_MODIFY_CONFLICT_OLD;
if ((entry->conflict_new
= apr_hash_get (atts, SVN_WC__ENTRY_ATTR_CONFLICT_NEW,
APR_HASH_KEY_STRING)))
*modify_flags |= SVN_WC__ENTRY_MODIFY_CONFLICT_NEW;
if ((entry->conflict_wrk
= apr_hash_get (atts, SVN_WC__ENTRY_ATTR_CONFLICT_WRK,
APR_HASH_KEY_STRING)))
*modify_flags |= SVN_WC__ENTRY_MODIFY_CONFLICT_WRK;
}
/* Is this entry copied? */
{
svn_stringbuf_t *copiedstr, *revstr;
copiedstr = apr_hash_get (atts, SVN_WC__ENTRY_ATTR_COPIED,
APR_HASH_KEY_STRING);
entry->copied = FALSE;
if (copiedstr)
{
if (! strcmp (copiedstr->data, "true"))
entry->copied = TRUE;
else if (! strcmp (copiedstr->data, "false"))
entry->copied = FALSE;
else if (! strcmp (copiedstr->data, ""))
entry->copied = FALSE;
else
return svn_error_createf
(SVN_ERR_ENTRY_ATTRIBUTE_INVALID, 0, NULL, pool,
"Entry '%s' has invalid '%s' value",
(name ? name->data : SVN_WC_ENTRY_THIS_DIR),
SVN_WC__ENTRY_ATTR_COPIED);
*modify_flags |= SVN_WC__ENTRY_MODIFY_COPIED;
}
entry->copyfrom_url = apr_hash_get (atts, SVN_WC__ENTRY_ATTR_COPYFROM_URL,
APR_HASH_KEY_STRING);
revstr = apr_hash_get (atts, SVN_WC__ENTRY_ATTR_COPYFROM_REV,
APR_HASH_KEY_STRING);
if (revstr)
entry->copyfrom_rev = SVN_STR_TO_REV (revstr->data);
}
/* Attempt to set up timestamps. */
{
svn_stringbuf_t *text_timestr, *prop_timestr;
text_timestr = apr_hash_get (atts, SVN_WC__ENTRY_ATTR_TEXT_TIME,
APR_HASH_KEY_STRING);
if (text_timestr)
{
if (! strcmp (text_timestr->data, SVN_WC_TIMESTAMP_WC))
{
/* Special case: a magic string that means 'get this value
from the working copy' -- we ignore it here, trusting
that the caller of this function know what to do about
it. */
}
else
entry->text_time = svn_time_from_nts (text_timestr->data);
*modify_flags |= SVN_WC__ENTRY_MODIFY_TEXT_TIME;
}
prop_timestr = apr_hash_get (atts, SVN_WC__ENTRY_ATTR_PROP_TIME,
APR_HASH_KEY_STRING);
if (prop_timestr)
{
if (! strcmp (prop_timestr->data, SVN_WC_TIMESTAMP_WC))
{
/* Special case: a magic string that means 'get this value
from the working copy' -- we ignore it here, trusting
that the caller of this function know what to do about
it. */
}
else
entry->prop_time = svn_time_from_nts (prop_timestr->data);
*modify_flags |= SVN_WC__ENTRY_MODIFY_PROP_TIME;
}
}
/* Setup last-committed values. */
{
svn_stringbuf_t *cmt_datestr, *cmt_revstr;
cmt_datestr = apr_hash_get (atts, SVN_WC__ENTRY_ATTR_CMT_DATE,
APR_HASH_KEY_STRING);
if (cmt_datestr)
{
entry->cmt_date = svn_time_from_nts (cmt_datestr->data);
*modify_flags |= SVN_WC__ENTRY_MODIFY_CMT_DATE;
}
else
entry->cmt_date = 0;
cmt_revstr = apr_hash_get (atts, SVN_WC__ENTRY_ATTR_CMT_REV,
APR_HASH_KEY_STRING);
if (cmt_revstr)
{
entry->cmt_rev = SVN_STR_TO_REV (cmt_revstr->data);
*modify_flags |= SVN_WC__ENTRY_MODIFY_CMT_REV;
}
else
entry->cmt_rev = SVN_INVALID_REVNUM;
entry->cmt_author = apr_hash_get (atts, SVN_WC__ENTRY_ATTR_CMT_AUTHOR,
APR_HASH_KEY_STRING);
if (entry->cmt_author)
*modify_flags |= SVN_WC__ENTRY_MODIFY_CMT_AUTHOR;
}
*new_entry = entry;
return SVN_NO_ERROR;
}
/* Called whenever we find an <open> tag of some kind. */
static void
handle_start_tag (void *userData, const char *tagname, const char **atts)
{
struct entries_accumulator *accum = userData;
apr_hash_t *attributes;
svn_wc_entry_t *entry;
svn_error_t *err;
apr_uint32_t modify_flags = 0;
/* We only care about the `entry' tag; all other tags, such as `xml'
and `wc-entries', are ignored. */
if (strcmp (tagname, SVN_WC__ENTRIES_ENTRY))
return;
/* Make an entry from the attributes. */
attributes = svn_xml_make_att_hash (atts, accum->pool);
err = svn_wc__atts_to_entry (&entry, &modify_flags, attributes, accum->pool);
if (err)
svn_xml_signal_bailout (err, accum->parser);
/* Find the name and set up the entry under that name. This
should *NOT* be NULL, since svn_wc__atts_to_entry() should
have made it into SVN_WC_ENTRY_THIS_DIR. */
apr_hash_set (accum->entries, entry->name->data, entry->name->len, entry);
}
/* Use entry SRC to fill in blank portions of entry DST. SRC itself
may not have any blanks, of course.
Typically, SRC is a parent directory's own entry, and DST is some
child in that directory. */
static void
take_from_entry (svn_wc_entry_t *src, svn_wc_entry_t *dst, apr_pool_t *pool)
{
/* Inherits parent's revision if doesn't have a revision of one's
own, unless this is a subdirectory. */
if ((dst->revision == SVN_INVALID_REVNUM) && (dst->kind != svn_node_dir))
dst->revision = src->revision;
/* Inherits parent's url if doesn't have a url of one's own and is not
marked for addition. An entry being added doesn't really have
url yet. */
if ((! dst->url)
&& (! ((dst->schedule == svn_wc_schedule_add)
|| (dst->schedule == svn_wc_schedule_replace))))
{
dst->url = svn_stringbuf_dup (src->url, pool);
svn_path_add_component (dst->url, dst->name);
}
}
/* Resolve any missing information in ENTRIES by deducing from the
directory's own entry (which must already be present in ENTRIES). */
static svn_error_t *
resolve_to_defaults (apr_hash_t *entries,
apr_pool_t *pool)
{
apr_hash_index_t *hi;
svn_wc_entry_t *default_entry
= apr_hash_get (entries, SVN_WC_ENTRY_THIS_DIR, APR_HASH_KEY_STRING);
/* First check the dir's own entry for consistency. */
if (! default_entry)
return svn_error_create (SVN_ERR_ENTRY_NOT_FOUND,
0,
NULL,
pool,
"missing default entry");
if (default_entry->revision == SVN_INVALID_REVNUM)
return svn_error_create (SVN_ERR_ENTRY_MISSING_REVISION,
0,
NULL,
pool,
"default entry has no revision number");
if (! default_entry->url)
return svn_error_create (SVN_ERR_ENTRY_MISSING_URL,
0,
NULL,
pool,
"default entry missing url");
/* Then use it to fill in missing information in other entries. */
for (hi = apr_hash_first (pool, entries); hi; hi = apr_hash_next (hi))
{
const void *key;
apr_ssize_t keylen;
void *val;
svn_wc_entry_t *this_entry;
svn_stringbuf_t *entryname;
apr_hash_this (hi, &key, &keylen, &val);
this_entry = val;
entryname = svn_stringbuf_ncreate (key, keylen, pool);
if (this_entry == default_entry)
/* THIS_DIR already has all the information it can possibly
have. */
continue;
if (this_entry->kind == svn_node_dir)
/* Entries that are directories have everything but their
name, kind, and state stored in the THIS_DIR entry of the
directory itself. However, we are disallowing the perusing
of any entries outside of the current entries file. If a
caller wants more info about a directory, it should look in
the entries file in the directory. */
continue;
if (this_entry->kind == svn_node_file)
/* For file nodes that do not explicitly have their ancestry
stated, this can be derived from the default entry of the
directory in which those files reside. */
take_from_entry (default_entry, this_entry, pool);
}
return SVN_NO_ERROR;
}
/* Fill ENTRIES according to PATH's entries file. */
static svn_error_t *
read_entries (apr_hash_t *entries,
svn_stringbuf_t *path,
apr_pool_t *pool)
{
svn_error_t *err;
apr_file_t *infile = NULL;
svn_xml_parser_t *svn_parser;
apr_status_t apr_err;
char buf[BUFSIZ];
apr_size_t bytes_read;
struct entries_accumulator *accum;
/* Open the entries file. */
SVN_ERR (svn_wc__open_adm_file (&infile, path, SVN_WC__ADM_ENTRIES,
APR_READ, pool));
/* Set up userData for the XML parser. */
accum = apr_palloc (pool, sizeof (*accum));
accum->entries = entries;
accum->pool = pool;
/* Create the XML parser */
svn_parser = svn_xml_make_parser (accum,
handle_start_tag,
NULL,
NULL,
pool);
/* Store parser in its own userdata, so callbacks can call
svn_xml_signal_bailout() */
accum->parser = svn_parser;
/* Parse. */
do {
apr_err = apr_file_read_full (infile, buf, sizeof(buf), &bytes_read);
if (apr_err && !APR_STATUS_IS_EOF(apr_err))
return svn_error_create
(apr_err, 0, NULL, pool, "read_entries: apr_file_read_full choked");
err = svn_xml_parse (svn_parser, buf, bytes_read,
APR_STATUS_IS_EOF(apr_err));
if (err)
return svn_error_createf (err->apr_err, 0, err, pool,
"read_entries: xml parser failed (%s).",
path->data);
} while (!APR_STATUS_IS_EOF(apr_err));
/* Close the entries file. */
SVN_ERR (svn_wc__close_adm_file (infile, path, SVN_WC__ADM_ENTRIES, 0, pool));
/* Clean up the xml parser */
svn_xml_free_parser (svn_parser);
/* Fill in any implied fields. */
SVN_ERR (resolve_to_defaults (entries, pool));
return SVN_NO_ERROR;
}
svn_error_t *
svn_wc_entry (svn_wc_entry_t **entry,
svn_stringbuf_t *path,
apr_pool_t *pool)
{
enum svn_node_kind kind;
apr_hash_t *entries = apr_hash_make (pool);
svn_boolean_t is_wc;
*entry = NULL;
SVN_ERR (svn_io_check_path (path->data, &kind, pool));
/* ### todo:
Make an innocent way to discover that a dir/path is or is not
under version control, so that this function can be robust. I
think svn_wc_entries_read() will return an error right now if,
for example, PATH represents a new dir that svn still thinks is a
regular file under version control. */
if (kind == svn_node_dir)
{
SVN_ERR (svn_wc_check_wc (path, &is_wc, pool));
if (! is_wc)
return svn_error_createf
(SVN_ERR_WC_OBSTRUCTED_UPDATE, 0, NULL, pool,
"svn_wc_entry: %s is not a working copy directory", path->data);
SVN_ERR (svn_wc_entries_read (&entries, path, pool));
*entry
= apr_hash_get (entries, SVN_WC_ENTRY_THIS_DIR, APR_HASH_KEY_STRING);
}
if (! *entry)
{
/* Maybe we're here because PATH is a directory, and we've
already tried and failed to retrieve its revision information
(we could have failed because PATH is under rev control as a
file, not a directory, i.e., the user rm'd the file and
created a dir there).
Or maybe we're here because PATH is a regular file.
Either way, if PATH is a versioned entity, it is versioned as
a file. So look split and look in parent for entry info. */
svn_stringbuf_t *dir, *basename;
svn_path_split (path, &dir, &basename, pool);
if (svn_path_is_empty (dir))
svn_stringbuf_set (dir, ".");
SVN_ERR (svn_wc_check_wc (dir, &is_wc, pool));
if (! is_wc)
return svn_error_createf
(SVN_ERR_WC_OBSTRUCTED_UPDATE, 0, NULL, pool,
"svn_wc_entry: %s is not a working copy directory", dir->data);
/* ### it would be nice to avoid reading all of these. or maybe read
### them into a subpool and copy the one that we need up to the
### specified pool. */
SVN_ERR (svn_wc_entries_read (&entries, dir, pool));
*entry = apr_hash_get (entries, basename->data, basename->len);
}
return SVN_NO_ERROR;
}
#if 0
/* This is #if 0'd out until I decide where to use it. --cmpilato */
/* Run a simple validity check on the ENTRIES (the list of entries
associated with the directory PATH). */
static svn_error_t *
check_entries (apr_hash_t *entries,
svn_stringbuf_t *path,
apr_pool_t *pool)
{
svn_wc_entry_t *default_entry;
apr_hash_index_t *hi;
default_entry = apr_hash_get (entries,
SVN_WC_ENTRY_THIS_DIR,
APR_HASH_KEY_STRING);
if (! default_entry)
return svn_error_createf
(SVN_ERR_WC_CORRUPT, 0, NULL, pool,
"'%s' has no default entry",
path->data);
/* Validate DEFAULT_ENTRY's current schedule. */
switch (default_entry->schedule)
{
case svn_wc_schedule_normal:
case svn_wc_schedule_add:
case svn_wc_schedule_delete:
case svn_wc_schedule_replace:
/* These are all valid states */
break;
default:
/* This is an invalid state */
return svn_error_createf
(SVN_ERR_WC_CORRUPT, 0, NULL, pool,
"Directory '%s' has an invalid schedule",
path->data);
}
for (hi = apr_hash_first (pool, entries); hi; hi = apr_hash_next (hi))
{
const void *key;
const char *name;
apr_size_t keylen;
void *val;
svn_wc_entry_t *this_entry;
/* Get the entry */
apr_hash_this (hi, &key, &keylen, &val);
this_entry = val;
name = (const char *)key;
/* We've already checked the "this dir" entry */
if (! strcmp (name, SVN_WC_ENTRY_THIS_DIR ))
continue;
/* Validate THIS_ENTRY's current schedule. */
switch (this_entry->schedule)
{
case svn_wc_schedule_normal:
case svn_wc_schedule_add:
case svn_wc_schedule_delete:
case svn_wc_schedule_replace:
/* These are all valid states */
break;
default:
/* This is an invalid state */
return svn_error_createf
(SVN_ERR_WC_CORRUPT, 0, NULL, pool,
"'%s' in directory '%s' has an invalid schedule",
name, path->data);
}
if ((default_entry->schedule == svn_wc_schedule_add)
&& (this_entry->schedule != svn_wc_schedule_add))
return svn_error_createf
(SVN_ERR_WC_CORRUPT, 0, NULL, pool,
"'%s' in directory '%s' (which is scheduled for addition) "
"is not itself scheduled for addition",
name, path->data);
if ((default_entry->schedule == svn_wc_schedule_delete)
&& (this_entry->schedule != svn_wc_schedule_delete))
return svn_error_createf
(SVN_ERR_WC_CORRUPT, 0, NULL, pool,
"'%s' in directory '%s' (which is scheduled for deletion) "
"is not itself scheduled for deletion",
name, path->data);
if ((default_entry->schedule == svn_wc_schedule_replace)
&& (this_entry->schedule == svn_wc_schedule_normal))
return svn_error_createf
(SVN_ERR_WC_CORRUPT, 0, NULL, pool,
"'%s' in directory '%s' (which is scheduled for replacement) "
"has in invalid schedule",
name, path->data);
}
return SVN_NO_ERROR;
}
#endif
svn_error_t *
svn_wc_entries_read (apr_hash_t **entries,
svn_stringbuf_t *path,
apr_pool_t *pool)
{
apr_hash_t *new_entries;
new_entries = apr_hash_make (pool);
SVN_ERR (read_entries (new_entries, path, pool));
*entries = new_entries;
return SVN_NO_ERROR;
}
/* Append a single entry THIS_ENTRY to the string OUTPUT, using the
entry for "this dir" THIS_DIR for comparison/optimization.
Allocations are done in POOL. */
static svn_error_t *
write_entry (svn_stringbuf_t **output,
svn_wc_entry_t *entry,
const char *name,
svn_wc_entry_t *this_dir,
apr_pool_t *pool)
{
apr_hash_t *atts = apr_hash_make (pool);
svn_stringbuf_t *valuestr;
/*** Create a hash that represents an entry. ***/
assert (name);
/* Name */
entry->name = svn_stringbuf_create (name, pool);
apr_hash_set (atts, SVN_WC__ENTRY_ATTR_NAME, APR_HASH_KEY_STRING,
entry->name);
/* Revision */
if (SVN_IS_VALID_REVNUM (entry->revision))
apr_hash_set (atts, SVN_WC__ENTRY_ATTR_REVISION, APR_HASH_KEY_STRING,
svn_stringbuf_createf (pool, "%ld", entry->revision));
/* URL */
if (entry->url)
apr_hash_set (atts, SVN_WC__ENTRY_ATTR_URL, APR_HASH_KEY_STRING,
entry->url);
/* Kind */
switch (entry->kind)
{
case svn_node_dir:
valuestr = svn_stringbuf_create (SVN_WC__ENTRIES_ATTR_DIR_STR, pool);
break;
case svn_node_none:
valuestr = NULL;
break;
case svn_node_file:
case svn_node_unknown:
default:
valuestr = svn_stringbuf_create (SVN_WC__ENTRIES_ATTR_FILE_STR, pool);
break;
}
apr_hash_set (atts, SVN_WC__ENTRY_ATTR_KIND, APR_HASH_KEY_STRING, valuestr);
/* Schedule */
switch (entry->schedule)
{
case svn_wc_schedule_add:
valuestr = svn_stringbuf_create (SVN_WC__ENTRY_VALUE_ADD, pool);
break;
case svn_wc_schedule_delete:
valuestr = svn_stringbuf_create (SVN_WC__ENTRY_VALUE_DELETE, pool);
break;
case svn_wc_schedule_replace:
valuestr = svn_stringbuf_create (SVN_WC__ENTRY_VALUE_REPLACE, pool);
break;
case svn_wc_schedule_normal:
default:
valuestr = NULL;
break;
}
apr_hash_set (atts, SVN_WC__ENTRY_ATTR_SCHEDULE, APR_HASH_KEY_STRING,
valuestr);
/* Conflicts */
if (entry->conflict_old)
apr_hash_set (atts, SVN_WC__ENTRY_ATTR_CONFLICT_OLD, APR_HASH_KEY_STRING,
entry->conflict_old);
if (entry->conflict_new)
apr_hash_set (atts, SVN_WC__ENTRY_ATTR_CONFLICT_NEW, APR_HASH_KEY_STRING,
entry->conflict_new);
if (entry->conflict_wrk)
apr_hash_set (atts, SVN_WC__ENTRY_ATTR_CONFLICT_WRK, APR_HASH_KEY_STRING,
entry->conflict_wrk);
if (entry->prejfile)
apr_hash_set (atts, SVN_WC__ENTRY_ATTR_PREJFILE, APR_HASH_KEY_STRING,
entry->prejfile);
/* Copy-related Stuff */
valuestr = entry->copied ? svn_stringbuf_create ("true", pool) : NULL;
apr_hash_set (atts, SVN_WC__ENTRY_ATTR_COPIED, APR_HASH_KEY_STRING,
valuestr);
if (SVN_IS_VALID_REVNUM (entry->copyfrom_rev))
apr_hash_set (atts, SVN_WC__ENTRY_ATTR_COPYFROM_REV, APR_HASH_KEY_STRING,
svn_stringbuf_createf (pool, "%ld", entry->copyfrom_rev));
if (entry->copyfrom_url)
apr_hash_set (atts, SVN_WC__ENTRY_ATTR_COPYFROM_URL, APR_HASH_KEY_STRING,
entry->copyfrom_url);
/* Timestamps */
if (entry->text_time)
{
const char *timestr = svn_time_to_nts (entry->text_time, pool);
apr_hash_set (atts, SVN_WC__ENTRY_ATTR_TEXT_TIME, APR_HASH_KEY_STRING,
svn_stringbuf_create (timestr, pool));
}
if (entry->prop_time)
{
const char *timestr = svn_time_to_nts (entry->prop_time, pool);
apr_hash_set (atts, SVN_WC__ENTRY_ATTR_PROP_TIME, APR_HASH_KEY_STRING,
svn_stringbuf_create (timestr, pool));
}
/* Last-commit Stuff */
if (SVN_IS_VALID_REVNUM (entry->cmt_rev))
apr_hash_set (atts, SVN_WC__ENTRY_ATTR_CMT_REV, APR_HASH_KEY_STRING,
svn_stringbuf_createf (pool, "%ld", entry->cmt_rev));
if (entry->cmt_author)
apr_hash_set (atts, SVN_WC__ENTRY_ATTR_CMT_AUTHOR, APR_HASH_KEY_STRING,
entry->cmt_author);
if (entry->cmt_date)
{
const char *timestr = svn_time_to_nts (entry->cmt_date, pool);
apr_hash_set (atts, SVN_WC__ENTRY_ATTR_CMT_DATE, APR_HASH_KEY_STRING,
svn_stringbuf_create (timestr, pool));
}
/*** Now, remove stuff that can be derived through inheritance rules. ***/
/* We only want to write out 'revision' and 'url' for the
following things:
1. the current directory's "this dir" entry.
2. non-directory entries:
a. which are marked for addition (and consequently should
have an invalid revnum)
b. whose revision or url is valid and different than
that of the "this dir" entry.
*/
if (strcmp (name, SVN_WC_ENTRY_THIS_DIR))
{
/* This is NOT the "this dir" entry */
if (! strcmp (name, "."))
{
/* By golly, if this isn't recognized as the "this dir"
entry, and it looks like '.', we're just asking for an
infinite recursion to happen. Abort! */
abort();
}
if (entry->kind == svn_node_dir)
{
/* We don't write url or revision for subdir
entries. */
apr_hash_set (atts, SVN_WC__ENTRY_ATTR_REVISION, APR_HASH_KEY_STRING,
NULL);
apr_hash_set (atts, SVN_WC__ENTRY_ATTR_URL, APR_HASH_KEY_STRING,
NULL);
}
else
{
svn_stringbuf_t *this_path;
/* If this is not the "this dir" entry, and the revision is
the same as that of the "this dir" entry, don't out the
revision. */
if (entry->revision == this_dir->revision)
apr_hash_set (atts, SVN_WC__ENTRY_ATTR_REVISION,
APR_HASH_KEY_STRING, NULL);
/* If this is not the "this dir" entry, and the url is
trivially calculable from that of the "this dir" entry,
don't write out the url */
if (entry->url)
{
this_path = svn_stringbuf_dup (this_dir->url, pool);
svn_path_add_component_nts (this_path, name);
if (svn_stringbuf_compare (this_path, entry->url))
apr_hash_set (atts, SVN_WC__ENTRY_ATTR_URL,
APR_HASH_KEY_STRING, NULL);
}
}
}
/* Append the entry onto the accumulating string. */
svn_xml_make_open_tag_hash (output,
pool,
svn_xml_self_closing,
SVN_WC__ENTRIES_ENTRY,
atts);
return SVN_NO_ERROR;
}
svn_error_t *
svn_wc__entries_write (apr_hash_t *entries,
svn_stringbuf_t *path,
apr_pool_t *pool)
{
svn_error_t *err = NULL, *err2 = NULL;
svn_stringbuf_t *bigstr = NULL;
apr_file_t *outfile = NULL;
apr_status_t apr_err;
apr_hash_index_t *hi;
svn_wc_entry_t *this_dir;
/* Get a copy of the "this dir" entry for comparison purposes. */
this_dir = apr_hash_get (entries, SVN_WC_ENTRY_THIS_DIR,
APR_HASH_KEY_STRING);
/* If there is no "this dir" entry, something is wrong. */
if (! this_dir)
return svn_error_createf (SVN_ERR_ENTRY_NOT_FOUND, 0, NULL, pool,
"No default entry in directory `%s'",
path->data);
/* Open entries file for writing. */
SVN_ERR (svn_wc__open_adm_file (&outfile, path, SVN_WC__ADM_ENTRIES,
(APR_WRITE | APR_CREATE | APR_EXCL),
pool));
svn_xml_make_header (&bigstr, pool);
svn_xml_make_open_tag (&bigstr, pool, svn_xml_normal,
SVN_WC__ENTRIES_TOPLEVEL,
"xmlns",
svn_stringbuf_create (SVN_XML_NAMESPACE, pool),
NULL);
/* Write out "this dir" */
SVN_ERR (write_entry (&bigstr, this_dir, SVN_WC_ENTRY_THIS_DIR,
this_dir, pool));
for (hi = apr_hash_first (pool, entries); hi; hi = apr_hash_next (hi))
{
const void *key;
apr_ssize_t keylen;
void *val;
svn_wc_entry_t *this_entry;
/* Get the entry and make sure its attributes are up-to-date. */
apr_hash_this (hi, &key, &keylen, &val);
this_entry = val;
/* Don't rewrite the "this dir" entry! */
if (! strcmp (key, SVN_WC_ENTRY_THIS_DIR ))
continue;
/* Append the entry to BIGSTR */
write_entry (&bigstr, this_entry, key, this_dir, pool);
}
svn_xml_make_close_tag (&bigstr, pool, SVN_WC__ENTRIES_TOPLEVEL);
apr_err = apr_file_write_full (outfile, bigstr->data, bigstr->len, NULL);
if (apr_err)
err = svn_error_createf (apr_err, 0, NULL, pool,
"svn_wc__entries_write: %s",
path->data);
/* Close & sync. */
err2 = svn_wc__close_adm_file (outfile, path, SVN_WC__ADM_ENTRIES, 1, pool);
if (err)
return err;
else if (err2)
return err2;
return SVN_NO_ERROR;
}
/* Update an entry NAME in ENTRIES, according to the combination of
entry data found in ENTRY and masked by MODIFY_FLAGS. If the entry
already exists, the requested changes will be folded (merged) into
the entry's existing state. If the entry doesn't exist, the entry
will be created with exactly those properties described by the set
of changes. */
static void
fold_entry (apr_hash_t *entries,
const svn_stringbuf_t *name,
apr_uint32_t modify_flags,
svn_wc_entry_t *entry,
apr_pool_t *pool)
{
svn_wc_entry_t *cur_entry = apr_hash_get (entries, name->data, name->len);
assert (name != NULL);
if (! cur_entry)
cur_entry = alloc_entry (pool);
/* Name (just a safeguard here, really) */
if (! cur_entry->name)
cur_entry->name = svn_stringbuf_dup (name, pool);
/* Revision */
if (modify_flags & SVN_WC__ENTRY_MODIFY_REVISION)
cur_entry->revision = entry->revision;
/* Ancestral URL in repository */
if (modify_flags & SVN_WC__ENTRY_MODIFY_URL)
cur_entry->url = entry->url
? svn_stringbuf_dup (entry->url, pool)
: NULL;
/* Kind */
if (modify_flags & SVN_WC__ENTRY_MODIFY_KIND)
cur_entry->kind = entry->kind;
/* Schedule */
if (modify_flags & SVN_WC__ENTRY_MODIFY_SCHEDULE)
cur_entry->schedule = entry->schedule;
/* Copy-related stuff */
if (modify_flags & SVN_WC__ENTRY_MODIFY_COPIED)
cur_entry->copied = entry->copied;
if (modify_flags & SVN_WC__ENTRY_MODIFY_COPYFROM_URL)
cur_entry->copyfrom_url = entry->copyfrom_url
? svn_stringbuf_dup (entry->copyfrom_url, pool)
: NULL;
if (modify_flags & SVN_WC__ENTRY_MODIFY_COPYFROM_REV)
cur_entry->copyfrom_rev = entry->copyfrom_rev;
/* Text/prop modification times */
if (modify_flags & SVN_WC__ENTRY_MODIFY_TEXT_TIME)
cur_entry->text_time = entry->text_time;
if (modify_flags & SVN_WC__ENTRY_MODIFY_PROP_TIME)
cur_entry->prop_time = entry->prop_time;
/* Conflict stuff */
if (modify_flags & SVN_WC__ENTRY_MODIFY_CONFLICT_OLD)
cur_entry->conflict_old = entry->conflict_old
? svn_stringbuf_dup (entry->conflict_old, pool)
: NULL;
if (modify_flags & SVN_WC__ENTRY_MODIFY_CONFLICT_NEW)
cur_entry->conflict_new = entry->conflict_new
? svn_stringbuf_dup (entry->conflict_new, pool)
: NULL;
if (modify_flags & SVN_WC__ENTRY_MODIFY_CONFLICT_WRK)
cur_entry->conflict_wrk = entry->conflict_wrk
? svn_stringbuf_dup (entry->conflict_wrk, pool)
: NULL;
if (modify_flags & SVN_WC__ENTRY_MODIFY_PREJFILE)
cur_entry->prejfile = entry->prejfile
? svn_stringbuf_dup (entry->prejfile, pool)
: NULL;
/* Last-commit stuff */
if (modify_flags & SVN_WC__ENTRY_MODIFY_CMT_REV)
cur_entry->cmt_rev = entry->cmt_rev;
if (modify_flags & SVN_WC__ENTRY_MODIFY_CMT_DATE)
cur_entry->cmt_date = entry->cmt_date;
if (modify_flags & SVN_WC__ENTRY_MODIFY_CMT_AUTHOR)
cur_entry->cmt_author = entry->cmt_author
? svn_stringbuf_dup (entry->cmt_author, pool)
: NULL;
/* Absorb defaults from the parent dir, if any, unless this is a
subdir entry. */
if (cur_entry->kind != svn_node_dir)
{
svn_wc_entry_t *default_entry
= apr_hash_get (entries, SVN_WC_ENTRY_THIS_DIR, APR_HASH_KEY_STRING);
if (default_entry)
take_from_entry (default_entry, cur_entry, pool);
}
/* Make sure the entry exists in the entries hash. Possibly it
already did, in which case this could have been skipped, but what
the heck. */
apr_hash_set (entries, name->data, name->len, cur_entry);
}
void
svn_wc__entry_remove (apr_hash_t *entries,
svn_stringbuf_t *name)
{
apr_hash_set (entries, name->data, name->len, NULL);
}
/* Our general purpose intelligence module for handling scheduling
changes to a single entry.
Given an entryname NAME in ENTRIES, examine the caller's requested
change in *SCHEDULE and the current state of the entry. Possibly
modify *SCHEDULE and *MODIFY_FLAGS so that when merged, it will
reflect the caller's original intent. */
static svn_error_t *
fold_scheduling (apr_hash_t *entries,
svn_stringbuf_t *name,
apr_uint32_t *modify_flags,
svn_wc_schedule_t *schedule,
apr_pool_t *pool)
{
svn_wc_entry_t *entry, *this_dir_entry;
/* If we're not supposed to be bothering with this anyway...return. */
if (! (*modify_flags & SVN_WC__ENTRY_MODIFY_SCHEDULE))
return SVN_NO_ERROR;
/* Get the current entry */
entry = apr_hash_get (entries, name->data, name->len);
/* If we're not merging in changes, only the _add, _delete, _replace
and _normal schedules are allowed. */
if (*modify_flags & SVN_WC__ENTRY_MODIFY_FORCE)
{
switch (*schedule)
{
case svn_wc_schedule_add:
case svn_wc_schedule_delete:
case svn_wc_schedule_replace:
case svn_wc_schedule_normal:
/* Since we aren't merging in a change, not only are these
schedules legal, but they are final. */
return SVN_NO_ERROR;
default:
return
svn_error_createf
(SVN_ERR_WC_SCHEDULE_CONFLICT, 0, NULL, pool,
"fold_state_changes: Illegal schedule in state set operation");
}
}
/* The only operation valid on an item not already in revision
control is addition. */
if (! entry)
{
if (*schedule == svn_wc_schedule_add)
return SVN_NO_ERROR;
else
return
svn_error_createf
(SVN_ERR_WC_SCHEDULE_CONFLICT, 0, NULL, pool,
"fold_state_changes: '%s' is not a versioned resource",
name->data);
}
/* Get the default entry */
this_dir_entry = apr_hash_get (entries, SVN_WC_ENTRY_THIS_DIR,
APR_HASH_KEY_STRING);
/* At this point, we know the following things:
1. There is already an entry for this item in the entries file
whose existence is either _normal or _added (or about to
become such), which for our purposes mean the same thing.
2. We have been asked to merge in a state change, not to
explicitly set the state. */
/* Here are some cases that are parent-directory sensitive.
Basically, we make sure that we are not allowing versioned
resources to just sorta dangle below directories marked for
deletion. */
if ((entry != this_dir_entry)
&& (this_dir_entry->schedule == svn_wc_schedule_delete))
{
if (*schedule == svn_wc_schedule_add)
return
svn_error_createf
(SVN_ERR_WC_SCHEDULE_CONFLICT, 0, NULL, pool,
"fold_state_changes: Can't add '%s' to deleted directory"
"--try undeleting its parent directory first",
name->data);
if (*schedule == svn_wc_schedule_replace)
return
svn_error_createf
(SVN_ERR_WC_SCHEDULE_CONFLICT, 0, NULL, pool,
"fold_state_changes: Can't replace '%s' in deleted directory"
"--try undeleting its parent directory first",
name->data);
}
switch (entry->schedule)
{
case svn_wc_schedule_normal:
switch (*schedule)
{
case svn_wc_schedule_normal:
/* Normal is a trivial no-op case. Reset the
schedule modification bit and move along. */
*modify_flags &= ~SVN_WC__ENTRY_MODIFY_SCHEDULE;
return SVN_NO_ERROR;
case svn_wc_schedule_delete:
case svn_wc_schedule_replace:
/* These are all good. */
return SVN_NO_ERROR;
case svn_wc_schedule_add:
/* You can't add something that's already been added to
revision control. */
return
svn_error_createf
(SVN_ERR_WC_SCHEDULE_CONFLICT, 0, NULL, pool,
"fold_state_changes: Entry '%s' already under revision control",
name->data);
}
break;
case svn_wc_schedule_add:
switch (*schedule)
{
case svn_wc_schedule_normal:
case svn_wc_schedule_add:
case svn_wc_schedule_replace:
/* These are all no-op cases. Normal is obvious, as is add.
Replace on an entry marked for addition breaks down to
(add + (delete + add)), which resolves to just (add), and
since this entry is already marked with (add), this too
is a no-op. */
*modify_flags &= ~SVN_WC__ENTRY_MODIFY_SCHEDULE;
return SVN_NO_ERROR;
case svn_wc_schedule_delete:
/* Not-yet-versioned item being deleted, Just remove
the entry. Check that we are not trying to remove
the SVN_WC_ENTRY_THIS_DIR entry as that would
leave the entries file in an invalid state. */
assert (entry != this_dir_entry);
apr_hash_set (entries, name->data, name->len, NULL);
return SVN_NO_ERROR;
}
break;
case svn_wc_schedule_delete:
switch (*schedule)
{
case svn_wc_schedule_normal:
case svn_wc_schedule_delete:
/* These are no-op cases. */
*modify_flags &= ~SVN_WC__ENTRY_MODIFY_SCHEDULE;
return SVN_NO_ERROR;
case svn_wc_schedule_add:
/* Re-adding an entry marked for deletion? This is really a
replace operation. */
*schedule = svn_wc_schedule_replace;
return SVN_NO_ERROR;
case svn_wc_schedule_replace:
/* Replacing an item marked for deletion breaks down to
(delete + (delete + add)), which might deserve a warning,
but whatever. */
return SVN_NO_ERROR;
}
break;
case svn_wc_schedule_replace:
switch (*schedule)
{
case svn_wc_schedule_normal:
/* These are all no-op cases. */
case svn_wc_schedule_add:
/* Adding a to-be-replaced entry breaks down to ((delete +
add) + add) which might deserve a warning, but we'll just
no-op it. */
case svn_wc_schedule_replace:
/* Replacing a to-be-replaced entry breaks down to ((delete
+ add) + (delete + add)), which is insane! Make up your
friggin' mind, dude! :-) Well, we'll no-op this one,
too. */
*modify_flags &= ~SVN_WC__ENTRY_MODIFY_SCHEDULE;
return SVN_NO_ERROR;
case svn_wc_schedule_delete:
/* Deleting a to-be-replaced entry breaks down to ((delete +
add) + delete) which resolves to a flat deletion. */
*schedule = svn_wc_schedule_delete;
return SVN_NO_ERROR;
}
break;
default:
return
svn_error_createf
(SVN_ERR_WC_SCHEDULE_CONFLICT, 0, NULL, pool,
"fold_state_changes: Entry '%s' has illegal schedule",
name->data);
}
return SVN_NO_ERROR;
}
svn_error_t *
svn_wc__entry_modify (svn_stringbuf_t *path,
svn_stringbuf_t *name,
svn_wc_entry_t *entry,
apr_uint32_t modify_flags,
apr_pool_t *pool)
{
apr_hash_t *entries;
svn_boolean_t entry_was_deleted_p = FALSE;
/* ENTRY is rather necessary, and ENTRY->kind is required to be valid! */
assert (entry);
/*
assert ((entry->kind == svn_node_dir) || (entry->kind == svn_node_file));
*/
/* Load PATH's whole entries file. */
SVN_ERR (svn_wc_entries_read (&entries, path, pool));
/* Ensure that NAME is valid. */
if (name == NULL)
name = svn_stringbuf_create (SVN_WC_ENTRY_THIS_DIR, pool);
if (modify_flags & SVN_WC__ENTRY_MODIFY_SCHEDULE)
{
svn_wc_entry_t *entry_before, *entry_after;
/* Keep a copy of the unmodified entry on hand. */
entry_before = apr_hash_get (entries, name->data, name->len);
/* If scheduling changes were made, we have a special routine to
manage those modifications. */
SVN_ERR (fold_scheduling (entries, name, &modify_flags,
&entry->schedule, pool));
/* Special case: fold_state_changes() may have actually REMOVED
the entry in question! If so, don't try to fold_entry, as
this will just recreate the entry again. */
entry_after = apr_hash_get (entries, name->data, name->len);
/* Note if this entry was deleted above so we don't accidentally
re-add it in the following steps. */
if (entry_before && (! entry_after))
entry_was_deleted_p = TRUE;
}
/* If the entry wasn't just removed from the entries hash, fold the
changes into the entry. */
if (! entry_was_deleted_p)
fold_entry (entries, name, modify_flags, entry, pool);
/* Sync changes to disk. */
return svn_wc__entries_write (entries, path, pool);
}
svn_wc_entry_t *
svn_wc_entry_dup (svn_wc_entry_t *entry, apr_pool_t *pool)
{
svn_wc_entry_t *dupentry = apr_pcalloc (pool, sizeof(*dupentry));
/* Perform a trivial copy ... */
*dupentry = *entry;
/* ...and then re-copy stuff that needs to be duped into our pool. */
if (entry->name)
dupentry->name = svn_stringbuf_dup (entry->name, pool);
if (entry->url)
dupentry->url = svn_stringbuf_dup (entry->url, pool);
if (entry->copyfrom_url)
dupentry->copyfrom_url = svn_stringbuf_dup (entry->copyfrom_url, pool);
if (entry->conflict_old)
dupentry->conflict_old = svn_stringbuf_dup (entry->conflict_old, pool);
if (entry->conflict_new)
dupentry->conflict_new = svn_stringbuf_dup (entry->conflict_new, pool);
if (entry->conflict_wrk)
dupentry->conflict_wrk = svn_stringbuf_dup (entry->conflict_wrk, pool);
if (entry->prejfile)
dupentry->prejfile = svn_stringbuf_dup (entry->prejfile, pool);
if (entry->cmt_author)
dupentry->cmt_author = svn_stringbuf_dup (entry->cmt_author, pool);
return dupentry;
}
svn_error_t *
svn_wc__tweak_entry (apr_hash_t *entries,
const svn_stringbuf_t *name,
const svn_stringbuf_t *new_url,
const svn_revnum_t new_rev,
apr_pool_t *pool)
{
svn_wc_entry_t *entry;
entry = apr_hash_get (entries, name->data, APR_HASH_KEY_STRING);
if (! entry)
return svn_error_createf (SVN_ERR_ENTRY_NOT_FOUND, 0, NULL, pool,
"No such entry: '%s'", name->data);
if (new_url != NULL)
entry->url = svn_stringbuf_dup (new_url, pool);
if ((SVN_IS_VALID_REVNUM (new_rev))
&& (entry->schedule != svn_wc_schedule_add)
&& (entry->schedule != svn_wc_schedule_replace))
entry->revision = new_rev;
return SVN_NO_ERROR;
}
#if 0
/*** Recursion on entries. ***/
/* todo: this is the right idea, but it doesn't handle two situations
* well right now. Superdirectories are problematic:
*
* svn commit ../../foo.c ../baz/bar/blah.c
*
* and sibling files can result in redundant descents:
*
* svn commit bar/baz/blim.c bar/baz/bloo.c
*
* The fix, especially for the latter, involves returning something
* other than just a hash of paths. Instead, we'll have to turn the
* hash into a hash of directory paths, where a null value means
* recurse on everyone in the directory, and a non-null value is a
* list/hash of filenames *in that directory* to care about.
*
* Fairly easy to turn the below into that, luckily.
*
* -------------------------------------------------------------------
* Recurse on the revisioned parts of a working copy tree, starting at
* PATH.
*
* Each time a directory is entered, ENTER_DIR is called with the
* directory's path and the BATON as arguments.
*
* Each time a directory is left, LEAVE_DIR is called with the
* directory's path and the BATON as arguments.
*
* Each time a file is seen, HANDLE_FILE is called with the parent
* directory, the file's basename, and the BATON as arguments.
*
* If NAMED_TARGETS is non-null, then those functions are only invoked
* on directories and files whose names are included (perhaps
* implicitly) in NAMED_TARGETS:
*
* Each key in NAMED_TARGETS is a path to a file or directory, and the
* value is the (svn_stringbuf_t *) corresponding to that path (this is
* done for convenience). The goal of NAMED_TARGETS is to reflect the
* behavior of svn on the command line. For example, if you invoke
*
* svn commit foo bar/baz/blim.c blah.c
*
* the commit should
*
* 1. descend into foo (which is a directory), calling ENTER_DIR
* and LEAVE_DIR on foo itself, and calling those two and
* HANDLE_FILE appropriately depending on what it finds
* underneath foo,
*
* 2. call ENTER_DIR and LEAVE_DIR on every intermediate dir
* leading up to blim.c, and call HANDLE_FILE on blim.c itself,
*
* 3. call handle_file on blah.c
*
* In order for that to happen with depth-firstness observed and no
* redundant entering or leaving of directories, the NAMED_TARGETS
* hash undergoes the following treatment:
*
* Every path P in NAMED_TARGETS is checked to make sure that a parent
* path of P is not also in NAMED_TARGETS. If P does have a parent, P
* is removed from NAMED_TARGETS, because recursion on the parent will
* be sufficient to reach P anyway.
*
* After this, there will be no two paths with a parent/descendant
* relationship in P -- all relationships will be sibling or cousin.
*
* Once NAMED_TARGETS is free of redundancies, recursion happens on
* each path P in NAMED_TARGETS like so:
*
* ENTER_DIR is called on the first component of P
* [ENTER_DIR is called on the first/second component of P]
* [ENTER_DIR is called on the first/second/third component of P]
* [...]
* [If P's last component is a file, then HANDLE_FILE is
* invoked on that file only. Else if P's last component
* is a directory, then we recurse on every entry in that
* directory, calling HANDLE_FILE and/or {ENTER,LEAVE}_DIR
* as appropriate.]
* [...]
* [LEAVE_DIR is called on the first/second/third component of P]
* [LEAVE_DIR is called on the first/second component of P]
* LEAVE_DIR is called on the first component of P
*/
static void
svn_wc__compose_paths (apr_hash_t *paths, apr_pool_t *pool)
{
apr_hash_index_t *hi;
/* First, iterate over the hash canonicalizing paths. */
for (hi = apr_hash_first (pool, paths); hi; hi = apr_hash_next (hi))
{
const void *key;
apr_size_t keylen;
void *val;
svn_stringbuf_t *path;
apr_hash_this (hi, &key, &keylen, &val);
path = val;
apr_hash_set (paths, key, keylen, NULL);
svn_path_canonicalize (path);
apr_hash_set (paths, path->data, path->len, path);
}
/* Now, iterate over the hash removing redundancies. */
for (hi = apr_hash_first (pool, paths); hi; hi = apr_hash_next (hi))
{
const void *key;
apr_size_t keylen;
void *val;
svn_stringbuf_t *path;
apr_hash_this (hi, &key, &keylen, &val);
path = val;
/* Untelescope path, checking at each stage to see if the new,
shorter parent path is already in the hash. If it is, remove
the original path from the hash. */
{
svn_stringbuf_t *shrinking = svn_stringbuf_dup (path, pool);
for (svn_path_remove_component (shrinking);
(! svn_stringbuf_isempty (shrinking));
svn_path_remove_component (shrinking))
{
if (apr_hash_get (paths, shrinking->data, shrinking->len))
apr_hash_set (paths, path->data, path->len, NULL);
}
}
}
}
#endif /* 0 */
/*
* local variables:
* eval: (load-file "../../tools/dev/svn-dev.el")
* end:
*/