#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-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 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-R\tProcess directories recursively (default).\n",
"\t-n\tDo not run module program (if any).\n",
"\t-r rev\tExport revision or tag.\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 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;
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;
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:flRpQqcsr: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 'Q':
case 'q':
#ifdef SERVER_SUPPORT
if (!server_active)
#endif
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;
noexec = 1;
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)
{
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
if (cat)
{
cat_module (status);
if (options)
{
free (options);
options = NULL;
}
return (0);
}
db = open_module ();
if (argc > 1)
shorten = 0;
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);
}
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)");
#ifdef CLIENT_SUPPORT
if ( current_parsed_root->isremote ) return 1;
#endif
current = xgetwd ();
if (current == NULL)
error (1, errno, "could not get working directory");
hardpath = xresolvepath ( current_parsed_root->directory );
if( where != NULL )
{
if( chdir( where ) != -1 )
{
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 )
{
char *parent;
where_location = xstrdup (where);
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
error( 1, errno, "could not change directory to requested checkout directory `%s'", where_location );
}
}
else
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[hardpath_len] == '/'
|| current[hardpath_len] == '\0')
retval = 0;
else
retval = 1;
}
else
retval = 1;
free (current);
free (hardpath);
return retval;
}
struct dir_to_build
{
char *repository;
char *dirpath;
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)
{
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,
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
}
}
}
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;
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);
if (preload_update_dir != NULL)
oldupdate = xstrdup (preload_update_dir);
where = xmalloc (strlen (argv[0])
+ (mfile == NULL ? 0 : strlen (mfile))
+ (mwhere == NULL ? 0 : strlen (mwhere))
+ (where_orig == NULL ? 0 : strlen (where_orig))
+ 10);
if (shorten)
{
if (where_orig != NULL)
{
(void) strcpy (where, where_orig);
}
else if (mwhere != NULL)
{
(void) strcpy (where, mwhere);
}
else
{
(void) strcpy (where, argv[0]);
}
}
else
{
*where = '\0';
if (where_orig != NULL)
{
(void) strcat (where, where_orig);
(void) strcat (where, "/");
}
if ((mwhere != NULL) && (! isabsolute (mwhere)))
(void) strcat (where, mwhere);
else
(void) strcat (where, argv[0]);
}
strip_trailing_slashes (where);
if (mfile != NULL)
{
char *cp;
char *path;
if (mfile[strlen (mfile) - 1] == '/')
{
error (0, 0, "checkout_proc: trailing slash on mfile (%s)!",
mfile);
}
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;
}
path = xmalloc (strlen (repository) + strlen (mfile) + 5);
(void) sprintf (path, "%s/%s", repository, mfile);
if (isdir (path))
{
(void) strcat (repository, "/");
(void) strcat (repository, mfile);
(void) strcat (where, "/");
(void) strcat (where, mfile);
}
else
{
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);
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);
head = (struct dir_to_build *) xmalloc (sizeof (struct dir_to_build));
head->repository = NULL;
head->dirpath = xstrdup (where);
head->next = NULL;
head->just_chdir = 0;
reposcopy = xstrdup (repository);
cp = where + strlen (where);
while (cp > where)
{
struct dir_to_build *new;
cp = findslash (where, cp - 1);
if (cp == NULL)
break;
new = (struct dir_to_build *)
xmalloc (sizeof (struct dir_to_build));
new->dirpath = xmalloc (strlen (where));
if (cp > where)
{
strncpy (new->dirpath, where, cp - where);
new->dirpath[cp - where] = '\0';
}
else
{
assert (where[0] != '\0');
strcpy (new->dirpath, "/");
}
new->next = head;
head = new;
if (where_orig != NULL
&& cp - where < strlen (where_orig))
{
new->repository = NULL;
new->just_chdir = 1;
continue;
}
new->just_chdir = 0;
if (strcmp (reposcopy, current_parsed_root->directory) == 0)
{
new->repository = emptydir_name ();
}
else
{
char *rp;
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)
{
(void) strcat (new->repository, "/.");
}
}
}
free (reposcopy);
if (!isabsolute (where) && top_level_admin && m_type == CHECKOUT)
{
build_one_dir (current_parsed_root->directory, ".", argc <= 1);
#ifdef SERVER_SUPPORT
if (server_active)
server_clear_entstat (".", current_parsed_root->directory);
#endif
}
if (build_dirs_and_chdir (head, argc <= 1) != 0)
{
error (0, 0, "ignoring module %s", omodule);
err = 1;
goto out;
}
if (!isfile (CVSADM))
{
FILE *fp;
if (!noexec && argc > 1)
{
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
{
if (!isdir (repository))
error (1, 0, "there is no repository %s", repository);
Create_Admin (".", preload_update_dir, repository, tag, date,
0, 0, m_type == CHECKOUT);
}
}
else
{
char *repos;
if (m_type == EXPORT)
error (1, 0, "cannot export into working directory");
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 (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 (!(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 ,
1 , aflag, checkout_prune_dirs,
pipeout, which, join_rev1, join_rev2,
preload_update_dir, m_type == CHECKOUT,
repository);
goto out;
}
if (!pipeout)
{
int i;
List *entries;
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];
finfo.update_dir = NULL;
finfo.fullname = argv[i];
finfo.repository = repository;
finfo.entries = entries;
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);
}
if (m_type == CHECKOUT && !pipeout)
history_write ('O', preload_update_dir, history_name, where,
repository);
err += do_update (argc - 1, argv + 1, options, tag, date,
force_tag_match, local_specified, 1 ,
aflag, checkout_prune_dirs, pipeout, which, join_rev1,
join_rev2, preload_update_dir, m_type == CHECKOUT,
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;
}
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;
}
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;
}