xref: /dragonfly/contrib/cvs-1.12/src/remove.c (revision 36a3d1d6)
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
26 static int remove_force_fileproc (void *callerdat,
27 					 struct file_info *finfo);
28 #endif
29 static int remove_fileproc (void *callerdat, struct file_info *finfo);
30 static Dtype remove_dirproc (void *callerdat, const char *dir,
31                              const char *repos, const char *update_dir,
32                              List *entries);
33 
34 static int force;
35 static int local;
36 static int removed_files;
37 static int existing_files;
38 
39 static 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 
49 int
50 cvsremove (int argc, char **argv)
51 {
52     int c, err;
53 
54     if (argc == -1)
55 	usage (remove_usage);
56 
57     optind = 0;
58     while ((c = getopt (argc, argv, "+flR")) != -1)
59     {
60 	switch (c)
61 	{
62 	    case 'f':
63 		force = 1;
64 		break;
65 	    case 'l':
66 		local = 1;
67 		break;
68 	    case 'R':
69 		local = 0;
70 		break;
71 	    case '?':
72 	    default:
73 		usage (remove_usage);
74 		break;
75 	}
76     }
77     argc -= optind;
78     argv += optind;
79 
80     wrap_setup ();
81 
82 #ifdef CLIENT_SUPPORT
83     if (current_parsed_root->isremote) {
84 	/* Call expand_wild so that the local removal of files will
85            work.  It's ok to do it always because we have to send the
86            file names expanded anyway.  */
87 	expand_wild (argc, argv, &argc, &argv);
88 
89 	if (force)
90 	{
91 	    if (!noexec)
92 	    {
93 		start_recursion (remove_force_fileproc, NULL, NULL, NULL,
94 				 NULL, argc, argv, local, W_LOCAL,
95 				 0, CVS_LOCK_NONE, NULL, 0, NULL);
96 	    }
97 	    /* else FIXME should probably act as if the file doesn't exist
98 	       in doing the following checks.  */
99 	}
100 
101 	start_server ();
102 	ign_setup ();
103 	if (local)
104 	    send_arg("-l");
105 	send_arg ("--");
106 	/* FIXME: Can't we set SEND_NO_CONTENTS here?  Needs investigation.  */
107 	send_files (argc, argv, local, 0, 0);
108 	send_file_names (argc, argv, 0);
109 	free_names (&argc, argv);
110 	send_to_server ("remove\012", 0);
111         return get_responses_and_close ();
112     }
113 #endif
114 
115     /* start the recursion processor */
116     err = start_recursion (remove_fileproc, NULL, remove_dirproc, NULL,
117 			   NULL, argc, argv, local, W_LOCAL, 0,
118 			   CVS_LOCK_READ, NULL, 1, NULL);
119 
120     if (removed_files && !really_quiet)
121 	error (0, 0, "use `%s commit' to remove %s permanently", program_name,
122 	       (removed_files == 1) ? "this file" : "these files");
123 
124     if (existing_files)
125 	error (0, 0,
126 	       ((existing_files == 1) ?
127 		"%d file exists; remove it first" :
128 		"%d files exist; remove them first"),
129 	       existing_files);
130 
131     return (err);
132 }
133 
134 #ifdef CLIENT_SUPPORT
135 
136 /*
137  * This is called via start_recursion if we are running as the client
138  * and the -f option was used.  We just physically remove the file.
139  */
140 
141 /*ARGSUSED*/
142 static int
143 remove_force_fileproc (void *callerdat, struct file_info *finfo)
144 {
145     if (CVS_UNLINK (finfo->file) < 0 && ! existence_error (errno))
146 	error (0, errno, "unable to remove %s", finfo->fullname);
147     return 0;
148 }
149 
150 #endif
151 
152 /*
153  * remove the file, only if it has already been physically removed
154  */
155 /* ARGSUSED */
156 static int
157 remove_fileproc (void *callerdat, struct file_info *finfo)
158 {
159     Vers_TS *vers;
160 
161     if (force)
162     {
163 	if (!noexec)
164 	{
165 	    if ( CVS_UNLINK (finfo->file) < 0 && ! existence_error (errno))
166 	    {
167 		error (0, errno, "unable to remove %s", finfo->fullname);
168 	    }
169 	}
170 	/* else FIXME should probably act as if the file doesn't exist
171 	   in doing the following checks.  */
172     }
173 
174     vers = Version_TS (finfo, NULL, NULL, NULL, 0, 0);
175 
176     if (vers->ts_user != NULL)
177     {
178 	existing_files++;
179 	if (!quiet)
180 	    error (0, 0, "file `%s' still in working directory",
181 		   finfo->fullname);
182     }
183     else if (vers->vn_user == NULL)
184     {
185 	if (!quiet)
186 	    error (0, 0, "nothing known about `%s'", finfo->fullname);
187     }
188     else if (vers->vn_user[0] == '0' && vers->vn_user[1] == '\0')
189     {
190 	char *fname;
191 
192 	/*
193 	 * It's a file that has been added, but not commited yet. So,
194 	 * remove the ,t file for it and scratch it from the
195 	 * entries file.  */
196 	Scratch_Entry (finfo->entries, finfo->file);
197 	fname = Xasprintf ("%s/%s%s", CVSADM, finfo->file, CVSEXT_LOG);
198 	if (unlink_file (fname) < 0
199 	    && !existence_error (errno))
200 	    error (0, errno, "cannot remove %s", CVSEXT_LOG);
201 	if (!quiet)
202 	    error (0, 0, "removed `%s'", finfo->fullname);
203 
204 #ifdef SERVER_SUPPORT
205 	if (server_active)
206 	    server_checked_in (finfo->file, finfo->update_dir, finfo->repository);
207 #endif
208 	free (fname);
209     }
210     else if (vers->vn_user[0] == '-')
211     {
212 	if (!quiet)
213 	    error (0, 0, "file `%s' already scheduled for removal",
214 		   finfo->fullname);
215     }
216     else if (vers->tag != NULL && isdigit ((unsigned char) *vers->tag))
217     {
218 	/* Commit will just give an error, and so there seems to be
219 	   little reason to allow the remove.  I mean, conflicts that
220 	   arise out of parallel development are one thing, but conflicts
221 	   that arise from sticky tags are quite another.
222 
223 	   I would have thought that non-branch sticky tags should be the
224 	   same but at least now, removing a file with a non-branch sticky
225 	   tag means to delete the tag from the file.  I'm not sure that
226 	   is a good behavior, but until it is changed, we need to allow
227 	   it.  */
228 	error (0, 0, "\
229 cannot remove file `%s' which has a numeric sticky tag of `%s'",
230 	       finfo->fullname, vers->tag);
231     }
232     else if (vers->date != NULL)
233     {
234 	/* Commit will just give an error, and so there seems to be
235 	   little reason to allow the remove.  */
236 	error (0, 0, "\
237 cannot remove file `%s' which has a sticky date of `%s'",
238 	       finfo->fullname, vers->date);
239     }
240     else
241     {
242 	char *fname;
243 
244 	/* Re-register it with a negative version number.  */
245 	fname = Xasprintf ("-%s", vers->vn_user);
246 	Register (finfo->entries, finfo->file, fname, vers->ts_rcs,
247 		  vers->options, vers->tag, vers->date, vers->ts_conflict);
248 	if (!quiet)
249 	    error (0, 0, "scheduling `%s' for removal", finfo->fullname);
250 	removed_files++;
251 
252 #ifdef SERVER_SUPPORT
253 	if (server_active)
254 	    server_checked_in (finfo->file, finfo->update_dir, finfo->repository);
255 #endif
256 	free (fname);
257     }
258 
259     freevers_ts (&vers);
260     return (0);
261 }
262 
263 
264 
265 /*
266  * Print a warm fuzzy message
267  */
268 /* ARGSUSED */
269 static Dtype
270 remove_dirproc (void *callerdat, const char *dir, const char *repos,
271                 const char *update_dir, List *entries)
272 {
273     if (!quiet)
274 	error (0, 0, "Removing %s", update_dir);
275     return (R_PROCESS);
276 }
277