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