Add our READMEs to the new cvs import
[dragonfly.git] / contrib / cvs-1.12 / src / release.c
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 }