This is checkout.c in view mode; [Download] [Up]
/*
* 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"
#ifndef lint
static const char rcsid[] = "$CVSid: @(#)checkout.c 1.78 94/10/07 $";
USE(rcsid)
#endif
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 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 ();
}
if (fprintf
(to_server,
strcmp (command_name, "export") == 0 ? "export\n" : "co\n")
< 0)
error (1, errno, "writing to server");
return get_responses_and_close ();
}
#endif
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))
(void) CVS_MKDIR (repository, 0777);
/* 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);
}
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;
}
else
which = W_LOCAL | W_REPOS;
if (tag != NULL || date != NULL)
which |= W_ATTIC;
/*
* 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[MAXLINELEN];
char *user;
Vers_TS *vers;
user = argv[i];
vers = Version_TS (repository, options, tag, date, user,
force_tag_match, 0, entries, (List *) NULL);
if (vers->ts_user == NULL)
{
(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);
}
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 (".", cp, 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);
}
These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.