/* * 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] ... * * "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); }