release.c revision 128266
198524Sfenner/*
298524Sfenner * Release: "cancel" a checkout in the history log.
398524Sfenner *
498524Sfenner * - Enter a line in the history log indicating the "release". - If asked to,
598524Sfenner * delete the local working directory.
698524Sfenner */
798524Sfenner
898524Sfenner#include "cvs.h"
998524Sfenner#include "savecwd.h"
1098524Sfenner#include "getline.h"
1198524Sfenner
1298524Sfennerstatic const char *const release_usage[] =
1398524Sfenner{
1498524Sfenner    "Usage: %s %s [-d] directories...\n",
1598524Sfenner    "\t-d\tDelete the given directory.\n",
1698524Sfenner    "(Specify the --help global option for a list of other help options)\n",
1798524Sfenner    NULL
1898524Sfenner};
1998524Sfenner
2098524Sfenner#ifdef SERVER_SUPPORT
2198524Sfennerstatic int release_server PROTO ((int argc, char **argv));
2298524Sfenner
2398524Sfenner/* This is the server side of cvs release.  */
2498524Sfennerstatic int
2598524Sfennerrelease_server (argc, argv)
2698524Sfenner    int argc;
2798524Sfenner    char **argv;
2898524Sfenner{
2998524Sfenner    int i;
3098524Sfenner
3198524Sfenner    /* Note that we skip argv[0].  */
3298524Sfenner    for (i = 1; i < argc; ++i)
3398524Sfenner	history_write ('F', argv[i], "", argv[i], "");
3498524Sfenner    return 0;
3598524Sfenner}
3698524Sfenner
3798524Sfenner#endif /* SERVER_SUPPORT */
3898524Sfenner
3998524Sfenner/* There are various things to improve about this implementation:
4098524Sfenner
4198524Sfenner   1.  Using run_popen to run "cvs update" could be replaced by a
4298524Sfenner   fairly simple start_recursion/classify_file loop--a win for
4398524Sfenner   portability, performance, and cleanliness.  In particular, there is
4498524Sfenner   no particularly good way to find the right "cvs".
4598524Sfenner
4698524Sfenner   2.  The fact that "cvs update" contacts the server slows things down;
4798524Sfenner   it undermines the case for using "cvs release" rather than "rm -rf".
4898524Sfenner   However, for correctly printing "? foo" and correctly handling
4998524Sfenner   CVSROOTADM_IGNORE, we currently need to contact the server.  (One
5098524Sfenner   idea for how to fix this is to stash a copy of CVSROOTADM_IGNORE in
5198524Sfenner   the working directories; see comment at base_* in entries.c for a
5298524Sfenner   few thoughts on that).
5398524Sfenner
5498524Sfenner   3.  Would be nice to take processing things on the client side one step
5598524Sfenner   further, and making it like edit/unedit in terms of working well if
5698524Sfenner   disconnected from the network, and then sending a delayed
5798524Sfenner   notification.
5898524Sfenner
5998524Sfenner   4.  Having separate network turnarounds for the "Notify" request
6098524Sfenner   which we do as part of unedit, and for the "release" itself, is slow
6198524Sfenner   and unnecessary.  */
6298524Sfenner
6398524Sfennerint
6498524Sfennerrelease (argc, argv)
6598524Sfenner    int argc;
6698524Sfenner    char **argv;
6798524Sfenner{
6898524Sfenner    FILE *fp;
6998524Sfenner    int i, c;
7098524Sfenner    char *repository;
7198524Sfenner    char *line = NULL;
7298524Sfenner    size_t line_allocated = 0;
7398524Sfenner    char *update_cmd;
7498524Sfenner    char *thisarg;
7598524Sfenner    int arg_start_idx;
7698524Sfenner    int err = 0;
7798524Sfenner    short delete_flag = 0;
7898524Sfenner    struct saved_cwd cwd;
7998524Sfenner
8098524Sfenner#ifdef SERVER_SUPPORT
8198524Sfenner    if (server_active)
8298524Sfenner	return release_server (argc, argv);
8398524Sfenner#endif
8498524Sfenner
8598524Sfenner    /* Everything from here on is client or local.  */
8698524Sfenner    if (argc == -1)
8798524Sfenner	usage (release_usage);
8898524Sfenner    optind = 0;
8998524Sfenner    while ((c = getopt (argc, argv, "+Qdq")) != -1)
9098524Sfenner    {
9198524Sfenner	switch (c)
9298524Sfenner	{
9398524Sfenner	    case 'Q':
9498524Sfenner	    case 'q':
9598524Sfenner		error (1, 0,
9698524Sfenner		       "-q or -Q must be specified before \"%s\"",
9798524Sfenner		       cvs_cmd_name);
9898524Sfenner		break;
9998524Sfenner	    case 'd':
10098524Sfenner		delete_flag++;
10198524Sfenner		break;
10298524Sfenner	    case '?':
10398524Sfenner	    default:
10498524Sfenner		usage (release_usage);
10598524Sfenner		break;
10698524Sfenner	}
10798524Sfenner    }
10898524Sfenner    argc -= optind;
10998524Sfenner    argv += optind;
11098524Sfenner
11198524Sfenner    /* We're going to run "cvs -n -q update" and check its output; if
11298524Sfenner     * the output is sufficiently unalarming, then we release with no
11398524Sfenner     * questions asked.  Else we prompt, then maybe release.
11498524Sfenner     * (Well, actually we ask no matter what.  Our notion of "sufficiently
11598524Sfenner     * unalarming" doesn't take into account "? foo.c" files, so it is
11698524Sfenner     * up to the user to take note of them, at least currently
11798524Sfenner     * (ignore-193 in testsuite)).
11898524Sfenner     */
11998524Sfenner    /* Construct the update command.  Be sure to add authentication and
12098524Sfenner       encryption if we are using them currently, else our child process may
12198524Sfenner       not be able to communicate with the server.  */
12298524Sfenner    update_cmd = xmalloc (strlen (program_path)
12398524Sfenner                        + strlen (current_parsed_root->original)
12498524Sfenner                        + 1 + 3 + 3 + 16 + 1);
12598524Sfenner    sprintf (update_cmd, "%s %s%s-n -q -d %s update",
12698524Sfenner             program_path,
12798524Sfenner             cvsauthenticate ? "-a " : "",
12898524Sfenner             cvsencrypt ? "-x " : "",
12998524Sfenner             current_parsed_root->original);
13098524Sfenner
13198524Sfenner#ifdef CLIENT_SUPPORT
13298524Sfenner    /* Start the server; we'll close it after looping. */
13398524Sfenner    if (current_parsed_root->isremote)
13498524Sfenner    {
13598524Sfenner	start_server ();
13698524Sfenner	ign_setup ();
13798524Sfenner    }
13898524Sfenner#endif /* CLIENT_SUPPORT */
13998524Sfenner
14098524Sfenner    /* Remember the directory where "cvs release" was invoked because
14198524Sfenner       all args are relative to this directory and we chdir around.
14298524Sfenner       */
14398524Sfenner    if (save_cwd (&cwd))
14498524Sfenner        error_exit ();
14598524Sfenner
14698524Sfenner    arg_start_idx = 0;
14798524Sfenner
14898524Sfenner    for (i = arg_start_idx; i < argc; i++)
14998524Sfenner    {
15098524Sfenner	thisarg = argv[i];
15198524Sfenner
15298524Sfenner        if (isdir (thisarg))
15398524Sfenner        {
15498524Sfenner	    if (CVS_CHDIR (thisarg) < 0)
15598524Sfenner	    {
15698524Sfenner		if (!really_quiet)
15798524Sfenner		    error (0, errno, "can't chdir to: %s", thisarg);
15898524Sfenner		continue;
15998524Sfenner	    }
16098524Sfenner	    if (!isdir (CVSADM))
16198524Sfenner	    {
16298524Sfenner		if (!really_quiet)
16398524Sfenner		    error (0, 0, "no repository directory: %s", thisarg);
16498524Sfenner		if (restore_cwd (&cwd, NULL))
16598524Sfenner		    error_exit ();
16698524Sfenner		continue;
16798524Sfenner	    }
16898524Sfenner	}
16998524Sfenner	else
17098524Sfenner        {
17198524Sfenner	    if (!really_quiet)
17298524Sfenner		error (0, 0, "no such directory: %s", thisarg);
17398524Sfenner	    continue;
17498524Sfenner	}
17598524Sfenner
17698524Sfenner	repository = Name_Repository ((char *) NULL, (char *) NULL);
17798524Sfenner
17898524Sfenner	if (!really_quiet)
17998524Sfenner	{
18098524Sfenner	    int line_length;
18198524Sfenner
18298524Sfenner	    /* The "release" command piggybacks on "update", which
18398524Sfenner	       does the real work of finding out if anything is not
18498524Sfenner	       up-to-date with the repository.  Then "release" prompts
18598524Sfenner	       the user, telling her how many files have been
18698524Sfenner	       modified, and asking if she still wants to do the
18798524Sfenner	       release.  */
18898524Sfenner	    fp = run_popen (update_cmd, "r");
18998524Sfenner	    if (fp == NULL)
19098524Sfenner		error (1, 0, "cannot run command %s", update_cmd);
19198524Sfenner
19298524Sfenner	    c = 0;
19398524Sfenner
19498524Sfenner	    while ((line_length = getline (&line, &line_allocated, fp)) >= 0)
19598524Sfenner	    {
19698524Sfenner		if (strchr ("MARCZ", *line))
19798524Sfenner		    c++;
19898524Sfenner		(void) fputs (line, stdout);
19998524Sfenner	    }
20098524Sfenner	    if (line_length < 0 && !feof (fp))
20198524Sfenner		error (0, errno, "cannot read from subprocess");
20298524Sfenner
20398524Sfenner	    /* If the update exited with an error, then we just want to
20498524Sfenner	       complain and go on to the next arg.  Especially, we do
20598524Sfenner	       not want to delete the local copy, since it's obviously
20698524Sfenner	       not what the user thinks it is.  */
20798524Sfenner	    if ((pclose (fp)) != 0)
20898524Sfenner	    {
20998524Sfenner		error (0, 0, "unable to release `%s'", thisarg);
21098524Sfenner		free (repository);
21198524Sfenner		if (restore_cwd (&cwd, NULL))
21298524Sfenner		    error_exit ();
21398524Sfenner		continue;
21498524Sfenner	    }
21598524Sfenner
21698524Sfenner	    printf ("You have [%d] altered files in this repository.\n",
21798524Sfenner		    c);
21898524Sfenner	    printf ("Are you sure you want to release %sdirectory `%s': ",
21998524Sfenner		    delete_flag ? "(and delete) " : "", thisarg);
22098524Sfenner	    c = !yesno ();
22198524Sfenner	    if (c)			/* "No" */
22298524Sfenner	    {
22398524Sfenner		(void) fprintf (stderr, "** `%s' aborted by user choice.\n",
22498524Sfenner				cvs_cmd_name);
22598524Sfenner		free (repository);
22698524Sfenner		if (restore_cwd (&cwd, NULL))
22798524Sfenner		    error_exit ();
22898524Sfenner		continue;
22998524Sfenner	    }
23098524Sfenner	}
23198524Sfenner
23298524Sfenner        /* Note:  client.c doesn't like to have other code
23398524Sfenner           changing the current directory on it.  So a fair amount
23498524Sfenner           of effort is needed to make sure it doesn't get confused
23598524Sfenner           about the directory and (for example) overwrite
23698524Sfenner           CVS/Entries file in the wrong directory.  See release-17
23798524Sfenner           through release-23. */
23898524Sfenner
23998524Sfenner        free (repository);
24098524Sfenner	if (restore_cwd (&cwd, NULL))
24198524Sfenner	    exit (EXIT_FAILURE);
24298524Sfenner
24398524Sfenner	if (1
24498524Sfenner#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}
322