diff options
Diffstat (limited to 'contrib/cvs/src/checkout.c')
-rw-r--r-- | contrib/cvs/src/checkout.c | 1284 |
1 files changed, 1284 insertions, 0 deletions
diff --git a/contrib/cvs/src/checkout.c b/contrib/cvs/src/checkout.c new file mode 100644 index 0000000..a1cd6cc --- /dev/null +++ b/contrib/cvs/src/checkout.c @@ -0,0 +1,1284 @@ +/* + * Copyright (C) 1986-2005 The Free Software Foundation, Inc. + * + * Portions Copyright (C) 1998-2005 Derek Price, Ximbiot <http://ximbiot.com>, + * and others. + * + * Portions Copyright (C) 1992, Brian Berliner and Jeff Polk + * Portions 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 source distribution. + * + * 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.) + */ + +/* + * $FreeBSD$ + */ + +#include <assert.h> +#include "cvs.h" + +static char *findslash PROTO((char *start, char *p)); +static int checkout_proc PROTO((int argc, char **argv, char *where, + char *mwhere, char *mfile, int shorten, + int local_specified, char *omodule, + char *msg)); + +static const char *const checkout_usage[] = +{ + "Usage:\n %s %s [-ANPRcflnps] [-r rev] [-D date] [-d dir]\n", + " [-j rev1] [-j rev2] [-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-R\tProcess directories recursively.\n", + "\t-T\tCreate Template file from local repository for remote commit.\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 (avoids stickiness).\n", + "\t-s\tLike -c, but include module status.\n", + "\t-r rev\tCheck out revision or tag. (implies -P) (is sticky)\n", + "\t-D date\tCheck out revisions as of date. (implies -P) (is sticky)\n", + "\t-d dir\tCheck out into dir instead of module name.\n", + "\t-k kopt\tUse RCS kopt -k option on checkout. (is sticky)\n", + "\t-j rev\tMerge in changes made between current revision and rev.\n", + "(Specify the --help global option for a list of other help options)\n", + NULL +}; + +static const char *const export_usage[] = +{ + "Usage: %s %s [-NRfln] [-r tag] [-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-R\tProcess directories recursively (default).\n", + "\t-n\tDo not run module program (if any).\n", + "\t-r tag\tExport tagged revisions.\n", + "\t-D date\tExport revisions as of date.\n", + "\t-d dir\tExport into dir instead of module name.\n", + "\t-k kopt\tUse RCS kopt -k option on checkout.\n", + "(Specify the --help global option for a list of other help options)\n", + NULL +}; + +static int checkout_prune_dirs; +static int force_tag_match; +static int pipeout; +static int aflag; +static char *options; +static char *tag; +static int tag_validated; +static char *date; +static char *join_rev1; +static char *join_rev2; +static int join_tags_validated; +static int pull_template; +static char *preload_update_dir; +static char *history_name; +static enum mtype m_type; + +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; + + /* initialize static options */ + force_tag_match = 1; + if (options) + { + free (options); + options = NULL; + } + tag = date = join_rev1 = join_rev2 = preload_update_dir = NULL; + history_name = NULL; + tag_validated = join_tags_validated = 0; + + + /* + * 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 (cvs_cmd_name, "export") == 0) + { + m_type = EXPORT; + valid_options = "+Nnk:d:flRQqr:D:"; + valid_usage = export_usage; + } + else + { + m_type = CHECKOUT; + valid_options = "+ANnk:d:flRpTQqcsr:D:j:P"; + valid_usage = checkout_usage; + } + + if (argc == -1) + usage (valid_usage); + + ign_setup (); + wrap_setup (); + + optind = 0; + 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 'T': + pull_template = 1; + break; + case 'Q': + case 'q': + /* The CVS 1.5 client sends these options (in addition to + Global_option requests), so we must ignore them. */ + if (!server_active) + error (1, 0, + "-q or -Q must be specified before \"%s\"", + cvs_cmd_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': + cat = 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 && argc != 0) + error (1, 0, "-c and -s must not get any arguments"); + + if (!cat && argc == 0) + error (1, 0, "must specify at least one module or directory"); + + if (where && pipeout) + error (1, 0, "-d and -p are mutually exclusive"); + + if (m_type == EXPORT) + { + if (!tag && !date) + error (1, 0, "must specify a tag or date"); + + if (tag && isdigit ((unsigned char) tag[0])) + error (1, 0, "tag `%s' must be a symbolic tag", tag); + } + +#ifdef SERVER_SUPPORT + if (server_active && where != NULL) + { + server_pathname_check (where); + } +#endif + + if (!cat && !pipeout && !safe_location( where )) { + error(1, 0, "Cannot check out files into the repository itself"); + } + +#ifdef CLIENT_SUPPORT + if (current_parsed_root->isremote) + { + int expand_modules; + + start_server (); + + ign_setup (); + + expand_modules = (!cat && !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 && m_type == CHECKOUT) + send_arg("-P"); + client_prune_dirs = checkout_prune_dirs; + if (cat && !status) + send_arg("-c"); + if (where != NULL) + option_with_arg ("-d", where); + if (status) + send_arg("-s"); + if (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); + send_arg ("--"); + + if (expand_modules) + { + client_send_expansions (local, where, 1); + } + else + { + int i; + for (i = 0; i < argc; ++i) + send_arg (argv[i]); + client_nonexpanded_setup (); + } + + send_to_server (m_type == EXPORT ? "export\012" : "co\012", 0); + return get_responses_and_close (); + } +#endif /* CLIENT_SUPPORT */ + + if (cat) + { + cat_module (status); + if (options) + { + free (options); + options = NULL; + } + return (0); + } + db = open_module (); + + + /* If we've specified something like "cvs co foo/bar baz/quux" + don't try to shorten names. There are a few cases in which we + could shorten (e.g. "cvs co foo/bar foo/baz"), but we don't + handle those yet. Better to have an extra directory created + than the thing checked out under the wrong directory name. */ + + if (argc > 1) + shorten = 0; + + + /* If we will be calling history_write, work out the name to pass + it. */ + if (!pipeout) + { + if (!date) + history_name = tag; + else if (!tag) + history_name = date; + else + { + history_name = xmalloc (strlen (tag) + strlen (date) + 2); + sprintf (history_name, "%s:%s", tag, date); + } + } + + + for (i = 0; i < argc; i++) + err += do_module (db, argv[i], m_type, "Updating", checkout_proc, + where, shorten, local, run_module_prog, !pipeout, + (char *) NULL); + close_module (db); + if (options) + { + free (options); + options = NULL; + } + if (history_name != tag && history_name != date && history_name != NULL) + free (history_name); + return (err); +} + +/* FIXME: This is and emptydir_name are in checkout.c for historical + reasons, probably want to move them. */ + +/* int + * safe_location ( char *where ) + * + * Return true if where is a safe destination for a checkout. + * + * INPUTS + * where The requested destination directory. + * + * GLOBALS + * current_parsed_root->directory + * current_parsed_root->isremote + * Used to locate our CVSROOT. + * + * RETURNS + * true If we are running in client mode or if where is not located + * within the CVSROOT. + * false Otherwise. + * + * ERRORS + * Exits with a fatal error message when various events occur, such as not + * being able to resolve a path or failing ot chdir to a path. + */ +int +safe_location (where) + char *where; +{ + char *current; + char *where_location; + char *hardpath; + size_t hardpath_len; + int retval; + + if (trace) + (void) fprintf (stderr, "%s-> safe_location( where=%s )\n", + CLIENT_SERVER_STR, + where ? where : "(null)"); + + /* Don't compare remote CVSROOTs to our destination directory. */ + if (current_parsed_root->isremote) return 1; + + /* set current - even if where is set we'll need to cd back... */ + current = xgetwd (); + if (current == NULL) + error (1, errno, "could not get working directory"); + + hardpath = xresolvepath ( current_parsed_root->directory ); + + /* if where is set, set current to where, where - last_component( where ), + * or fail, depending on whether the directories exist or not. + */ + if( where != NULL ) + { + if( chdir( where ) != -1 ) + { + /* where */ + where_location = xgetwd(); + if( where_location == NULL ) + error( 1, errno, "could not get working directory" ); + + if( chdir( current ) == -1 ) + error( 1, errno, "could not change directory to `%s'", current ); + + free( current ); + current = where_location; + } + else if( errno == ENOENT ) + { + if ( last_component( where ) != where ) + { + /* where - last_component( where ) */ + char *parent; + + /* strip the last_component */ + where_location = xstrdup (where); + /* It's okay to cast out the const below since we know we just + * allocated where_location and can do what we like with it. + */ + parent = (char *)last_component (where_location); + parent[-1] = '\0'; + + if( chdir( where_location ) != -1 ) + { + free( where_location ); + where_location = xgetwd(); + if( where_location == NULL ) + error( 1, errno, "could not get working directory (nominally `%s')", where_location ); + + if( chdir( current ) == -1 ) + error( 1, errno, "could not change directory to `%s'", current ); + + free( current ); + current = where_location; + } + else + /* fail */ + error( 1, errno, "could not change directory to requested checkout directory `%s'", where_location ); + } + /* else: ERRNO == ENOENT & last_component(where) == where + * for example, 'cvs co -d newdir module', where newdir hasn't + * been created yet, so leave current set to '.' and check that + */ + } + else + /* fail */ + error( 1, errno, "could not change directory to requested checkout directory `%s'", where ); + } + + hardpath_len = strlen (hardpath); + if (strlen (current) >= hardpath_len + && strncmp (current, hardpath, hardpath_len) == 0) + { + if (/* Current is a subdirectory of hardpath. */ + current[hardpath_len] == '/' + + /* Current is hardpath itself. */ + || current[hardpath_len] == '\0') + retval = 0; + else + /* It isn't a problem. For example, current is + "/foo/cvsroot-bar" and hardpath is "/foo/cvsroot". */ + retval = 1; + } + else + retval = 1; + free (current); + free (hardpath); + return retval; +} + +struct dir_to_build +{ + /* What to put in CVS/Repository. */ + char *repository; + /* The path to the directory. */ + char *dirpath; + + /* If set, don't build the directory, just change to it. + The caller will also want to set REPOSITORY to NULL. */ + int just_chdir; + + struct dir_to_build *next; +}; + +static int build_dirs_and_chdir PROTO ((struct dir_to_build *list, + int sticky)); + +static void build_one_dir PROTO ((char *, char *, int)); + +static void +build_one_dir (repository, dirpath, sticky) + char *repository; + char *dirpath; + int sticky; +{ + FILE *fp; + + if (isfile (CVSADM)) + { + if (m_type == EXPORT) + error (1, 0, "cannot export into a working directory"); + } + else if (m_type == CHECKOUT) + { + /* I suspect that this check could be omitted. */ + if (!isdir (repository)) + error (1, 0, "there is no repository %s", repository); + + if (Create_Admin (".", dirpath, repository, + sticky ? tag : (char *) NULL, + sticky ? date : (char *) NULL, + + /* FIXME? This is a guess. If it is important + for nonbranch to be set correctly here I + think we need to write it one way now and + then rewrite it later via WriteTag, once + we've had a chance to call RCS_nodeisbranch + on each file. */ + 0, 1, 1)) + return; + + 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 (dirpath, repository); +#endif + } + } +} + +/* + * process_module calls us back here so we do the actual checkout stuff + */ +/* ARGSUSED */ +static int +checkout_proc (argc, argv, where_orig, mwhere, mfile, shorten, + local_specified, omodule, msg) + int argc; + char **argv; + char *where_orig; + char *mwhere; + char *mfile; + int shorten; + int local_specified; + char *omodule; + char *msg; +{ + char *myargv[2]; + int err = 0; + int which; + char *cp; + char *repository; + char *oldupdate = NULL; + char *where; + + /* + * 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 = 1 says shorten as much as possible + * omodule is the original arg to do_module() + */ + + /* Set up the repository (maybe) for the bottom directory. + Allocate more space than we need so we don't need to keep + reallocating this string. */ + repository = xmalloc (strlen (current_parsed_root->directory) + + strlen (argv[0]) + + (mfile == NULL ? 0 : strlen (mfile)) + + 10); + (void) sprintf (repository, "%s/%s", current_parsed_root->directory, argv[0]); + Sanitize_Repository_Name (repository); + + + /* save the original value of preload_update_dir */ + if (preload_update_dir != NULL) + oldupdate = xstrdup (preload_update_dir); + + + /* Allocate space and set up the where variable. We allocate more + space than necessary here so that we don't have to keep + reallocaing it later on. */ + + where = xmalloc (strlen (argv[0]) + + (mfile == NULL ? 0 : strlen (mfile)) + + (mwhere == NULL ? 0 : strlen (mwhere)) + + (where_orig == NULL ? 0 : strlen (where_orig)) + + 10); + + /* Yes, this could be written in a less verbose way, but in this + form it is quite easy to read. + + FIXME? The following code that sets should probably be moved + to do_module in modules.c, since there is similar code in + patch.c and rtag.c. */ + + if (shorten) + { + if (where_orig != NULL) + { + /* If the user has specified a directory with `-d' on the + command line, use it preferentially, even over the `-d' + flag in the modules file. */ + + (void) strcpy (where, where_orig); + } + else if (mwhere != NULL) + { + /* Second preference is the value of mwhere, which is from + the `-d' flag in the modules file. */ + + (void) strcpy (where, mwhere); + } + else + { + /* Third preference is the directory specified in argv[0] + which is this module'e directory in the repository. */ + + (void) strcpy (where, argv[0]); + } + } + else + { + /* Use the same preferences here, bug don't shorten -- that + is, tack on where_orig if it exists. */ + + *where = '\0'; + + if (where_orig != NULL) + { + (void) strcat (where, where_orig); + (void) strcat (where, "/"); + } + + /* If the -d flag in the modules file specified an absolute + directory, let the user override it with the command-line + -d option. */ + + if ((mwhere != NULL) && (! isabsolute (mwhere))) + (void) strcat (where, mwhere); + else + (void) strcat (where, argv[0]); + } + strip_trailing_slashes (where); /* necessary? */ + + + /* At this point, the user may have asked for a single file or + directory from within a module. In that case, we should modify + where, repository, and argv as appropriate. */ + + if (mfile != NULL) + { + /* The mfile variable can have one or more path elements. If + it has multiple elements, we want to tack those onto both + repository and where. The last element may refer to either + a file or directory. Here's what to do: + + it refers to a directory + -> simply tack it on to where and repository + it refers to a file + -> munge argv to contain `basename mfile` */ + + char *cp; + char *path; + + + /* Paranoia check. */ + + if (mfile[strlen (mfile) - 1] == '/') + { + error (0, 0, "checkout_proc: trailing slash on mfile (%s)!", + mfile); + } + + + /* Does mfile have multiple path elements? */ + + cp = strrchr (mfile, '/'); + if (cp != NULL) + { + *cp = '\0'; + (void) strcat (repository, "/"); + (void) strcat (repository, mfile); + (void) strcat (where, "/"); + (void) strcat (where, mfile); + mfile = cp + 1; + } + + + /* Now mfile is a single path element. */ + + path = xmalloc (strlen (repository) + strlen (mfile) + 5); + (void) sprintf (path, "%s/%s", repository, mfile); + if (isdir (path)) + { + /* It's a directory, so tack it on to repository and + where, as we did above. */ + + (void) strcat (repository, "/"); + (void) strcat (repository, mfile); + (void) strcat (where, "/"); + (void) strcat (where, mfile); + } + else + { + /* It's a file, which means we have to screw around with + argv. */ + myargv[0] = argv[0]; + myargv[1] = mfile; + argc = 2; + argv = myargv; + } + free (path); + } + + if (preload_update_dir != NULL) + { + preload_update_dir = + xrealloc (preload_update_dir, + strlen (preload_update_dir) + strlen (where) + 5); + strcat (preload_update_dir, "/"); + strcat (preload_update_dir, where); + } + 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. + * + * 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 walk the path backwards, one + * pathname component at a time, constucting a linked list of + * struct dir_to_build. + */ + + /* + * If we are sending everything to stdout, we can skip a whole bunch of + * work from here + */ + if (!pipeout) + { + struct dir_to_build *head; + char *reposcopy; + + if (strncmp (repository, current_parsed_root->directory, + strlen (current_parsed_root->directory)) != 0) + error (1, 0, "\ +internal error: %s doesn't start with %s in checkout_proc", + repository, current_parsed_root->directory); + + /* We always create at least one directory, which corresponds to + the entire strings for WHERE and REPOSITORY. */ + head = (struct dir_to_build *) xmalloc (sizeof (struct dir_to_build)); + /* Special marker to indicate that we don't want build_dirs_and_chdir + to create the CVSADM directory for us. */ + head->repository = NULL; + head->dirpath = xstrdup (where); + head->next = NULL; + head->just_chdir = 0; + + + /* Make a copy of the repository name to play with. */ + reposcopy = xstrdup (repository); + + /* FIXME: this should be written in terms of last_component + instead of hardcoding '/'. This presumably affects OS/2, + NT, &c, if the user specifies '\'. Likewise for the call + to findslash. */ + cp = where + strlen (where); + while (cp > where) + { + struct dir_to_build *new; + + cp = findslash (where, cp - 1); + if (cp == NULL) + break; /* we're done */ + + new = (struct dir_to_build *) + xmalloc (sizeof (struct dir_to_build)); + new->dirpath = xmalloc (strlen (where)); + + /* If the user specified an absolute path for where, the + last path element we create should be the top-level + directory. */ + + if (cp > where) + { + strncpy (new->dirpath, where, cp - where); + new->dirpath[cp - where] = '\0'; + } + else + { + /* where should always be at least one character long. */ + assert (where[0] != '\0'); + strcpy (new->dirpath, "/"); + } + new->next = head; + head = new; + + /* If where consists of multiple pathname components, + then we want to just cd into it, without creating + directories or modifying CVS directories as we go. + In CVS 1.9 and earlier, the code actually does a + CVS_CHDIR up-front; I'm not going to try to go back + to that exact code but this is somewhat similar + in spirit. */ + if (where_orig != NULL + && cp - where < strlen (where_orig)) + { + new->repository = NULL; + new->just_chdir = 1; + continue; + } + + new->just_chdir = 0; + + /* Now figure out what repository directory to generate. + The most complete case would be something like this: + + The modules file contains + foo -d bar/baz quux + + The command issued was: + cvs co -d what/ever -N foo + + The results in the CVS/Repository files should be: + . -> (don't touch CVS/Repository) + (I think this case might be buggy currently) + what -> (don't touch CVS/Repository) + ever -> . (same as "cd what/ever; cvs co -N foo") + bar -> Emptydir (generated dir -- not in repos) + baz -> quux (finally!) */ + + if (strcmp (reposcopy, current_parsed_root->directory) == 0) + { + /* We can't walk up past CVSROOT. Instead, the + repository should be Emptydir. */ + new->repository = emptydir_name (); + } + else + { + /* It's a directory in the repository! */ + + char *rp; + + /* We'll always be below CVSROOT, but check for + paranoia's sake. */ + rp = strrchr (reposcopy, '/'); + if (rp == NULL) + error (1, 0, + "internal error: %s doesn't contain a slash", + reposcopy); + + *rp = '\0'; + new->repository = xmalloc (strlen (reposcopy) + 5); + (void) strcpy (new->repository, reposcopy); + + if (strcmp (reposcopy, current_parsed_root->directory) == 0) + { + /* Special case -- the repository name needs + to be "/path/to/repos/." (the trailing dot + is important). We might be able to get rid + of this after the we check out the other + code that handles repository names. */ + (void) strcat (new->repository, "/."); + } + } + } + + /* clean up */ + free (reposcopy); + + /* The top-level CVSADM directory should always be + current_parsed_root->directory. Create it, but only if WHERE is + relative. If WHERE is absolute, our current directory + may not have a thing to do with where the sources are + being checked out. If it does, build_dirs_and_chdir + will take care of creating adm files here. */ + /* FIXME: checking is_absolute (where) is a horrid kludge; + I suspect we probably can just skip the call to + build_one_dir whenever the -d command option was specified + to checkout. */ + + if (!isabsolute (where) && top_level_admin && m_type == CHECKOUT) + { + /* It may be argued that we shouldn't set any sticky + bits for the top-level repository. FIXME? */ + build_one_dir (current_parsed_root->directory, ".", argc <= 1); + +#ifdef SERVER_SUPPORT + /* We _always_ want to have a top-level admin + directory. If we're running in client/server mode, + send a "Clear-static-directory" command to make + sure it is created on the client side. (See 5.10 + in cvsclient.dvi to convince yourself that this is + OK.) If this is a duplicate command being sent, it + will be ignored on the client side. */ + + if (server_active) + server_clear_entstat (".", current_parsed_root->directory); +#endif + } + + + /* 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 (head, argc <= 1) != 0) + { + error (0, 0, "ignoring module %s", omodule); + err = 1; + goto out; + } + + /* set up the repository (or make sure the old one matches) */ + if (!isfile (CVSADM)) + { + FILE *fp; + + if (!noexec && argc > 1) + { + /* I'm not sure whether this check is redundant. */ + if (!isdir (repository)) + error (1, 0, "there is no repository %s", repository); + + Create_Admin (".", preload_update_dir, repository, + (char *) NULL, (char *) NULL, 0, 0, + m_type == CHECKOUT); + 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 (".", preload_update_dir, repository, tag, date, + + /* FIXME? This is a guess. If it is important + for nonbranch to be set correctly here I + think we need to write it one way now and + then rewrite it later via WriteTag, once + we've had a chance to call RCS_nodeisbranch + on each file. */ + 0, 0, m_type == CHECKOUT); + } + } + else + { + char *repos; + + if (m_type == EXPORT) + error (1, 0, "cannot export into working directory"); + + /* 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); + err = 1; + goto out; + } + 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 ( CVS_CHDIR (repository) < 0) + { + error (0, errno, "cannot chdir to %s", repository); + err = 1; + goto out; + } + which = W_REPOS; + if (tag != NULL && !tag_validated) + { + tag_check_valid (tag, argc - 1, argv + 1, 0, aflag, + repository); + tag_validated = 1; + } + } + else + { + which = W_LOCAL | W_REPOS; + if (tag != NULL && !tag_validated) + { + tag_check_valid (tag, argc - 1, argv + 1, 0, aflag, + repository); + tag_validated = 1; + } + } + + if (tag != NULL || date != NULL || join_rev1 != NULL) + which |= W_ATTIC; + + if (! join_tags_validated) + { + if (join_rev1 != NULL) + tag_check_valid_join (join_rev1, argc - 1, argv + 1, 0, aflag, + repository); + if (join_rev2 != NULL) + tag_check_valid_join (join_rev2, argc - 1, argv + 1, 0, aflag, + repository); + join_tags_validated = 1; + } + + /* + * 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 || argc > 1)) + { + if (!pipeout) + history_write (m_type == CHECKOUT ? 'O' : 'E', preload_update_dir, + history_name, 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, pull_template, repository); + goto out; + } + + if (!pipeout) + { + int i; + List *entries; + + /* we are only doing files, so register them */ + entries = Entries_Open (0, NULL); + for (i = 1; i < argc; i++) + { + char *line; + Vers_TS *vers; + struct file_info finfo; + + memset (&finfo, 0, sizeof finfo); + finfo.file = argv[i]; + /* Shouldn't be used, so set to arbitrary value. */ + finfo.update_dir = NULL; + finfo.fullname = argv[i]; + finfo.repository = repository; + finfo.entries = entries; + /* The rcs slot is needed to get the options from the RCS + file */ + finfo.rcs = RCS_parse (finfo.file, repository); + + vers = Version_TS (&finfo, options, tag, date, + force_tag_match, 0); + if (vers->ts_user == NULL) + { + line = xmalloc (strlen (finfo.file) + 15); + (void) sprintf (line, "Initial %s", finfo.file); + Register (entries, finfo.file, + vers->vn_rcs ? vers->vn_rcs : "0", + line, vers->options, vers->tag, + vers->date, (char *) 0); + free (line); + } + freevers_ts (&vers); + freercsnode (&finfo.rcs); + } + + Entries_Close (entries); + } + + /* Don't log "export", just regular "checkouts" */ + if (m_type == CHECKOUT && !pipeout) + history_write ('O', preload_update_dir, history_name, where, + repository); + + /* go ahead and call update now that everything is set */ + err += do_update (argc - 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, pull_template, repository); +out: + free (preload_update_dir); + preload_update_dir = oldupdate; + free (where); + free (repository); + return (err); +} + +static char * +findslash (start, p) + char *start; + char *p; +{ + for (;;) + { + if (*p == '/') return p; + if (p == start) break; + --p; + } + return NULL; +} + +/* Return a newly malloc'd string containing a pathname for CVSNULLREPOS, + and make sure that it exists. If there is an error creating the + directory, give a fatal error. Otherwise, the directory is guaranteed + to exist when we return. */ +char * +emptydir_name () +{ + char *repository; + + repository = xmalloc (strlen (current_parsed_root->directory) + + sizeof (CVSROOTADM) + + sizeof (CVSNULLREPOS) + + 3); + (void) sprintf (repository, "%s/%s/%s", current_parsed_root->directory, + CVSROOTADM, CVSNULLREPOS); + if (!isfile (repository)) + { + mode_t omask; + omask = umask (cvsumask); + if (CVS_MKDIR (repository, 0777) < 0) + error (1, errno, "cannot create %s", repository); + (void) umask (omask); + } + return repository; +} + +/* Build all the dirs along the path to DIRS with CVS subdirs with appropriate + * repositories. If DIRS->repository is NULL or the directory already exists, + * do not create a CVSADM directory for that subdirectory; just CVS_CHDIR into + * it. Frees all storage used by DIRS. + * + * ASSUMPTIONS + * 1. Parent directories will be listed in DIRS before their children. + * 2. At most a single directory will need to be changed at one time. In + * other words, if we are in /a/b/c, and our final destination is + * /a/b/c/d/e/f, then we will build d, then d/e, then d/e/f. + * + * INPUTS + * dirs Simple list composed of dir_to_build structures, listing + * information about directories to build. + * sticky Passed to build_one_dir to tell it whether there are any sticky + * tags or dates to be concerned with. + * + * RETURNS + * 1 on error, 0 otherwise. + * + * ERRORS + * The only nonfatal error this function may return is if the CHDIR fails. + */ +static int +build_dirs_and_chdir (dirs, sticky) + struct dir_to_build *dirs; + int sticky; +{ + int retval = 0; + struct dir_to_build *nextdir; + + while (dirs != NULL) + { + const char *dir = last_component (dirs->dirpath); + + if (!dirs->just_chdir) + { + mkdir_if_needed (dir); + Subdir_Register (NULL, NULL, dir); + } + + if (CVS_CHDIR (dir) < 0) + { + error (0, errno, "cannot chdir to %s", dir); + retval = 1; + goto out; + } + if (dirs->repository != NULL) + { + build_one_dir (dirs->repository, dirs->dirpath, sticky); + free (dirs->repository); + } + nextdir = dirs->next; + free (dirs->dirpath); + free (dirs); + dirs = nextdir; + } + + out: + while (dirs != NULL) + { + if (dirs->repository != NULL) + free (dirs->repository); + nextdir = dirs->next; + free (dirs->dirpath); + free (dirs); + dirs = nextdir; + } + return retval; +} |