blob: 88f0e543f644054bde650c2039a56c2f6bcac444 [file] [log] [blame]
/*
* copy.c: wc 'copy' functionality.
*
* ====================================================================
* 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/.
* ====================================================================
*/
/* ==================================================================== */
/*** Includes. ***/
#include <string.h>
#include "svn_wc.h"
#include "svn_client.h"
#include "svn_string.h"
#include "svn_pools.h"
#include "svn_error.h"
#include "svn_path.h"
#include "wc.h"
#include "adm_files.h"
#include "props.h"
/*** Code. ***/
svn_error_t *
svn_wc__remove_wcprops (svn_stringbuf_t *path, apr_pool_t *pool)
{
apr_hash_t *entries;
apr_hash_index_t *hi;
svn_stringbuf_t *wcprop_path;
apr_pool_t *subpool = svn_pool_create (pool);
enum svn_node_kind kind;
SVN_ERR (svn_io_check_path (path->data, &kind, subpool));
if (kind != svn_node_dir)
return svn_error_createf
(SVN_ERR_WC_NOT_DIRECTORY, 0, NULL, pool,
"svn_wc__remove_wcprops: '%s' is not a directory.", path->data);
/* Read PATH's entries. */
SVN_ERR (svn_wc_entries_read (&entries, path, subpool));
/* Remove this_dir's wcprops */
SVN_ERR (svn_wc__wcprop_path (&wcprop_path, path, 0, subpool));
(void) apr_file_remove (wcprop_path->data, 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_path;
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;
child_path = svn_stringbuf_dup (path, subpool);
svn_path_add_component_nts (child_path, name);
/* If a file, remove it from wcprops. */
if (current_entry->kind == svn_node_file)
{
SVN_ERR (svn_wc__wcprop_path (&wcprop_path, child_path, 0, subpool));
(void) apr_file_remove (wcprop_path->data, subpool);
/* ignoring any error value from the removal; most likely,
apr_file_remove will complain about trying to a remove a
file that's not there. But this more efficient than
doing an independant stat for each file's existence
before trying to remove it, no? */
}
/* If a dir, recurse. */
else if (current_entry->kind == svn_node_dir)
SVN_ERR (svn_wc__remove_wcprops (child_path, subpool));
}
/* Cleanup */
svn_pool_destroy (subpool);
return SVN_NO_ERROR;
}
/* This function effectively creates and schedules a file for
addition, but does extra administrative things to allow it to
function as a 'copy'.
ASSUMPTIONS:
- src_path points to a file under version control
- dst_parent points to a dir under version control, in the same
working copy.
- dst_basename will be the 'new' name of the copied file in dst_parent
*/
static svn_error_t *
copy_file_administratively (svn_stringbuf_t *src_path,
svn_stringbuf_t *dst_parent,
svn_stringbuf_t *dst_basename,
svn_wc_notify_func_t notify_copied,
void *notify_baton,
apr_pool_t *pool)
{
enum svn_node_kind dst_kind;
svn_wc_entry_t *src_entry;
/* The 'dst_path' is simply dst_parent/dst_basename */
svn_stringbuf_t *dst_path = svn_stringbuf_dup (dst_parent, pool);
svn_path_add_component (dst_path, dst_basename);
/* Sanity check: if dst file exists already, don't allow overwrite. */
SVN_ERR (svn_io_check_path (dst_path->data, &dst_kind, pool));
if (dst_kind != svn_node_none)
return svn_error_createf (SVN_ERR_ENTRY_EXISTS, 0, NULL, pool,
"'%s' already exists and is in the way.",
dst_path->data);
/* Sanity check: you cannot make a copy of something that's not
in the repository. See comment at the bottom of this file for an
explanation. */
SVN_ERR (svn_wc_entry (&src_entry, src_path, pool));
if (! src_entry)
{
return svn_error_createf
(SVN_ERR_UNVERSIONED_RESOURCE, 0, NULL, pool,
"Cannot copy or move '%s' -- it's not under revision control",
src_path->data);
}
else if ((src_entry->schedule == svn_wc_schedule_add) || (! src_entry->url))
{
return svn_error_createf
(SVN_ERR_UNSUPPORTED_FEATURE, 0, NULL, pool,
"Cannot copy or move '%s' -- it's not in the repository yet.\n"
"Try committing first.",
src_path->data);
}
/* Now, make an actual copy of the working file. */
SVN_ERR (svn_io_copy_file (src_path->data, dst_path->data, TRUE, pool));
/* Copy the pristine text-base over. Why? Because it's the *only*
way we can detect any upcoming local mods on the copy.
In other words, we're talking about the scenario where somebody
makes local mods to 'foo.c', then does an 'svn cp foo.c bar.c'.
In this case, bar.c should still be locally modified too.
Why do we want the copy to have local mods? Even though the user
will only see an 'A' instead of an 'M', local mods means that the
client doesn't have to send anything but a small delta during
commit; the server can make efficient use of the copyfrom args.
As long as we're copying the text-base over, we should copy the
working and pristine propfiles over too. */
{
enum svn_node_kind kind;
svn_stringbuf_t *src_wprop, *src_bprop, *dst_wprop, *dst_bprop;
/* Discover the paths to the two text-base files */
svn_stringbuf_t *src_txtb = svn_wc__text_base_path (src_path, FALSE, pool);
svn_stringbuf_t *dst_txtb = svn_wc__text_base_path (dst_path, FALSE, pool);
/* Discover the paths to the four prop files */
SVN_ERR (svn_wc__prop_path (&src_wprop, src_path, 0, pool));
SVN_ERR (svn_wc__prop_base_path (&src_bprop, src_path, 0, pool));
SVN_ERR (svn_wc__prop_path (&dst_wprop, dst_path, 0, pool));
SVN_ERR (svn_wc__prop_base_path (&dst_bprop, dst_path, 0, pool));
/* Copy the text-base over unconditionally. */
SVN_ERR (svn_io_copy_file (src_txtb->data, dst_txtb->data, TRUE, pool));
/* Copy the props over if they exist. */
SVN_ERR (svn_io_check_path (src_wprop->data, &kind, pool));
if (kind == svn_node_file)
SVN_ERR (svn_io_copy_file (src_wprop->data, dst_wprop->data, TRUE, pool));
/* Copy the base-props over if they exist */
SVN_ERR (svn_io_check_path (src_bprop->data, &kind, pool));
if (kind == svn_node_file)
SVN_ERR (svn_io_copy_file (src_bprop->data, dst_bprop->data, TRUE, pool));
}
/* Schedule the new file for addition in its parent, WITH HISTORY. */
{
svn_stringbuf_t *copyfrom_url;
svn_revnum_t copyfrom_rev;
SVN_ERR (svn_wc_get_ancestry (&copyfrom_url, &copyfrom_rev,
src_path, pool));
SVN_ERR (svn_wc_add (dst_path, copyfrom_url, copyfrom_rev,
notify_copied, notify_baton, pool));
}
return SVN_NO_ERROR;
}
/* This function effectively creates and schedules a dir for
addition, but does extra administrative things to allow it to
function as a 'copy'.
ASSUMPTIONS:
- src_path points to a dir under version control
- dst_parent points to a dir under version control, in the same
working copy.
- dst_basename will be the 'new' name of the copied dir in dst_parent
*/
static svn_error_t *
copy_dir_administratively (svn_stringbuf_t *src_path,
svn_stringbuf_t *dst_parent,
svn_stringbuf_t *dst_basename,
svn_wc_notify_func_t notify_copied,
void *notify_baton,
apr_pool_t *pool)
{
svn_wc_entry_t *src_entry;
/* The 'dst_path' is simply dst_parent/dst_basename */
svn_stringbuf_t *dst_path = svn_stringbuf_dup (dst_parent, pool);
svn_path_add_component (dst_path, dst_basename);
/* Sanity check: you cannot make a copy of something that's not
in the repository. See comment at the bottom of this file for an
explanation. */
SVN_ERR (svn_wc_entry (&src_entry, src_path, pool));
if ((src_entry->schedule == svn_wc_schedule_add)
|| (! src_entry->url))
return svn_error_createf
(SVN_ERR_UNSUPPORTED_FEATURE, 0, NULL, pool,
"Not allowed to copy or move '%s' -- it's not in the repository yet.\n"
"Try committing first.",
src_path->data);
/* Recursively copy the whole directory over.
(This gets us all text-base, props, base-props, as well as entries,
local mods, schedulings, existences, etc.) */
SVN_ERR (svn_io_copy_dir_recursively (src_path, dst_parent, dst_basename,
TRUE, pool));
/* Remove all wcprops in the directory, because they're all bogus
now. After the commit, ra_dav should regenerate them and
re-store them as an optimization. */
SVN_ERR (svn_wc__remove_wcprops (dst_path, pool));
/* Schedule the directory for addition in both its parent and itself
(this_dir) -- WITH HISTORY. This function should leave the
existing administrative dir untouched. */
{
svn_stringbuf_t *copyfrom_url;
svn_revnum_t copyfrom_rev;
SVN_ERR (svn_wc_get_ancestry (&copyfrom_url, &copyfrom_rev,
src_path, pool));
SVN_ERR (svn_wc_add (dst_path, copyfrom_url, copyfrom_rev,
notify_copied, notify_baton, pool));
}
return SVN_NO_ERROR;
}
/* Public Interface */
svn_error_t *
svn_wc_copy (svn_stringbuf_t *src_path,
svn_stringbuf_t *dst_parent,
svn_stringbuf_t *dst_basename,
svn_wc_notify_func_t notify_func,
void *notify_baton,
apr_pool_t *pool)
{
enum svn_node_kind src_kind;
SVN_ERR (svn_io_check_path (src_path->data, &src_kind, pool));
if (src_kind == svn_node_file)
SVN_ERR (copy_file_administratively (src_path, dst_parent, dst_basename,
notify_func, notify_baton, pool));
else if (src_kind == svn_node_dir)
SVN_ERR (copy_dir_administratively (src_path, dst_parent, dst_basename,
notify_func, notify_baton, pool));
return SVN_NO_ERROR;
}
/*
Rabbinic Commentary
Q: Why can't we 'svn cp' something that we just copied?
i.e. 'svn cp foo foo2; svn cp foo2 foo3"
A: It leads to inconsistencies.
In the example above, foo2 has no associated repository URL,
because it hasn't been committed yet. But suppose foo3 simply
inherited foo's URL (i.e. foo3 'pointed' to foo as a copy
ancestor by virtue of transitivity.)
For one, this is not what the user would expect. That's
certainly not what the user typed! Second, suppose that the
user did a commit between the two 'svn cp' commands. Now foo3
really *would* point to foo2, but without that commit, it
pointed to foo. Ugly inconsistency, and the user has no idea
that foo3's ancestor would be different in each case.
And even if somehow we *could* make foo3 point to foo2 before
foo2 existed in the repository... what's to prevent a user from
committing foo3 first? That would break.
*/
/*
* local variables:
* eval: (load-file "../../tools/dev/svn-dev.el")
* end: */