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