/* * Release: "cancel" a checkout in the history log. * * - Don't allow release if anything is active - Don't allow release if not * above or inside repository. - Don't allow release if ./CVS/Repository is * not the same as the directory specified in the module database. * * - Enter a line in the history log indicating the "release". - If asked to, * delete the local working directory. */ #include "cvs.h" static void release_delete PROTO((char *dir)); static const char *const release_usage[] = { "Usage: %s %s [-d] modules...\n", "\t-d\tDelete the given directory.\n", NULL }; static short delete_flag; /* FIXME: This implementation is cheezy in quite a few ways: 1. The whole "cvs update" junk could be checked locally with a fairly simple start_recursion/classify_file loop--a win for portability, performance, and cleanliness. 2. Should be like edit/unedit in terms of working well if disconnected from the network, and then sending a delayed notification. 3. Way too many network turnarounds. More than one for each argument. Puh-leeze. 4. Oh, and as a purely stylistic nit, break this out into separate functions for client/local and for server. Those #ifdefs are a mess. */ int release (argc, argv) int argc; char **argv; { FILE *fp; register int i, c; char *repository, *srepos; char line[PATH_MAX], update_cmd[PATH_MAX]; char *thisarg; int arg_start_idx; int err = 0; #ifdef SERVER_SUPPORT if (!server_active) { #endif /* SERVER_SUPPORT */ if (argc == -1) usage (release_usage); optind = 1; while ((c = getopt (argc, argv, "Qdq")) != -1) { switch (c) { case 'Q': case 'q': #ifdef SERVER_SUPPORT /* The CVS 1.5 client sends these options (in addition to Global_option requests), so we must ignore them. */ if (!server_active) #endif error (1, 0, "-q or -Q must be specified before \"%s\"", command_name); break; case 'd': delete_flag++; break; case '?': default: usage (release_usage); break; } } argc -= optind; argv += optind; #ifdef SERVER_SUPPORT } #endif /* SERVER_SUPPORT */ /* We're going to run "cvs -n -q update" and check its output; if * the output is sufficiently unalarming, then we release with no * questions asked. Else we prompt, then maybe release. */ /* Construct the update command. */ sprintf (update_cmd, "%s -n -q -d %s update", program_path, CVSroot); #ifdef CLIENT_SUPPORT /* Start the server; we'll close it after looping. */ if (client_active) { start_server (); ign_setup (); } #endif /* CLIENT_SUPPORT */ /* If !server_active, we already skipped over argv[0] in the "argc -= optind;" statement above. But if server_active, we need to skip it now. */ #ifdef SERVER_SUPPORT if (server_active) arg_start_idx = 1; else #endif /* SERVER_SUPPORT */ arg_start_idx = 0; for (i = arg_start_idx; i < argc; i++) { thisarg = argv[i]; #ifdef SERVER_SUPPORT if (server_active) { /* Just log the release -- all the interesting stuff happened * on the client. */ history_write ('F', thisarg, "", thisarg, ""); /* F == Free */ } else { #endif /* SERVER_SUPPORT */ /* * If we are in a repository, do it. Else if we are in the parent of * a directory with the same name as the module, "cd" into it and * look for a repository there. */ if (isdir (thisarg)) { if (chdir (thisarg) < 0) { if (!really_quiet) error (0, 0, "can't chdir to: %s", thisarg); continue; } if (!isdir (CVSADM)) { if (!really_quiet) error (0, 0, "no repository module: %s", thisarg); continue; } } else { if (!really_quiet) error (0, 0, "no such directory: %s", thisarg); continue; } repository = Name_Repository ((char *) NULL, (char *) NULL); srepos = Short_Repository (repository); if (!really_quiet) { /* The "release" command piggybacks on "update", which * does the real work of finding out if anything is not * up-to-date with the repository. Then "release" prompts * the user, telling her how many files have been * modified, and asking if she still wants to do the * release. */ fp = run_popen (update_cmd, "r"); c = 0; while (fgets (line, sizeof (line), fp)) { if (strchr ("MARCZ", *line)) c++; (void) printf (line); } /* If the update exited with an error, then we just want to * complain and go on to the next arg. Especially, we do * not want to delete the local copy, since it's obviously * not what the user thinks it is. */ if ((pclose (fp)) != 0) { error (0, 0, "unable to release `%s'", thisarg); continue; } (void) printf ("You have [%d] altered files in this repository.\n", c); (void) printf ("Are you sure you want to release %smodule `%s': ", delete_flag ? "(and delete) " : "", thisarg); c = !yesno (); if (c) /* "No" */ { (void) fprintf (stderr, "** `%s' aborted by user choice.\n", command_name); free (repository); continue; } } if (1 #ifdef SERVER_SUPPORT && !server_active #endif #ifdef CLIENT_SUPPORT && !(client_active && (!supported_request ("noop") || !supported_request ("Notify"))) #endif ) { /* We are chdir'ed into the directory in question. So don't pass args to unedit. */ int argc = 1; char *argv[3]; argv[0] = "dummy"; argv[1] = NULL; err += unedit (argc, argv); } #ifdef CLIENT_SUPPORT if (client_active) { send_to_server ("Argument ", 0); send_to_server (thisarg, 0); send_to_server ("\012", 1); send_to_server ("release\012", 0); } else { #endif /* CLIENT_SUPPORT */ history_write ('F', thisarg, "", thisarg, ""); /* F == Free */ #ifdef CLIENT_SUPPORT } /* else client not active */ #endif /* CLIENT_SUPPORT */ free (repository); if (delete_flag) release_delete (thisarg); #ifdef CLIENT_SUPPORT if (client_active) return get_responses_and_close (); else #endif /* CLIENT_SUPPORT */ return (0); #ifdef SERVER_SUPPORT } /* else server not active */ #endif /* SERVER_SUPPORT */ } /* `for' loop */ return err; } /* We want to "rm -r" the working directory, but let us be a little paranoid. */ static void release_delete (dir) char *dir; { struct stat st; ino_t ino; (void) stat (".", &st); ino = st.st_ino; (void) chdir (".."); (void) stat (dir, &st); if (ino != st.st_ino) { error (0, 0, "Parent dir on a different disk, delete of %s aborted", dir); return; } /* * XXX - shouldn't this just delete the CVS-controlled files and, perhaps, * the files that would normally be ignored and leave everything else? */ if (unlink_file_dir (dir) < 0) error (0, errno, "deletion of directory %s failed", dir); }