#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, const char *dir,
const char *repos,
const 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,
const char *repos,
const char *update_dir,
List *entries));
static int checkaddfile PROTO((const char *file, const char *repository,
const char *tag, const char *options,
RCSNode **rcsnode));
static Dtype commit_direntproc PROTO ((void *callerdat, const char *dir,
const char *repos,
const char *update_dir,
List *entries));
static int commit_dirleaveproc PROTO ((void *callerdat, const char *dir,
int err, const 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,
const char *repository,
const 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((const char *user, RCSNode *rcs, const char *rev,
const char *repository));
static int precommit_list_proc PROTO((Node * p, void *closure));
static int precommit_proc PROTO((const char *repository, const char *filter));
static int remove_file PROTO ((struct file_info *finfo, char *tag,
char *message));
static void fixaddfile PROTO((const char *rcs));
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));
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 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 [-Rlf] [-m msg | -F logfile] [-r rev] files...\n",
" -R Process directories recursively.\n",
" -l Local directory only (not recursive).\n",
" -f Force the file to be committed; disables recursion.\n",
" -F logfile Read the log message from file.\n",
" -m msg Log message.\n",
" -r rev Commit 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;
const char *repository;
int force;
};
static Dtype find_dirent_proc PROTO ((void *callerdat, const char *dir,
const char *repository,
const char *update_dir,
List *entries));
static Dtype
find_dirent_proc (callerdat, dir, repository, update_dir, entries)
void *callerdat;
const char *dir;
const char *repository;
const 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 ((const char *, const char *));
static void
find_ignproc (file, dir)
const char *file;
const 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,
const char *repository,
const char *update_dir,
List *entries));
static int
find_filesdoneproc (callerdat, err, repository, update_dir, entries)
void *callerdat;
int err;
const char *repository;
const 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->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);
freevers_ts (&vers);
return 1;
}
if (vers->vn_user[0] == '-')
{
if (vers->ts_user != NULL)
{
error (0, 0,
"`%s' should be removed and is still there (or is back"
" again)", finfo->fullname);
freevers_ts (&vers);
return 1;
}
status = T_REMOVED;
}
else if (strcmp (vers->vn_user, "0") == 0)
{
if (vers->ts_user == NULL)
{
if (!really_quiet)
error (0, 0, "warning: new-born %s has disappeared",
finfo->fullname);
status = T_REMOVE_ENTRY;
}
else
status = T_ADDED;
}
else if (vers->ts_user == NULL)
{
freevers_ts (&vers);
return 0;
}
else if (vers->ts_rcs != NULL
&& (args->force || strcmp (vers->ts_user, vers->ts_rcs) != 0))
status = T_MODIFIED;
else
{
freevers_ts (&vers);
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 = 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
#ifdef SERVER_SUPPORT
# define COMMIT_OPTIONS "+nlRm:fF:r:"
#else
# define COMMIT_OPTIONS "+lRm:fF:r:"
#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
# ifdef CLIENT_SUPPORT
&& !current_parsed_root->isremote
# endif
)
{
struct passwd *pw;
if ((pw = (struct passwd *) getpwnam (getcaller ())) == NULL)
error (1, 0,
"your apparent username (%s) is unknown to this system",
getcaller ());
if (pw->pw_uid == (uid_t) 0)
error (1, 0, "'root' is not allowed to commit files");
}
#endif
optind = 0;
while ((c = getopt (argc, argv, COMMIT_OPTIONS)) != -1)
{
switch (c)
{
#ifdef SERVER_SUPPORT
case 'n':
break;
#endif
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 ((unsigned char) *saved_tag))
{
char *p = saved_tag + strlen (saved_tag);
aflag = 1;
while (*--p == '.') ;
p[1] = '\0';
while (saved_tag[0] == '0' && isdigit ((unsigned char) saved_tag[1]))
++saved_tag;
}
if (logfile)
{
size_t size = 0, len;
if (saved_message)
error (1, 0, "cannot specify both a message and a log file");
get_file (logfile, logfile, "r", &saved_message, &size, &len);
}
#ifdef CLIENT_SUPPORT
if (current_parsed_root->isremote)
{
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, CVS_LOCK_NONE,
(char *) NULL, 0, (char *) NULL);
if (err)
error (1, 0, "correct above errors first!");
if (find_args.argc == 0)
{
dellist (&find_args.ulist);
return 0;
}
if (size_overflow_p (xtimes (find_args.argc, sizeof (char **))))
{
find_args.argc = 0;
return 0;
}
find_args.argv = xmalloc (xtimes (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);
option_with_arg ("-m", saved_message ? 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");
option_with_arg ("-r", saved_tag);
send_arg ("--");
send_files (find_args.argc, find_args.argv, local, 0,
find_args.force ? SEND_FORCE : 0);
send_file_names (find_args.argc, find_args.argv, 0);
free (find_args.argv);
dellist (&find_args.ulist);
send_to_server ("ci\012", 0);
err = get_responses_and_close ();
if (err != 0 && use_editor && saved_message != NULL)
{
char *fname;
FILE *fp;
fp = cvs_temp_file (&fname);
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);
free (fname);
}
return err;
}
#endif
if (saved_tag != NULL)
tag_check_valid (saved_tag, argc, argv, local, aflag, "");
if (argc <= 0)
write_dirtag = saved_tag;
wrap_setup ();
lock_tree_for_write (argc, argv, local, W_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, CVS_LOCK_NONE,
(char *) NULL, 1, (char *) NULL);
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, CVS_LOCK_NONE,
(char *) NULL, 1, (char *) NULL);
Lock_Cleanup ();
dellist (&mulist);
#ifdef SERVER_SUPPORT
if (server_active)
return err;
#endif
if (last_register_time)
{
sleep_past (last_register_time);
}
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 ((unsigned char) *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;
const char *xdir;
Node *p;
List *ulist, *cilist;
Vers_TS *vers;
struct commit_info *ci;
struct logfile_info *li;
size_t cvsroot_len = strlen (current_parsed_root->directory);
if (!finfo->repository)
{
error (0, 0, "nothing known about `%s'", finfo->fullname);
return 1;
}
if (strncmp (finfo->repository, current_parsed_root->directory,
cvsroot_len) == 0
&& ISDIRSEP (finfo->repository[cvsroot_len])
&& strncmp (finfo->repository + cvsroot_len + 1,
CVSROOTADM,
sizeof (CVSROOTADM) - 1) == 0
&& ISDIRSEP (finfo->repository[cvsroot_len + sizeof (CVSROOTADM)])
&& strcmp (finfo->repository + cvsroot_len + sizeof (CVSROOTADM) + 1,
CVSNULLREPOS) == 0
)
error (1, 0, "cannot check in to %s", finfo->repository);
status = classify_file_internal (finfo, &vers);
if (force_ci && status == T_UPTODATE)
status = T_MODIFIED;
switch (status)
{
case T_CHECKOUT:
case T_PATCH:
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 ((unsigned char) *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)
{
if ( file_has_conflict ( finfo, vers->ts_conflict ) )
{
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)
{
if (vers->ts_user != NULL)
{
error (0, 0,
"`%s' should be removed and is still there (or is"
" back again)", finfo->fullname);
freevers_ts (&vers);
return 1;
}
if (vers->tag && isdigit ((unsigned char) *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)
{
if (finfo->rcs != NULL &&
!RCS_isdead (finfo->rcs, finfo->rcs->head))
{
error (0, 0,
"cannot add file `%s' when RCS file `%s' already exists",
finfo->fullname, finfo->rcs->path);
freevers_ts (&vers);
return 1;
}
}
else if (isdigit ((unsigned char) *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 = 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 = 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 ((unsigned char) *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 = 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 = 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;
const char *dir;
const char *repos;
const 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 = 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)
const char *repository;
const char *filter;
{
if (isabsolute (filter))
{
char *s, *cp;
s = xstrdup (filter);
for (cp = s; *cp; cp++)
if (isspace ((unsigned char) *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;
const char *repos;
const 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 (
#ifdef SERVER_SUPPORT
!server_active &&
#endif
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 = 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)
{
if (finfo->rcs != NULL)
fixaddfile (finfo->rcs->path);
err = 1;
goto out;
}
if (ci->tag
&& !isdigit ((unsigned char) ci->tag[0]))
{
if (finfo->rcs == NULL)
error (1, 0, "internal error: no parsed RCS file");
if (ci->rev)
free (ci->rev);
ci->rev = RCS_whatbranch (finfo->rcs, ci->tag);
err = Checkin ('A', finfo, 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 (finfo->rcs->head) {
int thisrev = atoi (finfo->rcs->head);
if (thisrev > maxrev)
maxrev = thisrev;
}
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, 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 = p->data;
li->rev_new = xstrdup (vers->vn_rcs);
freevers_ts (&vers);
}
}
}
if (SIG_inCrSect ())
SIG_endCrSect ();
return err;
}
static int
commit_filesdoneproc (callerdat, err, repository, update_dir, entries)
void *callerdat;
int err;
const char *repository;
const char *update_dir;
List *entries;
{
Node *p;
List *ulist;
assert (repository);
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);
{
const char *p;
if (strncmp (current_parsed_root->directory, repository,
strlen (current_parsed_root->directory)) != 0)
error (0, 0,
"internal error: repository (%s) doesn't begin with root (%s)",
repository, current_parsed_root->directory);
p = repository + strlen (current_parsed_root->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 (cvs_cmd_name, 0);
cvs_output (": Rebuilding administrative file database\n", 0);
mkmodules (admin_dir);
free (admin_dir);
}
}
return err;
}
static Dtype
commit_direntproc (callerdat, dir, repos, update_dir, entries)
void *callerdat;
const char *dir;
const char *repos;
const 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 (
#ifdef SERVER_SUPPORT
!server_active &&
#endif
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;
const char *dir;
int err;
const char *update_dir;
List *entries;
{
if (err == 0 && write_dirtag != NULL)
{
char *repos = Name_Repository (NULL, update_dir);
WriteTag (NULL, write_dirtag, NULL, write_dirnonbranch,
update_dir, repos);
free (repos);
}
return err;
}
static int
findmaxrev (p, closure)
Node *p;
void *closure;
{
int thisrev;
Entnode *entdata = p->data;
if (entdata->type != ENT_FILE)
return 0;
thisrev = atoi (entdata->version);
if (thisrev > maxrev)
maxrev = thisrev;
return 0;
}
static int
remove_file (finfo, tag, message)
struct file_info *finfo;
char *tag;
char *message;
{
int retcode;
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 (corev);
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);
}
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, 0,
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;
}
history_write ('R', NULL, finfo->rcs->head, finfo->file, finfo->repository);
if (rev != NULL)
free (rev);
old_path = xstrdup (finfo->rcs->path);
if (!branch)
RCS_setattic (finfo->rcs, 1);
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);
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;
ret = Checkin ('A', finfo, 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);
if (unlink_file (tmp) < 0
&& !existence_error (errno))
error (0, errno, "cannot remove %s", tmp);
free (tmp);
}
else if (finfo->rcs != NULL)
fixaddfile (finfo->rcs->path);
(void) time (&last_register_time);
return ret;
}
static void
unlockrcs (rcs)
RCSNode *rcs;
{
int retcode;
if ((retcode = RCS_unlock (rcs, NULL, 1)) != 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 (rcs)
const char *rcs;
{
RCSNode *rcsfile;
int save_really_quiet;
save_really_quiet = really_quiet;
really_quiet = 1;
if ((rcsfile = RCS_parsercsfile (rcs)) == NULL)
{
if (unlink_file (rcs) < 0)
error (0, errno, "cannot remove %s", rcs);
}
else
freercsnode (&rcsfile);
really_quiet = save_really_quiet;
}
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)
const char *file;
const char *repository;
const char *tag;
const char *options;
RCSNode **rcsnode;
{
RCSNode *rcs;
char *fname;
int newfile = 0;
int retval = 1;
int adding_on_branch;
assert (rcsnode != NULL);
if (options != NULL && options[0] == '\0')
options = NULL;
if (options != NULL)
assert (options[0] == '-' && options[1] == 'k');
adding_on_branch = tag != NULL && !isdigit ((unsigned char) tag[0]);
if (*rcsnode == NULL)
{
char *rcsname;
char *desc = NULL;
size_t descalloc = 0;
size_t desclen = 0;
const char *opt;
if ( adding_on_branch )
{
mode_t omask;
rcsname = xmalloc (strlen (repository)
+ sizeof (CVSATTIC)
+ strlen (file)
+ sizeof (RCSEXT)
+ 3);
(void) sprintf (rcsname, "%s/%s", repository, CVSATTIC);
omask = umask ( cvsumask );
if (CVS_MKDIR (rcsname, 0777 ) != 0 && errno != EEXIST)
error (1, errno, "cannot make directory `%s'", rcsname);
(void) umask ( omask );
(void) sprintf (rcsname,
"%s/%s/%s%s",
repository,
CVSATTIC,
file,
RCSEXT);
}
else
{
rcsname = xmalloc (strlen (repository)
+ strlen (file)
+ sizeof (RCSEXT)
+ 2);
(void) sprintf (rcsname,
"%s/%s%s",
repository,
file,
RCSEXT);
}
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 != NULL)
opt = options + 2;
else
opt = NULL;
cvs_output ("RCS file: ", 0);
cvs_output (rcsname, 0);
cvs_output ("\ndone\n", 0);
if (add_rcs_file (NULL, rcsname, file, NULL, opt,
NULL, NULL, 0, NULL,
desc, desclen, NULL) != 0)
{
if (rcsname != NULL)
free (rcsname);
goto out;
}
rcs = RCS_parsercsfile (rcsname);
newfile = 1;
if (rcsname != NULL)
free (rcsname);
if (desc != NULL)
free (desc);
*rcsnode = rcs;
}
else
{
char *rev;
char *oldexpand;
rcs = *rcsnode;
oldexpand = RCS_getexpand (rcs);
if ((oldexpand != NULL
&& options != NULL
&& strcmp (options + 2, oldexpand) != 0)
|| (oldexpand == NULL && options != NULL))
{
error (0, 0, "changing keyword expansion mode to %s", options);
RCS_setexpand (rcs, options + 2);
}
if (!adding_on_branch)
{
if (!(rcs->flags & INATTIC))
{
error (0, 0, "warning: expected %s to be in Attic",
rcs->path);
}
SIG_beginCrSect ();
if (RCS_setattic (rcs, 0))
{
goto out;
}
}
rev = RCS_getversion (rcs, tag, NULL, 1, (int *) NULL);
if (lock_RCS (file, rcs, rev, repository))
{
error (0, 0, "cannot lock `%s'.", rcs->path);
if (rev != NULL)
free (rev);
goto out;
}
if (rev != NULL)
free (rev);
}
if (adding_on_branch)
{
if (newfile)
{
char *tmp;
FILE *fp;
int retcode;
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 (rcs, NULL, tmp, NULL, 0,
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->path);
free (fname);
goto out;
}
rename_file (fname, file);
free (fname);
freercsnode (&rcs);
rcs = RCS_parse (file, repository);
if (rcs == NULL)
{
error (0, 0, "could not read %s", rcs->path);
goto out;
}
*rcsnode = rcs;
if (lock_RCS (file, rcs, NULL, repository))
{
error (0, 0, "cannot lock `%s'.", rcs->path);
goto out;
}
}
if (!RCS_nodeisbranch (rcs, tag))
{
char *head;
char *magicrev;
int retcode;
time_t headtime = -1;
char *revnum, *tmp;
FILE *fp;
time_t t = -1;
struct tm *ct;
fixbranch (rcs, sbranch);
head = RCS_getversion (rcs, NULL, NULL, 0, (int *) NULL);
if (!head)
error (1, 0, "No head revision in archive file `%s'.",
rcs->path);
magicrev = RCS_magicrev (rcs, head);
if (!newfile)
headtime = RCS_getrevtime (rcs, head, 0, 0);
retcode = RCS_settag (rcs, tag, magicrev);
RCS_rewrite (rcs, 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->path);
goto out;
}
if (!newfile && headtime != -1)
{
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);
t = time(NULL);
ct = gmtime(&t);
tmp = xmalloc (strlen (file) + strlen (tag) + 80);
(void) sprintf (tmp,
"file %s was added on branch %s on %d-%02d-%02d %02d:%02d:%02d +0000",
file, tag,
ct->tm_year + (ct->tm_year < 100 ? 0 : 1900),
ct->tm_mon + 1, ct->tm_mday,
ct->tm_hour, ct->tm_min, ct->tm_sec);
revnum = RCS_whatbranch (rcs, tag);
retcode = RCS_checkin (rcs, NULL, tmp, revnum, headtime,
RCS_FLAGS_DEAD |
RCS_FLAGS_QUIET |
RCS_FLAGS_USETIME);
free (revnum);
free (tmp);
if (retcode != 0)
{
error (retcode == -1 ? 1 : 0, retcode == -1 ? errno : 0,
"could not created dead stub %s for %s", tag,
rcs->path);
goto out;
}
rename_file (fname, file);
free (fname);
freercsnode (&rcs);
rcs = RCS_parse (file, repository);
if (rcs == NULL)
{
error (0, 0, "could not read %s", rcs->path);
goto out;
}
*rcsnode = rcs;
}
}
else
{
if (lock_RCS (file, rcs, NULL, repository))
{
error (0, 0, "cannot lock `%s'.", rcs->path);
goto out;
}
}
if (*rcsnode != rcs)
{
freercsnode(rcsnode);
*rcsnode = rcs;
}
}
fileattr_newfile (file);
retval = 0;
out:
if (retval != 0 && SIG_inCrSect ())
SIG_endCrSect ();
return retval;
}
static int
lock_RCS (user, rcs, rev, repository)
const char *user;
RCSNode *rcs;
const char *rev;
const char *repository;
{
char *branch = NULL;
int err = 0;
if (rev == NULL
|| (rev && isdigit ((unsigned char) *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
{
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;
}
void
update_delproc (p)
Node *p;
{
struct logfile_info *li = 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 = 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 = p->data;
dellist (&ml->ulist);
dellist (&ml->cilist);
free (ml);
}