#include <assert.h>
#include "cvs.h"
#include "getline.h"
#include "edit.h"
#include "fileattr.h"
#include "hardlink.h"
static Dtype check_direntproc PROTO ((void *callerdat, char *dir,
char *repos, char *update_dir,
List *entries));
static int check_fileproc PROTO ((void *callerdat, struct file_info *finfo));
static int check_filesdoneproc PROTO ((void *callerdat, int err,
char *repos, char *update_dir,
List *entries));
static int checkaddfile PROTO((char *file, char *repository, char *tag,
char *options, RCSNode **rcsnode));
static Dtype commit_direntproc PROTO ((void *callerdat, char *dir,
char *repos, char *update_dir,
List *entries));
static int commit_dirleaveproc PROTO ((void *callerdat, char *dir,
int err, char *update_dir,
List *entries));
static int commit_fileproc PROTO ((void *callerdat, struct file_info *finfo));
static int commit_filesdoneproc PROTO ((void *callerdat, int err,
char *repository, char *update_dir,
List *entries));
static int finaladd PROTO((struct file_info *finfo, char *revision, char *tag,
char *options));
static int findmaxrev PROTO((Node * p, void *closure));
static int lock_RCS PROTO((char *user, RCSNode *rcs, char *rev,
char *repository));
static int precommit_list_proc PROTO((Node * p, void *closure));
static int precommit_proc PROTO((char *repository, char *filter));
static int remove_file PROTO ((struct file_info *finfo, char *tag,
char *message));
static void fix_rcs_modes PROTO((char *rcs, char *user));
static void fixaddfile PROTO((char *file, char *repository));
static void fixbranch PROTO((RCSNode *, char *branch));
static void unlockrcs PROTO((RCSNode *rcs));
static void ci_delproc PROTO((Node *p));
static void masterlist_delproc PROTO((Node *p));
static char *locate_rcs PROTO((char *file, char *repository));
struct commit_info
{
Ctype status;
char *rev;
char *tag;
char *options;
};
struct master_lists
{
List *ulist;
List *cilist;
};
static int force_ci = 0;
static int got_message;
static int run_module_prog = 1;
static int aflag;
static char *saved_tag;
static char *write_dirtag;
static int write_dirnonbranch;
static char *logfile;
static List *mulist;
static List *saved_ulist;
static char *saved_message;
static time_t last_register_time;
static const char *const commit_usage[] =
{
"Usage: %s %s [-nRlf] [-m msg | -F logfile] [-r rev] files...\n",
"\t-n\tDo not run the module program (if any).\n",
"\t-R\tProcess directories recursively.\n",
"\t-l\tLocal directory only (not recursive).\n",
"\t-f\tForce the file to be committed; disables recursion.\n",
"\t-F file\tRead the log message from file.\n",
"\t-m msg\tLog message.\n",
"\t-r rev\tCommit to this branch or trunk revision.\n",
"(Specify the --help global option for a list of other help options)\n",
NULL
};
#ifdef CLIENT_SUPPORT
struct question {
char *dir;
char *repos;
char *file;
struct question *next;
};
struct find_data {
List *ulist;
int argc;
char **argv;
List *ignlist;
struct question *questionables;
char *repository;
int force;
};
static Dtype find_dirent_proc PROTO ((void *callerdat, char *dir,
char *repository, char *update_dir,
List *entries));
static Dtype
find_dirent_proc (callerdat, dir, repository, update_dir, entries)
void *callerdat;
char *dir;
char *repository;
char *update_dir;
List *entries;
{
struct find_data *find_data = (struct find_data *)callerdat;
if (!isdir (dir))
return (R_SKIP_ALL);
find_data->ignlist = getlist ();
if (!quiet)
error (0, 0, "Examining %s", update_dir);
return R_PROCESS;
}
static struct find_data *find_data_static;
static void find_ignproc PROTO ((char *, char *));
static void
find_ignproc (file, dir)
char *file;
char *dir;
{
struct question *p;
p = (struct question *) xmalloc (sizeof (struct question));
p->dir = xstrdup (dir);
p->repos = xstrdup (find_data_static->repository);
p->file = xstrdup (file);
p->next = find_data_static->questionables;
find_data_static->questionables = p;
}
static int find_filesdoneproc PROTO ((void *callerdat, int err,
char *repository, char *update_dir,
List *entries));
static int
find_filesdoneproc (callerdat, err, repository, update_dir, entries)
void *callerdat;
int err;
char *repository;
char *update_dir;
List *entries;
{
struct find_data *find_data = (struct find_data *)callerdat;
find_data->repository = repository;
if (find_data->ignlist)
{
find_data_static = find_data;
ignore_files (find_data->ignlist, entries, update_dir, find_ignproc);
dellist (&find_data->ignlist);
}
find_data->repository = NULL;
return err;
}
static int find_fileproc PROTO ((void *callerdat, struct file_info *finfo));
static int
find_fileproc (callerdat, finfo)
void *callerdat;
struct file_info *finfo;
{
Vers_TS *vers;
enum classify_type status;
Node *node;
struct find_data *args = (struct find_data *)callerdat;
struct logfile_info *data;
struct file_info xfinfo;
if (args->ignlist)
{
Node *p;
p = getnode ();
p->type = FILES;
p->key = xstrdup (finfo->file);
if (addnode (args->ignlist, p) != 0)
freenode (p);
}
xfinfo = *finfo;
xfinfo.repository = NULL;
xfinfo.rcs = NULL;
vers = Version_TS (&xfinfo, NULL, saved_tag, NULL, 0, 0);
if (vers->ts_user == NULL
&& vers->vn_user != NULL
&& vers->vn_user[0] == '-')
status = T_REMOVED;
else if (vers->vn_user == NULL)
{
if (vers->ts_user == NULL)
error (0, 0, "nothing known about `%s'", finfo->fullname);
else
error (0, 0, "use `%s add' to create an entry for %s",
program_name, finfo->fullname);
return 1;
}
else if (vers->ts_user != NULL
&& vers->vn_user != NULL
&& vers->vn_user[0] == '0')
status = T_ADDED;
else if (vers->ts_user != NULL
&& vers->ts_rcs != NULL
&& (args->force || strcmp (vers->ts_user, vers->ts_rcs) != 0))
status = T_MODIFIED;
else
{
return 0;
}
node = getnode ();
node->key = xstrdup (finfo->fullname);
data = (struct logfile_info *) xmalloc (sizeof (struct logfile_info));
data->type = status;
data->tag = xstrdup (vers->tag);
data->rev_old = data->rev_new = NULL;
node->type = UPDATE;
node->delproc = update_delproc;
node->data = (char *) data;
(void)addnode (args->ulist, node);
++args->argc;
freevers_ts (&vers);
return 0;
}
static int copy_ulist PROTO ((Node *, void *));
static int
copy_ulist (node, data)
Node *node;
void *data;
{
struct find_data *args = (struct find_data *)data;
args->argv[args->argc++] = node->key;
return 0;
}
#endif
int
commit (argc, argv)
int argc;
char **argv;
{
int c;
int err = 0;
int local = 0;
if (argc == -1)
usage (commit_usage);
#ifdef CVS_BADROOT
if (geteuid () == (uid_t) 0)
{
struct passwd *pw;
if ((pw = (struct passwd *) getpwnam (getcaller ())) == NULL)
error (1, 0, "you are unknown to this system");
if (pw->pw_uid == (uid_t) 0)
error (1, 0, "cannot commit files as 'root'");
}
#endif
optind = 0;
while ((c = getopt (argc, argv, "+nlRm:fF:r:")) != -1)
{
switch (c)
{
case 'n':
run_module_prog = 0;
break;
case 'm':
#ifdef FORCE_USE_EDITOR
use_editor = 1;
#else
use_editor = 0;
#endif
if (saved_message)
{
free (saved_message);
saved_message = NULL;
}
saved_message = xstrdup(optarg);
break;
case 'r':
if (saved_tag)
free (saved_tag);
saved_tag = xstrdup (optarg);
break;
case 'l':
local = 1;
break;
case 'R':
local = 0;
break;
case 'f':
force_ci = 1;
local = 1;
break;
case 'F':
#ifdef FORCE_USE_EDITOR
use_editor = 1;
#else
use_editor = 0;
#endif
logfile = optarg;
break;
case '?':
default:
usage (commit_usage);
break;
}
}
argc -= optind;
argv += optind;
if (saved_tag && isdigit (*saved_tag))
{
aflag = 1;
while (saved_tag[strlen (saved_tag) - 1] == '.')
saved_tag[strlen (saved_tag) - 1] = '\0';
}
if (logfile)
{
int n, logfd;
struct stat statbuf;
if (saved_message)
error (1, 0, "cannot specify both a message and a log file");
if ((logfd = CVS_OPEN (logfile, O_RDONLY | OPEN_BINARY)) < 0)
error (1, errno, "cannot open log file %s", logfile);
if (fstat(logfd, &statbuf) < 0)
error (1, errno, "cannot find size of log file %s", logfile);
saved_message = xmalloc (statbuf.st_size + 1);
if ((n = read (logfd, saved_message, statbuf.st_size + 1)) < 0)
error (1, errno, "cannot read log message from %s", logfile);
(void) close (logfd);
saved_message[n] = '\0';
}
wrap_setup ();
#ifdef CLIENT_SUPPORT
if (client_active)
{
struct find_data find_args;
ign_setup ();
find_args.ulist = getlist ();
find_args.argc = 0;
find_args.questionables = NULL;
find_args.ignlist = NULL;
find_args.repository = NULL;
find_args.force = force_ci || saved_tag != NULL;
err = start_recursion (find_fileproc, find_filesdoneproc,
find_dirent_proc, (DIRLEAVEPROC) NULL,
(void *)&find_args,
argc, argv, local, W_LOCAL, 0, 0,
(char *)NULL, 0);
if (err)
error (1, 0, "correct above errors first!");
if (find_args.argc == 0)
return 0;
find_args.argv = (char **) xmalloc (find_args.argc * sizeof (char **));
find_args.argc = 0;
walklist (find_args.ulist, copy_ulist, &find_args);
start_server ();
if (use_editor)
do_editor (".", &saved_message, (char *)NULL, find_args.ulist);
do_verify (saved_message, (char *)NULL);
option_with_arg ("-m", saved_message);
{
struct question *p;
struct question *q;
p = find_args.questionables;
while (p != NULL)
{
if (ign_inhibit_server || !supported_request ("Questionable"))
{
cvs_output ("? ", 2);
if (p->dir[0] != '\0')
{
cvs_output (p->dir, 0);
cvs_output ("/", 1);
}
cvs_output (p->file, 0);
cvs_output ("\n", 1);
}
else
{
send_to_server ("Directory ", 0);
send_to_server (p->dir[0] == '\0' ? "." : p->dir, 0);
send_to_server ("\012", 1);
send_to_server (p->repos, 0);
send_to_server ("\012", 1);
send_to_server ("Questionable ", 0);
send_to_server (p->file, 0);
send_to_server ("\012", 1);
}
free (p->dir);
free (p->repos);
free (p->file);
q = p->next;
free (p);
p = q;
}
}
if (local)
send_arg("-l");
if (force_ci)
send_arg("-f");
if (!run_module_prog)
send_arg("-n");
option_with_arg ("-r", saved_tag);
send_file_names (find_args.argc, find_args.argv, 0);
send_files (find_args.argc, find_args.argv, local, 0,
find_args.force ? SEND_FORCE : 0);
send_to_server ("ci\012", 0);
err = get_responses_and_close ();
if (err != 0 && use_editor && saved_message != NULL)
{
char *fname;
FILE *fp;
fname = cvs_temp_name ();
fp = CVS_FOPEN (fname, "w+");
if (fp == NULL)
error (1, 0, "cannot create temporary file %s", fname);
if (fwrite (saved_message, 1, strlen (saved_message), fp)
!= strlen (saved_message))
error (1, errno, "cannot write temporary file %s", fname);
if (fclose (fp) < 0)
error (0, errno, "cannot close temporary file %s", fname);
error (0, 0, "saving log message in %s", fname);
}
return err;
}
#endif
if (saved_tag != NULL)
tag_check_valid (saved_tag, argc, argv, local, aflag, "");
if (argc <= 0)
write_dirtag = saved_tag;
lock_tree_for_write (argc, argv, local, aflag);
mulist = getlist ();
#ifdef PRESERVE_PERMISSIONS_SUPPORT
if (preserve_perms)
{
hardlist = getlist ();
working_dir = xgetwd();
}
#endif
err = start_recursion (check_fileproc, check_filesdoneproc,
check_direntproc, (DIRLEAVEPROC) NULL, NULL, argc,
argv, local, W_LOCAL, aflag, 0, (char *) NULL, 1);
if (err)
{
Lock_Cleanup ();
error (1, 0, "correct above errors first!");
}
write_dirnonbranch = 0;
if (noexec == 0)
err = start_recursion (commit_fileproc, commit_filesdoneproc,
commit_direntproc, commit_dirleaveproc, NULL,
argc, argv, local, W_LOCAL, aflag, 0,
(char *) NULL, 1);
Lock_Cleanup ();
dellist (&mulist);
if (last_register_time)
{
time_t now;
(void) time (&now);
if (now == last_register_time)
{
sleep (1);
}
}
return (err);
}
static
Ctype
classify_file_internal (finfo, vers)
struct file_info *finfo;
Vers_TS **vers;
{
int save_noexec, save_quiet, save_really_quiet;
Ctype status;
save_noexec = noexec;
save_quiet = quiet;
save_really_quiet = really_quiet;
noexec = quiet = really_quiet = 1;
if (saved_tag && isdigit (*saved_tag))
{
if (numdots (saved_tag) < 2)
{
status = Classify_File (finfo, (char *) NULL, (char *) NULL,
(char *) NULL, 1, aflag, vers, 0);
if (status == T_UPTODATE || status == T_MODIFIED ||
status == T_ADDED)
{
Ctype xstatus;
freevers_ts (vers);
xstatus = Classify_File (finfo, saved_tag, (char *) NULL,
(char *) NULL, 1, aflag, vers, 0);
if (xstatus == T_REMOVE_ENTRY)
status = T_MODIFIED;
else if (status == T_MODIFIED && xstatus == T_CONFLICT)
status = T_MODIFIED;
else
status = xstatus;
}
}
else
{
char *xtag, *cp;
xtag = xstrdup (saved_tag);
if ((numdots (xtag) & 1) != 0)
{
cp = strrchr (xtag, '.');
*cp = '\0';
}
status = Classify_File (finfo, xtag, (char *) NULL,
(char *) NULL, 1, aflag, vers, 0);
if ((status == T_REMOVE_ENTRY || status == T_CONFLICT)
&& (cp = strrchr (xtag, '.')) != NULL)
{
*cp = '\0';
freevers_ts (vers);
status = Classify_File (finfo, xtag, (char *) NULL,
(char *) NULL, 1, aflag, vers, 0);
if (status == T_UPTODATE || status == T_REMOVE_ENTRY)
status = T_MODIFIED;
}
free ((*vers)->tag);
(*vers)->tag = xstrdup (saved_tag);
free (xtag);
}
}
else
status = Classify_File (finfo, saved_tag, (char *) NULL, (char *) NULL,
1, 0, vers, 0);
noexec = save_noexec;
quiet = save_quiet;
really_quiet = save_really_quiet;
return status;
}
static int
check_fileproc (callerdat, finfo)
void *callerdat;
struct file_info *finfo;
{
Ctype status;
char *xdir;
Node *p;
List *ulist, *cilist;
Vers_TS *vers;
struct commit_info *ci;
struct logfile_info *li;
status = classify_file_internal (finfo, &vers);
if (force_ci && status == T_UPTODATE)
status = T_MODIFIED;
switch (status)
{
case T_CHECKOUT:
#ifdef SERVER_SUPPORT
case T_PATCH:
#endif
case T_NEEDS_MERGE:
case T_CONFLICT:
case T_REMOVE_ENTRY:
error (0, 0, "Up-to-date check failed for `%s'", finfo->fullname);
freevers_ts (&vers);
return (1);
case T_MODIFIED:
case T_ADDED:
case T_REMOVED:
if (!saved_tag || !isdigit (*saved_tag))
{
if (vers->date)
{
error (0, 0,
"cannot commit with sticky date for file `%s'",
finfo->fullname);
freevers_ts (&vers);
return (1);
}
if (status == T_MODIFIED && vers->tag &&
!RCS_isbranch (finfo->rcs, vers->tag))
{
error (0, 0,
"sticky tag `%s' for file `%s' is not a branch",
vers->tag, finfo->fullname);
freevers_ts (&vers);
return (1);
}
}
if (status == T_MODIFIED && !force_ci && vers->ts_conflict)
{
char *filestamp;
int retcode;
#ifdef SERVER_SUPPORT
if (server_active)
retcode = vers->ts_conflict[0] != '=';
else {
filestamp = time_stamp (finfo->file);
retcode = strcmp (vers->ts_conflict, filestamp);
free (filestamp);
}
#else
filestamp = time_stamp (finfo->file);
retcode = strcmp (vers->ts_conflict, filestamp);
free (filestamp);
#endif
if (retcode == 0)
{
error (0, 0,
"file `%s' had a conflict and has not been modified",
finfo->fullname);
freevers_ts (&vers);
return (1);
}
if (file_has_markers (finfo))
{
error (0, 0,
"\
warning: file `%s' seems to still contain conflict indicators",
finfo->fullname);
}
}
if (status == T_REMOVED && vers->tag && isdigit (*vers->tag))
{
error (0, 0,
"cannot remove file `%s' which has a numeric sticky tag of `%s'",
finfo->fullname, vers->tag);
freevers_ts (&vers);
return (1);
}
if (status == T_ADDED)
{
if (vers->tag == NULL)
{
char *rcs;
rcs = xmalloc (strlen (finfo->repository)
+ strlen (finfo->file)
+ sizeof RCSEXT
+ 5);
sprintf(rcs, "%s/%s%s", finfo->repository, finfo->file,
RCSEXT);
if (isreadable (rcs))
{
error (0, 0,
"cannot add file `%s' when RCS file `%s' already exists",
finfo->fullname, rcs);
freevers_ts (&vers);
free (rcs);
return (1);
}
free (rcs);
}
if (vers->tag && isdigit (*vers->tag) &&
numdots (vers->tag) > 1)
{
error (0, 0,
"cannot add file `%s' with revision `%s'; must be on trunk",
finfo->fullname, vers->tag);
freevers_ts (&vers);
return (1);
}
}
if (finfo->update_dir[0] == '\0')
xdir = ".";
else
xdir = finfo->update_dir;
if ((p = findnode (mulist, xdir)) != NULL)
{
ulist = ((struct master_lists *) p->data)->ulist;
cilist = ((struct master_lists *) p->data)->cilist;
}
else
{
struct master_lists *ml;
ulist = getlist ();
cilist = getlist ();
p = getnode ();
p->key = xstrdup (xdir);
p->type = UPDATE;
ml = (struct master_lists *)
xmalloc (sizeof (struct master_lists));
ml->ulist = ulist;
ml->cilist = cilist;
p->data = (char *) ml;
p->delproc = masterlist_delproc;
(void) addnode (mulist, p);
}
p = getnode ();
p->key = xstrdup (finfo->file);
p->type = UPDATE;
p->delproc = update_delproc;
li = ((struct logfile_info *)
xmalloc (sizeof (struct logfile_info)));
li->type = status;
li->tag = xstrdup (vers->tag);
li->rev_old = xstrdup (vers->vn_rcs);
li->rev_new = NULL;
p->data = (char *) li;
(void) addnode (ulist, p);
p = getnode ();
p->key = xstrdup (finfo->file);
p->type = UPDATE;
p->delproc = ci_delproc;
ci = (struct commit_info *) xmalloc (sizeof (struct commit_info));
ci->status = status;
if (vers->tag)
if (isdigit (*vers->tag))
ci->rev = xstrdup (vers->tag);
else
ci->rev = RCS_whatbranch (finfo->rcs, vers->tag);
else
ci->rev = (char *) NULL;
ci->tag = xstrdup (vers->tag);
ci->options = xstrdup(vers->options);
p->data = (char *) ci;
(void) addnode (cilist, p);
#ifdef PRESERVE_PERMISSIONS_SUPPORT
if (preserve_perms)
{
char *fullpath;
Node *linkp;
struct hardlink_info *hlinfo;
fullpath = xmalloc (strlen(working_dir) +
strlen(finfo->fullname) + 2);
sprintf (fullpath, "%s/%s", working_dir, finfo->fullname);
linkp = lookup_file_by_inode (fullpath);
if (linkp != NULL)
{
hlinfo = (struct hardlink_info *)
xmalloc (sizeof (struct hardlink_info));
hlinfo->status = status;
linkp->data = (char *) hlinfo;
}
}
#endif
break;
case T_UNKNOWN:
error (0, 0, "nothing known about `%s'", finfo->fullname);
freevers_ts (&vers);
return (1);
case T_UPTODATE:
break;
default:
error (0, 0, "CVS internal error: unknown status %d", status);
break;
}
freevers_ts (&vers);
return (0);
}
static Dtype
check_direntproc (callerdat, dir, repos, update_dir, entries)
void *callerdat;
char *dir;
char *repos;
char *update_dir;
List *entries;
{
if (!isdir (dir))
return (R_SKIP_ALL);
if (!quiet)
error (0, 0, "Examining %s", update_dir);
return (R_PROCESS);
}
static int
precommit_list_proc (p, closure)
Node *p;
void *closure;
{
struct logfile_info *li;
li = (struct logfile_info *) p->data;
if (li->type == T_ADDED
|| li->type == T_MODIFIED
|| li->type == T_REMOVED)
{
run_arg (p->key);
}
return (0);
}
static int
precommit_proc (repository, filter)
char *repository;
char *filter;
{
if (isabsolute (filter))
{
char *s, *cp;
s = xstrdup (filter);
for (cp = s; *cp; cp++)
if (isspace (*cp))
{
*cp = '\0';
break;
}
if (!isfile (s))
{
error (0, errno, "cannot find pre-commit filter `%s'", s);
free (s);
return (1);
}
free (s);
}
run_setup (filter);
run_arg (repository);
(void) walklist (saved_ulist, precommit_list_proc, NULL);
return (run_exec (RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL|RUN_REALLY));
}
static int
check_filesdoneproc (callerdat, err, repos, update_dir, entries)
void *callerdat;
int err;
char *repos;
char *update_dir;
List *entries;
{
int n;
Node *p;
p = findnode (mulist, update_dir);
if (p != NULL)
saved_ulist = ((struct master_lists *) p->data)->ulist;
else
saved_ulist = (List *) NULL;
if (saved_ulist == NULL || saved_ulist->list->next == saved_ulist->list)
return (err);
if ((n = Parse_Info (CVSROOTADM_COMMITINFO, repos, precommit_proc, 1)) > 0)
{
error (0, 0, "Pre-commit check failed");
err += n;
}
return (err);
}
static int maxrev;
static char *sbranch;
static int
commit_fileproc (callerdat, finfo)
void *callerdat;
struct file_info *finfo;
{
Node *p;
int err = 0;
List *ulist, *cilist;
struct commit_info *ci;
if (write_dirtag != NULL
&& finfo->rcs != NULL)
{
char *rev = RCS_getversion (finfo->rcs, write_dirtag, NULL, 1, NULL);
if (rev != NULL
&& !RCS_nodeisbranch (finfo->rcs, write_dirtag))
write_dirnonbranch = 1;
if (rev != NULL)
free (rev);
}
if (finfo->update_dir[0] == '\0')
p = findnode (mulist, ".");
else
p = findnode (mulist, finfo->update_dir);
if (p == NULL)
return (0);
ulist = ((struct master_lists *) p->data)->ulist;
cilist = ((struct master_lists *) p->data)->cilist;
if (!(got_message))
{
got_message = 1;
if (use_editor)
do_editor (finfo->update_dir, &saved_message,
finfo->repository, ulist);
do_verify (saved_message, finfo->repository);
}
p = findnode (cilist, finfo->file);
if (p == NULL)
return (0);
ci = (struct commit_info *) p->data;
if (ci->status == T_MODIFIED)
{
if (finfo->rcs == NULL)
error (1, 0, "internal error: no parsed RCS file");
if (lock_RCS (finfo->file, finfo->rcs, ci->rev,
finfo->repository) != 0)
{
unlockrcs (finfo->rcs);
err = 1;
goto out;
}
}
else if (ci->status == T_ADDED)
{
if (checkaddfile (finfo->file, finfo->repository, ci->tag, ci->options,
&finfo->rcs) != 0)
{
fixaddfile (finfo->file, finfo->repository);
err = 1;
goto out;
}
if (ci->tag)
{
if (finfo->rcs == NULL)
error (1, 0, "internal error: no parsed RCS file");
ci->rev = RCS_whatbranch (finfo->rcs, ci->tag);
err = Checkin ('A', finfo, finfo->rcs->path, ci->rev,
ci->tag, ci->options, saved_message);
if (err != 0)
{
unlockrcs (finfo->rcs);
fixbranch (finfo->rcs, sbranch);
}
(void) time (&last_register_time);
ci->status = T_UPTODATE;
}
}
if (ci->status == T_ADDED)
{
char *xrev = (char *) NULL;
if (ci->rev == NULL)
{
maxrev = 0;
(void) walklist (finfo->entries, findmaxrev, NULL);
if (maxrev == 0)
maxrev = 1;
xrev = xmalloc (20);
(void) sprintf (xrev, "%d", maxrev);
}
err = finaladd (finfo, ci->rev ? ci->rev : xrev, ci->tag, ci->options);
if (xrev)
free (xrev);
}
else if (ci->status == T_MODIFIED)
{
err = Checkin ('M', finfo,
finfo->rcs->path, ci->rev, ci->tag,
ci->options, saved_message);
(void) time (&last_register_time);
if (err != 0)
{
unlockrcs (finfo->rcs);
fixbranch (finfo->rcs, sbranch);
}
}
else if (ci->status == T_REMOVED)
{
err = remove_file (finfo, ci->tag, saved_message);
#ifdef SERVER_SUPPORT
if (server_active) {
server_scratch_entry_only ();
server_updated (finfo,
NULL,
SERVER_UPDATED,
(mode_t) -1,
(unsigned char *) NULL,
(struct buffer *) NULL);
}
#endif
}
notify_do ('C', finfo->file, getcaller (), NULL, NULL, finfo->repository);
out:
if (err != 0)
{
p = findnode (ulist, finfo->file);
if (p)
delnode (p);
}
else
{
if (ci->status != T_REMOVED)
{
p = findnode (ulist, finfo->file);
if (p)
{
Vers_TS *vers;
struct logfile_info *li;
(void) classify_file_internal (finfo, &vers);
li = (struct logfile_info *) p->data;
li->rev_new = xstrdup (vers->vn_rcs);
freevers_ts (&vers);
}
}
}
return (err);
}
static int
commit_filesdoneproc (callerdat, err, repository, update_dir, entries)
void *callerdat;
int err;
char *repository;
char *update_dir;
List *entries;
{
Node *p;
List *ulist;
p = findnode (mulist, update_dir);
if (p == NULL)
return (err);
ulist = ((struct master_lists *) p->data)->ulist;
got_message = 0;
Update_Logfile (repository, saved_message, (FILE *) 0, ulist);
{
char *p;
if (strncmp (CVSroot_directory, repository,
strlen (CVSroot_directory)) != 0)
error (0, 0,
"internal error: repository (%s) doesn't begin with root (%s)",
repository, CVSroot_directory);
p = repository + strlen (CVSroot_directory);
if (*p == '/')
++p;
if (strcmp ("CVSROOT", p) == 0
|| strncmp ("CVSROOT/", p, strlen ("CVSROOT/")) == 0
)
{
char *admin_dir = xstrdup (repository);
int cvsrootlen = strlen ("CVSROOT");
assert (admin_dir[p - repository + cvsrootlen] == '\0'
|| admin_dir[p - repository + cvsrootlen] == '/');
admin_dir[p - repository + cvsrootlen] = '\0';
cvs_output (program_name, 0);
cvs_output (" ", 1);
cvs_output (command_name, 0);
cvs_output (": Rebuilding administrative file database\n", 0);
mkmodules (admin_dir);
free (admin_dir);
}
}
if (err == 0 && run_module_prog)
{
FILE *fp;
if ((fp = CVS_FOPEN (CVSADM_CIPROG, "r")) != NULL)
{
char *line;
int line_length;
size_t line_chars_allocated;
char *repos;
line = NULL;
line_chars_allocated = 0;
line_length = getline (&line, &line_chars_allocated, fp);
if (line_length > 0)
{
if (line[line_length - 1] == '\n')
line[--line_length] = '\0';
repos = Name_Repository ((char *) NULL, update_dir);
run_setup (line);
run_arg (repos);
cvs_output (program_name, 0);
cvs_output (" ", 1);
cvs_output (command_name, 0);
cvs_output (": Executing '", 0);
run_print (stdout);
cvs_output ("'\n", 0);
(void) run_exec (RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL);
free (repos);
}
else
{
if (ferror (fp))
error (0, errno, "warning: error reading %s",
CVSADM_CIPROG);
}
if (line != NULL)
free (line);
if (fclose (fp) < 0)
error (0, errno, "warning: cannot close %s", CVSADM_CIPROG);
}
else
{
if (! existence_error (errno))
error (0, errno, "warning: cannot open %s", CVSADM_CIPROG);
}
}
return (err);
}
static Dtype
commit_direntproc (callerdat, dir, repos, update_dir, entries)
void *callerdat;
char *dir;
char *repos;
char *update_dir;
List *entries;
{
Node *p;
List *ulist;
char *real_repos;
if (!isdir (dir))
return (R_SKIP_ALL);
p = findnode (mulist, update_dir);
if (p != NULL)
ulist = ((struct master_lists *) p->data)->ulist;
else
ulist = (List *) NULL;
if (ulist == NULL || ulist->list->next == ulist->list)
return (R_SKIP_FILES);
real_repos = Name_Repository (dir, update_dir);
got_message = 1;
if (use_editor)
do_editor (update_dir, &saved_message, real_repos, ulist);
do_verify (saved_message, real_repos);
free (real_repos);
return (R_PROCESS);
}
static int
commit_dirleaveproc (callerdat, dir, err, update_dir, entries)
void *callerdat;
char *dir;
int err;
char *update_dir;
List *entries;
{
if (err == 0 && write_dirtag != NULL)
{
WriteTag (NULL, write_dirtag, NULL, write_dirnonbranch,
update_dir, Name_Repository (dir, update_dir));
}
return (err);
}
static int
findmaxrev (p, closure)
Node *p;
void *closure;
{
char *cp;
int thisrev;
Entnode *entdata;
entdata = (Entnode *) p->data;
if (entdata->type != ENT_FILE)
return (0);
cp = strchr (entdata->version, '.');
if (cp != NULL)
*cp = '\0';
thisrev = atoi (entdata->version);
if (cp != NULL)
*cp = '.';
if (thisrev > maxrev)
maxrev = thisrev;
return (0);
}
static int
remove_file (finfo, tag, message)
struct file_info *finfo;
char *tag;
char *message;
{
mode_t omask;
int retcode;
char *tmp;
int branch;
int lockflag;
char *corev;
char *rev;
char *prev_rev;
char *old_path;
corev = NULL;
rev = NULL;
prev_rev = NULL;
retcode = 0;
if (finfo->rcs == NULL)
error (1, 0, "internal error: no parsed RCS file");
branch = 0;
if (tag && !(branch = RCS_nodeisbranch (finfo->rcs, tag)))
{
if ((retcode = RCS_deltag (finfo->rcs, tag)) != 0)
{
if (!quiet)
error (0, retcode == -1 ? errno : 0,
"failed to remove tag `%s' from `%s'", tag,
finfo->fullname);
return (1);
}
RCS_rewrite (finfo->rcs, NULL, NULL);
Scratch_Entry (finfo->entries, finfo->file);
return (0);
}
cvs_output ("Removing ", 0);
cvs_output (finfo->fullname, 0);
cvs_output (";\n", 0);
rev = NULL;
lockflag = 1;
if (branch)
{
char *branchname;
rev = RCS_whatbranch (finfo->rcs, tag);
if (rev == NULL)
{
error (0, 0, "cannot find branch \"%s\".", tag);
return (1);
}
branchname = RCS_getbranch (finfo->rcs, rev, 1);
if (branchname == NULL)
{
corev = RCS_gettag (finfo->rcs, tag, 1, (int *) NULL);
prev_rev = xstrdup(rev);
lockflag = 0;
} else
{
corev = xstrdup (rev);
prev_rev = xstrdup(branchname);
free (branchname);
}
} else
{
prev_rev = RCS_head (finfo->rcs);
}
if (!tag && !branch)
{
if (RCS_setbranch (finfo->rcs, NULL) != 0)
{
error (0, 0, "cannot change branch to default for %s",
finfo->fullname);
return (1);
}
RCS_rewrite (finfo->rcs, NULL, NULL);
}
#ifdef SERVER_SUPPORT
if (server_active) {
unlink_file (finfo->file);
}
#endif
retcode = RCS_checkout (finfo->rcs, finfo->file, rev ? corev : NULL,
(char *) NULL, (char *) NULL, RUN_TTY,
(RCSCHECKOUTPROC) NULL, (void *) NULL);
if (retcode != 0)
{
error (0, 0,
"failed to check out `%s'", finfo->fullname);
return (1);
}
if (lockflag)
{
if (RCS_lock (finfo->rcs, rev ? corev : NULL, 1) == 0)
RCS_rewrite (finfo->rcs, NULL, NULL);
}
if (corev != NULL)
free (corev);
retcode = RCS_checkin (finfo->rcs, finfo->file, message, rev,
RCS_FLAGS_DEAD | RCS_FLAGS_QUIET);
if (retcode != 0)
{
if (!quiet)
error (0, retcode == -1 ? errno : 0,
"failed to commit dead revision for `%s'", finfo->fullname);
return (1);
}
if (rev != NULL)
free (rev);
old_path = finfo->rcs->path;
if (!branch)
{
tmp = xmalloc(strlen(finfo->repository) +
sizeof('/') +
sizeof(CVSATTIC) +
sizeof('/') +
strlen(finfo->file) +
sizeof(RCSEXT) + 1);
(void) sprintf (tmp, "%s/%s", finfo->repository, CVSATTIC);
omask = umask (cvsumask);
(void) CVS_MKDIR (tmp, 0777);
(void) umask (omask);
(void) sprintf (tmp, "%s/%s/%s%s", finfo->repository, CVSATTIC,
finfo->file, RCSEXT);
if (strcmp (finfo->rcs->path, tmp) != 0
&& CVS_RENAME (finfo->rcs->path, tmp) == -1
&& (isreadable (finfo->rcs->path) || !isreadable (tmp)))
{
free(tmp);
return (1);
}
finfo->rcs->path = tmp;
}
cvs_output (old_path, 0);
cvs_output (" <-- ", 0);
cvs_output (finfo->file, 0);
cvs_output ("\nnew revision: delete; previous revision: ", 0);
cvs_output (prev_rev, 0);
cvs_output ("\ndone\n", 0);
free(prev_rev);
if (old_path != finfo->rcs->path)
free (old_path);
Scratch_Entry (finfo->entries, finfo->file);
return (0);
}
static int
finaladd (finfo, rev, tag, options)
struct file_info *finfo;
char *rev;
char *tag;
char *options;
{
int ret;
char *rcs;
rcs = locate_rcs (finfo->file, finfo->repository);
ret = Checkin ('A', finfo, rcs, rev, tag, options, saved_message);
if (ret == 0)
{
char *tmp = xmalloc (strlen (finfo->file) + sizeof (CVSADM)
+ sizeof (CVSEXT_LOG) + 10);
(void) sprintf (tmp, "%s/%s%s", CVSADM, finfo->file, CVSEXT_LOG);
(void) unlink_file (tmp);
free (tmp);
}
else
fixaddfile (finfo->file, finfo->repository);
(void) time (&last_register_time);
free (rcs);
return (ret);
}
static void
unlockrcs (rcs)
RCSNode *rcs;
{
int retcode;
if ((retcode = RCS_unlock (rcs, NULL, 0)) != 0)
error (retcode == -1 ? 1 : 0, retcode == -1 ? errno : 0,
"could not unlock %s", rcs->path);
else
RCS_rewrite (rcs, NULL, NULL);
}
static void
fixaddfile (file, repository)
char *file;
char *repository;
{
RCSNode *rcsfile;
char *rcs;
int save_really_quiet;
rcs = locate_rcs (file, repository);
save_really_quiet = really_quiet;
really_quiet = 1;
if ((rcsfile = RCS_parsercsfile (rcs)) == NULL)
(void) unlink_file (rcs);
else
freercsnode (&rcsfile);
really_quiet = save_really_quiet;
free (rcs);
}
static void
fixbranch (rcs, branch)
RCSNode *rcs;
char *branch;
{
int retcode;
if (branch != NULL)
{
if ((retcode = RCS_setbranch (rcs, branch)) != 0)
error (retcode == -1 ? 1 : 0, retcode == -1 ? errno : 0,
"cannot restore branch to %s for %s", branch, rcs->path);
RCS_rewrite (rcs, NULL, NULL);
}
}
static int
checkaddfile (file, repository, tag, options, rcsnode)
char *file;
char *repository;
char *tag;
char *options;
RCSNode **rcsnode;
{
char *rcs;
char *fname;
mode_t omask;
int retcode = 0;
int newfile = 0;
RCSNode *rcsfile = NULL;
int retval;
if (tag)
{
rcs = xmalloc (strlen (repository) + strlen (file)
+ sizeof (RCSEXT) + sizeof (CVSATTIC) + 10);
(void) sprintf (rcs, "%s/%s%s", repository, file, RCSEXT);
if (! isreadable (rcs))
{
(void) sprintf(rcs, "%s/%s", repository, CVSATTIC);
omask = umask (cvsumask);
if (CVS_MKDIR (rcs, 0777) != 0 && errno != EEXIST)
error (1, errno, "cannot make directory `%s'", rcs);;
(void) umask (omask);
(void) sprintf (rcs, "%s/%s/%s%s", repository, CVSATTIC, file,
RCSEXT);
}
}
else
rcs = locate_rcs (file, repository);
if (isreadable (rcs))
{
char *rev;
if ((rcsfile = *rcsnode) == NULL)
{
error (0, 0, "could not find parsed rcsfile %s", file);
retval = 1;
goto out;
}
if (tag == NULL)
{
char *oldfile;
oldfile = xstrdup (rcs);
sprintf (rcs, "%s/%s%s", repository, file, RCSEXT);
if (strcmp (oldfile, rcs) == 0)
{
error (0, 0, "internal error: confused about attic for %s",
oldfile);
out1:
free (oldfile);
retval = 1;
goto out;
}
if (CVS_RENAME (oldfile, rcs) != 0)
{
error (0, errno, "failed to move `%s' out of the attic",
oldfile);
goto out1;
}
if (isreadable (oldfile)
|| !isreadable (rcs))
{
error (0, 0, "\
internal error: `%s' didn't move out of the attic",
oldfile);
goto out1;
}
free (oldfile);
free (rcsfile->path);
rcsfile->path = xstrdup (rcs);
}
rev = RCS_getversion (rcsfile, tag, NULL, 1, (int *) NULL);
if (lock_RCS (file, rcsfile, rev, repository))
{
error (0, 0, "cannot lock `%s'.", rcs);
if (rev != NULL)
free (rev);
retval = 1;
goto out;
}
if (rev != NULL)
free (rev);
}
else
{
char *desc;
size_t descalloc;
size_t desclen;
char *opt;
desc = NULL;
descalloc = 0;
desclen = 0;
fname = xmalloc (strlen (file) + sizeof (CVSADM)
+ sizeof (CVSEXT_LOG) + 10);
(void) sprintf (fname, "%s/%s%s", CVSADM, file, CVSEXT_LOG);
if (isfile (fname))
get_file (fname, fname, "r", &desc, &descalloc, &desclen);
free (fname);
if (desclen > 0)
{
expand_string (&desc, &descalloc, desclen + 1);
desc[desclen++] = '\012';
}
if (options && options[0] == '-' && options[1] == 'k')
opt = options + 2;
else
opt = NULL;
cvs_output ("RCS file: ", 0);
cvs_output (rcs, 0);
cvs_output ("\ndone\n", 0);
if (add_rcs_file (NULL, rcs, file, NULL, opt,
NULL, NULL, 0, NULL,
desc, desclen, NULL) != 0)
{
retval = 1;
goto out;
}
rcsfile = RCS_parsercsfile (rcs);
newfile = 1;
if (desc != NULL)
free (desc);
}
if (tag && newfile)
{
char *tmp;
FILE *fp;
fname = xmalloc (strlen (file) + sizeof (CVSADM)
+ sizeof (CVSPREFIX) + 10);
(void) sprintf (fname, "%s/%s%s", CVSADM, CVSPREFIX, file);
rename_file (file, fname);
fp = fopen (file, "w");
if (fp == NULL)
error (1, errno, "cannot open %s for writing", file);
if (fclose (fp) < 0)
error (0, errno, "cannot close %s", file);
tmp = xmalloc (strlen (file) + strlen (tag) + 80);
(void) sprintf (tmp, "file %s was initially added on branch %s.",
file, tag);
retcode = RCS_checkin (rcsfile, NULL, tmp, NULL,
RCS_FLAGS_DEAD | RCS_FLAGS_QUIET);
free (tmp);
if (retcode != 0)
{
error (retcode == -1 ? 1 : 0, retcode == -1 ? errno : 0,
"could not create initial dead revision %s", rcs);
retval = 1;
goto out;
}
rename_file (fname, file);
free (fname);
freercsnode (&rcsfile);
rcsfile = RCS_parse (file, repository);
if (rcsfile == NULL)
{
error (0, 0, "could not read %s", rcs);
retval = 1;
goto out;
}
if (rcsnode != NULL)
{
assert (*rcsnode == NULL);
*rcsnode = rcsfile;
}
if (lock_RCS (file, rcsfile, NULL, repository))
{
error (0, 0, "cannot lock `%s'.", rcs);
retval = 1;
goto out;
}
}
if (tag != NULL)
{
if (rcsfile == NULL)
{
if (rcsnode != NULL && *rcsnode != NULL)
rcsfile = *rcsnode;
else
{
rcsfile = RCS_parse (file, repository);
if (rcsfile == NULL)
{
error (0, 0, "could not read %s", rcs);
retval = 1;
goto out;
}
}
}
if (!RCS_nodeisbranch (rcsfile, tag))
{
char *head;
char *magicrev;
head = RCS_getversion (rcsfile, NULL, NULL, 0, (int *) NULL);
magicrev = RCS_magicrev (rcsfile, head);
retcode = RCS_settag (rcsfile, tag, magicrev);
RCS_rewrite (rcsfile, NULL, NULL);
free (head);
free (magicrev);
if (retcode != 0)
{
error (retcode == -1 ? 1 : 0, retcode == -1 ? errno : 0,
"could not stub branch %s for %s", tag, rcs);
retval = 1;
goto out;
}
}
else
{
if (lock_RCS (file, rcsfile, NULL, repository))
{
error (0, 0, "cannot lock `%s'.", rcs);
retval = 1;
goto out;
}
}
if (rcsnode && *rcsnode != rcsfile)
{
freercsnode(rcsnode);
*rcsnode = rcsfile;
}
}
fileattr_newfile (file);
fix_rcs_modes (rcs, file);
retval = 0;
out:
free (rcs);
return retval;
}
static int
lock_RCS (user, rcs, rev, repository)
char *user;
RCSNode *rcs;
char *rev;
char *repository;
{
char *branch = NULL;
int err = 0;
if (rev == NULL || (rev && isdigit (*rev) && numdots (rev) < 2))
{
branch = xstrdup (rcs->branch);
if (branch != NULL)
{
if (RCS_setbranch (rcs, NULL) != 0)
{
error (0, 0, "cannot change branch to default for %s",
rcs->path);
if (branch)
free (branch);
return (1);
}
}
err = RCS_lock(rcs, NULL, 1);
}
else
{
(void) RCS_lock(rcs, rev, 1);
}
if (err == 0)
{
if (sbranch != NULL)
free (sbranch);
sbranch = branch;
return (0);
}
if (branch != NULL)
fixbranch (rcs, branch);
if (branch)
free (branch);
return (1);
}
static void
fix_rcs_modes (rcs, user)
char *rcs;
char *user;
{
struct stat sb;
mode_t rcs_mode;
#ifdef PRESERVE_PERMISSIONS_SUPPORT
if (preserve_perms && islink (user))
return;
#endif
if (CVS_STAT (user, &sb) < 0)
{
error (0, errno, "warning: cannot stat %s", user);
return;
}
rcs_mode = 0;
if (sb.st_mode & S_IRUSR)
rcs_mode |= S_IRUSR | S_IRGRP | S_IROTH;
if (sb.st_mode & S_IXUSR)
rcs_mode |= S_IXUSR | S_IXGRP | S_IXOTH;
rcs_mode &= ~cvsumask;
if (chmod (rcs, rcs_mode) < 0)
error (0, errno, "warning: cannot change mode of %s", rcs);
}
void
update_delproc (p)
Node *p;
{
struct logfile_info *li;
li = (struct logfile_info *) p->data;
if (li->tag)
free (li->tag);
if (li->rev_old)
free (li->rev_old);
if (li->rev_new)
free (li->rev_new);
free (li);
}
static void
ci_delproc (p)
Node *p;
{
struct commit_info *ci;
ci = (struct commit_info *) p->data;
if (ci->rev)
free (ci->rev);
if (ci->tag)
free (ci->tag);
if (ci->options)
free (ci->options);
free (ci);
}
static void
masterlist_delproc (p)
Node *p;
{
struct master_lists *ml;
ml = (struct master_lists *) p->data;
dellist (&ml->ulist);
dellist (&ml->cilist);
free (ml);
}
static char *
locate_rcs (file, repository)
char *file;
char *repository;
{
char *rcs;
rcs = xmalloc (strlen (repository) + strlen (file) + sizeof (RCSEXT) + 10);
(void) sprintf (rcs, "%s/%s%s", repository, file, RCSEXT);
if (!isreadable (rcs))
{
(void) sprintf (rcs, "%s/%s/%s%s", repository, CVSATTIC, file, RCSEXT);
if (!isreadable (rcs))
(void) sprintf (rcs, "%s/%s%s", repository, file, RCSEXT);
}
return rcs;
}