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