1 /*
2 * Release: "cancel" a checkout in the history log.
3 *
4 * - Enter a line in the history log indicating the "release". - If asked to,
5 * delete the local working directory.
6 */
7
8 #include "cvs.h"
9 #include "savecwd.h"
10 #include "getline.h"
11
12 static const char *const release_usage[] =
13 {
14 "Usage: %s %s [-d] directories...\n",
15 "\t-d\tDelete the given directory.\n",
16 "(Specify the --help global option for a list of other help options)\n",
17 NULL
18 };
19
20 #ifdef SERVER_SUPPORT
21 static int release_server PROTO ((int argc, char **argv));
22
23 /* This is the server side of cvs release. */
24 static int
release_server(argc,argv)25 release_server (argc, argv)
26 int argc;
27 char **argv;
28 {
29 int i;
30
31 /* Note that we skip argv[0]. */
32 for (i = 1; i < argc; ++i)
33 history_write ('F', argv[i], "", argv[i], "");
34 return 0;
35 }
36
37 #endif /* SERVER_SUPPORT */
38
39 /* There are various things to improve about this implementation:
40
41 1. Using run_popen to run "cvs update" could be replaced by a
42 fairly simple start_recursion/classify_file loop--a win for
43 portability, performance, and cleanliness. In particular, there is
44 no particularly good way to find the right "cvs".
45
46 2. The fact that "cvs update" contacts the server slows things down;
47 it undermines the case for using "cvs release" rather than "rm -rf".
48 However, for correctly printing "? foo" and correctly handling
49 CVSROOTADM_IGNORE, we currently need to contact the server. (One
50 idea for how to fix this is to stash a copy of CVSROOTADM_IGNORE in
51 the working directories; see comment at base_* in entries.c for a
52 few thoughts on that).
53
54 3. Would be nice to take processing things on the client side one step
55 further, and making it like edit/unedit in terms of working well if
56 disconnected from the network, and then sending a delayed
57 notification.
58
59 4. Having separate network turnarounds for the "Notify" request
60 which we do as part of unedit, and for the "release" itself, is slow
61 and unnecessary. */
62
63 int
release(argc,argv)64 release (argc, argv)
65 int argc;
66 char **argv;
67 {
68 FILE *fp;
69 int i, c;
70 char *repository;
71 char *line = NULL;
72 size_t line_allocated = 0;
73 char *update_cmd;
74 char *thisarg;
75 int arg_start_idx;
76 int err = 0;
77 short delete_flag = 0;
78 struct saved_cwd cwd;
79
80 #ifdef SERVER_SUPPORT
81 if (server_active)
82 return release_server (argc, argv);
83 #endif
84
85 /* Everything from here on is client or local. */
86 if (argc == -1)
87 usage (release_usage);
88 optind = 0;
89 while ((c = getopt (argc, argv, "+Qdq")) != -1)
90 {
91 switch (c)
92 {
93 case 'Q':
94 case 'q':
95 error (1, 0,
96 "-q or -Q must be specified before \"%s\"",
97 command_name);
98 break;
99 case 'd':
100 delete_flag++;
101 break;
102 case '?':
103 default:
104 usage (release_usage);
105 break;
106 }
107 }
108 argc -= optind;
109 argv += optind;
110
111 /* We're going to run "cvs -n -q update" and check its output; if
112 * the output is sufficiently unalarming, then we release with no
113 * questions asked. Else we prompt, then maybe release.
114 * (Well, actually we ask no matter what. Our notion of "sufficiently
115 * unalarming" doesn't take into account "? foo.c" files, so it is
116 * up to the user to take note of them, at least currently
117 * (ignore-193 in testsuite)).
118 */
119 /* Construct the update command. */
120 update_cmd = xmalloc (strlen (program_path)
121 + strlen (current_parsed_root->original)
122 + 20);
123 sprintf (update_cmd, "%s -n -q -d %s update",
124 program_path, current_parsed_root->original);
125
126 #ifdef CLIENT_SUPPORT
127 /* Start the server; we'll close it after looping. */
128 if (current_parsed_root->isremote)
129 {
130 start_server ();
131 ign_setup ();
132 }
133 #endif /* CLIENT_SUPPORT */
134
135 /* Remember the directory where "cvs release" was invoked because
136 all args are relative to this directory and we chdir around.
137 */
138 if (save_cwd (&cwd))
139 error_exit ();
140
141 arg_start_idx = 0;
142
143 for (i = arg_start_idx; i < argc; i++)
144 {
145 thisarg = argv[i];
146
147 if (isdir (thisarg))
148 {
149 if (CVS_CHDIR (thisarg) < 0)
150 {
151 if (!really_quiet)
152 error (0, errno, "can't chdir to: %s", thisarg);
153 continue;
154 }
155 if (!isdir (CVSADM))
156 {
157 if (!really_quiet)
158 error (0, 0, "no repository directory: %s", thisarg);
159 if (restore_cwd (&cwd, NULL))
160 error_exit ();
161 continue;
162 }
163 }
164 else
165 {
166 if (!really_quiet)
167 error (0, 0, "no such directory: %s", thisarg);
168 continue;
169 }
170
171 repository = Name_Repository ((char *) NULL, (char *) NULL);
172
173 if (!really_quiet)
174 {
175 int line_length;
176
177 /* The "release" command piggybacks on "update", which
178 does the real work of finding out if anything is not
179 up-to-date with the repository. Then "release" prompts
180 the user, telling her how many files have been
181 modified, and asking if she still wants to do the
182 release. */
183 fp = run_popen (update_cmd, "r");
184 if (fp == NULL)
185 error (1, 0, "cannot run command %s", update_cmd);
186
187 c = 0;
188
189 while ((line_length = get_line (&line, &line_allocated, fp)) >= 0)
190 {
191 if (strchr ("MARCZ", *line))
192 c++;
193 (void) fputs (line, stdout);
194 }
195 if (line_length < 0 && !feof (fp))
196 error (0, errno, "cannot read from subprocess");
197
198 /* If the update exited with an error, then we just want to
199 complain and go on to the next arg. Especially, we do
200 not want to delete the local copy, since it's obviously
201 not what the user thinks it is. */
202 if ((pclose (fp)) != 0)
203 {
204 error (0, 0, "unable to release `%s'", thisarg);
205 free (repository);
206 if (restore_cwd (&cwd, NULL))
207 error_exit ();
208 continue;
209 }
210
211 printf ("You have [%d] altered files in this repository.\n",
212 c);
213 printf ("Are you sure you want to release %sdirectory `%s': ",
214 delete_flag ? "(and delete) " : "", thisarg);
215 c = !yesno ();
216 if (c) /* "No" */
217 {
218 (void) fprintf (stderr, "** `%s' aborted by user choice.\n",
219 command_name);
220 free (repository);
221 if (restore_cwd (&cwd, NULL))
222 error_exit ();
223 continue;
224 }
225 }
226
227 if (1
228 #ifdef CLIENT_SUPPORT
229 && !(current_parsed_root->isremote
230 && (!supported_request ("noop")
231 || !supported_request ("Notify")))
232 #endif
233 )
234 {
235 /* We are chdir'ed into the directory in question.
236 So don't pass args to unedit. */
237 int argc = 1;
238 char *argv[3];
239 argv[0] = "dummy";
240 argv[1] = NULL;
241 err += unedit (argc, argv);
242 }
243
244 #ifdef CLIENT_SUPPORT
245 if (current_parsed_root->isremote)
246 {
247 send_to_server ("Argument ", 0);
248 send_to_server (thisarg, 0);
249 send_to_server ("\012", 1);
250 send_to_server ("release\012", 0);
251 }
252 else
253 #endif /* CLIENT_SUPPORT */
254 {
255 history_write ('F', thisarg, "", thisarg, ""); /* F == Free */
256 }
257
258 free (repository);
259
260 if (restore_cwd (&cwd, NULL))
261 error_exit ();
262
263 if (delete_flag)
264 {
265 /* FIXME? Shouldn't this just delete the CVS-controlled
266 files and, perhaps, the files that would normally be
267 ignored and leave everything else? */
268
269 if (unlink_file_dir (thisarg) < 0)
270 error (0, errno, "deletion of directory %s failed", thisarg);
271 }
272
273 #ifdef CLIENT_SUPPORT
274 if (current_parsed_root->isremote)
275 err += get_server_responses ();
276 #endif /* CLIENT_SUPPORT */
277 }
278
279 if (restore_cwd (&cwd, NULL))
280 error_exit ();
281 free_cwd (&cwd);
282
283 #ifdef CLIENT_SUPPORT
284 if (current_parsed_root->isremote)
285 {
286 /* Unfortunately, client.c doesn't offer a way to close
287 the connection without waiting for responses. The extra
288 network turnaround here is quite unnecessary other than
289 that.... */
290 send_to_server ("noop\012", 0);
291 err += get_responses_and_close ();
292 }
293 #endif /* CLIENT_SUPPORT */
294
295 free (update_cmd);
296 if (line != NULL)
297 free (line);
298 return err;
299 }
300