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