release.c revision 17721
1/*
2 * Release: "cancel" a checkout in the history log.
3 *
4 * - Don't allow release if anything is active - Don't allow release if not
5 * above or inside repository. - Don't allow release if ./CVS/Repository is
6 * not the same as the directory specified in the module database.
7 *
8 * - Enter a line in the history log indicating the "release". - If asked to,
9 * delete the local working directory.
10 */
11
12#include "cvs.h"
13
14static void release_delete PROTO((char *dir));
15
16static const char *const release_usage[] =
17{
18    "Usage: %s %s [-d] modules...\n",
19    "\t-d\tDelete the given directory.\n",
20    NULL
21};
22
23static short delete_flag;
24
25/* FIXME: This implementation is cheezy in quite a few ways:
26
27   1.  The whole "cvs update" junk could be checked locally with a
28   fairly simple start_recursion/classify_file loop--a win for
29   portability, performance, and cleanliness.
30
31   2.  Should be like edit/unedit in terms of working well if disconnected
32   from the network, and then sending a delayed notification.
33
34   3.  Way too many network turnarounds.  More than one for each argument.
35   Puh-leeze.
36
37   4.  Oh, and as a purely stylistic nit, break this out into separate
38   functions for client/local and for server.  Those #ifdefs are a mess.  */
39
40int
41release (argc, argv)
42    int argc;
43    char **argv;
44{
45    FILE *fp;
46    register int i, c;
47    char *repository, *srepos;
48    char line[PATH_MAX], update_cmd[PATH_MAX];
49    char *thisarg;
50    int arg_start_idx;
51    int err = 0;
52
53#ifdef SERVER_SUPPORT
54    if (!server_active)
55      {
56#endif /* SERVER_SUPPORT */
57        if (argc == -1)
58          usage (release_usage);
59        optind = 1;
60        while ((c = getopt (argc, argv, "Qdq")) != -1)
61          {
62            switch (c)
63              {
64              case 'Q':
65              case 'q':
66#ifdef SERVER_SUPPORT
67		/* The CVS 1.5 client sends these options (in addition to
68		   Global_option requests), so we must ignore them.  */
69		if (!server_active)
70#endif
71                  error (1, 0,
72                         "-q or -Q must be specified before \"%s\"",
73                         command_name);
74		break;
75              case 'd':
76		delete_flag++;
77		break;
78              case '?':
79              default:
80		usage (release_usage);
81		break;
82              }
83          }
84        argc -= optind;
85        argv += optind;
86#ifdef SERVER_SUPPORT
87      }
88#endif /* SERVER_SUPPORT */
89
90    /* We're going to run "cvs -n -q update" and check its output; if
91     * the output is sufficiently unalarming, then we release with no
92     * questions asked.  Else we prompt, then maybe release.
93     */
94    /* Construct the update command. */
95    sprintf (update_cmd, "%s -n -q -d %s update",
96             program_path, CVSroot);
97
98#ifdef CLIENT_SUPPORT
99    /* Start the server; we'll close it after looping. */
100    if (client_active)
101      {
102	start_server ();
103	ign_setup ();
104      }
105#endif /* CLIENT_SUPPORT */
106
107    /* If !server_active, we already skipped over argv[0] in the "argc
108       -= optind;" statement above.  But if server_active, we need to
109       skip it now.  */
110#ifdef SERVER_SUPPORT
111    if (server_active)
112      arg_start_idx = 1;
113    else
114#endif /* SERVER_SUPPORT */
115      arg_start_idx = 0;
116
117    for (i = arg_start_idx; i < argc; i++)
118    {
119      thisarg = argv[i];
120
121#ifdef SERVER_SUPPORT
122      if (server_active)
123      {
124        /* Just log the release -- all the interesting stuff happened
125         * on the client.
126         */
127        history_write ('F', thisarg, "", thisarg, "");	/* F == Free */
128      }
129      else
130      {
131#endif /* SERVER_SUPPORT */
132
133        /*
134         * If we are in a repository, do it.  Else if we are in the parent of
135         * a directory with the same name as the module, "cd" into it and
136         * look for a repository there.
137         */
138        if (isdir (thisarg))
139        {
140          if (chdir (thisarg) < 0)
141          {
142            if (!really_quiet)
143              error (0, 0, "can't chdir to: %s", thisarg);
144            continue;
145          }
146          if (!isdir (CVSADM))
147          {
148            if (!really_quiet)
149              error (0, 0, "no repository module: %s", thisarg);
150            continue;
151          }
152	}
153	else
154        {
155          if (!really_quiet)
156            error (0, 0, "no such directory: %s", thisarg);
157          continue;
158	}
159
160	repository = Name_Repository ((char *) NULL, (char *) NULL);
161	srepos = Short_Repository (repository);
162
163	if (!really_quiet)
164	{
165          /* The "release" command piggybacks on "update", which
166           * does the real work of finding out if anything is not
167           * up-to-date with the repository.  Then "release" prompts
168           * the user, telling her how many files have been
169           * modified, and asking if she still wants to do the
170           * release.
171           */
172          fp = run_popen (update_cmd, "r");
173          c = 0;
174
175          while (fgets (line, sizeof (line), fp))
176          {
177            if (strchr ("MARCZ", *line))
178              c++;
179            (void) printf (line);
180          }
181
182          /* If the update exited with an error, then we just want to
183           * complain and go on to the next arg.  Especially, we do
184           * not want to delete the local copy, since it's obviously
185           * not what the user thinks it is.
186           */
187          if ((pclose (fp)) != 0)
188          {
189            error (0, 0, "unable to release `%s'", thisarg);
190            continue;
191          }
192
193          (void) printf ("You have [%d] altered files in this repository.\n",
194                         c);
195          (void) printf ("Are you sure you want to release %smodule `%s': ",
196                         delete_flag ? "(and delete) " : "", thisarg);
197          c = !yesno ();
198          if (c)			/* "No" */
199          {
200            (void) fprintf (stderr, "** `%s' aborted by user choice.\n",
201                            command_name);
202            free (repository);
203            continue;
204          }
205	}
206
207	if (1
208#ifdef SERVER_SUPPORT
209	    && !server_active
210#endif
211#ifdef CLIENT_SUPPORT
212	    && !(client_active
213		 && (!supported_request ("noop")
214		     || !supported_request ("Notify")))
215#endif
216	    )
217	{
218	  /* We are chdir'ed into the directory in question.
219	     So don't pass args to unedit.  */
220	  int argc = 1;
221	  char *argv[3];
222	  argv[0] = "dummy";
223	  argv[1] = NULL;
224	  err += unedit (argc, argv);
225	}
226
227#ifdef CLIENT_SUPPORT
228        if (client_active)
229        {
230          send_to_server ("Argument ", 0);
231          send_to_server (thisarg, 0);
232          send_to_server ("\012", 1);
233          send_to_server ("release\012", 0);
234        }
235        else
236        {
237#endif /* CLIENT_SUPPORT */
238          history_write ('F', thisarg, "", thisarg, ""); /* F == Free */
239#ifdef CLIENT_SUPPORT
240        } /* else client not active */
241#endif /* CLIENT_SUPPORT */
242
243        free (repository);
244        if (delete_flag) release_delete (thisarg);
245
246#ifdef CLIENT_SUPPORT
247        if (client_active)
248          return get_responses_and_close ();
249        else
250#endif /* CLIENT_SUPPORT */
251          return (0);
252
253#ifdef SERVER_SUPPORT
254      } /* else server not active */
255#endif  /* SERVER_SUPPORT */
256    }   /* `for' loop */
257    return err;
258}
259
260
261/* We want to "rm -r" the working directory, but let us be a little
262   paranoid.  */
263static void
264release_delete (dir)
265    char *dir;
266{
267    struct stat st;
268    ino_t ino;
269
270    (void) stat (".", &st);
271    ino = st.st_ino;
272    (void) chdir ("..");
273    (void) stat (dir, &st);
274    if (ino != st.st_ino)
275    {
276	error (0, 0,
277	       "Parent dir on a different disk, delete of %s aborted", dir);
278	return;
279    }
280    /*
281     * XXX - shouldn't this just delete the CVS-controlled files and, perhaps,
282     * the files that would normally be ignored and leave everything else?
283     */
284    if (unlink_file_dir (dir) < 0)
285	error (0, errno, "deletion of directory %s failed", dir);
286}
287