117721Speter/*
2175261Sobrien * Copyright (C) 1994-2005 The Free Software Foundation, Inc.
3175261Sobrien *
4175261Sobrien * This program is free software; you can redistribute it and/or modify
5175261Sobrien * it under the terms of the GNU General Public License as published by
6175261Sobrien * the Free Software Foundation; either version 2, or (at your option)
7175261Sobrien * any later version.
8175261Sobrien *
9175261Sobrien * This program is distributed in the hope that it will be useful,
10175261Sobrien * but WITHOUT ANY WARRANTY; without even the implied warranty of
11175261Sobrien * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12175261Sobrien * GNU General Public License for more details.
13175261Sobrien */
14175261Sobrien
15175261Sobrien/*
1617721Speter * Release: "cancel" a checkout in the history log.
1717721Speter *
1817721Speter * - Enter a line in the history log indicating the "release". - If asked to,
1917721Speter * delete the local working directory.
2017721Speter */
2117721Speter
2217721Speter#include "cvs.h"
2354427Speter#include "savecwd.h"
2425839Speter#include "getline.h"
2517721Speter
2617721Speterstatic const char *const release_usage[] =
2717721Speter{
2825839Speter    "Usage: %s %s [-d] directories...\n",
2917721Speter    "\t-d\tDelete the given directory.\n",
3032785Speter    "(Specify the --help global option for a list of other help options)\n",
3117721Speter    NULL
3217721Speter};
3317721Speter
3425839Speter#ifdef SERVER_SUPPORT
3525839Speterstatic int release_server PROTO ((int argc, char **argv));
3617721Speter
3725839Speter/* This is the server side of cvs release.  */
3825839Speterstatic int
3925839Speterrelease_server (argc, argv)
4025839Speter    int argc;
4125839Speter    char **argv;
4225839Speter{
4325839Speter    int i;
4417721Speter
4525839Speter    /* Note that we skip argv[0].  */
4625839Speter    for (i = 1; i < argc; ++i)
4725839Speter	history_write ('F', argv[i], "", argv[i], "");
4825839Speter    return 0;
4925839Speter}
5025839Speter
5125839Speter#endif /* SERVER_SUPPORT */
5225839Speter
5325839Speter/* There are various things to improve about this implementation:
5425839Speter
5525839Speter   1.  Using run_popen to run "cvs update" could be replaced by a
5617721Speter   fairly simple start_recursion/classify_file loop--a win for
5725839Speter   portability, performance, and cleanliness.  In particular, there is
5825839Speter   no particularly good way to find the right "cvs".
5917721Speter
6025839Speter   2.  The fact that "cvs update" contacts the server slows things down;
6125839Speter   it undermines the case for using "cvs release" rather than "rm -rf".
6225839Speter   However, for correctly printing "? foo" and correctly handling
6332785Speter   CVSROOTADM_IGNORE, we currently need to contact the server.  (One
6432785Speter   idea for how to fix this is to stash a copy of CVSROOTADM_IGNORE in
6532785Speter   the working directories; see comment at base_* in entries.c for a
6632785Speter   few thoughts on that).
6717721Speter
6825839Speter   3.  Would be nice to take processing things on the client side one step
6925839Speter   further, and making it like edit/unedit in terms of working well if
7025839Speter   disconnected from the network, and then sending a delayed
7125839Speter   notification.
7217721Speter
7325839Speter   4.  Having separate network turnarounds for the "Notify" request
7425839Speter   which we do as part of unedit, and for the "release" itself, is slow
7525839Speter   and unnecessary.  */
7617721Speter
7717721Speterint
7817721Speterrelease (argc, argv)
7917721Speter    int argc;
8017721Speter    char **argv;
8117721Speter{
8217721Speter    FILE *fp;
8325839Speter    int i, c;
8425839Speter    char *line = NULL;
8525839Speter    size_t line_allocated = 0;
8625839Speter    char *update_cmd;
8717721Speter    char *thisarg;
8817721Speter    int arg_start_idx;
8917721Speter    int err = 0;
9025839Speter    short delete_flag = 0;
9154427Speter    struct saved_cwd cwd;
9217721Speter
9317721Speter#ifdef SERVER_SUPPORT
9425839Speter    if (server_active)
9525839Speter	return release_server (argc, argv);
9617721Speter#endif
9725839Speter
9825839Speter    /* Everything from here on is client or local.  */
9925839Speter    if (argc == -1)
10025839Speter	usage (release_usage);
10126065Speter    optind = 0;
10225839Speter    while ((c = getopt (argc, argv, "+Qdq")) != -1)
10325839Speter    {
10425839Speter	switch (c)
10525839Speter	{
10625839Speter	    case 'Q':
10725839Speter	    case 'q':
10825839Speter		error (1, 0,
10925839Speter		       "-q or -Q must be specified before \"%s\"",
110128266Speter		       cvs_cmd_name);
11117721Speter		break;
11225839Speter	    case 'd':
11317721Speter		delete_flag++;
11417721Speter		break;
11525839Speter	    case '?':
11625839Speter	    default:
11717721Speter		usage (release_usage);
11817721Speter		break;
11925839Speter	}
12025839Speter    }
12125839Speter    argc -= optind;
12225839Speter    argv += optind;
12317721Speter
12417721Speter    /* We're going to run "cvs -n -q update" and check its output; if
12517721Speter     * the output is sufficiently unalarming, then we release with no
12617721Speter     * questions asked.  Else we prompt, then maybe release.
12754427Speter     * (Well, actually we ask no matter what.  Our notion of "sufficiently
12854427Speter     * unalarming" doesn't take into account "? foo.c" files, so it is
12954427Speter     * up to the user to take note of them, at least currently
13054427Speter     * (ignore-193 in testsuite)).
13117721Speter     */
132128266Speter    /* Construct the update command.  Be sure to add authentication and
133128266Speter       encryption if we are using them currently, else our child process may
134128266Speter       not be able to communicate with the server.  */
13525839Speter    update_cmd = xmalloc (strlen (program_path)
136128266Speter                        + strlen (current_parsed_root->original)
137128266Speter                        + 1 + 3 + 3 + 16 + 1);
138128266Speter    sprintf (update_cmd, "%s %s%s-n -q -d %s update",
139128266Speter             program_path,
140175261Sobrien#if defined (CLIENT_SUPPORT) || defined (SERVER_SUPPORT)
141128266Speter             cvsauthenticate ? "-a " : "",
142128266Speter             cvsencrypt ? "-x " : "",
143175261Sobrien#else
144175261Sobrien	     "", "",
145175261Sobrien#endif
146128266Speter             current_parsed_root->original);
14717721Speter
14817721Speter#ifdef CLIENT_SUPPORT
14917721Speter    /* Start the server; we'll close it after looping. */
15081404Speter    if (current_parsed_root->isremote)
15125839Speter    {
15217721Speter	start_server ();
15317721Speter	ign_setup ();
15425839Speter    }
15517721Speter#endif /* CLIENT_SUPPORT */
15617721Speter
15754427Speter    /* Remember the directory where "cvs release" was invoked because
15854427Speter       all args are relative to this directory and we chdir around.
15954427Speter       */
16054427Speter    if (save_cwd (&cwd))
16154427Speter        error_exit ();
16254427Speter
16325839Speter    arg_start_idx = 0;
16417721Speter
16517721Speter    for (i = arg_start_idx; i < argc; i++)
16617721Speter    {
16725839Speter	thisarg = argv[i];
16825839Speter
16917721Speter        if (isdir (thisarg))
17017721Speter        {
17125839Speter	    if (CVS_CHDIR (thisarg) < 0)
17225839Speter	    {
17325839Speter		if (!really_quiet)
17425839Speter		    error (0, errno, "can't chdir to: %s", thisarg);
17525839Speter		continue;
17625839Speter	    }
17725839Speter	    if (!isdir (CVSADM))
17825839Speter	    {
17925839Speter		if (!really_quiet)
18025839Speter		    error (0, 0, "no repository directory: %s", thisarg);
18154427Speter		if (restore_cwd (&cwd, NULL))
18254427Speter		    error_exit ();
18325839Speter		continue;
18425839Speter	    }
18517721Speter	}
18617721Speter	else
18717721Speter        {
18825839Speter	    if (!really_quiet)
18925839Speter		error (0, 0, "no such directory: %s", thisarg);
19025839Speter	    continue;
19117721Speter	}
19217721Speter
19317721Speter	if (!really_quiet)
19417721Speter	{
195175261Sobrien	    int line_length, status;
19632785Speter
19725839Speter	    /* The "release" command piggybacks on "update", which
19825839Speter	       does the real work of finding out if anything is not
19925839Speter	       up-to-date with the repository.  Then "release" prompts
20025839Speter	       the user, telling her how many files have been
20125839Speter	       modified, and asking if she still wants to do the
20225839Speter	       release.  */
20325839Speter	    fp = run_popen (update_cmd, "r");
20432785Speter	    if (fp == NULL)
20532785Speter		error (1, 0, "cannot run command %s", update_cmd);
20632785Speter
20725839Speter	    c = 0;
20817721Speter
20932785Speter	    while ((line_length = getline (&line, &line_allocated, fp)) >= 0)
21025839Speter	    {
21125839Speter		if (strchr ("MARCZ", *line))
21225839Speter		    c++;
21366525Speter		(void) fputs (line, stdout);
21425839Speter	    }
21532785Speter	    if (line_length < 0 && !feof (fp))
21632785Speter		error (0, errno, "cannot read from subprocess");
21717721Speter
21825839Speter	    /* If the update exited with an error, then we just want to
21925839Speter	       complain and go on to the next arg.  Especially, we do
22025839Speter	       not want to delete the local copy, since it's obviously
22125839Speter	       not what the user thinks it is.  */
222175261Sobrien	    status = pclose (fp);
223175261Sobrien	    if (status != 0)
22425839Speter	    {
225175261Sobrien		error (0, 0, "unable to release `%s' (%d)", thisarg, status);
22654427Speter		if (restore_cwd (&cwd, NULL))
22754427Speter		    error_exit ();
22825839Speter		continue;
22925839Speter	    }
23017721Speter
23125839Speter	    printf ("You have [%d] altered files in this repository.\n",
23225839Speter		    c);
23325839Speter	    printf ("Are you sure you want to release %sdirectory `%s': ",
23425839Speter		    delete_flag ? "(and delete) " : "", thisarg);
23525839Speter	    c = !yesno ();
23625839Speter	    if (c)			/* "No" */
23725839Speter	    {
23825839Speter		(void) fprintf (stderr, "** `%s' aborted by user choice.\n",
239128266Speter				cvs_cmd_name);
24054427Speter		if (restore_cwd (&cwd, NULL))
24154427Speter		    error_exit ();
24225839Speter		continue;
24325839Speter	    }
24417721Speter	}
24517721Speter
246128266Speter        /* Note:  client.c doesn't like to have other code
247128266Speter           changing the current directory on it.  So a fair amount
248128266Speter           of effort is needed to make sure it doesn't get confused
249128266Speter           about the directory and (for example) overwrite
250128266Speter           CVS/Entries file in the wrong directory.  See release-17
251128266Speter           through release-23. */
252128266Speter
253128266Speter	if (restore_cwd (&cwd, NULL))
254175261Sobrien	    error_exit ();
255128266Speter
25617721Speter	if (1
25717721Speter#ifdef CLIENT_SUPPORT
25881404Speter	    && !(current_parsed_root->isremote
25917721Speter		 && (!supported_request ("noop")
26017721Speter		     || !supported_request ("Notify")))
26117721Speter#endif
26217721Speter	    )
26317721Speter	{
264128266Speter	    int argc = 2;
26525839Speter	    char *argv[3];
26625839Speter	    argv[0] = "dummy";
267128266Speter	    argv[1] = thisarg;
268128266Speter	    argv[2] = NULL;
26925839Speter	    err += unedit (argc, argv);
270128266Speter            if (restore_cwd (&cwd, NULL))
271175261Sobrien                error_exit ();
27217721Speter	}
27317721Speter
27417721Speter#ifdef CLIENT_SUPPORT
27581404Speter        if (current_parsed_root->isremote)
27617721Speter        {
27725839Speter	    send_to_server ("Argument ", 0);
27825839Speter	    send_to_server (thisarg, 0);
27925839Speter	    send_to_server ("\012", 1);
28025839Speter	    send_to_server ("release\012", 0);
28125839Speter	}
28217721Speter        else
28325839Speter#endif /* CLIENT_SUPPORT */
28417721Speter        {
28525839Speter	    history_write ('F', thisarg, "", thisarg, ""); /* F == Free */
28625839Speter        }
28725839Speter
28854427Speter	if (delete_flag)
28954427Speter	{
29054427Speter	    /* FIXME?  Shouldn't this just delete the CVS-controlled
29154427Speter	       files and, perhaps, the files that would normally be
29254427Speter	       ignored and leave everything else?  */
29354427Speter
29454427Speter	    if (unlink_file_dir (thisarg) < 0)
29554427Speter		error (0, errno, "deletion of directory %s failed", thisarg);
29654427Speter	}
29754427Speter
29817721Speter#ifdef CLIENT_SUPPORT
29981404Speter        if (current_parsed_root->isremote)
300128266Speter        {
301128266Speter	    /* FIXME:
302128266Speter	     * Is there a good reason why get_server_responses() isn't
303128266Speter	     * responsible for restoring its initial directory itself when
304128266Speter	     * finished?
305128266Speter	     */
306128266Speter            err += get_server_responses ();
307128266Speter
308128266Speter            if (restore_cwd (&cwd, NULL))
309175261Sobrien                error_exit ();
310128266Speter        }
31117721Speter#endif /* CLIENT_SUPPORT */
31225839Speter    }
31325839Speter
31454427Speter    if (restore_cwd (&cwd, NULL))
31554427Speter	error_exit ();
31654427Speter    free_cwd (&cwd);
31754427Speter
31825839Speter#ifdef CLIENT_SUPPORT
31981404Speter    if (current_parsed_root->isremote)
32025839Speter    {
32125839Speter	/* Unfortunately, client.c doesn't offer a way to close
32225839Speter	   the connection without waiting for responses.  The extra
32325839Speter	   network turnaround here is quite unnecessary other than
32425839Speter	   that....  */
32525839Speter	send_to_server ("noop\012", 0);
32625839Speter	err += get_responses_and_close ();
32725839Speter    }
32825839Speter#endif /* CLIENT_SUPPORT */
32925839Speter
33025839Speter    free (update_cmd);
33125839Speter    if (line != NULL)
33225839Speter	free (line);
33317721Speter    return err;
33417721Speter}
335