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