#include "cvs.h"
#include "getline.h"
#include "edit.h"
#include "fileattr.h"
#include "hardlink.h"
static Dtype check_direntproc (void *callerdat, const char *dir,
const char *repos, const char *update_dir,
List *entries);
static int check_fileproc (void *callerdat, struct file_info *finfo);
static int check_filesdoneproc (void *callerdat, int err, const char *repos,
const char *update_dir, List *entries);
static int checkaddfile (const char *file, const char *repository,
const char *tag, const char *options,
RCSNode **rcsnode);
static Dtype commit_direntproc (void *callerdat, const char *dir,
const char *repos, const char *update_dir,
List *entries);
static int commit_dirleaveproc (void *callerdat, const char *dir, int err,
const char *update_dir, List *entries);
static int commit_fileproc (void *callerdat, struct file_info *finfo);
static int commit_filesdoneproc (void *callerdat, int err,
const char *repository,
const char *update_dir, List *entries);
static int finaladd (struct file_info *finfo, char *revision, char *tag,
char *options);
static int findmaxrev (Node * p, void *closure);
static int lock_RCS (const char *user, RCSNode *rcs, const char *rev,
const char *repository);
static int precommit_list_to_args_proc (Node * p, void *closure);
static int precommit_proc (const char *repository, const char *filter,
void *closure);
static int remove_file (struct file_info *finfo, char *tag,
char *message);
static void fixaddfile (const char *rcs);
static void fixbranch (RCSNode *, char *branch);
static void unlockrcs (RCSNode *rcs);
static void ci_delproc (Node *p);
static void masterlist_delproc (Node *p);
struct commit_info
{
Ctype status;
char *rev;
char *tag;
char *options;
};
struct master_lists
{
List *ulist;
List *cilist;
};
static int check_valid_edit = 0;
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 char *saved_message;
static time_t last_register_time;
static const char *const commit_usage[] =
{
"Usage: %s %s [-cRlf] [-m msg | -F logfile] [-r rev] files...\n",
" -c Check for valid edits before committing.\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 (void *callerdat, const char *dir, const char *repository,
const char *update_dir, List *entries)
{
struct find_data *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 (const char *file, const char *dir)
{
struct question *p;
p = 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 (void *callerdat, int err, const char *repository,
const char *update_dir, List *entries)
{
struct find_data *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 (void *callerdat, struct file_info *finfo)
{
Vers_TS *vers;
enum classify_type status;
Node *node;
struct find_data *args = 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 = 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 (Node *node, void *data)
{
struct find_data *args = data;
args->argv[args->argc++] = node->key;
return 0;
}
#endif
#ifdef SERVER_SUPPORT
# define COMMIT_OPTIONS "+cnlRm:fF:r:"
#else
# define COMMIT_OPTIONS "+clRm:fF:r:"
#endif
int
commit (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 && !current_parsed_root->isremote)
{
struct passwd *pw;
if ((pw = 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)
{
case 'c':
check_valid_edit = 1;
break;
#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;
check_valid_edit = 0;
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, NULL,
&find_args, argc, argv, local, W_LOCAL, 0, CVS_LOCK_NONE,
NULL, 0, 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 = xnmalloc (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, 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_a_repository (p->dir, p->repos, p->dir);
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 (check_valid_edit)
send_arg ("-c");
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, "", false);
if (argc <= 0)
write_dirtag = saved_tag;
wrap_setup ();
lock_tree_promotably (argc, argv, local, W_LOCAL, aflag);
mulist = getlist ();
#ifdef PRESERVE_PERMISSIONS_SUPPORT
if (preserve_perms)
{
hardlist = getlist ();
working_dir = xgetcwd ();
}
#endif
err = start_recursion (check_fileproc, check_filesdoneproc,
check_direntproc, NULL, NULL, argc, argv, local,
W_LOCAL, aflag, CVS_LOCK_NONE, NULL, 1, NULL);
if (err)
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_WRITE, NULL, 1, NULL);
Lock_Cleanup ();
dellist (&mulist);
if (!server_active && last_register_time)
{
sleep_past (last_register_time);
}
return err;
}
static
Ctype
classify_file_internal (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, NULL, NULL,
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, NULL,
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, NULL,
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, NULL,
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, NULL, NULL, 1, 0, vers, 0);
noexec = save_noexec;
quiet = save_quiet;
really_quiet = save_really_quiet;
return status;
}
static int
check_fileproc (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;
int retval = 1;
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
&& ISSLASH (finfo->repository[cvsroot_len])
&& strncmp (finfo->repository + cvsroot_len + 1,
CVSROOTADM,
sizeof (CVSROOTADM) - 1) == 0
&& ISSLASH (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_REMOVE_ENTRY:
error (0, 0, "Up-to-date check failed for `%s'", finfo->fullname);
goto out;
case T_CONFLICT:
case T_MODIFIED:
case T_ADDED:
case T_REMOVED:
{
char *editor;
if (!saved_tag || !isdigit ((unsigned char) *saved_tag))
{
if (vers->date)
{
error (0, 0,
"cannot commit with sticky date for file `%s'",
finfo->fullname);
goto out;
}
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);
goto out;
}
}
if (status == T_CONFLICT && !force_ci)
{
error (0, 0,
"file `%s' had a conflict and has not been modified",
finfo->fullname);
goto out;
}
if (status == T_MODIFIED && !force_ci && 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);
goto out;
}
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);
goto out;
}
}
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);
goto out;
}
}
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);
goto out;
}
}
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;
ml = xmalloc (sizeof (struct master_lists));
ulist = ml->ulist = getlist ();
cilist = ml->cilist = getlist ();
p = getnode ();
p->key = xstrdup (xdir);
p->type = UPDATE;
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 = xmalloc (sizeof (struct logfile_info));
li->type = status;
if (check_valid_edit)
{
char *editors = NULL;
editor = NULL;
editors = fileattr_get0 (finfo->file, "_editors");
if (editors != NULL)
{
char *caller = getcaller ();
char *p = NULL;
char *p0 = NULL;
p = editors;
p0 = p;
while (*p != '\0')
{
p = strchr (p, '>');
if (p == NULL)
{
break;
}
*p = '\0';
if (strcmp (caller, p0) == 0)
{
break;
}
p = strchr (p + 1, ',');
if (p == NULL)
{
break;
}
++p;
p0 = p;
}
if (strcmp (caller, p0) == 0)
{
editor = caller;
}
free (editors);
}
}
if (check_valid_edit && editor == NULL)
{
error (0, 0, "Valid edit does not exist for %s",
finfo->fullname);
freevers_ts (&vers);
return 1;
}
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 = 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 = 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 = Xasprintf ("%s/%s", working_dir, finfo->fullname);
linkp = lookup_file_by_inode (fullpath);
if (linkp != NULL)
{
hlinfo = 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);
goto out;
case T_UPTODATE:
break;
default:
error (0, 0, "CVS internal error: unknown status %d", status);
break;
}
retval = 0;
out:
freevers_ts (&vers);
return retval;
}
static Dtype
check_direntproc (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_to_args_proc (p, closure)
Node *p;
void *closure;
{
struct format_cmdline_walklist_closure *c = closure;
struct logfile_info *li;
char *arg = NULL;
const char *f;
char *d;
size_t doff;
if (p->data == NULL) return 1;
f = c->format;
d = *c->d;
while (*f)
{
switch (*f++)
{
case 's':
li = p->data;
if (li->type == T_ADDED
|| li->type == T_MODIFIED
|| li->type == T_REMOVED)
{
arg = p->key;
}
break;
default:
error (1, 0,
"Unknown format character or not a list attribute: %c",
f[-1]);
break;
}
if (c->quotes)
{
arg = cmdlineescape (c->quotes, arg);
}
else
{
arg = cmdlinequote ('"', arg);
}
doff = d - *c->buf;
expand_string (c->buf, c->length, doff + strlen (arg));
d = *c->buf + doff;
strncpy (d, arg, strlen (arg));
d += strlen (arg);
free (arg);
doff = d - *c->buf;
expand_string (c->buf, c->length, doff + 1);
d = *c->buf + doff;
*d++ = ' ';
}
*c->d = d;
return 0;
}
static int
precommit_proc (const char *repository, const char *filter, void *closure)
{
char *newfilter = NULL;
char *cmdline;
const char *srepos = Short_Repository (repository);
List *ulist = closure;
#ifdef SUPPORT_OLD_INFO_FMT_STRINGS
if (!strchr (filter, '%'))
{
error (0, 0,
"warning: commitinfo line contains no format strings:\n"
" \"%s\"\n"
"Appending defaults (\" %%r/%%p %%s\"), but please be aware that this usage is\n"
"deprecated.", filter);
newfilter = Xasprintf ("%s %%r/%%p %%s", filter);
filter = newfilter;
}
#endif
cmdline = format_cmdline (
#ifdef SUPPORT_OLD_INFO_FMT_STRINGS
false, srepos,
#endif
filter,
"c", "s", cvs_cmd_name,
#ifdef SERVER_SUPPORT
"R", "s", referrer ? referrer->original : "NONE",
#endif
"p", "s", srepos,
"r", "s", current_parsed_root->directory,
"s", ",", ulist, precommit_list_to_args_proc,
(void *) NULL,
(char *) NULL);
if (newfilter) free (newfilter);
if (!cmdline || !strlen (cmdline))
{
if (cmdline) free (cmdline);
error (0, 0, "precommit proc resolved to the empty string!");
return 1;
}
run_setup (cmdline);
free (cmdline);
return run_exec (RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL | RUN_REALLY);
}
static int
check_filesdoneproc (void *callerdat, int err, const char *repos,
const char *update_dir, List *entries)
{
int n;
Node *p;
List *saved_ulist;
p = findnode (mulist, update_dir);
if (p != NULL)
saved_ulist = ((struct master_lists *) p->data)->ulist;
else
saved_ulist = NULL;
if (saved_ulist == NULL || saved_ulist->list->next == saved_ulist->list)
return err;
n = Parse_Info (CVSROOTADM_COMMITINFO, repos, precommit_proc, PIOPT_ALL,
saved_ulist);
if (n > 0)
{
error (0, 0, "Pre-commit check failed");
err += n;
}
return err;
}
static int maxrev;
static char *sbranch;
static int
commit_fileproc (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 (!server_active && use_editor)
do_editor (finfo->update_dir, &saved_message,
finfo->repository, ulist);
do_verify (&saved_message, finfo->repository, ulist);
}
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 = 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 = Xasprintf ("%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,
NULL,
NULL);
}
#endif
}
notify_do ('C', finfo->file, finfo->update_dir, 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 (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;
{
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';
if (!really_quiet)
{
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);
WriteTemplate (".", 1, repository);
}
}
Update_Logfile (repository, saved_message, NULL, ulist);
return err;
}
static Dtype
commit_direntproc (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 = NULL;
if (ulist == NULL || ulist->list->next == ulist->list)
return R_SKIP_FILES;
got_message = 1;
real_repos = Name_Repository (dir, update_dir);
if (!server_active && use_editor)
do_editor (update_dir, &saved_message, real_repos, ulist);
do_verify (&saved_message, real_repos, ulist);
free (real_repos);
return R_PROCESS;
}
static int
commit_dirleaveproc (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 (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 (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;
}
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, 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,
NULL, NULL, RUN_TTY, NULL, 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, NULL, 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);
if (!really_quiet)
{
cvs_output (old_path, 0);
cvs_output (" <-- ", 0);
if (finfo->update_dir && strlen (finfo->update_dir))
{
cvs_output (finfo->update_dir, 0);
cvs_output ("/", 1);
}
cvs_output (finfo->file, 0);
cvs_output ("\nnew revision: delete; previous revision: ", 0);
cvs_output (prev_rev, 0);
cvs_output ("\n", 0);
}
free (prev_rev);
free (old_path);
Scratch_Entry (finfo->entries, finfo->file);
return 0;
}
static int
finaladd (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 = Xasprintf ("%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 (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 (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 (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 (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 = Xasprintf ("%s/%s%s", repository, file, RCSEXT);
fname = Xasprintf ("%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;
if (add_rcs_file (NULL, rcsname, file, NULL, opt,
NULL, NULL, 0, NULL,
desc, desclen, NULL, 0) != 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, NULL);
if (lock_RCS (file, rcs, rev, repository))
{
error (0, 0, "cannot lock revision %s in `%s'.",
rev ? rev : tag ? tag : "HEAD", 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 = Xasprintf ("%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 = Xasprintf ("file %s was initially added on branch %s.",
file, tag);
retcode = RCS_checkin (rcs, NULL, 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 initial revision in `%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, NULL);
if (!head)
error (1, 0, "No head revision in archive file `%s'.",
rcs->print_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 = Xasprintf ("%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 = Xasprintf ("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, 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 head revision in `%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 (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 (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 (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 (Node *p)
{
struct master_lists *ml = p->data;
dellist (&ml->ulist);
dellist (&ml->cilist);
free (ml);
}