blob: 156888d2aeb96d9ec7c88ce39135d40f5e37639d [file] [log] [blame]
/*
* adm_ops.c: routines for affecting working copy administrative
* information. NOTE: this code doesn't know where the adm
* info is actually stored. Instead, generic handles to
* adm data are requested via a reference to some PATH
* (PATH being a regular, non-administrative directory or
* file in the working copy).
*
* ====================================================================
* 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_pools.h>
#include <apr_hash.h>
#include <apr_file_io.h>
#include <apr_time.h>
#include "svn_types.h"
#include "svn_pools.h"
#include "svn_string.h"
#include "svn_error.h"
#include "svn_hash.h"
#include "svn_path.h"
#include "svn_wc.h"
#include "wc.h"
#include "log.h"
#include "adm_files.h"
#include "adm_ops.h"
#include "entries.h"
#include "props.h"
#include "translate.h"
/*** Finishing updates and commits. ***/
/* The main recursive body of svn_wc__do_update_cleanup. */
static svn_error_t *
recursively_tweak_entries (svn_stringbuf_t *dirpath,
const svn_stringbuf_t *base_url,
const svn_revnum_t new_rev,
apr_pool_t *pool)
{
apr_hash_t *entries;
apr_hash_index_t *hi;
apr_pool_t *subpool = svn_pool_create (pool);
/* Read DIRPATH's entries. */
SVN_ERR (svn_wc_entries_read (&entries, dirpath, subpool));
/* Tweak "this_dir" */
SVN_ERR (svn_wc__tweak_entry (entries,
svn_stringbuf_create (SVN_WC_ENTRY_THIS_DIR,
subpool),
base_url, new_rev, subpool));
/* Recursively loop over all children. */
for (hi = apr_hash_first (subpool, entries); hi; hi = apr_hash_next (hi))
{
const void *key;
apr_ssize_t keylen;
void *val;
const char *name;
svn_wc_entry_t *current_entry;
svn_stringbuf_t *child_url = NULL;
apr_hash_this (hi, &key, &keylen, &val);
name = (const char *) key;
current_entry = (svn_wc_entry_t *) val;
/* Ignore the "this dir" entry. */
if (! strcmp (name, SVN_WC_ENTRY_THIS_DIR))
continue;
/* Derive the new URL for the current (child) entry */
if (base_url)
{
child_url = svn_stringbuf_dup (base_url, subpool);
svn_path_add_component_nts (child_url, name);
}
/* If a file, tweak the entry. */
if (current_entry->kind == svn_node_file)
SVN_ERR (svn_wc__tweak_entry (entries,
svn_stringbuf_create (name, subpool),
child_url, new_rev, subpool));
/* If a dir, recurse. */
else if (current_entry->kind == svn_node_dir)
{
svn_stringbuf_t *child_path = svn_stringbuf_dup (dirpath, subpool);
svn_path_add_component_nts (child_path, name);
SVN_ERR (recursively_tweak_entries
(child_path, child_url, new_rev, subpool));
}
}
/* Write a shiny new entries file to disk. */
SVN_ERR (svn_wc__entries_write (entries, dirpath, subpool));
/* Cleanup */
svn_pool_destroy (subpool);
return SVN_NO_ERROR;
}
svn_error_t *
svn_wc__do_update_cleanup (svn_stringbuf_t *path,
const svn_boolean_t recursive,
const svn_stringbuf_t *base_url,
const svn_revnum_t new_revision,
apr_pool_t *pool)
{
apr_hash_t *entries;
svn_wc_entry_t *entry;
SVN_ERR (svn_wc_entry (&entry, path, pool));
if (entry == NULL)
return SVN_NO_ERROR;
if (entry->kind == svn_node_file)
{
svn_stringbuf_t *parent, *basename;
svn_path_split (path, &parent, &basename, pool);
SVN_ERR (svn_wc_entries_read (&entries, parent, pool));
SVN_ERR (svn_wc__tweak_entry (entries, basename,
base_url, new_revision, pool));
SVN_ERR (svn_wc__entries_write (entries, parent, pool));
}
else if (entry->kind == svn_node_dir)
{
if (! recursive)
{
SVN_ERR (svn_wc_entries_read (&entries, path, pool));
SVN_ERR (svn_wc__tweak_entry (entries,
svn_stringbuf_create
(SVN_WC_ENTRY_THIS_DIR,
pool),
base_url, new_revision, pool));
SVN_ERR (svn_wc__entries_write (entries, path, pool));
}
else
SVN_ERR (recursively_tweak_entries (path, base_url,
new_revision, pool));
}
else
return svn_error_createf (SVN_ERR_UNKNOWN_NODE_KIND, 0, NULL, pool,
"Unrecognized node kind: '%s'\n", path->data);
return SVN_NO_ERROR;
}
/* ### todo: Might just make more sense to expose the
svn_wc_wcprop_get/set functions themselves than to have mindless
wrappers around them. */
svn_error_t *
svn_wc_get_wc_prop (const char *path,
const char *name,
const svn_string_t **value,
apr_pool_t *pool)
{
return svn_wc__wcprop_get (value, name, path, pool);
}
svn_error_t *
svn_wc_set_wc_prop (const char *path,
const char *name,
const svn_string_t *value,
apr_pool_t *pool)
{
return svn_wc__wcprop_set (name, value, path, pool);
}
/* Process an absolute PATH that has just been successfully committed.
Specifically, its working revision will be set to NEW_REVNUM; if
REV_DATE and REV_AUTHOR are both non-NULL, then three entry values
will be set (overwritten): 'committed-rev', 'committed-date',
'last-author'.
If RECURSE is true (assuming PATH is a directory), this post-commit
processing will happen recursively down from PATH.
*/
svn_error_t *
svn_wc_process_committed (svn_stringbuf_t *path,
svn_boolean_t recurse,
svn_revnum_t new_revnum,
const char *rev_date,
const char *rev_author,
apr_pool_t *pool)
{
svn_error_t *err;
apr_status_t apr_err;
svn_stringbuf_t *log_parent, *logtag, *basename;
apr_file_t *log_fp = NULL;
char *revstr = apr_psprintf (pool, "%ld", new_revnum);
/* Write a log file in the adm dir of path. */
/* (First, try to write a logfile directly in PATH.) */
log_parent = path;
basename = svn_stringbuf_create (SVN_WC_ENTRY_THIS_DIR, pool);
err = svn_wc__open_adm_file (&log_fp, log_parent, SVN_WC__ADM_LOG,
(APR_WRITE | APR_APPEND | APR_CREATE),
pool);
if (err)
{
/* (Ah, PATH must be a file. So create a logfile in its
parent instead.) */
svn_error_clear_all (err);
svn_path_split (path, &log_parent, &basename, pool);
if (svn_path_is_empty (log_parent))
svn_stringbuf_set (log_parent, ".");
SVN_ERR (svn_wc__open_adm_file (&log_fp, log_parent, SVN_WC__ADM_LOG,
(APR_WRITE|APR_APPEND|APR_CREATE),
pool));
/* Oh, and recursing at this point isn't really sensible. */
recurse = FALSE;
}
else
{
/* PATH must be a dir */
svn_stringbuf_t *pdir;
svn_wc_entry_t tmp_entry;
if (svn_path_is_empty (log_parent))
{
/* We have an empty path. Since there is no way to examine
the parent of an empty path, we ensure that the parent
directory is '.', and that we are looking at the "this
dir" entry. */
pdir = svn_stringbuf_create (".", pool);
}
else
{
/* We were given a directory, so we look at that dir's "this
dir" entry. */
pdir = log_parent;
}
tmp_entry.kind = svn_node_dir;
tmp_entry.revision = new_revnum;
SVN_ERR (svn_wc__entry_modify (pdir, basename, &tmp_entry,
SVN_WC__ENTRY_MODIFY_REVISION, pool));
}
logtag = svn_stringbuf_create ("", pool);
/* Append a log command to set (overwrite) the 'committed-rev',
'committed-date', and 'last-author' attributes in the entry.
Note: it's important that this log command come *before* the
LOG_COMMITTED command, because log_do_committed() might actually
remove the entry! */
if (rev_date && rev_author)
svn_xml_make_open_tag (&logtag, pool, svn_xml_self_closing,
SVN_WC__LOG_MODIFY_ENTRY,
SVN_WC__LOG_ATTR_NAME, basename,
SVN_WC__ENTRY_ATTR_CMT_REV,
svn_stringbuf_create (revstr, pool),
SVN_WC__ENTRY_ATTR_CMT_DATE,
svn_stringbuf_create (rev_date, pool),
SVN_WC__ENTRY_ATTR_CMT_AUTHOR,
svn_stringbuf_create (rev_author, pool),
NULL);
/* Regardless of whether it's a file or dir, the "main" logfile
contains a command to bump the revision attribute (and
timestamp.) */
svn_xml_make_open_tag (&logtag, pool, svn_xml_self_closing,
SVN_WC__LOG_COMMITTED,
SVN_WC__LOG_ATTR_NAME, basename,
SVN_WC__LOG_ATTR_REVISION,
svn_stringbuf_create (revstr, pool),
NULL);
apr_err = apr_file_write_full (log_fp, logtag->data, logtag->len, NULL);
if (apr_err)
{
apr_file_close (log_fp);
return svn_error_createf (apr_err, 0, NULL, pool,
"process_committed: "
"error writing %s's log file",
path->data);
}
SVN_ERR (svn_wc__close_adm_file (log_fp, log_parent, SVN_WC__ADM_LOG,
TRUE, /* sync */
pool));
/* Run the log file we just created. */
SVN_ERR (svn_wc__run_log (log_parent, pool));
/* The client's commit routine will take care of removing all
locks en masse. */
if (recurse)
{
apr_hash_t *entries;
apr_hash_index_t *hi;
apr_pool_t *subpool = svn_pool_create (pool);
/* Read PATH's entries; this is the absolute path. */
SVN_ERR (svn_wc_entries_read (&entries, path, pool));
/* Recursively loop over all children. */
for (hi = apr_hash_first (pool, entries); hi; hi = apr_hash_next (hi))
{
const void *key;
void *val;
const char *name;
svn_wc_entry_t *current_entry;
apr_hash_this (hi, &key, NULL, &val);
name = (const char *) key;
current_entry = (svn_wc_entry_t *) val;
/* Ignore the "this dir" entry. */
if (! strcmp (name, SVN_WC_ENTRY_THIS_DIR))
continue;
/* Create child path by telescoping the main path. */
svn_path_add_component_nts (path, name);
/* Recurse, but only allow further recursion if the child is
a directory. */
SVN_ERR (svn_wc_process_committed
(path,
(current_entry->kind == svn_node_dir) ? TRUE : FALSE,
new_revnum, rev_date, rev_author, subpool));
/* De-telescope the path. */
svn_path_remove_component (path);
svn_pool_clear (subpool);
}
svn_pool_destroy (subpool);
}
return SVN_NO_ERROR;
}
/* Remove FILE if it exists and is a file. If it does not exist, do
nothing. If it is not a file, error. */
static svn_error_t *
remove_file_if_present (svn_stringbuf_t *file, apr_pool_t *pool)
{
svn_node_kind_t kind;
/* Does this file exist? If not, get outta here. */
SVN_ERR (svn_io_check_path (file->data, &kind, pool));
if (kind == svn_node_none)
return SVN_NO_ERROR;
/* Else, remove the file. */
return svn_io_remove_file (file->data, pool);
}
/* Recursively mark a tree DIR for with a SCHEDULE and/or EXISTENCE
flag and/or COPIED flag, depending on the state of MODIFY_FLAGS. */
static svn_error_t *
mark_tree (svn_stringbuf_t *dir,
apr_uint32_t modify_flags,
svn_wc_schedule_t schedule,
svn_boolean_t copied,
svn_wc_notify_func_t notify_func,
void *notify_baton,
apr_pool_t *pool)
{
apr_pool_t *subpool = svn_pool_create (pool);
apr_hash_t *entries;
apr_hash_index_t *hi;
svn_stringbuf_t *fullpath = svn_stringbuf_dup (dir, pool);
svn_wc_entry_t *entry;
/* Read the entries file for this directory. */
SVN_ERR (svn_wc_entries_read (&entries, dir, pool));
/* Mark each entry in the entries file. */
for (hi = apr_hash_first (pool, entries); hi; hi = apr_hash_next (hi))
{
const void *key;
apr_ssize_t klen;
void *val;
svn_stringbuf_t *basename;
/* Get the next entry */
apr_hash_this (hi, &key, &klen, &val);
entry = (svn_wc_entry_t *) val;
/* Skip "this dir". */
if (! strcmp ((const char *)key, SVN_WC_ENTRY_THIS_DIR))
continue;
basename = svn_stringbuf_create ((const char *) key, subpool);
svn_path_add_component (fullpath, basename);
/* If this is a directory, recurse. */
if (entry->kind == svn_node_dir)
SVN_ERR (mark_tree (fullpath, modify_flags,
schedule, copied,
notify_func, notify_baton,
subpool));
/* Mark this entry. */
entry->schedule = schedule;
entry->copied = copied;
SVN_ERR (svn_wc__entry_modify (dir, basename, entry,
modify_flags, pool));
/* Tell someone what we've done. */
if (schedule == svn_wc_schedule_delete && notify_func != NULL)
(*notify_func) (notify_baton, svn_wc_notify_delete, fullpath->data);
/* Reset FULLPATH to just hold this dir's name. */
svn_stringbuf_set (fullpath, dir->data);
/* Clear our per-iteration pool. */
svn_pool_clear (subpool);
}
/* Handle "this dir" for states that need it done post-recursion. */
entry = apr_hash_get (entries, SVN_WC_ENTRY_THIS_DIR, APR_HASH_KEY_STRING);
entry->schedule = schedule;
entry->copied = copied;
SVN_ERR (svn_wc__entry_modify (dir, NULL, entry, modify_flags, pool));
/* Destroy our per-iteration pool. */
svn_pool_destroy (subpool);
return SVN_NO_ERROR;
}
svn_error_t *
svn_wc_delete (svn_stringbuf_t *path,
svn_wc_notify_func_t notify_func,
void *notify_baton,
apr_pool_t *pool)
{
svn_stringbuf_t *dir, *basename;
svn_wc_entry_t *entry;
svn_boolean_t dir_unadded = FALSE;
/* Get the entry for the path we are deleting. */
SVN_ERR (svn_wc_entry (&entry, path, pool));
if (! entry)
return svn_error_createf
(SVN_ERR_ENTRY_NOT_FOUND, 0, NULL, pool,
"'%s' does not appear to be under revision control", path->data);
if (entry->kind == svn_node_dir)
{
/* Special case, delete of a newly added dir. */
if (entry->schedule == svn_wc_schedule_add)
dir_unadded = TRUE;
else
/* Recursively mark a whole tree for deletion. */
SVN_ERR (mark_tree (path, SVN_WC__ENTRY_MODIFY_SCHEDULE,
svn_wc_schedule_delete, FALSE,
notify_func, notify_baton,
pool));
}
/* Deleting a directory that has been added but not yet
committed is easy, just remove the adminstrative dir. */
if (dir_unadded)
{
svn_stringbuf_t *this_dir =
svn_stringbuf_create (SVN_WC_ENTRY_THIS_DIR, pool);
SVN_ERR (svn_wc_remove_from_revision_control (path,
this_dir,
FALSE, pool));
}
else
{
/* We need to mark this entry for deletion in its parent's entries
file, so we split off basename from the parent path, then fold in
the addition of a delete flag. */
svn_path_split (path, &dir, &basename, pool);
if (svn_path_is_empty (dir))
svn_stringbuf_set (dir, ".");
entry->schedule = svn_wc_schedule_delete;
SVN_ERR (svn_wc__entry_modify (dir, basename, entry,
SVN_WC__ENTRY_MODIFY_SCHEDULE, pool));
}
/* Report the deletion to the caller. */
if (notify_func != NULL)
(*notify_func) (notify_baton, svn_wc_notify_delete, path->data);
return SVN_NO_ERROR;
}
svn_error_t *
svn_wc_get_ancestry (svn_stringbuf_t **url,
svn_revnum_t *rev,
svn_stringbuf_t *path,
apr_pool_t *pool)
{
svn_wc_entry_t *ent;
SVN_ERR (svn_wc_entry (&ent, path, pool));
*url = svn_stringbuf_dup (ent->url, pool);
*rev = ent->revision;
return SVN_NO_ERROR;
}
svn_error_t *
svn_wc_add (svn_stringbuf_t *path,
svn_stringbuf_t *copyfrom_url,
svn_revnum_t copyfrom_rev,
svn_wc_notify_func_t notify_func,
void *notify_baton,
apr_pool_t *pool)
{
svn_stringbuf_t *parent_dir, *basename;
svn_wc_entry_t *orig_entry, *parent_entry, tmp_entry;
svn_boolean_t is_replace = FALSE;
enum svn_node_kind kind;
apr_uint32_t modify_flags = 0;
/* Make sure something's there. */
SVN_ERR (svn_io_check_path (path->data, &kind, pool));
if (kind == svn_node_none)
return svn_error_createf (SVN_ERR_WC_PATH_NOT_FOUND, 0, NULL, pool,
"'%s' not found", path->data);
/* Get the original entry for this path if one exists (perhaps
this is actually a replacement of a previously deleted thing). */
if (svn_wc_entry (&orig_entry, path, pool))
orig_entry = NULL;
/* You can only add something that is not in revision control, or
that is slated for deletion from revision control, unless, of
course, you're specifying an addition with -history-; then it's
okay for the object to be under version control already; it's not
really new. */
if (orig_entry)
{
if ((! copyfrom_url) && (orig_entry->schedule != svn_wc_schedule_delete))
{
return svn_error_createf
(SVN_ERR_ENTRY_EXISTS, 0, NULL, pool,
"'%s' is already under revision control", path->data);
}
else if (orig_entry->kind != kind)
{
/* ### todo: At some point, we obviously don't want to block
replacements where the node kind changes. When this
happens, svn_wc_revert() needs to learn how to revert
this situation. */
return svn_error_createf
(SVN_ERR_UNSUPPORTED_FEATURE, 0, NULL, pool,
"Could not replace '%s' with a node of a differing type"
" -- try committing your deletion first and then re-adding '%s'",
path->data, path->data);
}
if (orig_entry->schedule == svn_wc_schedule_delete)
is_replace = TRUE;
}
/* Split off the basename from the parent directory. */
svn_path_split (path, &parent_dir, &basename, pool);
if (svn_path_is_empty (parent_dir))
parent_dir = svn_stringbuf_create (".", pool);
/* Init the modify flags. */
modify_flags = SVN_WC__ENTRY_MODIFY_SCHEDULE | SVN_WC__ENTRY_MODIFY_KIND;;
if (! (is_replace || copyfrom_url))
modify_flags |= SVN_WC__ENTRY_MODIFY_REVISION;
/* If a copy ancestor was given, put the proper ancestry info in a hash. */
if (copyfrom_url)
{
tmp_entry.copyfrom_url = copyfrom_url;
tmp_entry.copyfrom_rev = copyfrom_rev;
tmp_entry.copied = TRUE;
modify_flags |= SVN_WC__ENTRY_MODIFY_COPYFROM_URL;
modify_flags |= SVN_WC__ENTRY_MODIFY_COPYFROM_REV;
modify_flags |= SVN_WC__ENTRY_MODIFY_COPIED;
}
tmp_entry.revision = 0;
tmp_entry.kind = kind;
tmp_entry.schedule = svn_wc_schedule_add;
/* Now, add the entry for this item to the parent_dir's
entries file, marking it for addition. */
SVN_ERR (svn_wc__entry_modify (parent_dir, basename, &tmp_entry,
modify_flags, pool));
/* If this is a replacement, we need to reset the properties for
PATH. */
if (orig_entry)
{
svn_stringbuf_t *prop_path;
SVN_ERR (svn_wc__prop_path (&prop_path, path, FALSE, pool));
SVN_ERR (remove_file_if_present (prop_path, pool));
}
if (kind == svn_node_file)
{
const char *mimetype;
/* Try to detect the mime-type of this new addition. */
SVN_ERR (svn_io_detect_mimetype (&mimetype, path->data, pool));
if (mimetype)
{
svn_string_t mt_str;
mt_str.data = mimetype;
mt_str.len = strlen(mimetype);
SVN_ERR (svn_wc_prop_set (SVN_PROP_MIME_TYPE, &mt_str, path->data,
pool));
}
}
else /* scheduling a directory for addition */
{
svn_wc_entry_t *p_entry;
svn_stringbuf_t *p_path;
/* Get the entry for this directory's parent. We need to snatch
the ancestor path out of there. */
SVN_ERR (svn_wc_entry (&p_entry, parent_dir, pool));
/* Derive the parent path for our new addition here. */
p_path = svn_stringbuf_dup (p_entry->url, pool);
svn_path_add_component (p_path, basename);
/* Make sure this new directory has an admistrative subdirectory
created inside of it */
SVN_ERR (svn_wc__ensure_adm (path, p_path, 0, pool));
/* We're making the same mods we made above, but this time we'll
force the scheduling. */
modify_flags |= SVN_WC__ENTRY_MODIFY_FORCE;
tmp_entry.schedule = is_replace
? svn_wc_schedule_replace
: svn_wc_schedule_add;
SVN_ERR (svn_wc__entry_modify (path, NULL, &tmp_entry,
modify_flags, pool));
if (copyfrom_url)
{
/* If this new directory has ancestry, it's not enough to
schedule it for addition with copyfrom args. We also
need to rewrite its ancestor-url, and rewrite the
ancestor-url of ALL its children!
We're doing this because our current commit model (for
hysterical raisins, presumably) assumes an entry's URL is
correct before commit -- i.e. the URL is not tweaked in
the post-commit bumping process. We might want to change
this model someday. */
/* Figure out what the new url should be. */
svn_stringbuf_t *url;
SVN_ERR (svn_wc_entry (&parent_entry, parent_dir, pool));
url = svn_stringbuf_dup (parent_entry->url, pool);
svn_path_add_component (url, basename);
/* Change the entry urls recursively (but not the working rev). */
SVN_ERR (svn_wc__do_update_cleanup (path, TRUE, /* recursive */
url, SVN_INVALID_REVNUM, pool));
/* Recursively add the 'copied' existence flag as well! */
SVN_ERR (mark_tree (path, SVN_WC__ENTRY_MODIFY_COPIED,
svn_wc_schedule_normal, TRUE,
NULL, NULL, /* N/A cuz we aren't deleting */
pool));
/* Clean out the now-obsolete wcprops. */
SVN_ERR (svn_wc__remove_wcprops (path, pool));
}
}
/* Report the addition to the caller. */
if (notify_func != NULL)
(*notify_func) (notify_baton, svn_wc_notify_add, path->data);
return SVN_NO_ERROR;
}
/* Thoughts on Reversion.
What does is mean to revert a given PATH in a tree? We'll
consider things by their modifications.
Adds
- For files, svn_wc_remove_from_revision_control(), baby.
- Added directories may contain nothing but added children, and
reverting the addition of a directory necessary means reverting
the addition of all the directory's children. Again,
svn_wc_remove_from_revision_control() should do the trick.
Deletes
- Restore properties to their unmodified state.
- For files, restore the pristine contents, and reset the schedule
to 'normal'.
- For directories, reset the schedule to 'normal'. All children
of a directory marked for deletion must also be marked for
deletion, but it's okay for those children to remain deleted even
if their parent directory is restored. That's what the
recursive flag is for.
Replaces
- Restore properties to their unmodified state.
- For files, restore the pristine contents, and reset the schedule
to 'normal'.
- For directories, reset the schedule to normal. A replaced
directory can have deleted children (left over from the initial
deletion), replaced children (children of the initial deletion
now re-added), and added children (new entries under the
replaced directory). Since this is technically an addition, it
necessitates recursion.
Modifications
- Restore properties and, for files, contents to their unmodified
state.
*/
/* Return a new wrapping of error ERR regarding the revert subcommand,
while doing VERB on PATH. Use POOL for allocations.
*/
static svn_error_t *
revert_error (svn_error_t *err,
svn_stringbuf_t *path,
const char *verb,
apr_pool_t *pool)
{
return svn_error_quick_wrap
(err, apr_psprintf (pool, "revert: error %s for `%s'", verb, path->data));
}
/* Revert ENTRY for NAME in directory PARENT_DIR, altering
*MODIFY_FLAGS to indicate what parts of the entry were reverted
(for example, if property changes were reverted, then set the
SVN_WC__ENTRY_MODIFY_PROP_TIME bit in MODIFY_FLAGS).
Use POOL for any temporary allocations.*/
static svn_error_t *
revert_admin_things (svn_stringbuf_t *parent_dir,
svn_stringbuf_t *name,
svn_wc_entry_t *entry,
apr_uint32_t *modify_flags,
apr_pool_t *pool)
{
svn_stringbuf_t *fullpath, *thing, *pthing;
enum svn_node_kind kind;
svn_boolean_t modified_p;
svn_error_t *err;
apr_time_t tstamp;
/* Build the full path of the thing we're reverting. */
fullpath = svn_stringbuf_dup (parent_dir, pool);
if (name && (strcmp (name->data, SVN_WC_ENTRY_THIS_DIR)))
svn_path_add_component (fullpath, name);
/* Check for prop changes. */
SVN_ERR (svn_wc_props_modified_p (&modified_p, fullpath, pool));
if (modified_p)
{
SVN_ERR (svn_wc__prop_path (&thing, fullpath, 0, pool));
SVN_ERR (svn_wc__prop_base_path (&pthing, fullpath, 0, pool));
/* If there is a pristing property file, copy it out as the
working property file, else just remove the working property
file. */
SVN_ERR (svn_io_check_path (pthing->data, &kind, pool));
if (kind == svn_node_file)
{
if ((err = svn_io_set_file_read_write (thing->data, FALSE, pool)))
return revert_error (err, fullpath, "restoring props", pool);
if ((err = svn_io_copy_file (pthing->data, thing->data, FALSE, pool)))
return revert_error (err, fullpath, "restoring props", pool);
SVN_ERR (svn_io_file_affected_time (&tstamp, thing, pool));
entry->prop_time = tstamp;
}
else
{
if ((err = svn_io_set_file_read_write (thing->data, FALSE, pool)))
return revert_error (err, fullpath, "removing props", pool);
if ((err = svn_io_remove_file (thing->data, pool)))
return revert_error (err, fullpath, "removing props", pool);
}
/* Modify our entry structure. */
*modify_flags |= SVN_WC__ENTRY_MODIFY_PROP_TIME;
}
if (entry->kind == svn_node_file)
{
SVN_ERR (svn_io_check_path (fullpath->data, &kind, pool));
SVN_ERR (svn_wc_text_modified_p (&modified_p, fullpath, pool));
if ((modified_p) || (kind == svn_node_none))
{
/* If there are textual mods (or if the working file is
missing altogether), copy the text-base out into
the working copy, and update the timestamp in the entries
file. */
svn_wc_keywords_t *keywords;
enum svn_wc__eol_style eol_style;
const char *eol;
pthing = svn_wc__text_base_path (fullpath, 0, pool);
SVN_ERR (svn_wc__get_eol_style
(&eol_style, &eol, fullpath->data, pool));
SVN_ERR (svn_wc__get_keywords
(&keywords, fullpath->data, NULL, pool));
/* When copying the text-base out to the working copy, make
sure to do any eol translations or keyword substitutions,
as dictated by the property values. If these properties
are turned off, then this is just a normal copy. */
if ((err = svn_wc_copy_and_translate (pthing->data,
fullpath->data,
eol, FALSE, /* don't repair */
keywords,
TRUE, /* expand keywords */
pool)))
return revert_error (err, fullpath, "restoring text", pool);
/* Modify our entry structure. */
SVN_ERR (svn_io_file_affected_time (&tstamp, fullpath, pool));
*modify_flags |= SVN_WC__ENTRY_MODIFY_TEXT_TIME;
entry->text_time = tstamp;
}
}
/* Remove conflict state (and conflict files), if any. */
if (entry->prejfile || entry->conflict_old
|| entry->conflict_new || entry->conflict_wrk)
{
svn_stringbuf_t *rmfile;
/* Handle the three possible text conflict files. */
if (entry->conflict_old)
{
rmfile = svn_stringbuf_dup (parent_dir, pool);
svn_path_add_component (rmfile, entry->conflict_old);
SVN_ERR (remove_file_if_present (rmfile, pool));
*modify_flags |= SVN_WC__ENTRY_MODIFY_CONFLICT_OLD;
}
if (entry->conflict_new)
{
rmfile = svn_stringbuf_dup (parent_dir, pool);
svn_path_add_component (rmfile, entry->conflict_new);
SVN_ERR (remove_file_if_present (rmfile, pool));
*modify_flags |= SVN_WC__ENTRY_MODIFY_CONFLICT_NEW;
}
if (entry->conflict_wrk)
{
rmfile = svn_stringbuf_dup (parent_dir, pool);
svn_path_add_component (rmfile, entry->conflict_wrk);
SVN_ERR (remove_file_if_present (rmfile, pool));
*modify_flags |= SVN_WC__ENTRY_MODIFY_CONFLICT_WRK;
}
/* Remove the prej-file if the entry lists one (and it exists) */
if (entry->prejfile)
{
rmfile = svn_stringbuf_dup (parent_dir, pool);
svn_path_add_component (rmfile, entry->prejfile);
SVN_ERR (remove_file_if_present (rmfile, pool));
*modify_flags |= SVN_WC__ENTRY_MODIFY_PREJFILE;
}
}
return SVN_NO_ERROR;
}
svn_error_t *
svn_wc_revert (svn_stringbuf_t *path,
svn_boolean_t recursive,
svn_wc_notify_func_t notify_func,
void *notify_baton,
apr_pool_t *pool)
{
enum svn_node_kind kind;
svn_stringbuf_t *p_dir = NULL, *bname = NULL;
svn_wc_entry_t *entry;
svn_boolean_t wc_root, reverted = FALSE;
apr_uint32_t modify_flags = 0;
/* Safeguard 1: is this a versioned resource? */
SVN_ERR (svn_wc_entry (&entry, path, pool));
if (! entry)
return svn_error_createf
(SVN_ERR_ENTRY_NOT_FOUND, 0, NULL, pool,
"Cannot revert '%s' -- not a versioned resource", path->data);
/* Safeguard 2: can we handle this node kind? */
if ((entry->kind != svn_node_file) && (entry->kind != svn_node_dir))
return svn_error_createf
(SVN_ERR_UNSUPPORTED_FEATURE, 0, NULL, pool,
"Cannot revert '%s' -- unsupported entry node kind", path->data);
/* Safeguard 3: can we deal with the node kind of PATH current in
the working copy? */
SVN_ERR (svn_io_check_path (path->data, &kind, pool));
if ((kind != svn_node_none)
&& (kind != svn_node_file)
&& (kind != svn_node_dir))
return svn_error_createf
(SVN_ERR_UNSUPPORTED_FEATURE, 0, NULL, pool,
"Cannot revert '%s' -- unsupported node kind in working copy",
path->data);
/* Determine if PATH is a WC root. If PATH is a file, it should
definitely NOT be a WC root. */
SVN_ERR (svn_wc_is_wc_root (&wc_root, path, pool));
if (! wc_root)
{
/* Split the basename from the parent path. */
svn_path_split (path, &p_dir, &bname, pool);
if (svn_path_is_empty (p_dir))
p_dir = svn_stringbuf_create (".", pool);
}
/* Additions. */
if (entry->schedule == svn_wc_schedule_add)
{
/* Remove the item from revision control. */
if (entry->kind == svn_node_dir)
SVN_ERR (svn_wc_remove_from_revision_control
(path,
svn_stringbuf_create (SVN_WC_ENTRY_THIS_DIR, pool),
FALSE, pool));
else
SVN_ERR (svn_wc_remove_from_revision_control (p_dir, bname,
FALSE, pool));
/* Recursivity is taken care of by svn_wc_remove_from_revision_control,
and we've definitely reverted PATH at this point. */
recursive = FALSE;
reverted = TRUE;
}
/* Regular prop and text edit. */
else if (entry->schedule == svn_wc_schedule_normal)
{
/* Revert the prop and text mods (if any). */
if (entry->kind == svn_node_file)
SVN_ERR (revert_admin_things (p_dir, bname, entry, &modify_flags,
pool));
if (entry->kind == svn_node_dir)
SVN_ERR (revert_admin_things (path, NULL, entry, &modify_flags,
pool));
}
/* Deletions and replacements. */
else if ((entry->schedule == svn_wc_schedule_delete)
|| (entry->schedule == svn_wc_schedule_replace))
{
/* Revert the prop and text mods (if any). */
if (entry->kind == svn_node_file)
SVN_ERR (revert_admin_things (p_dir, bname, entry, &modify_flags,
pool));
if (entry->kind == svn_node_dir)
SVN_ERR (revert_admin_things (path, NULL, entry, &modify_flags,
pool));
modify_flags |= SVN_WC__ENTRY_MODIFY_SCHEDULE;
}
/* All our disk modifications should be finished by now. Let's
update our entries files. */
if (modify_flags)
{
/* Force recursion on replaced directories. */
if ((entry->kind == svn_node_dir)
&& (entry->schedule == svn_wc_schedule_replace))
recursive = TRUE;
/* Reset the schedule to normal. */
entry->schedule = svn_wc_schedule_normal;
entry->conflict_old = NULL;
entry->conflict_new = NULL;
entry->conflict_wrk = NULL;
entry->prejfile = NULL;
if (! wc_root)
SVN_ERR (svn_wc__entry_modify (p_dir, bname, entry,
modify_flags
| SVN_WC__ENTRY_MODIFY_FORCE,
pool));
/* For directories, reset the schedule to normal in the
directory itself. */
if (entry->kind == svn_node_dir)
SVN_ERR (svn_wc__entry_modify (path, NULL, entry,
SVN_WC__ENTRY_MODIFY_SCHEDULE
| SVN_WC__ENTRY_MODIFY_PREJFILE
| SVN_WC__ENTRY_MODIFY_FORCE,
pool));
/* Note that this was reverted. */
reverted = TRUE;
}
/* If PATH was reverted, tell our client that. */
if ((notify_func != NULL) && reverted)
(*notify_func) (notify_baton, svn_wc_notify_revert, path->data);
/* Finally, recurse if requested. */
if (recursive && (entry->kind == svn_node_dir))
{
apr_hash_t *entries;
apr_hash_index_t *hi;
svn_stringbuf_t *full_entry_path = svn_stringbuf_dup (path, pool);
SVN_ERR (svn_wc_entries_read (&entries, path, pool));
for (hi = apr_hash_first (pool, entries); hi; hi = apr_hash_next (hi))
{
const void *key;
const char *keystring;
apr_ssize_t klen;
void *val;
/* Get the next entry */
apr_hash_this (hi, &key, &klen, &val);
keystring = (const char *) key;
/* Skip "this dir" */
if (! strcmp (keystring, SVN_WC_ENTRY_THIS_DIR))
continue;
/* Add the entry name to FULL_ENTRY_PATH. */
svn_path_add_component_nts (full_entry_path, keystring);
/* Revert the entry. */
SVN_ERR (svn_wc_revert (full_entry_path, TRUE,
notify_func, notify_baton, pool));
/* Return FULL_ENTRY_PATH to its pre-appended state. */
svn_stringbuf_set (full_entry_path, path->data);
}
}
return SVN_NO_ERROR;
}
svn_error_t *
svn_wc_get_pristine_copy_path (svn_stringbuf_t *path,
svn_stringbuf_t **pristine_path,
apr_pool_t *pool)
{
*pristine_path = svn_wc__text_base_path (path, FALSE, pool);
return SVN_NO_ERROR;
}
svn_error_t *
svn_wc_remove_from_revision_control (svn_stringbuf_t *path,
svn_stringbuf_t *name,
svn_boolean_t destroy_wf,
apr_pool_t *pool)
{
apr_status_t apr_err;
svn_error_t *err;
svn_boolean_t is_file;
svn_boolean_t left_a_file = FALSE;
apr_pool_t *subpool = svn_pool_create (pool);
apr_hash_t *entries = NULL;
svn_stringbuf_t *full_path = svn_stringbuf_dup (path, pool);
/* NAME is either a file's basename or SVN_WC_ENTRY_THIS_DIR. */
is_file = (strcmp (name->data, SVN_WC_ENTRY_THIS_DIR)) ? TRUE : FALSE;
if (is_file)
{
svn_path_add_component (full_path, name);
if (destroy_wf)
{
/* Check for local mods. */
svn_boolean_t text_modified_p;
SVN_ERR (svn_wc_text_modified_p (&text_modified_p, full_path,
subpool));
if (text_modified_p) /* don't kill local mods */
{
return svn_error_create (SVN_ERR_WC_LEFT_LOCAL_MOD,
0, NULL, subpool, "");
}
else
{
/* The working file is still present; remove it. */
SVN_ERR (remove_file_if_present (full_path, subpool));
}
}
/* Remove NAME from PATH's entries file: */
SVN_ERR (svn_wc_entries_read (&entries, path, pool));
svn_wc__entry_remove (entries, name);
SVN_ERR (svn_wc__entries_write (entries, path, pool));
/* Remove text-base/NAME.svn-base, prop/NAME, prop-base/NAME.svn-base,
wcprops/NAME */
{
svn_stringbuf_t *svn_thang;
/* Text base. */
svn_thang = svn_wc__text_base_path (full_path, 0, subpool);
SVN_ERR (remove_file_if_present (svn_thang, subpool));
/* Working prop file. */
SVN_ERR (svn_wc__prop_path (&svn_thang, full_path, 0, subpool));
SVN_ERR (remove_file_if_present (svn_thang, subpool));
/* Prop base file. */
SVN_ERR (svn_wc__prop_base_path (&svn_thang, full_path, 0, subpool));
SVN_ERR (remove_file_if_present (svn_thang, subpool));
/* wc-prop file. */
SVN_ERR (svn_wc__wcprop_path (&svn_thang, full_path, 0, subpool));
SVN_ERR (remove_file_if_present (svn_thang, subpool));
}
} /* done with file case */
else /* looking at THIS_DIR */
{
svn_stringbuf_t *parent_dir, *basename;
apr_hash_index_t *hi;
/* ### sanity check: check 2 places for DELETED flag? */
/* Remove self from parent's entries file */
svn_path_split (full_path, &parent_dir, &basename, pool);
if (svn_path_is_empty (parent_dir))
svn_stringbuf_set (parent_dir, ".");
/* ### sanity check: is parent_dir even a working copy?
if not, it should not be a fatal error. we're just removing
the top of the wc. */
SVN_ERR (svn_wc_entries_read (&entries, parent_dir, pool));
svn_wc__entry_remove (entries, basename);
SVN_ERR (svn_wc__entries_write (entries, parent_dir, pool));
/* Recurse on each file and dir entry. */
SVN_ERR (svn_wc_entries_read (&entries, path, subpool));
for (hi = apr_hash_first (subpool, entries);
hi;
hi = apr_hash_next (hi))
{
const void *key;
apr_ssize_t klen;
void *val;
svn_stringbuf_t *current_entry_name;
svn_wc_entry_t *current_entry;
apr_hash_this (hi, &key, &klen, &val);
current_entry = (svn_wc_entry_t *) val;
if (! strcmp ((const char *)key, SVN_WC_ENTRY_THIS_DIR))
current_entry_name = NULL;
else
current_entry_name = svn_stringbuf_create((const char *)key,
subpool);
if (current_entry->kind == svn_node_file)
{
err = svn_wc_remove_from_revision_control (path,
current_entry_name,
destroy_wf, subpool);
if (err && (err->apr_err == SVN_ERR_WC_LEFT_LOCAL_MOD))
{
svn_error_clear_all (err);
left_a_file = TRUE;
}
else if (err)
return err;
}
else if (current_entry_name && (current_entry->kind == svn_node_dir))
{
svn_stringbuf_t *this_dir = svn_stringbuf_create
(SVN_WC_ENTRY_THIS_DIR, subpool);
svn_stringbuf_t *entrypath = svn_stringbuf_dup (path, subpool);
svn_path_add_component (entrypath, current_entry_name);
err = svn_wc_remove_from_revision_control (entrypath, this_dir,
destroy_wf, subpool);
if (err && (err->apr_err == SVN_ERR_WC_LEFT_LOCAL_MOD))
{
svn_error_clear_all (err);
left_a_file = TRUE;
}
else if (err)
return err;
}
}
/* At this point, every directory below this one has been
removed from revision control. */
/* Remove the entire administrative .svn area, thereby removing
_this_ dir from revision control too. */
SVN_ERR (svn_wc__adm_destroy (path, subpool));
/* If caller wants us to recursively nuke everything on disk, go
ahead, provided that there are no dangling local-mod files
below */
if (destroy_wf && (! left_a_file))
{
/* If the dir is *truly* empty (i.e. has no unversioned
resources, all versioned files are gone, all .svn dirs are
gone, and contains nothing but empty dirs), then a
*non*-recursive dir_remove should work. If it doesn't,
no big deal. Just assume there are unversioned items in
there and set "left_a_file" */
apr_err = apr_dir_remove (path->data, subpool);
if (apr_err)
left_a_file = TRUE;
}
} /* end of directory case */
svn_pool_destroy (subpool);
if (left_a_file)
return svn_error_create (SVN_ERR_WC_LEFT_LOCAL_MOD, 0, NULL, pool, "");
else
return SVN_NO_ERROR;
}
/* Helper for svn_wc_resolve_conflict */
static svn_error_t *
attempt_deletion (svn_stringbuf_t *parent_dir,
svn_stringbuf_t *basename,
apr_pool_t *pool)
{
svn_stringbuf_t *full_path = svn_stringbuf_dup (parent_dir, pool);
svn_path_add_component (full_path, basename);
return remove_file_if_present (full_path, pool);
}
svn_error_t *
svn_wc_resolve_conflict (svn_stringbuf_t *path,
svn_wc_notify_func_t notify_func,
void *notify_baton,
apr_pool_t *pool)
{
svn_stringbuf_t *parent, *basename;
svn_boolean_t text_conflict, prop_conflict;
svn_wc_entry_t *entry = NULL;
/* Feh, ignoring the return value here. We just want to know
whether we got the entry or not. */
svn_wc_entry (&entry, path, pool);
if (! entry)
return svn_error_createf (SVN_ERR_ENTRY_NOT_FOUND, 0, NULL, pool,
"Not under version control: '%s'", path->data);
svn_path_split (path, &parent, &basename, pool);
/* Sanity check: see if libsvn_wc thinks this item is in a state of
conflict at all. If not, just go home.*/
SVN_ERR (svn_wc_conflicted_p (&text_conflict, &prop_conflict,
parent, entry, pool));
if ((! text_conflict) && (! prop_conflict))
return SVN_NO_ERROR;
/* Yes indeed, being able to map a function over a list would be nice. */
if (entry->conflict_old)
SVN_ERR (attempt_deletion (parent, entry->conflict_old, pool));
if (entry->conflict_new)
SVN_ERR (attempt_deletion (parent, entry->conflict_new, pool));
if (entry->conflict_wrk)
SVN_ERR (attempt_deletion (parent, entry->conflict_wrk, pool));
if (entry->prejfile)
SVN_ERR (attempt_deletion (parent, entry->prejfile, pool));
if (notify_func)
{
/* Sanity check: see if libsvn_wc *still* thinks this item is in a
state of conflict. If not, report the successful resolution. */
SVN_ERR (svn_wc_conflicted_p (&text_conflict, &prop_conflict,
parent, entry, pool));
if ((! text_conflict) && (! prop_conflict))
(*notify_func) (notify_baton, svn_wc_notify_resolve, path->data);
}
return SVN_NO_ERROR;
}
svn_error_t *
svn_wc_get_auth_file (svn_stringbuf_t *path,
const char *filename,
svn_stringbuf_t **contents,
apr_pool_t *pool)
{
apr_file_t *file;
svn_stringbuf_t *fname = svn_stringbuf_create(filename, pool);
SVN_ERR (svn_wc__open_auth_file (&file, path, fname, APR_READ, pool));
/* Read the file's contents into a stringbuf, allocated in POOL. */
SVN_ERR (svn_string_from_aprfile (contents, file, pool));
SVN_ERR (svn_wc__close_auth_file (file, path, fname,
0 /* Don't sync */, pool));
return SVN_NO_ERROR;
}
svn_error_t *
svn_wc_set_auth_file (svn_stringbuf_t *path,
svn_boolean_t recurse,
const char *filename,
svn_stringbuf_t *contents,
apr_pool_t *pool)
{
apr_status_t status;
apr_file_t *fp;
apr_size_t sz;
svn_stringbuf_t *file = svn_stringbuf_create (filename, pool);
/* Create/overwrite the file in PATH's administrative area.
(In reality, this opens a file 'path/.svn/tmp/auth/filename'.) */
SVN_ERR (svn_wc__open_auth_file (&fp, path, file,
(APR_WRITE | APR_CREATE | APR_TRUNCATE),
pool));
status = apr_file_write_full (fp, contents->data, contents->len, &sz);
if (status)
return svn_error_createf (status, 0, NULL, pool,
"error writing to auth file '%s' in '%s'",
filename, path->data);
SVN_ERR (svn_wc__close_auth_file (fp, path, file, TRUE /* sync */, pool));
if (recurse)
{
/* Loop over PATH's entries, and recurse into directories. */
apr_hash_index_t *hi;
apr_hash_t *entries;
const char *basename;
svn_wc_entry_t *entry;
SVN_ERR (svn_wc_entries_read (&entries, path, pool));
for (hi = apr_hash_first (pool, entries); hi; hi = apr_hash_next (hi))
{
const void *key;
apr_ssize_t keylen;
void *val;
apr_hash_this (hi, &key, &keylen, &val);
basename = (const char *) key;
entry = (svn_wc_entry_t *) val;
if ((entry->kind == svn_node_dir)
&& (strcmp (basename, SVN_WC_ENTRY_THIS_DIR)))
{
svn_stringbuf_t *childpath;
childpath = svn_stringbuf_dup (path, pool);
svn_path_add_component (childpath,
svn_stringbuf_create (basename, pool));
SVN_ERR (svn_wc_set_auth_file (childpath, TRUE,
filename, contents, pool));
}
}
}
return SVN_NO_ERROR;
}
/*
* local variables:
* eval: (load-file "../../tools/dev/svn-dev.el")
* end:
*/