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
12static 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
21static int release_server PROTO ((int argc, char **argv));
22
23/* This is the server side of cvs release.  */
24static int
25release_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
63int
64release (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		       command_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. */
120    update_cmd = xmalloc (strlen (program_path)
121			  + strlen (current_parsed_root->original)
122			  + 20);
123    sprintf (update_cmd, "%s -n -q -d %s update",
124             program_path, current_parsed_root->original);
125
126#ifdef CLIENT_SUPPORT
127    /* Start the server; we'll close it after looping. */
128    if (current_parsed_root->isremote)
129    {
130	start_server ();
131	ign_setup ();
132    }
133#endif /* CLIENT_SUPPORT */
134
135    /* Remember the directory where "cvs release" was invoked because
136       all args are relative to this directory and we chdir around.
137       */
138    if (save_cwd (&cwd))
139        error_exit ();
140
141    arg_start_idx = 0;
142
143    for (i = arg_start_idx; i < argc; i++)
144    {
145	thisarg = argv[i];
146
147        if (isdir (thisarg))
148        {
149	    if (CVS_CHDIR (thisarg) < 0)
150	    {
151		if (!really_quiet)
152		    error (0, errno, "can't chdir to: %s", thisarg);
153		continue;
154	    }
155	    if (!isdir (CVSADM))
156	    {
157		if (!really_quiet)
158		    error (0, 0, "no repository directory: %s", thisarg);
159		if (restore_cwd (&cwd, NULL))
160		    error_exit ();
161		continue;
162	    }
163	}
164	else
165        {
166	    if (!really_quiet)
167		error (0, 0, "no such directory: %s", thisarg);
168	    continue;
169	}
170
171	repository = Name_Repository ((char *) NULL, (char *) NULL);
172
173	if (!really_quiet)
174	{
175	    int line_length;
176
177	    /* The "release" command piggybacks on "update", which
178	       does the real work of finding out if anything is not
179	       up-to-date with the repository.  Then "release" prompts
180	       the user, telling her how many files have been
181	       modified, and asking if she still wants to do the
182	       release.  */
183	    fp = run_popen (update_cmd, "r");
184	    if (fp == NULL)
185		error (1, 0, "cannot run command %s", update_cmd);
186
187	    c = 0;
188
189	    while ((line_length = get_line (&line, &line_allocated, fp)) >= 0)
190	    {
191		if (strchr ("MARCZ", *line))
192		    c++;
193		(void) fputs (line, stdout);
194	    }
195	    if (line_length < 0 && !feof (fp))
196		error (0, errno, "cannot read from subprocess");
197
198	    /* If the update exited with an error, then we just want to
199	       complain and go on to the next arg.  Especially, we do
200	       not want to delete the local copy, since it's obviously
201	       not what the user thinks it is.  */
202	    if ((pclose (fp)) != 0)
203	    {
204		error (0, 0, "unable to release `%s'", thisarg);
205		free (repository);
206		if (restore_cwd (&cwd, NULL))
207		    error_exit ();
208		continue;
209	    }
210
211	    printf ("You have [%d] altered files in this repository.\n",
212		    c);
213	    printf ("Are you sure you want to release %sdirectory `%s': ",
214		    delete_flag ? "(and delete) " : "", thisarg);
215	    c = !yesno ();
216	    if (c)			/* "No" */
217	    {
218		(void) fprintf (stderr, "** `%s' aborted by user choice.\n",
219				command_name);
220		free (repository);
221		if (restore_cwd (&cwd, NULL))
222		    error_exit ();
223		continue;
224	    }
225	}
226
227	if (1
228#ifdef CLIENT_SUPPORT
229	    && !(current_parsed_root->isremote
230		 && (!supported_request ("noop")
231		     || !supported_request ("Notify")))
232#endif
233	    )
234	{
235	    /* We are chdir'ed into the directory in question.
236	       So don't pass args to unedit.  */
237	    int argc = 1;
238	    char *argv[3];
239	    argv[0] = "dummy";
240	    argv[1] = NULL;
241	    err += unedit (argc, argv);
242	}
243
244#ifdef CLIENT_SUPPORT
245        if (current_parsed_root->isremote)
246        {
247	    send_to_server ("Argument ", 0);
248	    send_to_server (thisarg, 0);
249	    send_to_server ("\012", 1);
250	    send_to_server ("release\012", 0);
251	}
252        else
253#endif /* CLIENT_SUPPORT */
254        {
255	    history_write ('F', thisarg, "", thisarg, ""); /* F == Free */
256        }
257
258        free (repository);
259
260	if (restore_cwd (&cwd, NULL))
261	    error_exit ();
262
263	if (delete_flag)
264	{
265	    /* FIXME?  Shouldn't this just delete the CVS-controlled
266	       files and, perhaps, the files that would normally be
267	       ignored and leave everything else?  */
268
269	    if (unlink_file_dir (thisarg) < 0)
270		error (0, errno, "deletion of directory %s failed", thisarg);
271	}
272
273#ifdef CLIENT_SUPPORT
274        if (current_parsed_root->isremote)
275	    err += get_server_responses ();
276#endif /* CLIENT_SUPPORT */
277    }
278
279    if (restore_cwd (&cwd, NULL))
280	error_exit ();
281    free_cwd (&cwd);
282
283#ifdef CLIENT_SUPPORT
284    if (current_parsed_root->isremote)
285    {
286	/* Unfortunately, client.c doesn't offer a way to close
287	   the connection without waiting for responses.  The extra
288	   network turnaround here is quite unnecessary other than
289	   that....  */
290	send_to_server ("noop\012", 0);
291	err += get_responses_and_close ();
292    }
293#endif /* CLIENT_SUPPORT */
294
295    free (update_cmd);
296    if (line != NULL)
297	free (line);
298    return err;
299}
300