xref: /netbsd-src/external/gpl2/xcvs/dist/src/release.c (revision 5a6c14c844c4c665da5632061aebde7bb2cb5766)
1 /*
2  * Copyright (C) 1994-2005 The Free Software Foundation, Inc.
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; either version 2, or (at your option)
7  * any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  */
14 #include <sys/cdefs.h>
15 __RCSID("$NetBSD: release.c,v 1.3 2016/05/17 14:00:09 christos Exp $");
16 
17 /*
18  * Release: "cancel" a checkout in the history log.
19  *
20  * - Enter a line in the history log indicating the "release". - If asked to,
21  * delete the local working directory.
22  */
23 
24 #include "cvs.h"
25 #include "save-cwd.h"
26 #include "getline.h"
27 #include "yesno.h"
28 
29 static const char *const release_usage[] =
30 {
31     "Usage: %s %s [-d] directories...\n",
32     "\t-d\tDelete the given directory.\n",
33     "(Specify the --help global option for a list of other help options)\n",
34     NULL
35 };
36 
37 #ifdef SERVER_SUPPORT
38 static int release_server (int argc, char **argv);
39 
40 /* This is the server side of cvs release.  */
41 static int
release_server(int argc,char ** argv)42 release_server (int argc, char **argv)
43 {
44     int i;
45 
46     /* Note that we skip argv[0].  */
47     for (i = 1; i < argc; ++i)
48 	history_write ('F', argv[i], "", argv[i], "");
49     return 0;
50 }
51 
52 #endif /* SERVER_SUPPORT */
53 
54 /* Construct an update command.  Be sure to add authentication and
55 * encryption if we are using them currently, else our child process may
56 * not be able to communicate with the server.
57 */
58 static FILE *
setup_update_command(pid_t * child_pid)59 setup_update_command (pid_t *child_pid)
60 {
61     int tofd, fromfd;
62 
63     run_setup (program_path);
64 
65 #if defined (CLIENT_SUPPORT) || defined (SERVER_SUPPORT)
66     /* Be sure to add authentication and encryption if we are using them
67      * currently, else our child process may not be able to communicate with
68      * the server.
69      */
70     if (cvsauthenticate) run_add_arg ("-a");
71     if (cvsencrypt) run_add_arg ("-x");
72 #endif
73 
74     /* Don't really change anything.  */
75     run_add_arg ("-n");
76 
77     /* Don't need full verbosity.  */
78     run_add_arg ("-q");
79 
80     /* Propogate our CVSROOT.  */
81     run_add_arg ("-d");
82     run_add_arg (original_parsed_root->original);
83 
84     run_add_arg ("update");
85     *child_pid = run_piped (&tofd, &fromfd);
86     if (*child_pid < 0)
87 	error (1, 0, "could not fork server process");
88 
89     close (tofd);
90 
91     return fdopen (fromfd, "r");
92 }
93 
94 
95 
96 static int
close_update_command(FILE * fp,pid_t child_pid)97 close_update_command (FILE *fp, pid_t child_pid)
98 {
99     int status;
100     pid_t w;
101 
102     do
103 	w = waitpid (child_pid, &status, 0);
104     while (w == -1 && errno == EINTR);
105     if (w == -1)
106 	error (1, errno, "waiting for process %d", child_pid);
107 
108     return status;
109 }
110 
111 
112 
113 /* There are various things to improve about this implementation:
114 
115    1.  Using run_popen to run "cvs update" could be replaced by a
116    fairly simple start_recursion/classify_file loop--a win for
117    portability, performance, and cleanliness.  In particular, there is
118    no particularly good way to find the right "cvs".
119 
120    2.  The fact that "cvs update" contacts the server slows things down;
121    it undermines the case for using "cvs release" rather than "rm -rf".
122    However, for correctly printing "? foo" and correctly handling
123    CVSROOTADM_IGNORE, we currently need to contact the server.  (One
124    idea for how to fix this is to stash a copy of CVSROOTADM_IGNORE in
125    the working directories; see comment at base_* in entries.c for a
126    few thoughts on that).
127 
128    3.  Would be nice to take processing things on the client side one step
129    further, and making it like edit/unedit in terms of working well if
130    disconnected from the network, and then sending a delayed
131    notification.
132 
133    4.  Having separate network turnarounds for the "Notify" request
134    which we do as part of unedit, and for the "release" itself, is slow
135    and unnecessary.  */
136 
137 int
release(int argc,char ** argv)138 release (int argc, char **argv)
139 {
140     FILE *fp;
141     int i, c;
142     char *line = NULL;
143     size_t line_allocated = 0;
144     char *thisarg;
145     int arg_start_idx;
146     int err = 0;
147     short delete_flag = 0;
148     struct saved_cwd cwd;
149 
150 #ifdef SERVER_SUPPORT
151     if (server_active)
152 	return release_server (argc, argv);
153 #endif
154 
155     /* Everything from here on is client or local.  */
156     if (argc == -1)
157 	usage (release_usage);
158     getoptreset ();
159     while ((c = getopt (argc, argv, "+Qdq")) != -1)
160     {
161 	switch (c)
162 	{
163 	    case 'Q':
164 	    case 'q':
165 		error (1, 0,
166 		       "-q or -Q must be specified before \"%s\"",
167 		       cvs_cmd_name);
168 		break;
169 	    case 'd':
170 		delete_flag++;
171 		break;
172 	    case '?':
173 	    default:
174 		usage (release_usage);
175 		break;
176 	}
177     }
178     argc -= optind;
179     argv += optind;
180 
181     /* We're going to run "cvs -n -q update" and check its output; if
182      * the output is sufficiently unalarming, then we release with no
183      * questions asked.  Else we prompt, then maybe release.
184      * (Well, actually we ask no matter what.  Our notion of "sufficiently
185      * unalarming" doesn't take into account "? foo.c" files, so it is
186      * up to the user to take note of them, at least currently
187      * (ignore-193 in testsuite)).
188      */
189 
190 #ifdef CLIENT_SUPPORT
191     /* Start the server; we'll close it after looping. */
192     if (current_parsed_root->isremote)
193     {
194 	start_server ();
195 	ign_setup ();
196     }
197 #endif /* CLIENT_SUPPORT */
198 
199     /* Remember the directory where "cvs release" was invoked because
200        all args are relative to this directory and we chdir around.
201        */
202     if (save_cwd (&cwd))
203 	error (1, errno, "Failed to save current directory.");
204 
205     arg_start_idx = 0;
206 
207     for (i = arg_start_idx; i < argc; i++)
208     {
209 	thisarg = argv[i];
210 
211         if (isdir (thisarg))
212         {
213 	    if (CVS_CHDIR (thisarg) < 0)
214 	    {
215 		if (!really_quiet)
216 		    error (0, errno, "can't chdir to: %s", thisarg);
217 		continue;
218 	    }
219 	    if (!isdir (CVSADM))
220 	    {
221 		if (!really_quiet)
222 		    error (0, 0, "no repository directory: %s", thisarg);
223 		if (restore_cwd (&cwd))
224 		    error (1, errno,
225 		           "Failed to restore current directory, `%s'.",
226 		           cwd.name);
227 		continue;
228 	    }
229 	}
230 	else
231         {
232 	    if (!really_quiet)
233 		error (0, 0, "no such directory: %s", thisarg);
234 	    continue;
235 	}
236 
237 	if (!really_quiet)
238 	{
239 	    int line_length, status;
240 	    pid_t child_pid;
241 
242 	    /* The "release" command piggybacks on "update", which
243 	       does the real work of finding out if anything is not
244 	       up-to-date with the repository.  Then "release" prompts
245 	       the user, telling her how many files have been
246 	       modified, and asking if she still wants to do the
247 	       release.  */
248 	    fp = setup_update_command (&child_pid);
249 	    if (fp == NULL)
250 	    {
251 		error (0, 0, "cannot run command:");
252 	        run_print (stderr);
253 		error (1, 0, "Exiting due to fatal error referenced above.");
254 	    }
255 
256 	    c = 0;
257 
258 	    while ((line_length = getline (&line, &line_allocated, fp)) >= 0)
259 	    {
260 		if (strchr ("MARCZ", *line))
261 		    c++;
262 		(void) fputs (line, stdout);
263 	    }
264 	    if (line_length < 0 && !feof (fp))
265 		error (0, errno, "cannot read from subprocess");
266 
267 	    /* If the update exited with an error, then we just want to
268 	       complain and go on to the next arg.  Especially, we do
269 	       not want to delete the local copy, since it's obviously
270 	       not what the user thinks it is.  */
271 	    status = close_update_command (fp, child_pid);
272 	    if (status != 0)
273 	    {
274 		error (0, 0, "unable to release `%s' (%d)", thisarg, status);
275 		if (restore_cwd (&cwd))
276 		    error (1, errno,
277 		           "Failed to restore current directory, `%s'.",
278 		           cwd.name);
279 		continue;
280 	    }
281 
282 	    printf ("You have [%d] altered files in this repository.\n",
283 		    c);
284 
285 	    if (!noexec)
286 	    {
287 		printf ("Are you sure you want to release %sdirectory `%s': ",
288 			delete_flag ? "(and delete) " : "", thisarg);
289 		fflush (stderr);
290 		fflush (stdout);
291 		if (!yesno ())			/* "No" */
292 		{
293 		    (void) fprintf (stderr,
294 				    "** `%s' aborted by user choice.\n",
295 				    cvs_cmd_name);
296 		    if (restore_cwd (&cwd))
297 			error (1, errno,
298 			       "Failed to restore current directory, `%s'.",
299 			       cwd.name);
300 		    continue;
301 		}
302 	    }
303 	    else
304 	    {
305 		if (restore_cwd (&cwd))
306 		    error (1, errno,
307 			   "Failed to restore current directory, `%s'.",
308 			   cwd.name);
309 		continue;
310 	    }
311 	}
312 
313         /* Note:  client.c doesn't like to have other code
314            changing the current directory on it.  So a fair amount
315            of effort is needed to make sure it doesn't get confused
316            about the directory and (for example) overwrite
317            CVS/Entries file in the wrong directory.  See release-17
318            through release-23. */
319 
320 	if (restore_cwd (&cwd))
321 	    error (1, errno, "Failed to restore current directory, `%s'.",
322 		   cwd.name);
323 
324 #ifdef CLIENT_SUPPORT
325 	if (!current_parsed_root->isremote
326 	    || (supported_request ("noop") && supported_request ("Notify")))
327 #endif
328 	{
329 	    int argc = 2;
330 	    char *argv[3];
331 	    argv[0] = "dummy";
332 	    argv[1] = thisarg;
333 	    argv[2] = NULL;
334 	    err += unedit (argc, argv);
335             if (restore_cwd (&cwd))
336 		error (1, errno, "Failed to restore current directory, `%s'.",
337 		       cwd.name);
338 	}
339 
340 #ifdef CLIENT_SUPPORT
341         if (current_parsed_root->isremote)
342         {
343 	    send_to_server ("Argument ", 0);
344 	    send_to_server (thisarg, 0);
345 	    send_to_server ("\012", 1);
346 	    send_to_server ("release\012", 0);
347 	}
348         else
349 #endif /* CLIENT_SUPPORT */
350         {
351 	    history_write ('F', thisarg, "", thisarg, ""); /* F == Free */
352         }
353 
354 	if (delete_flag)
355 	{
356 	    /* FIXME?  Shouldn't this just delete the CVS-controlled
357 	       files and, perhaps, the files that would normally be
358 	       ignored and leave everything else?  */
359 
360 	    if (unlink_file_dir (thisarg) < 0)
361 		error (0, errno, "deletion of directory %s failed", thisarg);
362 	}
363 
364 #ifdef CLIENT_SUPPORT
365         if (current_parsed_root->isremote)
366         {
367 	    /* FIXME:
368 	     * Is there a good reason why get_server_responses() isn't
369 	     * responsible for restoring its initial directory itself when
370 	     * finished?
371 	     */
372             err += get_server_responses ();
373 
374             if (restore_cwd (&cwd))
375 		error (1, errno, "Failed to restore current directory, `%s'.",
376 		       cwd.name);
377         }
378 #endif /* CLIENT_SUPPORT */
379     }
380 
381     if (restore_cwd (&cwd))
382 	error (1, errno, "Failed to restore current directory, `%s'.",
383 	       cwd.name);
384     free_cwd (&cwd);
385 
386 #ifdef CLIENT_SUPPORT
387     if (current_parsed_root->isremote)
388     {
389 	/* Unfortunately, client.c doesn't offer a way to close
390 	   the connection without waiting for responses.  The extra
391 	   network turnaround here is quite unnecessary other than
392 	   that....  */
393 	send_to_server ("noop\012", 0);
394 	err += get_responses_and_close ();
395     }
396 #endif /* CLIENT_SUPPORT */
397 
398     if (line != NULL)
399 	free (line);
400     return err;
401 }
402