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