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