diff options
Diffstat (limited to 'contrib/cvs/src/checkout.c')
-rw-r--r-- | contrib/cvs/src/checkout.c | 889 |
1 files changed, 889 insertions, 0 deletions
diff --git a/contrib/cvs/src/checkout.c b/contrib/cvs/src/checkout.c new file mode 100644 index 0000000000000..64aa10e247e9b --- /dev/null +++ b/contrib/cvs/src/checkout.c @@ -0,0 +1,889 @@ +/* + * 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.4 kit. + * + * Create Version + * + * "checkout" creates a "version" of an RCS repository. This version is owned + * totally by the user and is actually an independent copy, to be dealt with + * as seen fit. Once "checkout" has been called in a given directory, it + * never needs to be called again. The user can keep up-to-date by calling + * "update" when he feels like it; this will supply him with a merge of his + * own modifications and the changes made in the RCS original. See "update" + * for details. + * + * "checkout" can be given a list of directories or files to be updated and in + * the case of a directory, will recursivley create any sub-directories that + * exist in the repository. + * + * When the user is satisfied with his own modifications, the present version + * can be committed by "commit"; this keeps the present version in tact, + * usually. + * + * The call is cvs checkout [options] <module-name>... + * + * "checkout" creates a directory ./CVS, in which it keeps its administration, + * in two files, Repository and Entries. The first contains the name of the + * repository. The second contains one line for each registered file, + * consisting of the version number it derives from, its time stamp at + * derivation time and its name. Both files are normal files and can be + * edited by the user, if necessary (when the repository is moved, e.g.) + */ + +#include "cvs.h" + +static char *findslash PROTO((char *start, char *p)); +static int build_dirs_and_chdir PROTO((char *dir, char *prepath, char *realdir, + int sticky)); +static int checkout_proc PROTO((int *pargc, char **argv, char *where, + char *mwhere, char *mfile, int shorten, + int local_specified, char *omodule, + char *msg)); +static int safe_location PROTO((void)); + +static const char *const checkout_usage[] = +{ + "Usage:\n %s %s [-ANPcflnps] [-r rev | -D date] [-d dir] [-k kopt] modules...\n", + "\t-A\tReset any sticky tags/date/kopts.\n", + "\t-N\tDon't shorten module paths if -d specified.\n", + "\t-P\tPrune empty directories.\n", + "\t-c\t\"cat\" the module database.\n", + "\t-f\tForce a head revision match if tag/date not found.\n", + "\t-l\tLocal directory only, not recursive\n", + "\t-n\tDo not run module program (if any).\n", + "\t-p\tCheck out files to standard output.\n", + "\t-s\tLike -c, but include module status.\n", + "\t-r rev\tCheck out revision or tag. (implies -P)\n", + "\t-D date\tCheck out revisions as of date. (implies -P)\n", + "\t-d dir\tCheck out into dir instead of module name.\n", + "\t-k kopt\tUse RCS kopt -k option on checkout.\n", + "\t-j rev\tMerge in changes made between current revision and rev.\n", + NULL +}; + +static const char *const export_usage[] = +{ + "Usage: %s %s [-NPfln] [-r rev | -D date] [-d dir] [-k kopt] module...\n", + "\t-N\tDon't shorten module paths if -d specified.\n", + "\t-f\tForce a head revision match if tag/date not found.\n", + "\t-l\tLocal directory only, not recursive\n", + "\t-n\tDo not run module program (if any).\n", + "\t-r rev\tCheck out revision or tag.\n", + "\t-D date\tCheck out revisions as of date.\n", + "\t-d dir\tCheck out into dir instead of module name.\n", + "\t-k kopt\tUse RCS kopt -k option on checkout.\n", + NULL +}; + +static int checkout_prune_dirs; +static int force_tag_match = 1; +static int pipeout; +static int aflag; +static char *options = NULL; +static char *tag = NULL; +static int tag_validated = 0; +static char *date = NULL; +static char *join_rev1 = NULL; +static char *join_rev2 = NULL; +static char *preload_update_dir = NULL; + +int +checkout (argc, argv) + int argc; + char **argv; +{ + int i; + int c; + DBM *db; + int cat = 0, err = 0, status = 0; + int run_module_prog = 1; + int local = 0; + int shorten = -1; + char *where = NULL; + char *valid_options; + const char *const *valid_usage; + enum mtype m_type; + + /* + * A smaller subset of options are allowed for the export command, which + * is essentially like checkout, except that it hard-codes certain + * options to be default (like -kv) and takes care to remove the CVS + * directory when it has done its duty + */ + if (strcmp (command_name, "export") == 0) + { + m_type = EXPORT; + valid_options = "Nnk:d:flRQqr:D:"; + valid_usage = export_usage; + } + else + { + m_type = CHECKOUT; + valid_options = "ANnk:d:flRpQqcsr:D:j:P"; + valid_usage = checkout_usage; + } + + if (argc == -1) + usage (valid_usage); + + ign_setup (); + wrap_setup (); + + optind = 1; + while ((c = getopt (argc, argv, valid_options)) != -1) + { + switch (c) + { + case 'A': + aflag = 1; + break; + case 'N': + shorten = 0; + break; + case 'k': + if (options) + free (options); + options = RCS_check_kflag (optarg); + break; + case 'n': + run_module_prog = 0; + break; + 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 'l': + local = 1; + break; + case 'R': + local = 0; + break; + case 'P': + checkout_prune_dirs = 1; + break; + case 'p': + pipeout = 1; + run_module_prog = 0; /* don't run module prog when piping */ + noexec = 1; /* so no locks will be created */ + break; + case 'c': + cat = 1; + break; + case 'd': + where = optarg; + if (shorten == -1) + shorten = 1; + break; + case 's': + status = 1; + break; + case 'f': + force_tag_match = 0; + break; + case 'r': + tag = optarg; + checkout_prune_dirs = 1; + break; + case 'D': + date = Make_Date (optarg); + checkout_prune_dirs = 1; + break; + case 'j': + if (join_rev2) + error (1, 0, "only two -j options can be specified"); + if (join_rev1) + join_rev2 = optarg; + else + join_rev1 = optarg; + break; + case '?': + default: + usage (valid_usage); + break; + } + } + argc -= optind; + argv += optind; + + if (shorten == -1) + shorten = 0; + + if ((!(cat + status) && argc == 0) || ((cat + status) && argc != 0) + || (tag && date)) + usage (valid_usage); + + if (where && pipeout) + error (1, 0, "-d and -p are mutually exclusive"); + + if (strcmp (command_name, "export") == 0) + { + if (!tag && !date) + { + error (0, 0, "must specify a tag or date"); + usage (valid_usage); + } + if (tag && isdigit (tag[0])) + error (1, 0, "tag `%s' must be a symbolic tag", tag); +/* + * mhy 950615: -kv doesn't work for binaries with RCS keywords. + * Instead use the default provided in the RCS file (-ko for binaries). + */ +#if 0 + if (!options) + options = RCS_check_kflag ("v");/* -kv is default */ +#endif + } + + if (!safe_location()) { + error(1, 0, "Cannot check out files into the repository itself"); + } + +#ifdef CLIENT_SUPPORT + if (client_active) + { + int expand_modules; + + start_server (); + + ign_setup (); + + /* We have to expand names here because the "expand-modules" + directive to the server has the side-effect of having the + server send the check-in and update programs for the + various modules/dirs requested. If we turn this off and + simply request the names of the modules and directories (as + below in !expand_modules), those files (CVS/Checking.prog + or CVS/Update.prog) don't get created. Grrr. */ + + expand_modules = (!cat && !status && !pipeout + && supported_request ("expand-modules")); + + if (expand_modules) + { + /* This is done here because we need to read responses + from the server before we send the command checkout or + export files. */ + + client_expand_modules (argc, argv, local); + } + + if (!run_module_prog) send_arg ("-n"); + if (local) send_arg ("-l"); + if (pipeout) send_arg ("-p"); + if (!force_tag_match) send_arg ("-f"); + if (aflag) + send_arg("-A"); + if (!shorten) + send_arg("-N"); + if (checkout_prune_dirs && strcmp (command_name, "export") != 0) + send_arg("-P"); + client_prune_dirs = checkout_prune_dirs; + if (cat) + send_arg("-c"); + if (where != NULL) + { + option_with_arg ("-d", where); + } + if (status) + send_arg("-s"); + if (strcmp (command_name, "export") != 0 + && options != NULL + && options[0] != '\0') + send_arg (options); + option_with_arg ("-r", tag); + if (date) + client_senddate (date); + if (join_rev1 != NULL) + option_with_arg ("-j", join_rev1); + if (join_rev2 != NULL) + option_with_arg ("-j", join_rev2); + + if (expand_modules) + { + client_send_expansions (local); + } + else + { + int i; + for (i = 0; i < argc; ++i) + send_arg (argv[i]); + client_nonexpanded_setup (); + } + + send_to_server (strcmp (command_name, "export") == 0 ? + "export\012" : "co\012", + 0); + + return get_responses_and_close (); + } +#endif /* CLIENT_SUPPORT */ + + if (cat || status) + { + cat_module (status); + return (0); + } + db = open_module (); + + /* + * if we have more than one argument and where was specified, we make the + * where, cd into it, and try to shorten names as much as possible. + * Otherwise, we pass the where as a single argument to do_module. + */ + if (argc > 1 && where != NULL) + { + char repository[PATH_MAX]; + + (void) CVS_MKDIR (where, 0777); + if (chdir (where) < 0) + error (1, errno, "cannot chdir to %s", where); + preload_update_dir = xstrdup (where); + where = (char *) NULL; + if (!isfile (CVSADM)) + { + (void) sprintf (repository, "%s/%s/%s", CVSroot, CVSROOTADM, + CVSNULLREPOS); + if (!isfile (repository)) + { + mode_t omask; + omask = umask (cvsumask); + (void) CVS_MKDIR (repository, 0777); + (void) umask (omask); + } + + /* I'm not sure whether this check is redundant. */ + if (!isdir (repository)) + error (1, 0, "there is no repository %s", repository); + + Create_Admin (".", where, repository, + (char *) NULL, (char *) NULL); + if (!noexec) + { + FILE *fp; + + fp = open_file (CVSADM_ENTSTAT, "w+"); + if (fclose(fp) == EOF) + error(1, errno, "cannot close %s", CVSADM_ENTSTAT); +#ifdef SERVER_SUPPORT + if (server_active) + server_set_entstat (preload_update_dir, repository); +#endif + } + } + } + + /* + * if where was specified (-d) and we have not taken care of it already + * with the multiple arg stuff, and it was not a simple directory name + * but rather a path, we strip off everything but the last component and + * attempt to cd to the indicated place. where then becomes simply the + * last component + */ + if (where != NULL && strchr (where, '/') != NULL) + { + char *slash; + + slash = strrchr (where, '/'); + *slash = '\0'; + + if (chdir (where) < 0) + error (1, errno, "cannot chdir to %s", where); + + preload_update_dir = xstrdup (where); + + where = slash + 1; + if (*where == '\0') + where = NULL; + } + + for (i = 0; i < argc; i++) + err += do_module (db, argv[i], m_type, "Updating", checkout_proc, + where, shorten, local, run_module_prog, + (char *) NULL); + close_module (db); + return (err); +} + +static int +safe_location () +{ + char current[PATH_MAX]; + char hardpath[PATH_MAX+5]; + int x; + + x = readlink(CVSroot, hardpath, sizeof hardpath - 1); + if (x == -1) + { + strcpy(hardpath, CVSroot); + } + else + { + hardpath[x] = '\0'; + } + getwd (current); + if (strncmp(current, hardpath, strlen(hardpath)) == 0) + { + return (0); + } + return (1); +} + +/* + * process_module calls us back here so we do the actual checkout stuff + */ +/* ARGSUSED */ +static int +checkout_proc (pargc, argv, where, mwhere, mfile, shorten, + local_specified, omodule, msg) + int *pargc; + char **argv; + char *where; + char *mwhere; + char *mfile; + int shorten; + int local_specified; + char *omodule; + char *msg; +{ + int err = 0; + int which; + char *cp; + char *cp2; + char repository[PATH_MAX]; + char xwhere[PATH_MAX]; + char *oldupdate = NULL; + char *prepath; + char *realdirs; + + /* + * OK, so we're doing the checkout! Our args are as follows: + * argc,argv contain either dir or dir followed by a list of files + * where contains where to put it (if supplied by checkout) + * mwhere contains the module name or -d from module file + * mfile says do only that part of the module + * shorten = TRUE says shorten as much as possible + * omodule is the original arg to do_module() + */ + + /* set up the repository (maybe) for the bottom directory */ + (void) sprintf (repository, "%s/%s", CVSroot, argv[0]); + + /* save the original value of preload_update_dir */ + if (preload_update_dir != NULL) + oldupdate = xstrdup (preload_update_dir); + + /* fix up argv[] for the case of partial modules */ + if (mfile != NULL) + { + char file[PATH_MAX]; + + /* if mfile is really a path, straighten it out first */ + if ((cp = strrchr (mfile, '/')) != NULL) + { + *cp = 0; + (void) strcat (repository, "/"); + (void) strcat (repository, mfile); + + /* + * Now we need to fill in the where correctly. if !shorten, tack + * the rest of the path onto where if where is filled in + * otherwise tack the rest of the path onto mwhere and make that + * the where + * + * If shorten is enabled, we might use mwhere to set where if + * nobody set it yet, so we'll need to setup mwhere as the last + * component of the path we are tacking onto repository + */ + if (!shorten) + { + if (where != NULL) + (void) sprintf (xwhere, "%s/%s", where, mfile); + else + (void) sprintf (xwhere, "%s/%s", mwhere, mfile); + where = xwhere; + } + else + { + char *slash; + + if ((slash = strrchr (mfile, '/')) != NULL) + mwhere = slash + 1; + else + mwhere = mfile; + } + mfile = cp + 1; + } + + (void) sprintf (file, "%s/%s", repository, mfile); + if (isdir (file)) + { + + /* + * The portion of a module was a directory, so kludge up where to + * be the subdir, and fix up repository + */ + (void) strcpy (repository, file); + + /* + * At this point, if shorten is not enabled, we make where either + * where with mfile concatenated, or if where hadn't been set we + * set it to mwhere with mfile concatenated. + * + * If shorten is enabled and where hasn't been set yet, then where + * becomes mfile + */ + if (!shorten) + { + if (where != NULL) + (void) sprintf (xwhere, "%s/%s", where, mfile); + else + (void) sprintf (xwhere, "%s/%s", mwhere, mfile); + where = xwhere; + } + else if (where == NULL) + where = mfile; + } + else + { + int i; + + /* + * The portion of a module was a file, so kludge up argv to be + * correct + */ + for (i = 1; i < *pargc; i++)/* free the old ones */ + free (argv[i]); + argv[1] = xstrdup (mfile); /* set up the new one */ + *pargc = 2; + + /* where gets mwhere if where isn't set */ + if (where == NULL) + where = mwhere; + } + } + + /* + * if shorten is enabled and where isn't specified yet, we pluck the last + * directory component of argv[0] and make it the where + */ + if (shorten && where == NULL) + { + if ((cp = strrchr (argv[0], '/')) != NULL) + { + (void) strcpy (xwhere, cp + 1); + where = xwhere; + } + } + + /* if where is still NULL, use mwhere if set or the argv[0] dir */ + if (where == NULL) + { + if (mwhere) + where = mwhere; + else + { + (void) strcpy (xwhere, argv[0]); + where = xwhere; + } + } + + if (preload_update_dir != NULL) + { + char tmp[PATH_MAX]; + + (void) sprintf (tmp, "%s/%s", preload_update_dir, where); + free (preload_update_dir); + preload_update_dir = xstrdup (tmp); + } + else + preload_update_dir = xstrdup (where); + + /* + * At this point, where is the directory we want to build, repository is + * the repository for the lowest level of the path. + */ + + /* + * If we are sending everything to stdout, we can skip a whole bunch of + * work from here + */ + if (!pipeout) + { + + /* + * We need to tell build_dirs not only the path we want it to build, + * but also the repositories we want it to populate the path with. To + * accomplish this, we pass build_dirs a ``real path'' with valid + * repositories and a string to pre-pend based on how many path + * elements exist in where. Big Black Magic + */ + prepath = xstrdup (repository); + cp = strrchr (where, '/'); + cp2 = strrchr (prepath, '/'); + while (cp != NULL) + { + cp = findslash (where, cp - 1); + cp2 = findslash (prepath, cp2 - 1); + } + *cp2 = '\0'; + realdirs = cp2 + 1; + + /* + * build dirs on the path if necessary and leave us in the bottom + * directory (where if where was specified) doesn't contain a CVS + * subdir yet, but all the others contain CVS and Entries.Static + * files + */ + if (build_dirs_and_chdir (where, prepath, realdirs, *pargc <= 1) != 0) + { + error (0, 0, "ignoring module %s", omodule); + free (prepath); + free (preload_update_dir); + preload_update_dir = oldupdate; + return (1); + } + + /* clean up */ + free (prepath); + + /* set up the repository (or make sure the old one matches) */ + if (!isfile (CVSADM)) + { + FILE *fp; + + if (!noexec && *pargc > 1) + { + /* I'm not sure whether this check is redundant. */ + if (!isdir (repository)) + error (1, 0, "there is no repository %s", repository); + + Create_Admin (".", where, repository, + (char *) NULL, (char *) NULL); + fp = open_file (CVSADM_ENTSTAT, "w+"); + if (fclose(fp) == EOF) + error(1, errno, "cannot close %s", CVSADM_ENTSTAT); +#ifdef SERVER_SUPPORT + if (server_active) + server_set_entstat (where, repository); +#endif + } + else + { + /* I'm not sure whether this check is redundant. */ + if (!isdir (repository)) + error (1, 0, "there is no repository %s", repository); + + Create_Admin (".", where, repository, tag, date); + } + } + else + { + char *repos; + + /* get the contents of the previously existing repository */ + repos = Name_Repository ((char *) NULL, preload_update_dir); + if (fncmp (repository, repos) != 0) + { + error (0, 0, "existing repository %s does not match %s", + repos, repository); + error (0, 0, "ignoring module %s", omodule); + free (repos); + free (preload_update_dir); + preload_update_dir = oldupdate; + return (1); + } + free (repos); + } + } + + /* + * If we are going to be updating to stdout, we need to cd to the + * repository directory so the recursion processor can use the current + * directory as the place to find repository information + */ + if (pipeout) + { + if (chdir (repository) < 0) + { + error (0, errno, "cannot chdir to %s", repository); + free (preload_update_dir); + preload_update_dir = oldupdate; + return (1); + } + which = W_REPOS; + if (tag != NULL && !tag_validated) + { + tag_check_valid (tag, *pargc - 1, argv + 1, 0, aflag, NULL); + tag_validated = 1; + } + } + else + { + which = W_LOCAL | W_REPOS; + if (tag != NULL && !tag_validated) + { + tag_check_valid (tag, *pargc - 1, argv + 1, 0, aflag, + repository); + tag_validated = 1; + } + } + + if (tag != NULL || date != NULL) + which |= W_ATTIC; + + /* FIXME: We don't call tag_check_valid on join_rev1 and join_rev2 + yet (make sure to handle ':' correctly if we do, though). */ + + /* + * if we are going to be recursive (building dirs), go ahead and call the + * update recursion processor. We will be recursive unless either local + * only was specified, or we were passed arguments + */ + if (!(local_specified || *pargc > 1)) + { + if (strcmp (command_name, "export") != 0 && !pipeout) + history_write ('O', preload_update_dir, tag ? tag : date, where, + repository); + err += do_update (0, (char **) NULL, options, tag, date, + force_tag_match, 0 /* !local */ , + 1 /* update -d */ , aflag, checkout_prune_dirs, + pipeout, which, join_rev1, join_rev2, + preload_update_dir); + free (preload_update_dir); + preload_update_dir = oldupdate; + return (err); + } + + if (!pipeout) + { + int i; + List *entries; + + /* we are only doing files, so register them */ + entries = Entries_Open (0); + for (i = 1; i < *pargc; i++) + { + char *line; + char *user; + Vers_TS *vers; + + user = argv[i]; + vers = Version_TS (repository, options, tag, date, user, + force_tag_match, 0, entries, (RCSNode *) NULL); + if (vers->ts_user == NULL) + { + line = xmalloc (strlen (user) + 15); + (void) sprintf (line, "Initial %s", user); + Register (entries, user, vers->vn_rcs ? vers->vn_rcs : "0", + line, vers->options, vers->tag, + vers->date, (char *) 0); + free (line); + } + freevers_ts (&vers); + } + + Entries_Close (entries); + } + + /* Don't log "export", just regular "checkouts" */ + if (strcmp (command_name, "export") != 0 && !pipeout) + history_write ('O', preload_update_dir, (tag ? tag : date), where, + repository); + + /* go ahead and call update now that everything is set */ + err += do_update (*pargc - 1, argv + 1, options, tag, date, + force_tag_match, local_specified, 1 /* update -d */, + aflag, checkout_prune_dirs, pipeout, which, join_rev1, + join_rev2, preload_update_dir); + free (preload_update_dir); + preload_update_dir = oldupdate; + return (err); +} + +static char * +findslash (start, p) + char *start; + char *p; +{ + while (p >= start && *p != '/') + p--; + if (p < start) + return (NULL); + else + return (p); +} + +/* + * build all the dirs along the path to dir with CVS subdirs with appropriate + * repositories and Entries.Static files + */ +static int +build_dirs_and_chdir (dir, prepath, realdir, sticky) + char *dir; + char *prepath; + char *realdir; + int sticky; +{ + FILE *fp; + char repository[PATH_MAX]; + char path[PATH_MAX]; + char path2[PATH_MAX]; + char *slash; + char *slash2; + char *cp; + char *cp2; + + (void) strcpy (path, dir); + (void) strcpy (path2, realdir); + for (cp = path, cp2 = path2; + (slash = strchr (cp, '/')) != NULL && (slash2 = strchr (cp2, '/')) != NULL; + cp = slash + 1, cp2 = slash2 + 1) + { + *slash = '\0'; + *slash2 = '\0'; + (void) CVS_MKDIR (cp, 0777); + if (chdir (cp) < 0) + { + error (0, errno, "cannot chdir to %s", cp); + return (1); + } + if (!isfile (CVSADM) && strcmp (command_name, "export") != 0) + { + (void) sprintf (repository, "%s/%s", prepath, path2); + /* I'm not sure whether this check is redundant. */ + if (!isdir (repository)) + error (1, 0, "there is no repository %s", repository); + Create_Admin (".", path, repository, sticky ? (char *) NULL : tag, + sticky ? (char *) NULL : date); + if (!noexec) + { + fp = open_file (CVSADM_ENTSTAT, "w+"); + if (fclose(fp) == EOF) + error(1, errno, "cannot close %s", CVSADM_ENTSTAT); +#ifdef SERVER_SUPPORT + if (server_active) + server_set_entstat (path, repository); +#endif + } + } + *slash = '/'; + *slash2 = '/'; + } + (void) CVS_MKDIR (cp, 0777); + if (chdir (cp) < 0) + { + error (0, errno, "cannot chdir to %s", cp); + return (1); + } + return (0); +} |