/*
* Copyright (C) 1986-2005 The Free Software Foundation, Inc.
*
* Portions Copyright (C) 1998-2005 Derek Price, Ximbiot <http://ximbiot.com>,
* and others.
*
* Portions Copyright (C) 1992, Brian Berliner and Jeff Polk
* Portions Copyright (C) 1989-1992, Brian Berliner
*
* You may distribute under the terms of the GNU General Public License as
* specified in the README file that comes with the CVS source distribution.
*
* Remove a File
*
* Removes entries from the present version. The entries will be removed from
* the RCS repository upon the next "commit".
*
* "remove" accepts no options, only file names that are to be removed. The
* file must not exist in the current directory for "remove" to work
* correctly.
*/
#include <sys/cdefs.h>
__RCSID("$NetBSD: remove.c,v 1.4 2016/05/17 14:00:09 christos Exp $");
#include "cvs.h"
#ifdef CLIENT_SUPPORT
static int remove_force_fileproc (void *callerdat,
struct file_info *finfo);
#endif
static int remove_fileproc (void *callerdat, struct file_info *finfo);
static Dtype remove_dirproc (void *callerdat, const char *dir,
const char *repos, const char *update_dir,
List *entries);
static int force;
static int local;
static int removed_files;
static int existing_files;
static const char *const remove_usage[] =
{
"Usage: %s %s [-flR] [files...]\n",
"\t-f\tDelete the file before removing it.\n",
"\t-l\tProcess this directory only (not recursive).\n",
"\t-R\tProcess directories recursively.\n",
"(Specify the --help global option for a list of other help options)\n",
NULL
};
int
cvsremove (int argc, char **argv)
{
int c, err;
if (argc == -1)
usage (remove_usage);
getoptreset ();
while ((c = getopt (argc, argv, "+flR")) != -1)
{
switch (c)
{
case 'f':
force = 1;
break;
case 'l':
local = 1;
break;
case 'R':
local = 0;
break;
case '?':
default:
usage (remove_usage);
break;
}
}
argc -= optind;
argv += optind;
wrap_setup ();
#ifdef CLIENT_SUPPORT
if (current_parsed_root->isremote) {
/* Call expand_wild so that the local removal of files will
work. It's ok to do it always because we have to send the
file names expanded anyway. */
expand_wild (argc, argv, &argc, &argv);
if (force)
{
if (!noexec)
{
start_recursion (remove_force_fileproc, NULL, NULL, NULL,
NULL, argc, argv, local, W_LOCAL,
0, CVS_LOCK_NONE, NULL, 0, NULL);
}
/* else FIXME should probably act as if the file doesn't exist
in doing the following checks. */
}
start_server ();
ign_setup ();
if (local)
send_arg("-l");
send_arg ("--");
/* FIXME: Can't we set SEND_NO_CONTENTS here? Needs investigation. */
send_files (argc, argv, local, 0, 0);
send_file_names (argc, argv, 0);
free_names (&argc, argv);
send_to_server ("remove\012", 0);
return get_responses_and_close ();
}
#endif
/* start the recursion processor */
err = start_recursion (remove_fileproc, NULL, remove_dirproc, NULL,
NULL, argc, argv, local, W_LOCAL, 0,
CVS_LOCK_READ, NULL, 1, NULL);
if (removed_files && !really_quiet)
error (0, 0, "use `%s commit' to remove %s permanently", program_name,
(removed_files == 1) ? "this file" : "these files");
if (existing_files)
error (0, 0,
((existing_files == 1) ?
"%d file exists; remove it first" :
"%d files exist; remove them first"),
existing_files);
return (err);
}
#ifdef CLIENT_SUPPORT
/*
* This is called via start_recursion if we are running as the client
* and the -f option was used. We just physically remove the file.
*/
/*ARGSUSED*/
static int
remove_force_fileproc (void *callerdat, struct file_info *finfo)
{
if (CVS_UNLINK (finfo->file) < 0 && ! existence_error (errno))
error (0, errno, "unable to remove %s", finfo->fullname);
return 0;
}
#endif
/*
* remove the file, only if it has already been physically removed
*/
/* ARGSUSED */
static int
remove_fileproc (void *callerdat, struct file_info *finfo)
{
Vers_TS *vers;
if (force)
{
if (!noexec)
{
if ( CVS_UNLINK (finfo->file) < 0 && ! existence_error (errno))
{
error (0, errno, "unable to remove %s", finfo->fullname);
}
}
/* else FIXME should probably act as if the file doesn't exist
in doing the following checks. */
}
vers = Version_TS (finfo, NULL, NULL, NULL, 0, 0);
if (vers->ts_user != NULL)
{
existing_files++;
if (!quiet)
error (0, 0, "file `%s' still in working directory",
finfo->fullname);
}
else if (vers->vn_user == NULL)
{
if (!quiet)
error (0, 0, "nothing known about `%s'", finfo->fullname);
}
else if (vers->vn_user[0] == '0' && vers->vn_user[1] == '\0')
{
char *fname;
/*
* It's a file that has been added, but not commited yet. So,
* remove the ,t file for it and scratch it from the
* entries file. */
Scratch_Entry (finfo->entries, finfo->file);
fname = Xasprintf ("%s/%s%s", CVSADM, finfo->file, CVSEXT_LOG);
if (unlink_file (fname) < 0
&& !existence_error (errno))
error (0, errno, "cannot remove %s", CVSEXT_LOG);
if (!quiet)
error (0, 0, "removed `%s'", finfo->fullname);
#ifdef SERVER_SUPPORT
if (server_active)
server_checked_in (finfo->file, finfo->update_dir, finfo->repository);
#endif
free (fname);
}
else if (vers->vn_user[0] == '-')
{
if (!quiet)
error (0, 0, "file `%s' already scheduled for removal",
finfo->fullname);
}
else if (vers->tag != NULL && isdigit ((unsigned char) *vers->tag))
{
/* Commit will just give an error, and so there seems to be
little reason to allow the remove. I mean, conflicts that
arise out of parallel development are one thing, but conflicts
that arise from sticky tags are quite another.
I would have thought that non-branch sticky tags should be the
same but at least now, removing a file with a non-branch sticky
tag means to delete the tag from the file. I'm not sure that
is a good behavior, but until it is changed, we need to allow
it. */
error (0, 0, "\
cannot remove file `%s' which has a numeric sticky tag of `%s'",
finfo->fullname, vers->tag);
}
else if (vers->date != NULL)
{
/* Commit will just give an error, and so there seems to be
little reason to allow the remove. */
error (0, 0, "\
cannot remove file `%s' which has a sticky date of `%s'",
finfo->fullname, vers->date);
}
else
{
char *fname;
/* cvsacl patch */
#ifdef SERVER_SUPPORT
if (use_cvs_acl /* && server_active */)
{
if (!access_allowed (finfo->file, finfo->repository, vers->tag, 7,
NULL, NULL, 1))
{
if (stop_at_first_permission_denied)
error (1, 0, "permission denied for %s",
Short_Repository (finfo->repository));
else
error (0, 0, "permission denied for %s/%s",
Short_Repository (finfo->repository), finfo->file);
return (0);
}
}
#endif
/* Re-register it with a negative version number. */
fname = Xasprintf ("-%s", vers->vn_user);
Register (finfo->entries, finfo->file, fname, vers->ts_rcs,
vers->options, vers->tag, vers->date, vers->ts_conflict);
if (!quiet)
error (0, 0, "scheduling `%s' for removal", finfo->fullname);
removed_files++;
#ifdef SERVER_SUPPORT
if (server_active)
server_checked_in (finfo->file, finfo->update_dir, finfo->repository);
#endif
free (fname);
}
freevers_ts (&vers);
return (0);
}
/*
* Print a warm fuzzy message
*/
/* ARGSUSED */
static Dtype
remove_dirproc (void *callerdat, const char *dir, const char *repos,
const char *update_dir, List *entries)
{
if (!quiet)
error (0, 0, "Removing %s", update_dir);
return (R_PROCESS);
}