1/*
2 * Copyright (C) 1986-2005 The Free Software Foundation, Inc.
3 *
4 * Portions Copyright (C) 1998-2005 Derek Price, Ximbiot <http://ximbiot.com>,
5 *                                  and others.
6 *
7 * Portions Copyright (C) 1992, Brian Berliner and Jeff Polk
8 * Portions Copyright (C) 1989-1992, Brian Berliner
9 *
10 * You may distribute under the terms of the GNU General Public License as
11 * specified in the README file that comes with the CVS source distribution.
12 *
13 * Remove a File
14 *
15 * Removes entries from the present version. The entries will be removed from
16 * the RCS repository upon the next "commit".
17 *
18 * "remove" accepts no options, only file names that are to be removed.  The
19 * file must not exist in the current directory for "remove" to work
20 * correctly.
21 */
22
23#include "cvs.h"
24
25#ifdef CLIENT_SUPPORT
26static int remove_force_fileproc PROTO ((void *callerdat,
27					 struct file_info *finfo));
28#endif
29static int remove_fileproc PROTO ((void *callerdat, struct file_info *finfo));
30static Dtype remove_dirproc PROTO ((void *callerdat, const char *dir,
31				    const char *repos, const char *update_dir,
32				    List *entries));
33
34static int force;
35static int local;
36static int removed_files;
37static int existing_files;
38
39static const char *const remove_usage[] =
40{
41    "Usage: %s %s [-flR] [files...]\n",
42    "\t-f\tDelete the file before removing it.\n",
43    "\t-l\tProcess this directory only (not recursive).\n",
44    "\t-R\tProcess directories recursively.\n",
45    "(Specify the --help global option for a list of other help options)\n",
46    NULL
47};
48
49int
50cvsremove (argc, argv)
51    int argc;
52    char **argv;
53{
54    int c, err;
55
56    if (argc == -1)
57	usage (remove_usage);
58
59    optind = 0;
60    while ((c = getopt (argc, argv, "+flR")) != -1)
61    {
62	switch (c)
63	{
64	    case 'f':
65		force = 1;
66		break;
67	    case 'l':
68		local = 1;
69		break;
70	    case 'R':
71		local = 0;
72		break;
73	    case '?':
74	    default:
75		usage (remove_usage);
76		break;
77	}
78    }
79    argc -= optind;
80    argv += optind;
81
82    wrap_setup ();
83
84#ifdef CLIENT_SUPPORT
85    if (current_parsed_root->isremote) {
86	/* Call expand_wild so that the local removal of files will
87           work.  It's ok to do it always because we have to send the
88           file names expanded anyway.  */
89	expand_wild (argc, argv, &argc, &argv);
90
91	if (force)
92	{
93	    if (!noexec)
94	    {
95		start_recursion (remove_force_fileproc, (FILESDONEPROC) NULL,
96				 (DIRENTPROC) NULL, (DIRLEAVEPROC) NULL,
97				 (void *) NULL, argc, argv, local, W_LOCAL,
98				 0, CVS_LOCK_NONE, (char *) NULL, 0,
99				 (char *) NULL);
100	    }
101	    /* else FIXME should probably act as if the file doesn't exist
102	       in doing the following checks.  */
103	}
104
105	start_server ();
106	ign_setup ();
107	if (local)
108	    send_arg("-l");
109	send_arg ("--");
110	/* FIXME: Can't we set SEND_NO_CONTENTS here?  Needs investigation.  */
111	send_files (argc, argv, local, 0, 0);
112	send_file_names (argc, argv, 0);
113	free_names (&argc, argv);
114	send_to_server ("remove\012", 0);
115        return get_responses_and_close ();
116    }
117#endif
118
119    /* start the recursion processor */
120    err = start_recursion (remove_fileproc, (FILESDONEPROC) NULL,
121                           remove_dirproc, (DIRLEAVEPROC) NULL, NULL,
122			   argc, argv,
123                           local, W_LOCAL, 0, CVS_LOCK_READ, (char *) NULL, 1,
124			   (char *) NULL);
125
126    if (removed_files && !really_quiet)
127	error (0, 0, "use '%s commit' to remove %s permanently", program_name,
128	       (removed_files == 1) ? "this file" : "these files");
129
130    if (existing_files)
131	error (0, 0,
132	       ((existing_files == 1) ?
133		"%d file exists; remove it first" :
134		"%d files exist; remove them first"),
135	       existing_files);
136
137    return (err);
138}
139
140#ifdef CLIENT_SUPPORT
141
142/*
143 * This is called via start_recursion if we are running as the client
144 * and the -f option was used.  We just physically remove the file.
145 */
146
147/*ARGSUSED*/
148static int
149remove_force_fileproc (callerdat, finfo)
150     void *callerdat;
151     struct file_info *finfo;
152{
153    if (CVS_UNLINK (finfo->file) < 0 && ! existence_error (errno))
154	error (0, errno, "unable to remove %s", finfo->fullname);
155    return 0;
156}
157
158#endif
159
160/*
161 * remove the file, only if it has already been physically removed
162 */
163/* ARGSUSED */
164static int
165remove_fileproc (callerdat, finfo)
166    void *callerdat;
167    struct file_info *finfo;
168{
169    Vers_TS *vers;
170
171    if (force)
172    {
173	if (!noexec)
174	{
175	    if ( CVS_UNLINK (finfo->file) < 0 && ! existence_error (errno))
176	    {
177		error (0, errno, "unable to remove %s", finfo->fullname);
178	    }
179	}
180	/* else FIXME should probably act as if the file doesn't exist
181	   in doing the following checks.  */
182    }
183
184    vers = Version_TS (finfo, NULL, NULL, NULL, 0, 0);
185
186    if (vers->ts_user != NULL)
187    {
188	existing_files++;
189	if (!quiet)
190	    error (0, 0, "file `%s' still in working directory",
191		   finfo->fullname);
192    }
193    else if (vers->vn_user == NULL)
194    {
195	if (!quiet)
196	    error (0, 0, "nothing known about `%s'", finfo->fullname);
197    }
198    else if (vers->vn_user[0] == '0' && vers->vn_user[1] == '\0')
199    {
200	char *fname;
201
202	/*
203	 * It's a file that has been added, but not commited yet. So,
204	 * remove the ,t file for it and scratch it from the
205	 * entries file.  */
206	Scratch_Entry (finfo->entries, finfo->file);
207	fname = xmalloc (strlen (finfo->file)
208			 + sizeof (CVSADM)
209			 + sizeof (CVSEXT_LOG)
210			 + 10);
211	(void) sprintf (fname, "%s/%s%s", CVSADM, finfo->file, CVSEXT_LOG);
212	if (unlink_file (fname) < 0
213	    && !existence_error (errno))
214	    error (0, errno, "cannot remove %s", CVSEXT_LOG);
215	if (!quiet)
216	    error (0, 0, "removed `%s'", finfo->fullname);
217
218#ifdef SERVER_SUPPORT
219	if (server_active)
220	    server_checked_in (finfo->file, finfo->update_dir, finfo->repository);
221#endif
222	free (fname);
223    }
224    else if (vers->vn_user[0] == '-')
225    {
226	if (!quiet)
227	    error (0, 0, "file `%s' already scheduled for removal",
228		   finfo->fullname);
229    }
230    else if (vers->tag != NULL && isdigit ((unsigned char) *vers->tag))
231    {
232	/* Commit will just give an error, and so there seems to be
233	   little reason to allow the remove.  I mean, conflicts that
234	   arise out of parallel development are one thing, but conflicts
235	   that arise from sticky tags are quite another.
236
237	   I would have thought that non-branch sticky tags should be the
238	   same but at least now, removing a file with a non-branch sticky
239	   tag means to delete the tag from the file.  I'm not sure that
240	   is a good behavior, but until it is changed, we need to allow
241	   it.  */
242	error (0, 0, "\
243cannot remove file `%s' which has a numeric sticky tag of `%s'",
244	       finfo->fullname, vers->tag);
245    }
246    else if (vers->date != NULL)
247    {
248	/* Commit will just give an error, and so there seems to be
249	   little reason to allow the remove.  */
250	error (0, 0, "\
251cannot remove file `%s' which has a sticky date of `%s'",
252	       finfo->fullname, vers->date);
253    }
254    else
255    {
256	char *fname;
257
258	/* Re-register it with a negative version number.  */
259	fname = xmalloc (strlen (vers->vn_user) + 5);
260	(void) strcpy (fname, "-");
261	(void) strcat (fname, vers->vn_user);
262	Register (finfo->entries, finfo->file, fname, vers->ts_rcs, vers->options,
263		  vers->tag, vers->date, vers->ts_conflict);
264	if (!quiet)
265	    error (0, 0, "scheduling `%s' for removal", finfo->fullname);
266	removed_files++;
267
268#ifdef SERVER_SUPPORT
269	if (server_active)
270	    server_checked_in (finfo->file, finfo->update_dir, finfo->repository);
271#endif
272	free (fname);
273    }
274
275    freevers_ts (&vers);
276    return (0);
277}
278
279/*
280 * Print a warm fuzzy message
281 */
282/* ARGSUSED */
283static Dtype
284remove_dirproc (callerdat, dir, repos, update_dir, entries)
285    void *callerdat;
286    const char *dir;
287    const char *repos;
288    const char *update_dir;
289    List *entries;
290{
291    if (!quiet)
292	error (0, 0, "Removing %s", update_dir);
293    return (R_PROCESS);
294}
295