xref: /openbsd/gnu/usr.bin/cvs/src/release.c (revision f9bbbf45)
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 
12 static 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
21 static int release_server PROTO ((int argc, char **argv));
22 
23 /* This is the server side of cvs release.  */
24 static int
release_server(argc,argv)25 release_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 
63 int
release(argc,argv)64 release (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