]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - contrib/cvs/src/release.c
This commit was generated by cvs2svn to compensate for changes in r170222,
[FreeBSD/FreeBSD.git] / contrib / cvs / 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 PROTO ((int argc, char **argv));
22
23 /* This is the server side of cvs release.  */
24 static int
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
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                        cvs_cmd_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.  Be sure to add authentication and
120        encryption if we are using them currently, else our child process may
121        not be able to communicate with the server.  */
122     update_cmd = xmalloc (strlen (program_path)
123                         + strlen (current_parsed_root->original)
124                         + 1 + 3 + 3 + 16 + 1);
125     sprintf (update_cmd, "%s %s%s-n -q -d %s update",
126              program_path,
127              cvsauthenticate ? "-a " : "",
128              cvsencrypt ? "-x " : "",
129              current_parsed_root->original);
130
131 #ifdef CLIENT_SUPPORT
132     /* Start the server; we'll close it after looping. */
133     if (current_parsed_root->isremote)
134     {
135         start_server ();
136         ign_setup ();
137     }
138 #endif /* CLIENT_SUPPORT */
139
140     /* Remember the directory where "cvs release" was invoked because
141        all args are relative to this directory and we chdir around.
142        */
143     if (save_cwd (&cwd))
144         error_exit ();
145
146     arg_start_idx = 0;
147
148     for (i = arg_start_idx; i < argc; i++)
149     {
150         thisarg = argv[i];
151
152         if (isdir (thisarg))
153         {
154             if (CVS_CHDIR (thisarg) < 0)
155             {
156                 if (!really_quiet)
157                     error (0, errno, "can't chdir to: %s", thisarg);
158                 continue;
159             }
160             if (!isdir (CVSADM))
161             {
162                 if (!really_quiet)
163                     error (0, 0, "no repository directory: %s", thisarg);
164                 if (restore_cwd (&cwd, NULL))
165                     error_exit ();
166                 continue;
167             }
168         }
169         else
170         {
171             if (!really_quiet)
172                 error (0, 0, "no such directory: %s", thisarg);
173             continue;
174         }
175
176         repository = Name_Repository ((char *) NULL, (char *) NULL);
177
178         if (!really_quiet)
179         {
180             int line_length;
181
182             /* The "release" command piggybacks on "update", which
183                does the real work of finding out if anything is not
184                up-to-date with the repository.  Then "release" prompts
185                the user, telling her how many files have been
186                modified, and asking if she still wants to do the
187                release.  */
188             fp = run_popen (update_cmd, "r");
189             if (fp == NULL)
190                 error (1, 0, "cannot run command %s", update_cmd);
191
192             c = 0;
193
194             while ((line_length = getline (&line, &line_allocated, fp)) >= 0)
195             {
196                 if (strchr ("MARCZ", *line))
197                     c++;
198                 (void) fputs (line, stdout);
199             }
200             if (line_length < 0 && !feof (fp))
201                 error (0, errno, "cannot read from subprocess");
202
203             /* If the update exited with an error, then we just want to
204                complain and go on to the next arg.  Especially, we do
205                not want to delete the local copy, since it's obviously
206                not what the user thinks it is.  */
207             if ((pclose (fp)) != 0)
208             {
209                 error (0, 0, "unable to release `%s'", thisarg);
210                 free (repository);
211                 if (restore_cwd (&cwd, NULL))
212                     error_exit ();
213                 continue;
214             }
215
216             printf ("You have [%d] altered files in this repository.\n",
217                     c);
218             printf ("Are you sure you want to release %sdirectory `%s': ",
219                     delete_flag ? "(and delete) " : "", thisarg);
220             c = !yesno ();
221             if (c)                      /* "No" */
222             {
223                 (void) fprintf (stderr, "** `%s' aborted by user choice.\n",
224                                 cvs_cmd_name);
225                 free (repository);
226                 if (restore_cwd (&cwd, NULL))
227                     error_exit ();
228                 continue;
229             }
230         }
231
232         /* Note:  client.c doesn't like to have other code
233            changing the current directory on it.  So a fair amount
234            of effort is needed to make sure it doesn't get confused
235            about the directory and (for example) overwrite
236            CVS/Entries file in the wrong directory.  See release-17
237            through release-23. */
238
239         free (repository);
240         if (restore_cwd (&cwd, NULL))
241             exit (EXIT_FAILURE);
242
243         if (1
244 #ifdef CLIENT_SUPPORT
245             && !(current_parsed_root->isremote
246                  && (!supported_request ("noop")
247                      || !supported_request ("Notify")))
248 #endif
249             )
250         {
251             int argc = 2;
252             char *argv[3];
253             argv[0] = "dummy";
254             argv[1] = thisarg;
255             argv[2] = NULL;
256             err += unedit (argc, argv);
257             if (restore_cwd (&cwd, NULL))
258                 exit (EXIT_FAILURE);
259         }
260
261 #ifdef CLIENT_SUPPORT
262         if (current_parsed_root->isremote)
263         {
264             send_to_server ("Argument ", 0);
265             send_to_server (thisarg, 0);
266             send_to_server ("\012", 1);
267             send_to_server ("release\012", 0);
268         }
269         else
270 #endif /* CLIENT_SUPPORT */
271         {
272             history_write ('F', thisarg, "", thisarg, ""); /* F == Free */
273         }
274
275         if (delete_flag)
276         {
277             /* FIXME?  Shouldn't this just delete the CVS-controlled
278                files and, perhaps, the files that would normally be
279                ignored and leave everything else?  */
280
281             if (unlink_file_dir (thisarg) < 0)
282                 error (0, errno, "deletion of directory %s failed", thisarg);
283         }
284
285 #ifdef CLIENT_SUPPORT
286         if (current_parsed_root->isremote)
287         {
288             /* FIXME:
289              * Is there a good reason why get_server_responses() isn't
290              * responsible for restoring its initial directory itself when
291              * finished?
292              */
293             err += get_server_responses ();
294
295             if (restore_cwd (&cwd, NULL))
296                 exit (EXIT_FAILURE);
297         }
298 #endif /* CLIENT_SUPPORT */
299     }
300
301     if (restore_cwd (&cwd, NULL))
302         error_exit ();
303     free_cwd (&cwd);
304
305 #ifdef CLIENT_SUPPORT
306     if (current_parsed_root->isremote)
307     {
308         /* Unfortunately, client.c doesn't offer a way to close
309            the connection without waiting for responses.  The extra
310            network turnaround here is quite unnecessary other than
311            that....  */
312         send_to_server ("noop\012", 0);
313         err += get_responses_and_close ();
314     }
315 #endif /* CLIENT_SUPPORT */
316
317     free (update_cmd);
318     if (line != NULL)
319         free (line);
320     return err;
321 }