/* * Copyright (c) 1992, Brian Berliner and Jeff Polk * Copyright (c) 1989-1992, Brian Berliner * * You may distribute under the terms of the GNU General Public License * as specified in the README file that comes with the CVS 1.3 kit. * * This is the main C driver for the CVS system. * * Credit to Dick Grune, Vrije Universiteit, Amsterdam, for writing * the shell-script CVS system that this is based on. * * Usage: * cvs [options] command [options] [files/modules...] * * Where "command" is composed of: * admin RCS command * checkout Check out a module/dir/file * export Like checkout, but used for exporting sources * update Brings work tree in sync with repository * commit Checks files into the repository * diff Runs diffs between revisions * log Prints "rlog" information for files * add Adds an entry to the repository * remove Removes an entry from the repository * status Status info on the revisions * rdiff "patch" format diff listing between releases * tag Add/delete a symbolic tag to the RCS file * rtag Add/delete a symbolic tag to the RCS file * import Import sources into CVS, using vendor branches * release Indicate that Module is no longer in use. * history Display history of Users and Modules. */ #include "cvs.h" /* rcvs: */ #include "rcvs.h" #include "patchlevel.h" char rcsid[] = "@(#)main.c 1.64 92/09/11\n"; extern char *getenv (); char *program_name; char *command_name = ""; char *Module = ""; int use_editor = TRUE; int cvswrite = !CVSREAD_DFLT; int really_quiet = FALSE; int quiet = FALSE; int trace = FALSE; int noexec = FALSE; int logoff = FALSE; char *CurDir; /* * Defaults, for the environment variables that are not set */ char *Rcsbin = RCSBIN_DFLT; char *Editor = EDITOR_DFLT; char *CVSroot = CVSROOT_DFLT; #if __STDC__ int add (int argc, char **argv); int admin (int argc, char **argv); int checkout (int argc, char **argv); int commit (int argc, char **argv); int diff (int argc, char **argv); int history (int argc, char **argv); int import (int argc, char **argv); int cvslog (int argc, char **argv); int patch (int argc, char **argv); int release (int argc, char **argv); int cvsremove (int argc, char **argv); int rtag (int argc, char **argv); int status (int argc, char **argv); int tag (int argc, char **argv); int update (int argc, char **argv); #else int add (); int admin (); int checkout (); int commit (); int diff (); int history (); int import (); int cvslog (); int patch (); int release (); int cvsremove (); int rtag (); int status (); int tag (); int update (); #endif /* __STDC__ */ struct cmd { char *fullname; /* Full name of the function (e.g. "commit") */ char *nick1; /* alternate name (e.g. "ci") */ char *nick2; /* another alternate names (e.g. "ci") */ int (*func) (); /* Function takes (argc, argv) arguments. */ } cmds[] = { { "add", "ad", "new", add }, { "admin", "adm", "rcs", admin }, { "checkout", "co", "get", checkout }, { "commit", "ci", "com", commit }, { "diff", "di", "dif", diff }, { "export", "exp", "ex", checkout }, { "history", "hi", "his", history }, { "import", "im", "imp", import }, { "log", "lo", "rlog", cvslog }, { "rdiff", "patch", "pa", patch }, { "release", "re", "rel", release }, { "remove", "rm", "delete", cvsremove }, { "status", "st", "stat", status }, { "rtag", "rt", "rfreeze", rtag }, { "tag", "ta", "freeze", tag }, { "update", "up", "upd", update }, { NULL, NULL, NULL, NULL }, }; static char *usg[] = { "Usage: %s [cvs-options] command [command-options] [files...]\n", " Where 'cvs-options' are:\n", " -H Displays Usage information for command\n", " -Q Cause CVS to be really quiet.\n", " -q Cause CVS to be somewhat quiet.\n", " -r Make checked-out files read-only\n", " -w Make checked-out files read-write (default)\n", " -l Turn History logging off\n", " -n Do not execute anything that will change the disk\n", " -t Show trace of program execution -- Try with -n\n", " -v CVS version and copyright\n", " -b bindir Find RCS programs in 'bindir'\n", " -e editor Use 'editor' for editing log information\n", " -d CVS_root Overrides $CVSROOT as the root of the CVS tree\n", " -c change repository of local folder\n", " -C change repository of remote folder\n", " -R remote checkout from $CVSROOTr \n", " -P phases control phase of processing remote folder [1-3] \n", " -U Remove RCVS lock to recover from error situation\n", " -L set RCVS lock (internal usage) \n", " -S run RCVS sync (internal usage)\n", " -X cvsrec the receiver of rdist in phase 1(internal usage)\n", " -Y tmpfile run CVS command in shell (internal usage).\n", " -Z convert rcvs-0.3.9 remote folder. (will be obsolete).\n", "\n", " and where 'command' is:\n", " add Adds a new file/directory to the repository\n", " admin Administration front end for rcs\n", " checkout Checkout sources for editing\n", " commit Checks files into the repository\n", " diff Runs diffs between revisions\n", " history Shows status of files and users\n", " import Import sources into CVS, using vendor branches\n", " export Export sources from CVS, similar to checkout\n", " log Prints out 'rlog' information for files\n", " rdiff 'patch' format diffs between releases\n", " release Indicate that a Module is no longer in use\n", " remove Removes an entry from the repository\n", " status Status info on the revisions\n", " tag Add a symbolic tag to checked out version of RCS file\n", " rtag Add a symbolic tag to the RCS file\n", " update Brings work tree in sync with repository\n", NULL, }; static SIGTYPE main_cleanup () { /* rcvs: cleanup RCVS lock */ if (trace) fprintf (stderr, "-> main_cleanup: error, program is aborted\n"); if ((! rcvs_lock_phase3) && (rcvs_native || rcvs_sync_backward)) rcvs_Clear_my_lock (); if (rcvs_msgfile != NULL) (void) unlink(rcvs_msgfile); exit (1); } int main (argc, argv) int argc; char *argv[]; { extern char *version_string; char *cp; struct cmd *cm; int c, help = FALSE, err = 0; int rcsbin_update_env, cvs_update_env; char tmp[PATH_MAX]; /* * Just save the last component of the path for error messages */ if ((program_name = rindex (argv[0], '/')) == NULL) program_name = argv[0]; else program_name++; CurDir = xmalloc (PATH_MAX); if (!getwd (CurDir)) error (1, 0, "cannot get working directory: %s", CurDir); /* * Query the environment variables up-front, so that * they can be overridden by command line arguments */ rcsbin_update_env = *Rcsbin; /* RCSBIN_DFLT must be set */ if ((cp = getenv (RCSBIN_ENV)) != NULL) { Rcsbin = cp; rcsbin_update_env = 0; /* it's already there */ } if ((cp = getenv (EDITOR_ENV)) != NULL) Editor = cp; if ((cp = getenv (CVSROOT_ENV)) != NULL) { CVSroot = cp; cvs_update_env = 0; /* it's already there */ /* rcvs: */ CVSroot_v = cp; CVSroot_n = xstrdup (CVSROOT_ENV); } if (getenv (CVSREAD_ENV) != NULL) cvswrite = FALSE; /* rcvs: get CVSDOMAIN */ if ((cp = getenv (CVSDOMAIN_ENV)) != NULL) rcvs_domain = cp; /* rcvs: get CVSUSER */ if ((cp = getenv (CVSUSER_ENV)) != NULL) rcvs_username = cp; /* rcvs: get CVSBYPASS */ if ((cp = getenv (CVSBYPASS_ENV)) != NULL) rcvs_bypass = cp; /* rcvs: get CVSROOTr */ if ((cp = getenv (CVSROOTr_ENV)) != NULL) { CVSrootr_v = cp; } CVSroot_n = xstrdup (CVSROOT_ENV); CVSrootr_n = xstrdup (CVSROOTr_ENV); optind = 1; /* rcvs: add options for Remote CVS */ while ((c = gnu_getopt (argc, argv, "Qqrwtnlvb:e:d:HcCLP:RSUX:Y:Z")) != -1) { switch (c) { case 'Q': really_quiet = TRUE; /* FALL THROUGH */ case 'q': quiet = TRUE; break; case 'r': cvswrite = FALSE; break; case 'w': cvswrite = TRUE; break; case 't': trace = TRUE; break; case 'n': noexec = TRUE; case 'l': /* Fall through */ logoff = TRUE; break; case 'v': (void) fputs (rcsid, stdout); (void) fputs (version_string, stdout); (void) sprintf (tmp, "Patch Level: %d\n", PATCHLEVEL); (void) fputs (tmp, stdout); (void) fputs ("\nCopyright (c) 1992, Brian Berliner and Jeff Polk\nCopyright (c) 1989-1992, Brian Berliner\n\nCVS may be copied only under the terms of the GNU General Public License,\na copy of which can be found with the CVS 1.3 distribution kit.\n", stdout); exit (0); break; case 'b': Rcsbin = optarg; rcsbin_update_env = 1; /* need to update environment */ break; case 'e': Editor = optarg; /* rcvs: */ if (rcvs_sync_forward) { if (optarg != NULL) sscanf(optarg,"%d", &ver_rcvs_client); if (trace) fprintf(stderr, "-> client version=%d\n", ver_rcvs_client); ver_rcvs_server = ver_rcvs; } break; case 'd': CVSroot = optarg; cvs_update_env = 1; /* need to update environment */ /* rcvs: */ CVSrootd_v = optarg; CVSrootd_n = xstrdup ("-d"); break; case 'H': help = TRUE; break; /* rcvs: handle RCVS options */ case 'c': rcvs_copt_change_repos = TRUE; break; case 'C': case 'Z': rcvs_Copt_change_repos = TRUE; break; case 'L': rcvs_Lopt_lock = TRUE; break; case 'P': rcvs_Popt_phases = optarg; break; case 'R': rcvs_Ropt_remote = TRUE; break; case 'S': rcvs_sync = rcvs_sync_forward = TRUE; break; case 'U': rcvs_Uopt_unlock = TRUE; break; case 'X': rcvs_cvsrec = optarg; break; case 'Y': rcvs_msgfile = optarg; rcvs_inshell = TRUE; break; case '?': default: usage (usg); } } argc -= optind; argv += optind; if (argc < 1) usage (usg); /* rcvs: see if CVSROOT is in remote format */ rcvs_gen_optind = optind; (void) rcvs_do_cvsroot (cvs_update_env); /* rcvs: set phase 3 lock */ if (rcvs_Popt_phases != NULL && index(rcvs_Popt_phases,'9')) { rcvs_set_p3_lock = TRUE; rcvs_Check_lock(); exit (0); } /* * XXX - Compatibility. This can be removed in the release after CVS 1.3. * Try to rename the CVSROOT.adm file to CVSROOT, unless there already is * a CVSROOT directory. */ if (CVSroot != NULL) { char rootadm[PATH_MAX]; char orootadm[PATH_MAX]; (void) sprintf (rootadm, "%s/%s", CVSroot, CVSROOTADM); if (!isdir (rootadm)) { (void) sprintf (orootadm, "%s/%s", CVSroot, OCVSROOTADM); if (isdir (orootadm)) (void) rename (orootadm, rootadm); } strip_path (CVSroot); } /* rcvs: find all remote/local CVS folders */ if ( !(help || rcvs_sync || rcvs_inshell) ) { rcvs_native = TRUE; (void) rcvs_Find_folder (argc, argv, &cmds); if (rcvs_native_RCVS && !quiet) fprintf(stderr, "(%s on client)\n", version_rcvs); if (!rcvs_native_RCVS) rcvs_native_CVS = TRUE; } /* * Specifying just the '-H' flag to the sub-command causes a Usage * message to be displayed. */ command_name = cp = argv[0]; if (help == TRUE || (argc > 1 && strcmp (argv[1], "-H") == 0)) argc = -1; /* rcvs: check write access for local folder only */ else if ( !rcvs_native_RCVS ) { /* * Check to see if we can write into the history file. If not, * we assume that we can't work in the repository. * BUT, only if the history file exists. */ { char path[PATH_MAX]; int save_errno; if (!CVSroot || !*CVSroot) error (1, 0, "You don't have a %s environment variable", CVSROOT_ENV); (void) sprintf (path, "%s/%s", CVSroot, CVSROOTADM); if (access (path, R_OK | X_OK)) { save_errno = errno; error (0, 0, "Sorry, you don't have sufficient access to %s", CVSroot); error (1, save_errno, "%s", path); } (void) strcat (path, "/"); (void) strcat (path, CVSROOTADM_HISTORY); /* rcvs: do not need write access to history file access */ if (!rcvs_native_RCVS && isfile (path) && access (path, R_OK | W_OK)) { save_errno = errno; error (0, 0, "Sorry, you don't have read/write access to the history file"); error (1, save_errno, "%s", path); } } } #ifndef PUTENV_MISSING /* Now, see if we should update the environment with the Rcsbin value */ if (cvs_update_env) { char *env; env = xmalloc (strlen (CVSROOT_ENV) + strlen (CVSroot) + 1 + 1); (void) sprintf (env, "%s=%s", CVSROOT_ENV, CVSroot); (void) putenv (env); /* do not free env, as putenv has control of it */ } if (rcsbin_update_env) { char *env; env = xmalloc (strlen (RCSBIN_ENV) + strlen (Rcsbin) + 1 + 1); (void) sprintf (env, "%s=%s", RCSBIN_ENV, Rcsbin); (void) putenv (env); /* do not free env, as putenv has control of it */ } #endif /* * If Rcsbin is set to something, make sure it is terminated with * a slash character. If not, add one. */ if (*Rcsbin) { int len = strlen (Rcsbin); char *rcsbin; if (Rcsbin[len - 1] != '/') { rcsbin = Rcsbin; Rcsbin = xmalloc (len + 2); /* one for '/', one for NULL */ (void) strcpy (Rcsbin, rcsbin); (void) strcat (Rcsbin, "/"); } } for (cm = cmds; cm->fullname; cm++) { if (cm->nick1 && !strcmp (cp, cm->nick1)) break; if (cm->nick2 && !strcmp (cp, cm->nick2)) break; if (!strcmp (cp, cm->fullname)) break; } if (!cm->fullname) usage (usg); /* no match */ else { command_name = cm->fullname; /* Global pointer for later use */ (void) SIG_register (SIGHUP, main_cleanup); (void) SIG_register (SIGINT, main_cleanup); (void) SIG_register (SIGQUIT, main_cleanup); (void) SIG_register (SIGPIPE, main_cleanup); (void) SIG_register (SIGTERM, main_cleanup); #ifndef SETVBUF_MISSING /* * Make stdout line buffered, so 'tail -f' can monitor progress. * Patch creates too much output to monitor and it runs slowly. */ if (strcmp (cm->fullname, "patch")) (void) setvbuf (stdout, (char *) NULL, _IOLBF, 0); #endif /* rcvs: check RCVS locks, apply to both CVS and RCVS commands */ if (!rcvs_native_RCVS) (void) rcvs_Check_lock (); /* rcvs: call rcvs_main or rcvs_Sync */ if (rcvs_sync_forward) rcvs_Sync (argc, argv, cm); else if (rcvs_native_RCVS) (void) rcvs_main (argc, argv, cm); if (trace) if (rcvs_inshell) fprintf (stderr, "run CVS in shell, %s = %s\n", CVSROOTADM, CVSroot); else if (index (rcvs_ID, '@') != NULL) fprintf (stderr, "local run, %s = %s\n", CVSROOTADM, CVSroot); err = (*(cm->func)) (argc, argv); } /* * If the command's error count is modulo 256, we need to change it * so that we don't overflow the 8-bits we get to report exit status */ if (err && (err % 256) == 0) err = 1; Lock_Cleanup (); return (err); } char * Make_Date (rawdate) char *rawdate; { struct tm *ftm; time_t unixtime; char date[256]; /* XXX bigger than we'll ever need? */ char *ret; unixtime = get_date (rawdate, (struct timeb *) NULL); if (unixtime == (time_t) - 1) error (1, 0, "Can't parse date/time: %s", rawdate); #ifdef HAVE_RCS5 ftm = gmtime (&unixtime); #else ftm = localtime (&unixtime); #endif (void) sprintf (date, DATEFORM, ftm->tm_year + (ftm->tm_year < 100 ? 0 : 1900), ftm->tm_mon + 1, ftm->tm_mday, ftm->tm_hour, ftm->tm_min, ftm->tm_sec); ret = xstrdup (date); return (ret); } void usage (cpp) register char **cpp; { (void) fprintf (stderr, *cpp++, program_name, command_name); for (; *cpp; cpp++) (void) fprintf (stderr, *cpp); exit (1); }