release.c revision 81404
117721Speter/*
217721Speter * Release: "cancel" a checkout in the history log.
317721Speter *
417721Speter * - Enter a line in the history log indicating the "release". - If asked to,
517721Speter * delete the local working directory.
617721Speter */
717721Speter
817721Speter#include "cvs.h"
954427Speter#include "savecwd.h"
1025839Speter#include "getline.h"
1117721Speter
1217721Speterstatic const char *const release_usage[] =
1317721Speter{
1425839Speter    "Usage: %s %s [-d] directories...\n",
1517721Speter    "\t-d\tDelete the given directory.\n",
1632785Speter    "(Specify the --help global option for a list of other help options)\n",
1717721Speter    NULL
1817721Speter};
1917721Speter
2025839Speter#ifdef SERVER_SUPPORT
2125839Speterstatic int release_server PROTO ((int argc, char **argv));
2217721Speter
2325839Speter/* This is the server side of cvs release.  */
2425839Speterstatic int
2525839Speterrelease_server (argc, argv)
2625839Speter    int argc;
2725839Speter    char **argv;
2825839Speter{
2925839Speter    int i;
3017721Speter
3125839Speter    /* Note that we skip argv[0].  */
3225839Speter    for (i = 1; i < argc; ++i)
3325839Speter	history_write ('F', argv[i], "", argv[i], "");
3425839Speter    return 0;
3525839Speter}
3625839Speter
3725839Speter#endif /* SERVER_SUPPORT */
3825839Speter
3925839Speter/* There are various things to improve about this implementation:
4025839Speter
4125839Speter   1.  Using run_popen to run "cvs update" could be replaced by a
4217721Speter   fairly simple start_recursion/classify_file loop--a win for
4325839Speter   portability, performance, and cleanliness.  In particular, there is
4425839Speter   no particularly good way to find the right "cvs".
4517721Speter
4625839Speter   2.  The fact that "cvs update" contacts the server slows things down;
4725839Speter   it undermines the case for using "cvs release" rather than "rm -rf".
4825839Speter   However, for correctly printing "? foo" and correctly handling
4932785Speter   CVSROOTADM_IGNORE, we currently need to contact the server.  (One
5032785Speter   idea for how to fix this is to stash a copy of CVSROOTADM_IGNORE in
5132785Speter   the working directories; see comment at base_* in entries.c for a
5232785Speter   few thoughts on that).
5317721Speter
5425839Speter   3.  Would be nice to take processing things on the client side one step
5525839Speter   further, and making it like edit/unedit in terms of working well if
5625839Speter   disconnected from the network, and then sending a delayed
5725839Speter   notification.
5817721Speter
5925839Speter   4.  Having separate network turnarounds for the "Notify" request
6025839Speter   which we do as part of unedit, and for the "release" itself, is slow
6125839Speter   and unnecessary.  */
6217721Speter
6317721Speterint
6417721Speterrelease (argc, argv)
6517721Speter    int argc;
6617721Speter    char **argv;
6717721Speter{
6817721Speter    FILE *fp;
6925839Speter    int i, c;
7025839Speter    char *repository;
7125839Speter    char *line = NULL;
7225839Speter    size_t line_allocated = 0;
7325839Speter    char *update_cmd;
7417721Speter    char *thisarg;
7517721Speter    int arg_start_idx;
7617721Speter    int err = 0;
7725839Speter    short delete_flag = 0;
7854427Speter    struct saved_cwd cwd;
7917721Speter
8017721Speter#ifdef SERVER_SUPPORT
8125839Speter    if (server_active)
8225839Speter	return release_server (argc, argv);
8317721Speter#endif
8425839Speter
8525839Speter    /* Everything from here on is client or local.  */
8625839Speter    if (argc == -1)
8725839Speter	usage (release_usage);
8826065Speter    optind = 0;
8925839Speter    while ((c = getopt (argc, argv, "+Qdq")) != -1)
9025839Speter    {
9125839Speter	switch (c)
9225839Speter	{
9325839Speter	    case 'Q':
9425839Speter	    case 'q':
9525839Speter		error (1, 0,
9625839Speter		       "-q or -Q must be specified before \"%s\"",
9725839Speter		       command_name);
9817721Speter		break;
9925839Speter	    case 'd':
10017721Speter		delete_flag++;
10117721Speter		break;
10225839Speter	    case '?':
10325839Speter	    default:
10417721Speter		usage (release_usage);
10517721Speter		break;
10625839Speter	}
10725839Speter    }
10825839Speter    argc -= optind;
10925839Speter    argv += optind;
11017721Speter
11117721Speter    /* We're going to run "cvs -n -q update" and check its output; if
11217721Speter     * the output is sufficiently unalarming, then we release with no
11317721Speter     * questions asked.  Else we prompt, then maybe release.
11454427Speter     * (Well, actually we ask no matter what.  Our notion of "sufficiently
11554427Speter     * unalarming" doesn't take into account "? foo.c" files, so it is
11654427Speter     * up to the user to take note of them, at least currently
11754427Speter     * (ignore-193 in testsuite)).
11817721Speter     */
11917721Speter    /* Construct the update command. */
12025839Speter    update_cmd = xmalloc (strlen (program_path)
12181404Speter			  + strlen (current_parsed_root->original)
12225839Speter			  + 20);
12317721Speter    sprintf (update_cmd, "%s -n -q -d %s update",
12481404Speter             program_path, current_parsed_root->original);
12517721Speter
12617721Speter#ifdef CLIENT_SUPPORT
12717721Speter    /* Start the server; we'll close it after looping. */
12881404Speter    if (current_parsed_root->isremote)
12925839Speter    {
13017721Speter	start_server ();
13117721Speter	ign_setup ();
13225839Speter    }
13317721Speter#endif /* CLIENT_SUPPORT */
13417721Speter
13554427Speter    /* Remember the directory where "cvs release" was invoked because
13654427Speter       all args are relative to this directory and we chdir around.
13754427Speter       */
13854427Speter    if (save_cwd (&cwd))
13954427Speter        error_exit ();
14054427Speter
14125839Speter    arg_start_idx = 0;
14217721Speter
14317721Speter    for (i = arg_start_idx; i < argc; i++)
14417721Speter    {
14525839Speter	thisarg = argv[i];
14625839Speter
14717721Speter        if (isdir (thisarg))
14817721Speter        {
14925839Speter	    if (CVS_CHDIR (thisarg) < 0)
15025839Speter	    {
15125839Speter		if (!really_quiet)
15225839Speter		    error (0, errno, "can't chdir to: %s", thisarg);
15325839Speter		continue;
15425839Speter	    }
15525839Speter	    if (!isdir (CVSADM))
15625839Speter	    {
15725839Speter		if (!really_quiet)
15825839Speter		    error (0, 0, "no repository directory: %s", thisarg);
15954427Speter		if (restore_cwd (&cwd, NULL))
16054427Speter		    error_exit ();
16125839Speter		continue;
16225839Speter	    }
16317721Speter	}
16417721Speter	else
16517721Speter        {
16625839Speter	    if (!really_quiet)
16725839Speter		error (0, 0, "no such directory: %s", thisarg);
16825839Speter	    continue;
16917721Speter	}
17017721Speter
17117721Speter	repository = Name_Repository ((char *) NULL, (char *) NULL);
17225839Speter
17317721Speter	if (!really_quiet)
17417721Speter	{
17532785Speter	    int line_length;
17632785Speter
17725839Speter	    /* The "release" command piggybacks on "update", which
17825839Speter	       does the real work of finding out if anything is not
17925839Speter	       up-to-date with the repository.  Then "release" prompts
18025839Speter	       the user, telling her how many files have been
18125839Speter	       modified, and asking if she still wants to do the
18225839Speter	       release.  */
18325839Speter	    fp = run_popen (update_cmd, "r");
18432785Speter	    if (fp == NULL)
18532785Speter		error (1, 0, "cannot run command %s", update_cmd);
18632785Speter
18725839Speter	    c = 0;
18817721Speter
18932785Speter	    while ((line_length = getline (&line, &line_allocated, fp)) >= 0)
19025839Speter	    {
19125839Speter		if (strchr ("MARCZ", *line))
19225839Speter		    c++;
19366525Speter		(void) fputs (line, stdout);
19425839Speter	    }
19532785Speter	    if (line_length < 0 && !feof (fp))
19632785Speter		error (0, errno, "cannot read from subprocess");
19717721Speter
19825839Speter	    /* If the update exited with an error, then we just want to
19925839Speter	       complain and go on to the next arg.  Especially, we do
20025839Speter	       not want to delete the local copy, since it's obviously
20125839Speter	       not what the user thinks it is.  */
20225839Speter	    if ((pclose (fp)) != 0)
20325839Speter	    {
20425839Speter		error (0, 0, "unable to release `%s'", thisarg);
20554427Speter		free (repository);
20654427Speter		if (restore_cwd (&cwd, NULL))
20754427Speter		    error_exit ();
20825839Speter		continue;
20925839Speter	    }
21017721Speter
21125839Speter	    printf ("You have [%d] altered files in this repository.\n",
21225839Speter		    c);
21325839Speter	    printf ("Are you sure you want to release %sdirectory `%s': ",
21425839Speter		    delete_flag ? "(and delete) " : "", thisarg);
21525839Speter	    c = !yesno ();
21625839Speter	    if (c)			/* "No" */
21725839Speter	    {
21825839Speter		(void) fprintf (stderr, "** `%s' aborted by user choice.\n",
21925839Speter				command_name);
22025839Speter		free (repository);
22154427Speter		if (restore_cwd (&cwd, NULL))
22254427Speter		    error_exit ();
22325839Speter		continue;
22425839Speter	    }
22517721Speter	}
22617721Speter
22717721Speter	if (1
22817721Speter#ifdef CLIENT_SUPPORT
22981404Speter	    && !(current_parsed_root->isremote
23017721Speter		 && (!supported_request ("noop")
23117721Speter		     || !supported_request ("Notify")))
23217721Speter#endif
23317721Speter	    )
23417721Speter	{
23525839Speter	    /* We are chdir'ed into the directory in question.
23625839Speter	       So don't pass args to unedit.  */
23725839Speter	    int argc = 1;
23825839Speter	    char *argv[3];
23925839Speter	    argv[0] = "dummy";
24025839Speter	    argv[1] = NULL;
24125839Speter	    err += unedit (argc, argv);
24217721Speter	}
24317721Speter
24417721Speter#ifdef CLIENT_SUPPORT
24581404Speter        if (current_parsed_root->isremote)
24617721Speter        {
24725839Speter	    send_to_server ("Argument ", 0);
24825839Speter	    send_to_server (thisarg, 0);
24925839Speter	    send_to_server ("\012", 1);
25025839Speter	    send_to_server ("release\012", 0);
25125839Speter	}
25217721Speter        else
25325839Speter#endif /* CLIENT_SUPPORT */
25417721Speter        {
25525839Speter	    history_write ('F', thisarg, "", thisarg, ""); /* F == Free */
25625839Speter        }
25725839Speter
25817721Speter        free (repository);
25925839Speter
26054427Speter	if (restore_cwd (&cwd, NULL))
26154427Speter	    error_exit ();
26254427Speter
26354427Speter	if (delete_flag)
26454427Speter	{
26554427Speter	    /* FIXME?  Shouldn't this just delete the CVS-controlled
26654427Speter	       files and, perhaps, the files that would normally be
26754427Speter	       ignored and leave everything else?  */
26854427Speter
26954427Speter	    if (unlink_file_dir (thisarg) < 0)
27054427Speter		error (0, errno, "deletion of directory %s failed", thisarg);
27154427Speter	}
27254427Speter
27317721Speter#ifdef CLIENT_SUPPORT
27481404Speter        if (current_parsed_root->isremote)
27525839Speter	    err += get_server_responses ();
27617721Speter#endif /* CLIENT_SUPPORT */
27725839Speter    }
27825839Speter
27954427Speter    if (restore_cwd (&cwd, NULL))
28054427Speter	error_exit ();
28154427Speter    free_cwd (&cwd);
28254427Speter
28325839Speter#ifdef CLIENT_SUPPORT
28481404Speter    if (current_parsed_root->isremote)
28525839Speter    {
28625839Speter	/* Unfortunately, client.c doesn't offer a way to close
28725839Speter	   the connection without waiting for responses.  The extra
28825839Speter	   network turnaround here is quite unnecessary other than
28925839Speter	   that....  */
29025839Speter	send_to_server ("noop\012", 0);
29125839Speter	err += get_responses_and_close ();
29225839Speter    }
29325839Speter#endif /* CLIENT_SUPPORT */
29425839Speter
29525839Speter    free (update_cmd);
29625839Speter    if (line != NULL)
29725839Speter	free (line);
29817721Speter    return err;
29917721Speter}
300