xref: /openbsd-src/gnu/usr.bin/cvs/src/commit.c (revision f9bbbf4534171e957bb7652e706dd78782fbffa2)
11e72d8d2Sderaadt /*
21e72d8d2Sderaadt  * Copyright (c) 1992, Brian Berliner and Jeff Polk
31e72d8d2Sderaadt  * Copyright (c) 1989-1992, Brian Berliner
41e72d8d2Sderaadt  *
51e72d8d2Sderaadt  * You may distribute under the terms of the GNU General Public License as
6b2b690deStholo  * specified in the README file that comes with the CVS source distribution.
71e72d8d2Sderaadt  *
81e72d8d2Sderaadt  * Commit Files
91e72d8d2Sderaadt  *
101e72d8d2Sderaadt  * "commit" commits the present version to the RCS repository, AFTER
111e72d8d2Sderaadt  * having done a test on conflicts.
121e72d8d2Sderaadt  *
131e72d8d2Sderaadt  * The call is: cvs commit [options] files...
141e72d8d2Sderaadt  *
151e72d8d2Sderaadt  */
161e72d8d2Sderaadt 
177e4d5a85Stholo #include <assert.h>
181e72d8d2Sderaadt #include "cvs.h"
19e5b68881Stholo #include "getline.h"
20e5b68881Stholo #include "edit.h"
21e5b68881Stholo #include "fileattr.h"
2278c87a5cStholo #include "hardlink.h"
231e72d8d2Sderaadt 
247e4d5a85Stholo static Dtype check_direntproc PROTO ((void *callerdat, char *dir,
257e4d5a85Stholo 				      char *repos, char *update_dir,
267e4d5a85Stholo 				      List *entries));
277e4d5a85Stholo static int check_fileproc PROTO ((void *callerdat, struct file_info *finfo));
287e4d5a85Stholo static int check_filesdoneproc PROTO ((void *callerdat, int err,
297e4d5a85Stholo 				       char *repos, char *update_dir,
307e4d5a85Stholo 				       List *entries));
311e72d8d2Sderaadt static int checkaddfile PROTO((char *file, char *repository, char *tag,
329d9224ffStholo 			       char *options, RCSNode **rcsnode));
337e4d5a85Stholo static Dtype commit_direntproc PROTO ((void *callerdat, char *dir,
347e4d5a85Stholo 				       char *repos, char *update_dir,
357e4d5a85Stholo 				       List *entries));
367e4d5a85Stholo static int commit_dirleaveproc PROTO ((void *callerdat, char *dir,
377e4d5a85Stholo 				       int err, char *update_dir,
387e4d5a85Stholo 				       List *entries));
397e4d5a85Stholo static int commit_fileproc PROTO ((void *callerdat, struct file_info *finfo));
407e4d5a85Stholo static int commit_filesdoneproc PROTO ((void *callerdat, int err,
417e4d5a85Stholo 					char *repository, char *update_dir,
427e4d5a85Stholo 					List *entries));
437e4d5a85Stholo static int finaladd PROTO((struct file_info *finfo, char *revision, char *tag,
447e4d5a85Stholo 			   char *options));
451e72d8d2Sderaadt static int findmaxrev PROTO((Node * p, void *closure));
467e4d5a85Stholo static int lock_RCS PROTO((char *user, RCSNode *rcs, char *rev,
477e4d5a85Stholo 			   char *repository));
481e72d8d2Sderaadt static int precommit_list_proc PROTO((Node * p, void *closure));
491e72d8d2Sderaadt static int precommit_proc PROTO((char *repository, char *filter));
507e4d5a85Stholo static int remove_file PROTO ((struct file_info *finfo, char *tag,
517e4d5a85Stholo 			       char *message));
521e72d8d2Sderaadt static void fixaddfile PROTO((char *file, char *repository));
537e4d5a85Stholo static void fixbranch PROTO((RCSNode *, char *branch));
547e4d5a85Stholo static void unlockrcs PROTO((RCSNode *rcs));
551e72d8d2Sderaadt static void ci_delproc PROTO((Node *p));
561e72d8d2Sderaadt static void masterlist_delproc PROTO((Node *p));
57ae5e8a9bStholo static char *locate_rcs PROTO((char *file, char *repository));
581e72d8d2Sderaadt 
591e72d8d2Sderaadt struct commit_info
601e72d8d2Sderaadt {
611e72d8d2Sderaadt     Ctype status;			/* as returned from Classify_File() */
621e72d8d2Sderaadt     char *rev;				/* a numeric rev, if we know it */
631e72d8d2Sderaadt     char *tag;				/* any sticky tag, or -r option */
641e72d8d2Sderaadt     char *options;			/* Any sticky -k option */
651e72d8d2Sderaadt };
661e72d8d2Sderaadt struct master_lists
671e72d8d2Sderaadt {
681e72d8d2Sderaadt     List *ulist;			/* list for Update_Logfile */
691e72d8d2Sderaadt     List *cilist;			/* list with commit_info structs */
701e72d8d2Sderaadt };
711e72d8d2Sderaadt 
721e72d8d2Sderaadt static int force_ci = 0;
731e72d8d2Sderaadt static int got_message;
741e72d8d2Sderaadt static int run_module_prog = 1;
751e72d8d2Sderaadt static int aflag;
762e5b458aStholo static char *saved_tag;
771e72d8d2Sderaadt static char *write_dirtag;
78172be96aStholo static int write_dirnonbranch;
791e72d8d2Sderaadt static char *logfile;
801e72d8d2Sderaadt static List *mulist;
812e5b458aStholo static List *saved_ulist;
822e5b458aStholo static char *saved_message;
8345381119Stholo static time_t last_register_time;
8445381119Stholo 
851e72d8d2Sderaadt static const char *const commit_usage[] =
861e72d8d2Sderaadt {
871e72d8d2Sderaadt     "Usage: %s %s [-nRlf] [-m msg | -F logfile] [-r rev] files...\n",
8879822b2aStholo     "    -n          Do not run the module program (if any).\n",
8979822b2aStholo     "    -R          Process directories recursively.\n",
9079822b2aStholo     "    -l          Local directory only (not recursive).\n",
9179822b2aStholo     "    -f          Force the file to be committed; disables recursion.\n",
9279822b2aStholo     "    -F logfile  Read the log message from file.\n",
9379822b2aStholo     "    -m msg      Log message.\n",
9479822b2aStholo     "    -r rev      Commit to this branch or trunk revision.\n",
95b2b690deStholo     "(Specify the --help global option for a list of other help options)\n",
961e72d8d2Sderaadt     NULL
971e72d8d2Sderaadt };
981e72d8d2Sderaadt 
99e5b68881Stholo #ifdef CLIENT_SUPPORT
1007e4d5a85Stholo /* Identify a file which needs "? foo" or a Questionable request.  */
1017e4d5a85Stholo struct question {
1027e4d5a85Stholo     /* The two fields for the Directory request.  */
1037e4d5a85Stholo     char *dir;
1047e4d5a85Stholo     char *repos;
1057e4d5a85Stholo 
1067e4d5a85Stholo     /* The file name.  */
1077e4d5a85Stholo     char *file;
1087e4d5a85Stholo 
1097e4d5a85Stholo     struct question *next;
1107e4d5a85Stholo };
1117e4d5a85Stholo 
112e5b68881Stholo struct find_data {
113e5b68881Stholo     List *ulist;
114e5b68881Stholo     int argc;
115e5b68881Stholo     char **argv;
1167e4d5a85Stholo 
1177e4d5a85Stholo     /* This is used from dirent to filesdone time, for each directory,
1187e4d5a85Stholo        to make a list of files we have already seen.  */
1197e4d5a85Stholo     List *ignlist;
1207e4d5a85Stholo 
1217e4d5a85Stholo     /* Linked list of files which need "? foo" or a Questionable request.  */
1227e4d5a85Stholo     struct question *questionables;
1237e4d5a85Stholo 
1247e4d5a85Stholo     /* Only good within functions called from the filesdoneproc.  Stores
1257e4d5a85Stholo        the repository (pointer into storage managed by the recursion
1267e4d5a85Stholo        processor.  */
1277e4d5a85Stholo     char *repository;
128172be96aStholo 
129172be96aStholo     /* Non-zero if we should force the commit.  This is enabled by
130172be96aStholo        either -f or -r options, unlike force_ci which is just -f.  */
131172be96aStholo     int force;
132e5b68881Stholo };
133e5b68881Stholo 
1347e4d5a85Stholo static Dtype find_dirent_proc PROTO ((void *callerdat, char *dir,
1357e4d5a85Stholo 				      char *repository, char *update_dir,
1367e4d5a85Stholo 				      List *entries));
137e5b68881Stholo 
1387e4d5a85Stholo static Dtype
find_dirent_proc(callerdat,dir,repository,update_dir,entries)1397e4d5a85Stholo find_dirent_proc (callerdat, dir, repository, update_dir, entries)
1407e4d5a85Stholo     void *callerdat;
1417e4d5a85Stholo     char *dir;
1427e4d5a85Stholo     char *repository;
1437e4d5a85Stholo     char *update_dir;
1447e4d5a85Stholo     List *entries;
1457e4d5a85Stholo {
1467e4d5a85Stholo     struct find_data *find_data = (struct find_data *)callerdat;
1477e4d5a85Stholo 
148b2b690deStholo     /* This check seems to slowly be creeping throughout CVS (update
149b2b690deStholo        and send_dirent_proc by CVS 1.5, diff in 31 Oct 1995.  My guess
150b2b690deStholo        is that it (or some variant thereof) should go in all the
151b2b690deStholo        dirent procs.  Unless someone has some better idea...  */
152b2b690deStholo     if (!isdir (dir))
153b2b690deStholo 	return (R_SKIP_ALL);
154b2b690deStholo 
1557e4d5a85Stholo     /* initialize the ignore list for this directory */
1567e4d5a85Stholo     find_data->ignlist = getlist ();
157b2b690deStholo 
158b2b690deStholo     /* Print the same warm fuzzy as in check_direntproc, since that
159b2b690deStholo        code will never be run during client/server operation and we
160b2b690deStholo        want the messages to match. */
161b2b690deStholo     if (!quiet)
162b2b690deStholo 	error (0, 0, "Examining %s", update_dir);
163b2b690deStholo 
1647e4d5a85Stholo     return R_PROCESS;
1657e4d5a85Stholo }
1667e4d5a85Stholo 
1677e4d5a85Stholo /* Here as a static until we get around to fixing ignore_files to pass
1687e4d5a85Stholo    it along as an argument.  */
1697e4d5a85Stholo static struct find_data *find_data_static;
1707e4d5a85Stholo 
1717e4d5a85Stholo static void find_ignproc PROTO ((char *, char *));
1727e4d5a85Stholo 
1737e4d5a85Stholo static void
find_ignproc(file,dir)1747e4d5a85Stholo find_ignproc (file, dir)
1757e4d5a85Stholo     char *file;
1767e4d5a85Stholo     char *dir;
1777e4d5a85Stholo {
1787e4d5a85Stholo     struct question *p;
1797e4d5a85Stholo 
1807e4d5a85Stholo     p = (struct question *) xmalloc (sizeof (struct question));
1817e4d5a85Stholo     p->dir = xstrdup (dir);
1827e4d5a85Stholo     p->repos = xstrdup (find_data_static->repository);
1837e4d5a85Stholo     p->file = xstrdup (file);
1847e4d5a85Stholo     p->next = find_data_static->questionables;
1857e4d5a85Stholo     find_data_static->questionables = p;
1867e4d5a85Stholo }
1877e4d5a85Stholo 
1887e4d5a85Stholo static int find_filesdoneproc PROTO ((void *callerdat, int err,
1897e4d5a85Stholo 				      char *repository, char *update_dir,
1907e4d5a85Stholo 				      List *entries));
1917e4d5a85Stholo 
1927e4d5a85Stholo static int
find_filesdoneproc(callerdat,err,repository,update_dir,entries)1937e4d5a85Stholo find_filesdoneproc (callerdat, err, repository, update_dir, entries)
1947e4d5a85Stholo     void *callerdat;
1957e4d5a85Stholo     int err;
1967e4d5a85Stholo     char *repository;
1977e4d5a85Stholo     char *update_dir;
1987e4d5a85Stholo     List *entries;
1997e4d5a85Stholo {
2007e4d5a85Stholo     struct find_data *find_data = (struct find_data *)callerdat;
2017e4d5a85Stholo     find_data->repository = repository;
2027e4d5a85Stholo 
2037e4d5a85Stholo     /* if this directory has an ignore list, process it then free it */
2047e4d5a85Stholo     if (find_data->ignlist)
2057e4d5a85Stholo     {
2067e4d5a85Stholo 	find_data_static = find_data;
2077e4d5a85Stholo 	ignore_files (find_data->ignlist, entries, update_dir, find_ignproc);
2087e4d5a85Stholo 	dellist (&find_data->ignlist);
2097e4d5a85Stholo     }
2107e4d5a85Stholo 
2117e4d5a85Stholo     find_data->repository = NULL;
2127e4d5a85Stholo 
2137e4d5a85Stholo     return err;
2147e4d5a85Stholo }
2157e4d5a85Stholo 
2167e4d5a85Stholo static int find_fileproc PROTO ((void *callerdat, struct file_info *finfo));
217e5b68881Stholo 
218e5b68881Stholo /* Machinery to find out what is modified, added, and removed.  It is
219e5b68881Stholo    possible this should be broken out into a new client_classify function;
220e5b68881Stholo    merging it with classify_file is almost sure to be a mess, though,
221e5b68881Stholo    because classify_file has all kinds of repository processing.  */
222e5b68881Stholo static int
find_fileproc(callerdat,finfo)2237e4d5a85Stholo find_fileproc (callerdat, finfo)
2247e4d5a85Stholo     void *callerdat;
22545381119Stholo     struct file_info *finfo;
226e5b68881Stholo {
227e5b68881Stholo     Vers_TS *vers;
228e5b68881Stholo     enum classify_type status;
229e5b68881Stholo     Node *node;
2307e4d5a85Stholo     struct find_data *args = (struct find_data *)callerdat;
2317e4d5a85Stholo     struct logfile_info *data;
2327e4d5a85Stholo     struct file_info xfinfo;
233e5b68881Stholo 
2347e4d5a85Stholo     /* if this directory has an ignore list, add this file to it */
2357e4d5a85Stholo     if (args->ignlist)
2367e4d5a85Stholo     {
2377e4d5a85Stholo 	Node *p;
2387e4d5a85Stholo 
2397e4d5a85Stholo 	p = getnode ();
2407e4d5a85Stholo 	p->type = FILES;
2417e4d5a85Stholo 	p->key = xstrdup (finfo->file);
2427e4d5a85Stholo 	if (addnode (args->ignlist, p) != 0)
2437e4d5a85Stholo 	    freenode (p);
2447e4d5a85Stholo     }
2457e4d5a85Stholo 
2467e4d5a85Stholo     xfinfo = *finfo;
2477e4d5a85Stholo     xfinfo.repository = NULL;
2487e4d5a85Stholo     xfinfo.rcs = NULL;
2497e4d5a85Stholo 
2502e5b458aStholo     vers = Version_TS (&xfinfo, NULL, saved_tag, NULL, 0, 0);
251e5b68881Stholo     if (vers->ts_user == NULL
252e5b68881Stholo 	&& vers->vn_user != NULL
253e5b68881Stholo 	&& vers->vn_user[0] == '-')
2547e4d5a85Stholo 	/* FIXME: If vn_user is starts with "-" but ts_user is
2557e4d5a85Stholo 	   non-NULL, what classify_file does is print "%s should be
2567e4d5a85Stholo 	   removed and is still there".  I'm not sure what it does
2577e4d5a85Stholo 	   then.  We probably should do the same.  */
258e5b68881Stholo 	status = T_REMOVED;
2599d9224ffStholo     else if (vers->vn_user == NULL)
260e5b68881Stholo     {
2619d9224ffStholo 	if (vers->ts_user == NULL)
2629d9224ffStholo 	    error (0, 0, "nothing known about `%s'", finfo->fullname);
2639d9224ffStholo 	else
264b2b690deStholo 	    error (0, 0, "use `%s add' to create an entry for %s",
265b2b690deStholo 		   program_name, finfo->fullname);
266bde78045Stholo 	freevers_ts (&vers);
267e5b68881Stholo 	return 1;
268e5b68881Stholo     }
269e5b68881Stholo     else if (vers->ts_user != NULL
270e5b68881Stholo 	     && vers->vn_user != NULL
271e5b68881Stholo 	     && vers->vn_user[0] == '0')
2727e4d5a85Stholo 	/* FIXME: If vn_user is "0" but ts_user is NULL, what classify_file
2737e4d5a85Stholo 	   does is print "new-born %s has disappeared" and removes the entry.
2747e4d5a85Stholo 	   We probably should do the same.  */
275e5b68881Stholo 	status = T_ADDED;
276e5b68881Stholo     else if (vers->ts_user != NULL
277e5b68881Stholo 	     && vers->ts_rcs != NULL
278172be96aStholo 	     && (args->force || strcmp (vers->ts_user, vers->ts_rcs) != 0))
279c56b20e6Stholo 	/* If we are forcing commits, pretend that the file is
280c56b20e6Stholo            modified.  */
281e5b68881Stholo 	status = T_MODIFIED;
282e5b68881Stholo     else
283e5b68881Stholo     {
2849d9224ffStholo 	/* This covers unmodified files, as well as a variety of other
2859d9224ffStholo 	   cases.  FIXME: we probably should be printing a message and
2869d9224ffStholo 	   returning 1 for many of those cases (but I'm not sure
2879d9224ffStholo 	   exactly which ones).  */
288bde78045Stholo 	freevers_ts (&vers);
289e5b68881Stholo 	return 0;
290e5b68881Stholo     }
291e5b68881Stholo 
292e5b68881Stholo     node = getnode ();
2939d9224ffStholo     node->key = xstrdup (finfo->fullname);
294e5b68881Stholo 
2957e4d5a85Stholo     data = (struct logfile_info *) xmalloc (sizeof (struct logfile_info));
2967e4d5a85Stholo     data->type = status;
2977e4d5a85Stholo     data->tag = xstrdup (vers->tag);
298ae5e8a9bStholo     data->rev_old = data->rev_new = NULL;
2997e4d5a85Stholo 
300e5b68881Stholo     node->type = UPDATE;
301e5b68881Stholo     node->delproc = update_delproc;
3027e4d5a85Stholo     node->data = (char *) data;
303e5b68881Stholo     (void)addnode (args->ulist, node);
304e5b68881Stholo 
305e5b68881Stholo     ++args->argc;
306e5b68881Stholo 
3077e4d5a85Stholo     freevers_ts (&vers);
308e5b68881Stholo     return 0;
309e5b68881Stholo }
310e5b68881Stholo 
311e5b68881Stholo static int copy_ulist PROTO ((Node *, void *));
312e5b68881Stholo 
313e5b68881Stholo static int
copy_ulist(node,data)314e5b68881Stholo copy_ulist (node, data)
315e5b68881Stholo     Node *node;
316e5b68881Stholo     void *data;
317e5b68881Stholo {
318e5b68881Stholo     struct find_data *args = (struct find_data *)data;
319e5b68881Stholo     args->argv[args->argc++] = node->key;
320e5b68881Stholo     return 0;
321e5b68881Stholo }
322e5b68881Stholo #endif /* CLIENT_SUPPORT */
323e5b68881Stholo 
3241e72d8d2Sderaadt int
commit(argc,argv)3251e72d8d2Sderaadt commit (argc, argv)
3261e72d8d2Sderaadt     int argc;
3271e72d8d2Sderaadt     char **argv;
3281e72d8d2Sderaadt {
3291e72d8d2Sderaadt     int c;
3301e72d8d2Sderaadt     int err = 0;
3311e72d8d2Sderaadt     int local = 0;
3321e72d8d2Sderaadt 
3331e72d8d2Sderaadt     if (argc == -1)
3341e72d8d2Sderaadt 	usage (commit_usage);
3351e72d8d2Sderaadt 
3361e72d8d2Sderaadt #ifdef CVS_BADROOT
3371e72d8d2Sderaadt     /*
3381e72d8d2Sderaadt      * For log purposes, do not allow "root" to commit files.  If you look
3391e72d8d2Sderaadt      * like root, but are really logged in as a non-root user, it's OK.
3401e72d8d2Sderaadt      */
341b2b690deStholo     /* FIXME: Shouldn't this check be much more closely related to the
342b2b690deStholo        readonly user stuff (CVSROOT/readers, &c).  That is, why should
343b2b690deStholo        root be able to "cvs init", "cvs import", &c, but not "cvs ci"?  */
3449fe7c2c3Stholo     if (geteuid () == (uid_t) 0
3459fe7c2c3Stholo #  ifdef CLIENT_SUPPORT
3469fe7c2c3Stholo 	/* Who we are on the client side doesn't affect logging.  */
34779822b2aStholo 	&& !current_parsed_root->isremote
3489fe7c2c3Stholo #  endif
3499fe7c2c3Stholo 	)
3501e72d8d2Sderaadt     {
3511e72d8d2Sderaadt 	struct passwd *pw;
3521e72d8d2Sderaadt 
3531e72d8d2Sderaadt 	if ((pw = (struct passwd *) getpwnam (getcaller ())) == NULL)
3541e72d8d2Sderaadt 	    error (1, 0, "you are unknown to this system");
3551e72d8d2Sderaadt 	if (pw->pw_uid == (uid_t) 0)
3561e72d8d2Sderaadt 	    error (1, 0, "cannot commit files as 'root'");
3571e72d8d2Sderaadt     }
3581e72d8d2Sderaadt #endif /* CVS_BADROOT */
3591e72d8d2Sderaadt 
360066e68faStholo     optind = 0;
361172be96aStholo     while ((c = getopt (argc, argv, "+nlRm:fF:r:")) != -1)
3621e72d8d2Sderaadt     {
3631e72d8d2Sderaadt 	switch (c)
3641e72d8d2Sderaadt 	{
3651e72d8d2Sderaadt 	    case 'n':
3661e72d8d2Sderaadt 		run_module_prog = 0;
3671e72d8d2Sderaadt 		break;
3681e72d8d2Sderaadt 	    case 'm':
3691e72d8d2Sderaadt #ifdef FORCE_USE_EDITOR
370b2b690deStholo 		use_editor = 1;
3711e72d8d2Sderaadt #else
372b2b690deStholo 		use_editor = 0;
3731e72d8d2Sderaadt #endif
3742e5b458aStholo 		if (saved_message)
3751e72d8d2Sderaadt 		{
3762e5b458aStholo 		    free (saved_message);
3772e5b458aStholo 		    saved_message = NULL;
3781e72d8d2Sderaadt 		}
3791e72d8d2Sderaadt 
3802e5b458aStholo 		saved_message = xstrdup(optarg);
3811e72d8d2Sderaadt 		break;
3821e72d8d2Sderaadt 	    case 'r':
3832e5b458aStholo 		if (saved_tag)
3842e5b458aStholo 		    free (saved_tag);
3852e5b458aStholo 		saved_tag = xstrdup (optarg);
3861e72d8d2Sderaadt 		break;
3871e72d8d2Sderaadt 	    case 'l':
3881e72d8d2Sderaadt 		local = 1;
3891e72d8d2Sderaadt 		break;
3901e72d8d2Sderaadt 	    case 'R':
3911e72d8d2Sderaadt 		local = 0;
3921e72d8d2Sderaadt 		break;
3931e72d8d2Sderaadt 	    case 'f':
3941e72d8d2Sderaadt 		force_ci = 1;
3951e72d8d2Sderaadt 		local = 1;		/* also disable recursion */
3961e72d8d2Sderaadt 		break;
3971e72d8d2Sderaadt 	    case 'F':
3981e72d8d2Sderaadt #ifdef FORCE_USE_EDITOR
399b2b690deStholo 		use_editor = 1;
4001e72d8d2Sderaadt #else
401b2b690deStholo 		use_editor = 0;
4021e72d8d2Sderaadt #endif
4031e72d8d2Sderaadt 		logfile = optarg;
4041e72d8d2Sderaadt 		break;
4051e72d8d2Sderaadt 	    case '?':
4061e72d8d2Sderaadt 	    default:
4071e72d8d2Sderaadt 		usage (commit_usage);
4081e72d8d2Sderaadt 		break;
4091e72d8d2Sderaadt 	}
4101e72d8d2Sderaadt     }
4111e72d8d2Sderaadt     argc -= optind;
4121e72d8d2Sderaadt     argv += optind;
4131e72d8d2Sderaadt 
4141e72d8d2Sderaadt     /* numeric specified revision means we ignore sticky tags... */
4159fe7c2c3Stholo     if (saved_tag && isdigit ((unsigned char) *saved_tag))
4161e72d8d2Sderaadt     {
4171e72d8d2Sderaadt 	aflag = 1;
4181e72d8d2Sderaadt 	/* strip trailing dots */
4192e5b458aStholo 	while (saved_tag[strlen (saved_tag) - 1] == '.')
4202e5b458aStholo 	    saved_tag[strlen (saved_tag) - 1] = '\0';
4211e72d8d2Sderaadt     }
4221e72d8d2Sderaadt 
4231e72d8d2Sderaadt     /* some checks related to the "-F logfile" option */
4241e72d8d2Sderaadt     if (logfile)
4251e72d8d2Sderaadt     {
426bde78045Stholo 	size_t size = 0, len;
4271e72d8d2Sderaadt 
4282e5b458aStholo 	if (saved_message)
4291e72d8d2Sderaadt 	    error (1, 0, "cannot specify both a message and a log file");
4301e72d8d2Sderaadt 
431bde78045Stholo 	get_file (logfile, logfile, "r", &saved_message, &size, &len);
4321e72d8d2Sderaadt     }
4331e72d8d2Sderaadt 
4341e72d8d2Sderaadt #ifdef CLIENT_SUPPORT
43579822b2aStholo     if (current_parsed_root->isremote)
4361e72d8d2Sderaadt     {
437e5b68881Stholo 	struct find_data find_args;
4381e72d8d2Sderaadt 
4391e72d8d2Sderaadt 	ign_setup ();
4401e72d8d2Sderaadt 
441e5b68881Stholo 	find_args.ulist = getlist ();
442e5b68881Stholo 	find_args.argc = 0;
4437e4d5a85Stholo 	find_args.questionables = NULL;
4447e4d5a85Stholo 	find_args.ignlist = NULL;
4457e4d5a85Stholo 	find_args.repository = NULL;
4467e4d5a85Stholo 
447172be96aStholo 	/* It is possible that only a numeric tag should set this.
448172be96aStholo 	   I haven't really thought about it much.
449172be96aStholo 	   Anyway, I suspect that setting it unnecessarily only causes
450172be96aStholo 	   a little unneeded network traffic.  */
4512e5b458aStholo 	find_args.force = force_ci || saved_tag != NULL;
452172be96aStholo 
4537e4d5a85Stholo 	err = start_recursion (find_fileproc, find_filesdoneproc,
4547e4d5a85Stholo 			       find_dirent_proc, (DIRLEAVEPROC) NULL,
4557e4d5a85Stholo 			       (void *)&find_args,
456e5b68881Stholo 			       argc, argv, local, W_LOCAL, 0, 0,
4577e4d5a85Stholo 			       (char *)NULL, 0);
458e5b68881Stholo 	if (err)
459e5b68881Stholo 	    error (1, 0, "correct above errors first!");
460e5b68881Stholo 
461e5b68881Stholo 	if (find_args.argc == 0)
462bde78045Stholo 	{
4637e4d5a85Stholo 	    /* Nothing to commit.  Exit now without contacting the
4647e4d5a85Stholo 	       server (note that this means that we won't print "?
4657e4d5a85Stholo 	       foo" for files which merit it, because we don't know
4667e4d5a85Stholo 	       what is in the CVSROOT/cvsignore file).  */
467bde78045Stholo 	    dellist (&find_args.ulist);
468e5b68881Stholo 	    return 0;
469bde78045Stholo 	}
470e5b68881Stholo 
471e5b68881Stholo 	/* Now we keep track of which files we actually are going to
472e5b68881Stholo 	   operate on, and only work with those files in the future.
473e5b68881Stholo 	   This saves time--we don't want to search the file system
474e5b68881Stholo 	   of the working directory twice.  */
4752ddaa231Sotto 	if (size_overflow_p (xtimes (find_args.argc, sizeof (char **))))
4762ddaa231Sotto 	{
4772ddaa231Sotto 	    find_args.argc = 0;
4782ddaa231Sotto 	    return 0;
4792ddaa231Sotto 	}
4802ddaa231Sotto 	find_args.argv = xmalloc (xtimes (find_args.argc, sizeof (char **)));
481e5b68881Stholo 	find_args.argc = 0;
482e5b68881Stholo 	walklist (find_args.ulist, copy_ulist, &find_args);
483e5b68881Stholo 
4847e4d5a85Stholo 	/* Do this before calling do_editor; don't ask for a log
4857e4d5a85Stholo 	   message if we can't talk to the server.  But do it after we
4867e4d5a85Stholo 	   have made the checks that we can locally (to more quickly
4877e4d5a85Stholo 	   catch syntax errors, the case where no files are modified,
4887e4d5a85Stholo 	   added or removed, etc.).
4897e4d5a85Stholo 
4907e4d5a85Stholo 	   On the other hand, calling start_server before do_editor
4917e4d5a85Stholo 	   means that we chew up server resources the whole time that
4927e4d5a85Stholo 	   the user has the editor open (hours or days if the user
4937e4d5a85Stholo 	   forgets about it), which seems dubious.  */
494e5b68881Stholo 	start_server ();
495e5b68881Stholo 
4961e72d8d2Sderaadt 	/*
4971e72d8d2Sderaadt 	 * We do this once, not once for each directory as in normal CVS.
4981e72d8d2Sderaadt 	 * The protocol is designed this way.  This is a feature.
4991e72d8d2Sderaadt 	 */
5001e72d8d2Sderaadt 	if (use_editor)
5012e5b458aStholo 	    do_editor (".", &saved_message, (char *)NULL, find_args.ulist);
5021e72d8d2Sderaadt 
503ae5e8a9bStholo 	/* Run the user-defined script to verify/check information in
504ae5e8a9bStholo 	 *the log message
505ae5e8a9bStholo 	 */
5062e5b458aStholo 	do_verify (saved_message, (char *)NULL);
507ae5e8a9bStholo 
5081e72d8d2Sderaadt 	/* We always send some sort of message, even if empty.  */
509b2b690deStholo 	/* FIXME: is that true?  There seems to be some code in do_editor
510b2b690deStholo 	   which can leave the message NULL.  */
5112e5b458aStholo 	option_with_arg ("-m", saved_message);
5121e72d8d2Sderaadt 
5137e4d5a85Stholo 	/* OK, now process all the questionable files we have been saving
5147e4d5a85Stholo 	   up.  */
5157e4d5a85Stholo 	{
5167e4d5a85Stholo 	    struct question *p;
5177e4d5a85Stholo 	    struct question *q;
5187e4d5a85Stholo 
5197e4d5a85Stholo 	    p = find_args.questionables;
5207e4d5a85Stholo 	    while (p != NULL)
5217e4d5a85Stholo 	    {
5227e4d5a85Stholo 		if (ign_inhibit_server || !supported_request ("Questionable"))
5237e4d5a85Stholo 		{
5247e4d5a85Stholo 		    cvs_output ("? ", 2);
5257e4d5a85Stholo 		    if (p->dir[0] != '\0')
5267e4d5a85Stholo 		    {
5277e4d5a85Stholo 			cvs_output (p->dir, 0);
5287e4d5a85Stholo 			cvs_output ("/", 1);
5297e4d5a85Stholo 		    }
5307e4d5a85Stholo 		    cvs_output (p->file, 0);
5317e4d5a85Stholo 		    cvs_output ("\n", 1);
5327e4d5a85Stholo 		}
5337e4d5a85Stholo 		else
5347e4d5a85Stholo 		{
5357e4d5a85Stholo 		    send_to_server ("Directory ", 0);
5367e4d5a85Stholo 		    send_to_server (p->dir[0] == '\0' ? "." : p->dir, 0);
5377e4d5a85Stholo 		    send_to_server ("\012", 1);
5387e4d5a85Stholo 		    send_to_server (p->repos, 0);
5397e4d5a85Stholo 		    send_to_server ("\012", 1);
5407e4d5a85Stholo 
5417e4d5a85Stholo 		    send_to_server ("Questionable ", 0);
5427e4d5a85Stholo 		    send_to_server (p->file, 0);
5437e4d5a85Stholo 		    send_to_server ("\012", 1);
5447e4d5a85Stholo 		}
5457e4d5a85Stholo 		free (p->dir);
5467e4d5a85Stholo 		free (p->repos);
5477e4d5a85Stholo 		free (p->file);
5487e4d5a85Stholo 		q = p->next;
5497e4d5a85Stholo 		free (p);
5507e4d5a85Stholo 		p = q;
5517e4d5a85Stholo 	    }
5527e4d5a85Stholo 	}
5537e4d5a85Stholo 
5541e72d8d2Sderaadt 	if (local)
5551e72d8d2Sderaadt 	    send_arg("-l");
5561e72d8d2Sderaadt 	if (force_ci)
5571e72d8d2Sderaadt 	    send_arg("-f");
5581e72d8d2Sderaadt 	if (!run_module_prog)
5591e72d8d2Sderaadt 	    send_arg("-n");
5602e5b458aStholo 	option_with_arg ("-r", saved_tag);
5611e72d8d2Sderaadt 
562066e68faStholo 	/* FIXME: This whole find_args.force/SEND_FORCE business is a
563066e68faStholo 	   kludge.  It would seem to be a server bug that we have to
564066e68faStholo 	   say that files are modified when they are not.  This makes
565066e68faStholo 	   "cvs commit -r 2" across a whole bunch of files a very slow
566066e68faStholo 	   operation (and it isn't documented in cvsclient.texi).  I
567066e68faStholo 	   haven't looked at the server code carefully enough to be
568b2b690deStholo 	   _sure_ why this is needed, but if it is because the "ci"
569b2b690deStholo 	   program, which we used to call, wanted the file to exist,
570b2b690deStholo 	   then it would be relatively simple to fix in the server.  */
571172be96aStholo 	send_files (find_args.argc, find_args.argv, local, 0,
572172be96aStholo 		    find_args.force ? SEND_FORCE : 0);
5731e72d8d2Sderaadt 
5749fe7c2c3Stholo 	/* Sending only the names of the files which were modified, added,
5759fe7c2c3Stholo 	   or removed means that the server will only do an up-to-date
5769fe7c2c3Stholo 	   check on those files.  This is different from local CVS and
5779fe7c2c3Stholo 	   previous versions of client/server CVS, but it probably is a Good
5789fe7c2c3Stholo 	   Thing, or at least Not Such A Bad Thing.  */
5799fe7c2c3Stholo 	send_file_names (find_args.argc, find_args.argv, 0);
580bde78045Stholo 	free (find_args.argv);
581bde78045Stholo 	dellist (&find_args.ulist);
5829fe7c2c3Stholo 
583e5b68881Stholo 	send_to_server ("ci\012", 0);
584b2b690deStholo 	err = get_responses_and_close ();
5852e5b458aStholo 	if (err != 0 && use_editor && saved_message != NULL)
586b2b690deStholo 	{
587b2b690deStholo 	    /* If there was an error, don't nuke the user's carefully
588b2b690deStholo 	       constructed prose.  This is something of a kludge; a better
589b2b690deStholo 	       solution is probably more along the lines of #150 in TODO
590b2b690deStholo 	       (doing a second up-to-date check before accepting the
591b2b690deStholo 	       log message has also been suggested, but that seems kind of
592b2b690deStholo 	       iffy because the real up-to-date check could still fail,
593b2b690deStholo 	       another error could occur, &c.  Also, a second check would
594b2b690deStholo 	       slow things down).  */
595b2b690deStholo 
596b2b690deStholo 	    char *fname;
597b2b690deStholo 	    FILE *fp;
598b2b690deStholo 
59979822b2aStholo 	    fp = cvs_temp_file (&fname);
600b2b690deStholo 	    if (fp == NULL)
601b2b690deStholo 		error (1, 0, "cannot create temporary file %s", fname);
6022e5b458aStholo 	    if (fwrite (saved_message, 1, strlen (saved_message), fp)
6032e5b458aStholo 		!= strlen (saved_message))
604b2b690deStholo 		error (1, errno, "cannot write temporary file %s", fname);
605b2b690deStholo 	    if (fclose (fp) < 0)
606b2b690deStholo 		error (0, errno, "cannot close temporary file %s", fname);
607b2b690deStholo 	    error (0, 0, "saving log message in %s", fname);
60879822b2aStholo 	    free (fname);
609b2b690deStholo 	}
610b2b690deStholo 	return err;
6111e72d8d2Sderaadt     }
6121e72d8d2Sderaadt #endif
6131e72d8d2Sderaadt 
6142e5b458aStholo     if (saved_tag != NULL)
6152e5b458aStholo 	tag_check_valid (saved_tag, argc, argv, local, aflag, "");
616e5b68881Stholo 
6171e72d8d2Sderaadt     /* XXX - this is not the perfect check for this */
6181e72d8d2Sderaadt     if (argc <= 0)
6192e5b458aStholo 	write_dirtag = saved_tag;
6201e72d8d2Sderaadt 
6211e72d8d2Sderaadt     wrap_setup ();
6221e72d8d2Sderaadt 
62379822b2aStholo     lock_tree_for_write (argc, argv, local, W_LOCAL, aflag);
6241e72d8d2Sderaadt 
6251e72d8d2Sderaadt     /*
62678c87a5cStholo      * Set up the master update list and hard link list
6271e72d8d2Sderaadt      */
6281e72d8d2Sderaadt     mulist = getlist ();
6291e72d8d2Sderaadt 
63078c87a5cStholo #ifdef PRESERVE_PERMISSIONS_SUPPORT
63178c87a5cStholo     if (preserve_perms)
63278c87a5cStholo     {
63378c87a5cStholo 	hardlist = getlist ();
63478c87a5cStholo 
63578c87a5cStholo 	/*
63678c87a5cStholo 	 * We need to save the working directory so that
63778c87a5cStholo 	 * check_fileproc can construct a full pathname for each file.
63878c87a5cStholo 	 */
63978c87a5cStholo 	working_dir = xgetwd();
64078c87a5cStholo     }
64178c87a5cStholo #endif
64278c87a5cStholo 
6431e72d8d2Sderaadt     /*
6441e72d8d2Sderaadt      * Run the recursion processor to verify the files are all up-to-date
6451e72d8d2Sderaadt      */
6461e72d8d2Sderaadt     err = start_recursion (check_fileproc, check_filesdoneproc,
6477e4d5a85Stholo 			   check_direntproc, (DIRLEAVEPROC) NULL, NULL, argc,
6487e4d5a85Stholo 			   argv, local, W_LOCAL, aflag, 0, (char *) NULL, 1);
6491e72d8d2Sderaadt     if (err)
6501e72d8d2Sderaadt     {
651ae5e8a9bStholo 	Lock_Cleanup ();
6521e72d8d2Sderaadt 	error (1, 0, "correct above errors first!");
6531e72d8d2Sderaadt     }
6541e72d8d2Sderaadt 
6551e72d8d2Sderaadt     /*
6561e72d8d2Sderaadt      * Run the recursion processor to commit the files
6571e72d8d2Sderaadt      */
658172be96aStholo     write_dirnonbranch = 0;
6591e72d8d2Sderaadt     if (noexec == 0)
6601e72d8d2Sderaadt 	err = start_recursion (commit_fileproc, commit_filesdoneproc,
6617e4d5a85Stholo 			       commit_direntproc, commit_dirleaveproc, NULL,
6621e72d8d2Sderaadt 			       argc, argv, local, W_LOCAL, aflag, 0,
6637e4d5a85Stholo 			       (char *) NULL, 1);
6641e72d8d2Sderaadt 
6651e72d8d2Sderaadt     /*
6661e72d8d2Sderaadt      * Unlock all the dirs and clean up
6671e72d8d2Sderaadt      */
668ae5e8a9bStholo     Lock_Cleanup ();
6691e72d8d2Sderaadt     dellist (&mulist);
67045381119Stholo 
67179822b2aStholo #ifdef SERVER_SUPPORT
67279822b2aStholo     if (server_active)
67379822b2aStholo 	return err;
67479822b2aStholo #endif
67579822b2aStholo 
676bde78045Stholo     /* see if we need to sleep before returning to avoid time-stamp races */
67745381119Stholo     if (last_register_time)
67845381119Stholo     {
67979822b2aStholo 	sleep_past (last_register_time);
68045381119Stholo     }
68145381119Stholo 
6821e72d8d2Sderaadt     return (err);
6831e72d8d2Sderaadt }
6841e72d8d2Sderaadt 
685ae5e8a9bStholo /* This routine determines the status of a given file and retrieves
686ae5e8a9bStholo    the version information that is associated with that file. */
687ae5e8a9bStholo 
688ae5e8a9bStholo static
689ae5e8a9bStholo Ctype
classify_file_internal(finfo,vers)690ae5e8a9bStholo classify_file_internal (finfo, vers)
69145381119Stholo     struct file_info *finfo;
692ae5e8a9bStholo     Vers_TS **vers;
6931e72d8d2Sderaadt {
6941e72d8d2Sderaadt     int save_noexec, save_quiet, save_really_quiet;
695ae5e8a9bStholo     Ctype status;
6961e72d8d2Sderaadt 
697b2b690deStholo     /* FIXME: Do we need to save quiet as well as really_quiet?  Last
698b2b690deStholo        time I glanced at Classify_File I only saw it looking at really_quiet
699b2b690deStholo        not quiet.  */
7001e72d8d2Sderaadt     save_noexec = noexec;
7011e72d8d2Sderaadt     save_quiet = quiet;
7021e72d8d2Sderaadt     save_really_quiet = really_quiet;
7031e72d8d2Sderaadt     noexec = quiet = really_quiet = 1;
7041e72d8d2Sderaadt 
7051e72d8d2Sderaadt     /* handle specified numeric revision specially */
7069fe7c2c3Stholo     if (saved_tag && isdigit ((unsigned char) *saved_tag))
7071e72d8d2Sderaadt     {
7081e72d8d2Sderaadt 	/* If the tag is for the trunk, make sure we're at the head */
7092e5b458aStholo 	if (numdots (saved_tag) < 2)
7101e72d8d2Sderaadt 	{
7117e4d5a85Stholo 	    status = Classify_File (finfo, (char *) NULL, (char *) NULL,
712ae5e8a9bStholo 				    (char *) NULL, 1, aflag, vers, 0);
7131e72d8d2Sderaadt 	    if (status == T_UPTODATE || status == T_MODIFIED ||
7141e72d8d2Sderaadt 		status == T_ADDED)
7151e72d8d2Sderaadt 	    {
7161e72d8d2Sderaadt 		Ctype xstatus;
7171e72d8d2Sderaadt 
718ae5e8a9bStholo 		freevers_ts (vers);
7192e5b458aStholo 		xstatus = Classify_File (finfo, saved_tag, (char *) NULL,
720ae5e8a9bStholo 					 (char *) NULL, 1, aflag, vers, 0);
7211e72d8d2Sderaadt 		if (xstatus == T_REMOVE_ENTRY)
7221e72d8d2Sderaadt 		    status = T_MODIFIED;
7231e72d8d2Sderaadt 		else if (status == T_MODIFIED && xstatus == T_CONFLICT)
7241e72d8d2Sderaadt 		    status = T_MODIFIED;
7251e72d8d2Sderaadt 		else
7261e72d8d2Sderaadt 		    status = xstatus;
7271e72d8d2Sderaadt 	    }
7281e72d8d2Sderaadt 	}
7291e72d8d2Sderaadt 	else
7301e72d8d2Sderaadt 	{
7311e72d8d2Sderaadt 	    char *xtag, *cp;
7321e72d8d2Sderaadt 
7331e72d8d2Sderaadt 	    /*
7341e72d8d2Sderaadt 	     * The revision is off the main trunk; make sure we're
7351e72d8d2Sderaadt 	     * up-to-date with the head of the specified branch.
7361e72d8d2Sderaadt 	     */
7372e5b458aStholo 	    xtag = xstrdup (saved_tag);
7381e72d8d2Sderaadt 	    if ((numdots (xtag) & 1) != 0)
7391e72d8d2Sderaadt 	    {
7401e72d8d2Sderaadt 		cp = strrchr (xtag, '.');
7411e72d8d2Sderaadt 		*cp = '\0';
7421e72d8d2Sderaadt 	    }
7437e4d5a85Stholo 	    status = Classify_File (finfo, xtag, (char *) NULL,
744ae5e8a9bStholo 				    (char *) NULL, 1, aflag, vers, 0);
7451e72d8d2Sderaadt 	    if ((status == T_REMOVE_ENTRY || status == T_CONFLICT)
7461e72d8d2Sderaadt 		&& (cp = strrchr (xtag, '.')) != NULL)
7471e72d8d2Sderaadt 	    {
7481e72d8d2Sderaadt 		/* pluck one more dot off the revision */
7491e72d8d2Sderaadt 		*cp = '\0';
750ae5e8a9bStholo 		freevers_ts (vers);
7517e4d5a85Stholo 		status = Classify_File (finfo, xtag, (char *) NULL,
752ae5e8a9bStholo 					(char *) NULL, 1, aflag, vers, 0);
7531e72d8d2Sderaadt 		if (status == T_UPTODATE || status == T_REMOVE_ENTRY)
7541e72d8d2Sderaadt 		    status = T_MODIFIED;
7551e72d8d2Sderaadt 	    }
7561e72d8d2Sderaadt 	    /* now, muck with vers to make the tag correct */
757ae5e8a9bStholo 	    free ((*vers)->tag);
7582e5b458aStholo 	    (*vers)->tag = xstrdup (saved_tag);
7591e72d8d2Sderaadt 	    free (xtag);
7601e72d8d2Sderaadt 	}
7611e72d8d2Sderaadt     }
7621e72d8d2Sderaadt     else
7632e5b458aStholo 	status = Classify_File (finfo, saved_tag, (char *) NULL, (char *) NULL,
764ae5e8a9bStholo 				1, 0, vers, 0);
7651e72d8d2Sderaadt     noexec = save_noexec;
7661e72d8d2Sderaadt     quiet = save_quiet;
7671e72d8d2Sderaadt     really_quiet = save_really_quiet;
7681e72d8d2Sderaadt 
769ae5e8a9bStholo     return status;
770ae5e8a9bStholo }
771ae5e8a9bStholo 
772ae5e8a9bStholo /*
773ae5e8a9bStholo  * Check to see if a file is ok to commit and make sure all files are
774ae5e8a9bStholo  * up-to-date
775ae5e8a9bStholo  */
776ae5e8a9bStholo /* ARGSUSED */
777ae5e8a9bStholo static int
check_fileproc(callerdat,finfo)778ae5e8a9bStholo check_fileproc (callerdat, finfo)
779ae5e8a9bStholo     void *callerdat;
780ae5e8a9bStholo     struct file_info *finfo;
781ae5e8a9bStholo {
782ae5e8a9bStholo     Ctype status;
783ae5e8a9bStholo     char *xdir;
784ae5e8a9bStholo     Node *p;
785ae5e8a9bStholo     List *ulist, *cilist;
786ae5e8a9bStholo     Vers_TS *vers;
787ae5e8a9bStholo     struct commit_info *ci;
788ae5e8a9bStholo     struct logfile_info *li;
789ae5e8a9bStholo 
79079822b2aStholo     size_t cvsroot_len = strlen (current_parsed_root->directory);
7919fe7c2c3Stholo 
792bde78045Stholo     if (!finfo->repository)
793bde78045Stholo     {
794bde78045Stholo 	error (0, 0, "nothing known about `%s'", finfo->fullname);
795bde78045Stholo 	return (1);
796bde78045Stholo     }
797bde78045Stholo 
79879822b2aStholo     if (strncmp (finfo->repository, current_parsed_root->directory, cvsroot_len) == 0
7999fe7c2c3Stholo 	&& ISDIRSEP (finfo->repository[cvsroot_len])
8009fe7c2c3Stholo 	&& strncmp (finfo->repository + cvsroot_len + 1,
8019fe7c2c3Stholo 		    CVSROOTADM,
8029fe7c2c3Stholo 		    sizeof (CVSROOTADM) - 1) == 0
8039fe7c2c3Stholo 	&& ISDIRSEP (finfo->repository[cvsroot_len + sizeof (CVSROOTADM)])
8049fe7c2c3Stholo 	&& strcmp (finfo->repository + cvsroot_len + sizeof (CVSROOTADM) + 1,
8059fe7c2c3Stholo 		   CVSNULLREPOS) == 0
8069fe7c2c3Stholo 	)
8079fe7c2c3Stholo 	error (1, 0, "cannot check in to %s", finfo->repository);
8089fe7c2c3Stholo 
809ae5e8a9bStholo     status = classify_file_internal (finfo, &vers);
810ae5e8a9bStholo 
8111e72d8d2Sderaadt     /*
8121e72d8d2Sderaadt      * If the force-commit option is enabled, and the file in question
8131e72d8d2Sderaadt      * appears to be up-to-date, just make it look modified so that
8141e72d8d2Sderaadt      * it will be committed.
8151e72d8d2Sderaadt      */
8161e72d8d2Sderaadt     if (force_ci && status == T_UPTODATE)
8171e72d8d2Sderaadt 	status = T_MODIFIED;
8181e72d8d2Sderaadt 
8191e72d8d2Sderaadt     switch (status)
8201e72d8d2Sderaadt     {
8211e72d8d2Sderaadt 	case T_CHECKOUT:
8221e72d8d2Sderaadt 	case T_PATCH:
8231e72d8d2Sderaadt 	case T_NEEDS_MERGE:
8241e72d8d2Sderaadt 	case T_CONFLICT:
8251e72d8d2Sderaadt 	case T_REMOVE_ENTRY:
8269d9224ffStholo 	    error (0, 0, "Up-to-date check failed for `%s'", finfo->fullname);
8271e72d8d2Sderaadt 	    freevers_ts (&vers);
8281e72d8d2Sderaadt 	    return (1);
8291e72d8d2Sderaadt 	case T_MODIFIED:
8301e72d8d2Sderaadt 	case T_ADDED:
8311e72d8d2Sderaadt 	case T_REMOVED:
8321e72d8d2Sderaadt 	    /*
8331e72d8d2Sderaadt 	     * some quick sanity checks; if no numeric -r option specified:
8341e72d8d2Sderaadt 	     *	- can't have a sticky date
8351e72d8d2Sderaadt 	     *	- can't have a sticky tag that is not a branch
8361e72d8d2Sderaadt 	     * Also,
8371e72d8d2Sderaadt 	     *	- if status is T_REMOVED, can't have a numeric tag
8387e4d5a85Stholo 	     *	- if status is T_ADDED, rcs file must not exist unless on
83979822b2aStholo 	     *    a branch or head is dead
8401e72d8d2Sderaadt 	     *	- if status is T_ADDED, can't have a non-trunk numeric rev
8411e72d8d2Sderaadt 	     *	- if status is T_MODIFIED and a Conflict marker exists, don't
8421e72d8d2Sderaadt 	     *    allow the commit if timestamp is identical or if we find
8431e72d8d2Sderaadt 	     *    an RCS_MERGE_PAT in the file.
8441e72d8d2Sderaadt 	     */
8459fe7c2c3Stholo 	    if (!saved_tag || !isdigit ((unsigned char) *saved_tag))
8461e72d8d2Sderaadt 	    {
8471e72d8d2Sderaadt 		if (vers->date)
8481e72d8d2Sderaadt 		{
8491e72d8d2Sderaadt 		    error (0, 0,
8501e72d8d2Sderaadt 			   "cannot commit with sticky date for file `%s'",
8519d9224ffStholo 			   finfo->fullname);
8521e72d8d2Sderaadt 		    freevers_ts (&vers);
8531e72d8d2Sderaadt 		    return (1);
8541e72d8d2Sderaadt 		}
8551e72d8d2Sderaadt 		if (status == T_MODIFIED && vers->tag &&
8569d9224ffStholo 		    !RCS_isbranch (finfo->rcs, vers->tag))
8571e72d8d2Sderaadt 		{
8581e72d8d2Sderaadt 		    error (0, 0,
8591e72d8d2Sderaadt 			   "sticky tag `%s' for file `%s' is not a branch",
8609d9224ffStholo 			   vers->tag, finfo->fullname);
8611e72d8d2Sderaadt 		    freevers_ts (&vers);
8621e72d8d2Sderaadt 		    return (1);
8631e72d8d2Sderaadt 		}
8641e72d8d2Sderaadt 	    }
8651e72d8d2Sderaadt 	    if (status == T_MODIFIED && !force_ci && vers->ts_conflict)
8661e72d8d2Sderaadt 	    {
8671e72d8d2Sderaadt 		char *filestamp;
8681e72d8d2Sderaadt 		int retcode;
8691e72d8d2Sderaadt 
8701e72d8d2Sderaadt 		/*
8711e72d8d2Sderaadt 		 * We found a "conflict" marker.
8721e72d8d2Sderaadt 		 *
8731e72d8d2Sderaadt 		 * If the timestamp on the file is the same as the
8741e72d8d2Sderaadt 		 * timestamp stored in the Entries file, we block the commit.
8751e72d8d2Sderaadt 		 */
8761e72d8d2Sderaadt #ifdef SERVER_SUPPORT
8771e72d8d2Sderaadt 		if (server_active)
8781e72d8d2Sderaadt 		    retcode = vers->ts_conflict[0] != '=';
8791e72d8d2Sderaadt 		else {
88045381119Stholo 		    filestamp = time_stamp (finfo->file);
8811e72d8d2Sderaadt 		    retcode = strcmp (vers->ts_conflict, filestamp);
8821e72d8d2Sderaadt 		    free (filestamp);
8831e72d8d2Sderaadt 		}
8841e72d8d2Sderaadt #else
88545381119Stholo 		filestamp = time_stamp (finfo->file);
8861e72d8d2Sderaadt 		retcode = strcmp (vers->ts_conflict, filestamp);
8871e72d8d2Sderaadt 		free (filestamp);
8881e72d8d2Sderaadt #endif
8891e72d8d2Sderaadt 		if (retcode == 0)
8901e72d8d2Sderaadt 		{
8911e72d8d2Sderaadt 		    error (0, 0,
8921e72d8d2Sderaadt 			  "file `%s' had a conflict and has not been modified",
8939d9224ffStholo 			   finfo->fullname);
8941e72d8d2Sderaadt 		    freevers_ts (&vers);
8951e72d8d2Sderaadt 		    return (1);
8961e72d8d2Sderaadt 		}
8971e72d8d2Sderaadt 
898ae5e8a9bStholo 		if (file_has_markers (finfo))
8991e72d8d2Sderaadt 		{
900b2b690deStholo 		    /* Make this a warning, not an error, because we have
901b2b690deStholo 		       no way of knowing whether the "conflict indicators"
902b2b690deStholo 		       are really from a conflict or whether they are part
903b2b690deStholo 		       of the document itself (cvs.texinfo and sanity.sh in
904b2b690deStholo 		       CVS itself, for example, tend to want to have strings
905b2b690deStholo 		       like ">>>>>>>" at the start of a line).  Making people
906b2b690deStholo 		       kludge this the way they need to kludge keyword
907b2b690deStholo 		       expansion seems undesirable.  And it is worse than
908b2b690deStholo 		       keyword expansion, because there is no -ko
909b2b690deStholo 		       analogue.  */
9101e72d8d2Sderaadt 		    error (0, 0,
911b2b690deStholo 			   "\
912b2b690deStholo warning: file `%s' seems to still contain conflict indicators",
9139d9224ffStholo 			   finfo->fullname);
9141e72d8d2Sderaadt 		}
9151e72d8d2Sderaadt 	    }
9161e72d8d2Sderaadt 
9179fe7c2c3Stholo 	    if (status == T_REMOVED
9189fe7c2c3Stholo 		&& vers->tag
9199fe7c2c3Stholo 		&& isdigit ((unsigned char) *vers->tag))
9201e72d8d2Sderaadt 	    {
921b2b690deStholo 		/* Remove also tries to forbid this, but we should check
922b2b690deStholo 		   here.  I'm only _sure_ about somewhat obscure cases
923b2b690deStholo 		   (hacking the Entries file, using an old version of
924b2b690deStholo 		   CVS for the remove and a new one for the commit), but
925b2b690deStholo 		   there might be other cases.  */
9261e72d8d2Sderaadt 		error (0, 0,
9271e72d8d2Sderaadt 	"cannot remove file `%s' which has a numeric sticky tag of `%s'",
9289d9224ffStholo 			   finfo->fullname, vers->tag);
9291e72d8d2Sderaadt 		freevers_ts (&vers);
9301e72d8d2Sderaadt 		return (1);
9311e72d8d2Sderaadt 	    }
9321e72d8d2Sderaadt 	    if (status == T_ADDED)
9331e72d8d2Sderaadt 	    {
9347e4d5a85Stholo 	        if (vers->tag == NULL)
9357e4d5a85Stholo 		{
93679822b2aStholo 		    if (finfo->rcs != NULL &&
93779822b2aStholo 			!RCS_isdead (finfo->rcs, finfo->rcs->head))
9381e72d8d2Sderaadt 		    {
9391e72d8d2Sderaadt 			error (0, 0,
9401e72d8d2Sderaadt 		    "cannot add file `%s' when RCS file `%s' already exists",
94179822b2aStholo 			       finfo->fullname, finfo->rcs->path);
9421e72d8d2Sderaadt 			freevers_ts (&vers);
9431e72d8d2Sderaadt 			return (1);
9441e72d8d2Sderaadt 		    }
9457e4d5a85Stholo 		}
94679822b2aStholo 		else if (isdigit ((unsigned char) *vers->tag) &&
9471e72d8d2Sderaadt 		    numdots (vers->tag) > 1)
9481e72d8d2Sderaadt 		{
9491e72d8d2Sderaadt 		    error (0, 0,
9501e72d8d2Sderaadt 		"cannot add file `%s' with revision `%s'; must be on trunk",
9519d9224ffStholo 			       finfo->fullname, vers->tag);
9521e72d8d2Sderaadt 		    freevers_ts (&vers);
9531e72d8d2Sderaadt 		    return (1);
9541e72d8d2Sderaadt 		}
9551e72d8d2Sderaadt 	    }
9561e72d8d2Sderaadt 
9571e72d8d2Sderaadt 	    /* done with consistency checks; now, to get on with the commit */
95845381119Stholo 	    if (finfo->update_dir[0] == '\0')
9591e72d8d2Sderaadt 		xdir = ".";
9601e72d8d2Sderaadt 	    else
96145381119Stholo 		xdir = finfo->update_dir;
9621e72d8d2Sderaadt 	    if ((p = findnode (mulist, xdir)) != NULL)
9631e72d8d2Sderaadt 	    {
9641e72d8d2Sderaadt 		ulist = ((struct master_lists *) p->data)->ulist;
9651e72d8d2Sderaadt 		cilist = ((struct master_lists *) p->data)->cilist;
9661e72d8d2Sderaadt 	    }
9671e72d8d2Sderaadt 	    else
9681e72d8d2Sderaadt 	    {
9691e72d8d2Sderaadt 		struct master_lists *ml;
9701e72d8d2Sderaadt 
9711e72d8d2Sderaadt 		ulist = getlist ();
9721e72d8d2Sderaadt 		cilist = getlist ();
9731e72d8d2Sderaadt 		p = getnode ();
9741e72d8d2Sderaadt 		p->key = xstrdup (xdir);
9751e72d8d2Sderaadt 		p->type = UPDATE;
9761e72d8d2Sderaadt 		ml = (struct master_lists *)
9771e72d8d2Sderaadt 		    xmalloc (sizeof (struct master_lists));
9781e72d8d2Sderaadt 		ml->ulist = ulist;
9791e72d8d2Sderaadt 		ml->cilist = cilist;
9801e72d8d2Sderaadt 		p->data = (char *) ml;
9811e72d8d2Sderaadt 		p->delproc = masterlist_delproc;
9821e72d8d2Sderaadt 		(void) addnode (mulist, p);
9831e72d8d2Sderaadt 	    }
9841e72d8d2Sderaadt 
9851e72d8d2Sderaadt 	    /* first do ulist, then cilist */
9861e72d8d2Sderaadt 	    p = getnode ();
98745381119Stholo 	    p->key = xstrdup (finfo->file);
9881e72d8d2Sderaadt 	    p->type = UPDATE;
9891e72d8d2Sderaadt 	    p->delproc = update_delproc;
9907e4d5a85Stholo 	    li = ((struct logfile_info *)
9917e4d5a85Stholo 		  xmalloc (sizeof (struct logfile_info)));
9927e4d5a85Stholo 	    li->type = status;
9937e4d5a85Stholo 	    li->tag = xstrdup (vers->tag);
994ae5e8a9bStholo 	    li->rev_old = xstrdup (vers->vn_rcs);
995ae5e8a9bStholo 	    li->rev_new = NULL;
9967e4d5a85Stholo 	    p->data = (char *) li;
9971e72d8d2Sderaadt 	    (void) addnode (ulist, p);
9981e72d8d2Sderaadt 
9991e72d8d2Sderaadt 	    p = getnode ();
100045381119Stholo 	    p->key = xstrdup (finfo->file);
10011e72d8d2Sderaadt 	    p->type = UPDATE;
10021e72d8d2Sderaadt 	    p->delproc = ci_delproc;
10031e72d8d2Sderaadt 	    ci = (struct commit_info *) xmalloc (sizeof (struct commit_info));
10041e72d8d2Sderaadt 	    ci->status = status;
10051e72d8d2Sderaadt 	    if (vers->tag)
10069fe7c2c3Stholo 		if (isdigit ((unsigned char) *vers->tag))
10071e72d8d2Sderaadt 		    ci->rev = xstrdup (vers->tag);
10081e72d8d2Sderaadt 		else
10099d9224ffStholo 		    ci->rev = RCS_whatbranch (finfo->rcs, vers->tag);
10101e72d8d2Sderaadt 	    else
10111e72d8d2Sderaadt 		ci->rev = (char *) NULL;
10121e72d8d2Sderaadt 	    ci->tag = xstrdup (vers->tag);
10131e72d8d2Sderaadt 	    ci->options = xstrdup(vers->options);
10141e72d8d2Sderaadt 	    p->data = (char *) ci;
10151e72d8d2Sderaadt 	    (void) addnode (cilist, p);
101678c87a5cStholo 
101778c87a5cStholo #ifdef PRESERVE_PERMISSIONS_SUPPORT
101878c87a5cStholo 	    if (preserve_perms)
101978c87a5cStholo 	    {
102078c87a5cStholo 		/* Add this file to hardlist, indexed on its inode.  When
102178c87a5cStholo 		   we are done, we can find out what files are hardlinked
102278c87a5cStholo 		   to a given file by looking up its inode in hardlist. */
102378c87a5cStholo 		char *fullpath;
102478c87a5cStholo 		Node *linkp;
102578c87a5cStholo 		struct hardlink_info *hlinfo;
102678c87a5cStholo 
102778c87a5cStholo 		/* Get the full pathname of the current file. */
102878c87a5cStholo 		fullpath = xmalloc (strlen(working_dir) +
102978c87a5cStholo 				    strlen(finfo->fullname) + 2);
103078c87a5cStholo 		sprintf (fullpath, "%s/%s", working_dir, finfo->fullname);
103178c87a5cStholo 
103278c87a5cStholo 		/* To permit following links in subdirectories, files
103378c87a5cStholo                    are keyed on finfo->fullname, not on finfo->name. */
103478c87a5cStholo 		linkp = lookup_file_by_inode (fullpath);
103578c87a5cStholo 
103678c87a5cStholo 		/* If linkp is NULL, the file doesn't exist... maybe
103778c87a5cStholo 		   we're doing a remove operation? */
103878c87a5cStholo 		if (linkp != NULL)
103978c87a5cStholo 		{
104078c87a5cStholo 		    /* Create a new hardlink_info node, which will record
104178c87a5cStholo 		       the current file's status and the links listed in its
104278c87a5cStholo 		       `hardlinks' delta field.  We will append this
104378c87a5cStholo 		       hardlink_info node to the appropriate hardlist entry. */
104478c87a5cStholo 		    hlinfo = (struct hardlink_info *)
104578c87a5cStholo 			xmalloc (sizeof (struct hardlink_info));
104678c87a5cStholo 		    hlinfo->status = status;
104778c87a5cStholo 		    linkp->data = (char *) hlinfo;
104878c87a5cStholo 		}
104978c87a5cStholo 	    }
105078c87a5cStholo #endif
105178c87a5cStholo 
10521e72d8d2Sderaadt 	    break;
10531e72d8d2Sderaadt 	case T_UNKNOWN:
10549d9224ffStholo 	    error (0, 0, "nothing known about `%s'", finfo->fullname);
10551e72d8d2Sderaadt 	    freevers_ts (&vers);
10561e72d8d2Sderaadt 	    return (1);
10571e72d8d2Sderaadt 	case T_UPTODATE:
10581e72d8d2Sderaadt 	    break;
10591e72d8d2Sderaadt 	default:
10601e72d8d2Sderaadt 	    error (0, 0, "CVS internal error: unknown status %d", status);
10611e72d8d2Sderaadt 	    break;
10621e72d8d2Sderaadt     }
10631e72d8d2Sderaadt 
10641e72d8d2Sderaadt     freevers_ts (&vers);
10651e72d8d2Sderaadt     return (0);
10661e72d8d2Sderaadt }
10671e72d8d2Sderaadt 
10681e72d8d2Sderaadt /*
1069b2b690deStholo  * By default, return the code that tells do_recursion to examine all
1070b2b690deStholo  * directories
10711e72d8d2Sderaadt  */
10721e72d8d2Sderaadt /* ARGSUSED */
10731e72d8d2Sderaadt static Dtype
check_direntproc(callerdat,dir,repos,update_dir,entries)10747e4d5a85Stholo check_direntproc (callerdat, dir, repos, update_dir, entries)
10757e4d5a85Stholo     void *callerdat;
10761e72d8d2Sderaadt     char *dir;
10771e72d8d2Sderaadt     char *repos;
10781e72d8d2Sderaadt     char *update_dir;
10797e4d5a85Stholo     List *entries;
10801e72d8d2Sderaadt {
1081b2b690deStholo     if (!isdir (dir))
1082b2b690deStholo 	return (R_SKIP_ALL);
1083b2b690deStholo 
10841e72d8d2Sderaadt     if (!quiet)
10851e72d8d2Sderaadt 	error (0, 0, "Examining %s", update_dir);
10861e72d8d2Sderaadt 
10871e72d8d2Sderaadt     return (R_PROCESS);
10881e72d8d2Sderaadt }
10891e72d8d2Sderaadt 
10901e72d8d2Sderaadt /*
10911e72d8d2Sderaadt  * Walklist proc to run pre-commit checks
10921e72d8d2Sderaadt  */
10931e72d8d2Sderaadt static int
precommit_list_proc(p,closure)10941e72d8d2Sderaadt precommit_list_proc (p, closure)
10951e72d8d2Sderaadt     Node *p;
10961e72d8d2Sderaadt     void *closure;
10971e72d8d2Sderaadt {
10987e4d5a85Stholo     struct logfile_info *li;
10997e4d5a85Stholo 
11007e4d5a85Stholo     li = (struct logfile_info *) p->data;
11017e4d5a85Stholo     if (li->type == T_ADDED
11027e4d5a85Stholo 	|| li->type == T_MODIFIED
11037e4d5a85Stholo 	|| li->type == T_REMOVED)
11041e72d8d2Sderaadt     {
11051e72d8d2Sderaadt 	run_arg (p->key);
11061e72d8d2Sderaadt     }
11071e72d8d2Sderaadt     return (0);
11081e72d8d2Sderaadt }
11091e72d8d2Sderaadt 
11101e72d8d2Sderaadt /*
11111e72d8d2Sderaadt  * Callback proc for pre-commit checking
11121e72d8d2Sderaadt  */
11131e72d8d2Sderaadt static int
precommit_proc(repository,filter)11141e72d8d2Sderaadt precommit_proc (repository, filter)
11151e72d8d2Sderaadt     char *repository;
11161e72d8d2Sderaadt     char *filter;
11171e72d8d2Sderaadt {
11181e72d8d2Sderaadt     /* see if the filter is there, only if it's a full path */
11191e72d8d2Sderaadt     if (isabsolute (filter))
11201e72d8d2Sderaadt     {
11211e72d8d2Sderaadt     	char *s, *cp;
11221e72d8d2Sderaadt 
11231e72d8d2Sderaadt 	s = xstrdup (filter);
11241e72d8d2Sderaadt 	for (cp = s; *cp; cp++)
11259fe7c2c3Stholo 	    if (isspace ((unsigned char) *cp))
11261e72d8d2Sderaadt 	    {
11271e72d8d2Sderaadt 		*cp = '\0';
11281e72d8d2Sderaadt 		break;
11291e72d8d2Sderaadt 	    }
11301e72d8d2Sderaadt 	if (!isfile (s))
11311e72d8d2Sderaadt 	{
11321e72d8d2Sderaadt 	    error (0, errno, "cannot find pre-commit filter `%s'", s);
11331e72d8d2Sderaadt 	    free (s);
11341e72d8d2Sderaadt 	    return (1);			/* so it fails! */
11351e72d8d2Sderaadt 	}
11361e72d8d2Sderaadt 	free (s);
11371e72d8d2Sderaadt     }
11381e72d8d2Sderaadt 
1139b2b690deStholo     run_setup (filter);
1140b2b690deStholo     run_arg (repository);
11412e5b458aStholo     (void) walklist (saved_ulist, precommit_list_proc, NULL);
11421e72d8d2Sderaadt     return (run_exec (RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL|RUN_REALLY));
11431e72d8d2Sderaadt }
11441e72d8d2Sderaadt 
11451e72d8d2Sderaadt /*
11461e72d8d2Sderaadt  * Run the pre-commit checks for the dir
11471e72d8d2Sderaadt  */
11481e72d8d2Sderaadt /* ARGSUSED */
11491e72d8d2Sderaadt static int
check_filesdoneproc(callerdat,err,repos,update_dir,entries)11507e4d5a85Stholo check_filesdoneproc (callerdat, err, repos, update_dir, entries)
11517e4d5a85Stholo     void *callerdat;
11521e72d8d2Sderaadt     int err;
11531e72d8d2Sderaadt     char *repos;
11541e72d8d2Sderaadt     char *update_dir;
11557e4d5a85Stholo     List *entries;
11561e72d8d2Sderaadt {
11571e72d8d2Sderaadt     int n;
11581e72d8d2Sderaadt     Node *p;
11591e72d8d2Sderaadt 
11601e72d8d2Sderaadt     /* find the update list for this dir */
11611e72d8d2Sderaadt     p = findnode (mulist, update_dir);
11621e72d8d2Sderaadt     if (p != NULL)
11632e5b458aStholo 	saved_ulist = ((struct master_lists *) p->data)->ulist;
11641e72d8d2Sderaadt     else
11652e5b458aStholo 	saved_ulist = (List *) NULL;
11661e72d8d2Sderaadt 
11671e72d8d2Sderaadt     /* skip the checks if there's nothing to do */
11682e5b458aStholo     if (saved_ulist == NULL || saved_ulist->list->next == saved_ulist->list)
11691e72d8d2Sderaadt 	return (err);
11701e72d8d2Sderaadt 
11711e72d8d2Sderaadt     /* run any pre-commit checks */
11721e72d8d2Sderaadt     if ((n = Parse_Info (CVSROOTADM_COMMITINFO, repos, precommit_proc, 1)) > 0)
11731e72d8d2Sderaadt     {
11741e72d8d2Sderaadt 	error (0, 0, "Pre-commit check failed");
11751e72d8d2Sderaadt 	err += n;
11761e72d8d2Sderaadt     }
11771e72d8d2Sderaadt 
11781e72d8d2Sderaadt     return (err);
11791e72d8d2Sderaadt }
11801e72d8d2Sderaadt 
11811e72d8d2Sderaadt /*
11821e72d8d2Sderaadt  * Do the work of committing a file
11831e72d8d2Sderaadt  */
11841e72d8d2Sderaadt static int maxrev;
1185ae5e8a9bStholo static char *sbranch;
11861e72d8d2Sderaadt 
11871e72d8d2Sderaadt /* ARGSUSED */
11881e72d8d2Sderaadt static int
commit_fileproc(callerdat,finfo)11897e4d5a85Stholo commit_fileproc (callerdat, finfo)
11907e4d5a85Stholo     void *callerdat;
119145381119Stholo     struct file_info *finfo;
11921e72d8d2Sderaadt {
11931e72d8d2Sderaadt     Node *p;
11941e72d8d2Sderaadt     int err = 0;
11951e72d8d2Sderaadt     List *ulist, *cilist;
11961e72d8d2Sderaadt     struct commit_info *ci;
11971e72d8d2Sderaadt 
1198172be96aStholo     /* Keep track of whether write_dirtag is a branch tag.
1199172be96aStholo        Note that if it is a branch tag in some files and a nonbranch tag
1200172be96aStholo        in others, treat it as a nonbranch tag.  It is possible that case
1201172be96aStholo        should elicit a warning or an error.  */
1202172be96aStholo     if (write_dirtag != NULL
1203172be96aStholo 	&& finfo->rcs != NULL)
1204172be96aStholo     {
1205172be96aStholo 	char *rev = RCS_getversion (finfo->rcs, write_dirtag, NULL, 1, NULL);
1206172be96aStholo 	if (rev != NULL
1207172be96aStholo 	    && !RCS_nodeisbranch (finfo->rcs, write_dirtag))
1208172be96aStholo 	    write_dirnonbranch = 1;
1209172be96aStholo 	if (rev != NULL)
1210172be96aStholo 	    free (rev);
1211172be96aStholo     }
1212172be96aStholo 
121345381119Stholo     if (finfo->update_dir[0] == '\0')
12141e72d8d2Sderaadt 	p = findnode (mulist, ".");
12151e72d8d2Sderaadt     else
121645381119Stholo 	p = findnode (mulist, finfo->update_dir);
12171e72d8d2Sderaadt 
12181e72d8d2Sderaadt     /*
12191e72d8d2Sderaadt      * if p is null, there were file type command line args which were
12201e72d8d2Sderaadt      * all up-to-date so nothing really needs to be done
12211e72d8d2Sderaadt      */
12221e72d8d2Sderaadt     if (p == NULL)
12231e72d8d2Sderaadt 	return (0);
12241e72d8d2Sderaadt     ulist = ((struct master_lists *) p->data)->ulist;
12251e72d8d2Sderaadt     cilist = ((struct master_lists *) p->data)->cilist;
12261e72d8d2Sderaadt 
12271e72d8d2Sderaadt     /*
12281e72d8d2Sderaadt      * At this point, we should have the commit message unless we were called
12291e72d8d2Sderaadt      * with files as args from the command line.  In that latter case, we
12301e72d8d2Sderaadt      * need to get the commit message ourselves
12311e72d8d2Sderaadt      */
1232ae5e8a9bStholo     if (!(got_message))
12331e72d8d2Sderaadt     {
12341e72d8d2Sderaadt 	got_message = 1;
1235ae5e8a9bStholo 	if (use_editor)
12362e5b458aStholo 	    do_editor (finfo->update_dir, &saved_message,
12372e5b458aStholo 		       finfo->repository, ulist);
12382e5b458aStholo 	do_verify (saved_message, finfo->repository);
12391e72d8d2Sderaadt     }
12401e72d8d2Sderaadt 
124145381119Stholo     p = findnode (cilist, finfo->file);
12421e72d8d2Sderaadt     if (p == NULL)
12431e72d8d2Sderaadt 	return (0);
12441e72d8d2Sderaadt 
12451e72d8d2Sderaadt     ci = (struct commit_info *) p->data;
12461e72d8d2Sderaadt     if (ci->status == T_MODIFIED)
12471e72d8d2Sderaadt     {
12487e4d5a85Stholo 	if (finfo->rcs == NULL)
12497e4d5a85Stholo 	    error (1, 0, "internal error: no parsed RCS file");
12507e4d5a85Stholo 	if (lock_RCS (finfo->file, finfo->rcs, ci->rev,
12517e4d5a85Stholo 		      finfo->repository) != 0)
12521e72d8d2Sderaadt 	{
12537e4d5a85Stholo 	    unlockrcs (finfo->rcs);
12541e72d8d2Sderaadt 	    err = 1;
12551e72d8d2Sderaadt 	    goto out;
12561e72d8d2Sderaadt 	}
12571e72d8d2Sderaadt     }
12581e72d8d2Sderaadt     else if (ci->status == T_ADDED)
12591e72d8d2Sderaadt     {
126045381119Stholo 	if (checkaddfile (finfo->file, finfo->repository, ci->tag, ci->options,
12619d9224ffStholo 			  &finfo->rcs) != 0)
12621e72d8d2Sderaadt 	{
126345381119Stholo 	    fixaddfile (finfo->file, finfo->repository);
12641e72d8d2Sderaadt 	    err = 1;
12651e72d8d2Sderaadt 	    goto out;
12661e72d8d2Sderaadt 	}
12671e72d8d2Sderaadt 
12681e72d8d2Sderaadt 	/* adding files with a tag, now means adding them on a branch.
12691e72d8d2Sderaadt 	   Since the branch test was done in check_fileproc for
12701e72d8d2Sderaadt 	   modified files, we need to stub it in again here. */
12711e72d8d2Sderaadt 
12729fe7c2c3Stholo 	if (ci->tag
12739fe7c2c3Stholo 
12749fe7c2c3Stholo 	    /* If numeric, it is on the trunk; check_fileproc enforced
12759fe7c2c3Stholo 	       this.  */
12769fe7c2c3Stholo 	    && !isdigit ((unsigned char) ci->tag[0]))
12777e4d5a85Stholo 	{
12787e4d5a85Stholo 	    if (finfo->rcs == NULL)
12797e4d5a85Stholo 		error (1, 0, "internal error: no parsed RCS file");
1280bde78045Stholo 	    if (ci->rev)
1281bde78045Stholo 		free (ci->rev);
12829d9224ffStholo 	    ci->rev = RCS_whatbranch (finfo->rcs, ci->tag);
12837e4d5a85Stholo 	    err = Checkin ('A', finfo, finfo->rcs->path, ci->rev,
12842e5b458aStholo 			   ci->tag, ci->options, saved_message);
12851e72d8d2Sderaadt 	    if (err != 0)
12861e72d8d2Sderaadt 	    {
12877e4d5a85Stholo 		unlockrcs (finfo->rcs);
12887e4d5a85Stholo 		fixbranch (finfo->rcs, sbranch);
12891e72d8d2Sderaadt 	    }
12901e72d8d2Sderaadt 
129145381119Stholo 	    (void) time (&last_register_time);
129245381119Stholo 
12931e72d8d2Sderaadt 	    ci->status = T_UPTODATE;
12941e72d8d2Sderaadt 	}
12951e72d8d2Sderaadt     }
12961e72d8d2Sderaadt 
12971e72d8d2Sderaadt     /*
12981e72d8d2Sderaadt      * Add the file for real
12991e72d8d2Sderaadt      */
13001e72d8d2Sderaadt     if (ci->status == T_ADDED)
13011e72d8d2Sderaadt     {
13021e72d8d2Sderaadt 	char *xrev = (char *) NULL;
13031e72d8d2Sderaadt 
13041e72d8d2Sderaadt 	if (ci->rev == NULL)
13051e72d8d2Sderaadt 	{
13061e72d8d2Sderaadt 	    /* find the max major rev number in this directory */
13071e72d8d2Sderaadt 	    maxrev = 0;
130845381119Stholo 	    (void) walklist (finfo->entries, findmaxrev, NULL);
130979822b2aStholo 	    if (finfo->rcs->head) {
131079822b2aStholo 		/* resurrecting: include dead revision */
131179822b2aStholo 		int thisrev = atoi (finfo->rcs->head);
131279822b2aStholo 		if (thisrev > maxrev)
131379822b2aStholo 		    maxrev = thisrev;
131479822b2aStholo 	    }
13151e72d8d2Sderaadt 	    if (maxrev == 0)
13161e72d8d2Sderaadt 		maxrev = 1;
13171e72d8d2Sderaadt 	    xrev = xmalloc (20);
13181e72d8d2Sderaadt 	    (void) sprintf (xrev, "%d", maxrev);
13191e72d8d2Sderaadt 	}
13201e72d8d2Sderaadt 
13211e72d8d2Sderaadt 	/* XXX - an added file with symbolic -r should add tag as well */
13227e4d5a85Stholo 	err = finaladd (finfo, ci->rev ? ci->rev : xrev, ci->tag, ci->options);
13231e72d8d2Sderaadt 	if (xrev)
13241e72d8d2Sderaadt 	    free (xrev);
13251e72d8d2Sderaadt     }
13261e72d8d2Sderaadt     else if (ci->status == T_MODIFIED)
13271e72d8d2Sderaadt     {
13287e4d5a85Stholo 	err = Checkin ('M', finfo,
13297e4d5a85Stholo 		       finfo->rcs->path, ci->rev, ci->tag,
13302e5b458aStholo 		       ci->options, saved_message);
133145381119Stholo 
133245381119Stholo 	(void) time (&last_register_time);
133345381119Stholo 
13341e72d8d2Sderaadt 	if (err != 0)
13351e72d8d2Sderaadt 	{
13367e4d5a85Stholo 	    unlockrcs (finfo->rcs);
13377e4d5a85Stholo 	    fixbranch (finfo->rcs, sbranch);
13381e72d8d2Sderaadt 	}
13391e72d8d2Sderaadt     }
13401e72d8d2Sderaadt     else if (ci->status == T_REMOVED)
13411e72d8d2Sderaadt     {
13422e5b458aStholo 	err = remove_file (finfo, ci->tag, saved_message);
13431e72d8d2Sderaadt #ifdef SERVER_SUPPORT
13441e72d8d2Sderaadt 	if (server_active) {
13451e72d8d2Sderaadt 	    server_scratch_entry_only ();
13467e4d5a85Stholo 	    server_updated (finfo,
13477e4d5a85Stholo 			    NULL,
13487e4d5a85Stholo 
13491e72d8d2Sderaadt 			    /* Doesn't matter, it won't get checked.  */
13507e4d5a85Stholo 			    SERVER_UPDATED,
13517e4d5a85Stholo 
135278c87a5cStholo 			    (mode_t) -1,
135378c87a5cStholo 			    (unsigned char *) NULL,
135478c87a5cStholo 			    (struct buffer *) NULL);
13551e72d8d2Sderaadt 	}
13561e72d8d2Sderaadt #endif
13571e72d8d2Sderaadt     }
13581e72d8d2Sderaadt 
1359e5b68881Stholo     /* Clearly this is right for T_MODIFIED.  I haven't thought so much
1360e5b68881Stholo        about T_ADDED or T_REMOVED.  */
136145381119Stholo     notify_do ('C', finfo->file, getcaller (), NULL, NULL, finfo->repository);
1362e5b68881Stholo 
13631e72d8d2Sderaadt out:
13641e72d8d2Sderaadt     if (err != 0)
13651e72d8d2Sderaadt     {
13661e72d8d2Sderaadt 	/* on failure, remove the file from ulist */
136745381119Stholo 	p = findnode (ulist, finfo->file);
13681e72d8d2Sderaadt 	if (p)
13691e72d8d2Sderaadt 	    delnode (p);
13701e72d8d2Sderaadt     }
1371ae5e8a9bStholo     else
1372ae5e8a9bStholo     {
1373ae5e8a9bStholo 	/* On success, retrieve the new version number of the file and
1374ae5e8a9bStholo            copy it into the log information (see logmsg.c
1375ae5e8a9bStholo            (logfile_write) for more details).  We should only update
1376ae5e8a9bStholo            the version number for files that have been added or
1377ae5e8a9bStholo            modified but not removed.  Why?  classify_file_internal
1378ae5e8a9bStholo            will return the version number of a file even after it has
1379ae5e8a9bStholo            been removed from the archive, which is not the behavior we
1380ae5e8a9bStholo            want for our commitlog messages; we want the old version
1381ae5e8a9bStholo            number and then "NONE." */
1382ae5e8a9bStholo 
1383ae5e8a9bStholo 	if (ci->status != T_REMOVED)
1384ae5e8a9bStholo 	{
1385ae5e8a9bStholo 	    p = findnode (ulist, finfo->file);
1386ae5e8a9bStholo 	    if (p)
1387ae5e8a9bStholo 	    {
1388ae5e8a9bStholo 		Vers_TS *vers;
1389ae5e8a9bStholo 		struct logfile_info *li;
1390ae5e8a9bStholo 
1391ae5e8a9bStholo 		(void) classify_file_internal (finfo, &vers);
1392ae5e8a9bStholo 		li = (struct logfile_info *) p->data;
1393ae5e8a9bStholo 		li->rev_new = xstrdup (vers->vn_rcs);
1394ae5e8a9bStholo 		freevers_ts (&vers);
1395ae5e8a9bStholo 	    }
1396ae5e8a9bStholo 	}
1397ae5e8a9bStholo     }
1398bde78045Stholo     if (SIG_inCrSect ())
1399bde78045Stholo 	SIG_endCrSect ();
14001e72d8d2Sderaadt 
14011e72d8d2Sderaadt     return (err);
14021e72d8d2Sderaadt }
14031e72d8d2Sderaadt 
14041e72d8d2Sderaadt /*
14051e72d8d2Sderaadt  * Log the commit and clean up the update list
14061e72d8d2Sderaadt  */
14071e72d8d2Sderaadt /* ARGSUSED */
14081e72d8d2Sderaadt static int
commit_filesdoneproc(callerdat,err,repository,update_dir,entries)14097e4d5a85Stholo commit_filesdoneproc (callerdat, err, repository, update_dir, entries)
14107e4d5a85Stholo     void *callerdat;
14111e72d8d2Sderaadt     int err;
14121e72d8d2Sderaadt     char *repository;
14131e72d8d2Sderaadt     char *update_dir;
14147e4d5a85Stholo     List *entries;
14151e72d8d2Sderaadt {
14161e72d8d2Sderaadt     Node *p;
14171e72d8d2Sderaadt     List *ulist;
14181e72d8d2Sderaadt 
14191e72d8d2Sderaadt     p = findnode (mulist, update_dir);
14201e72d8d2Sderaadt     if (p == NULL)
14211e72d8d2Sderaadt 	return (err);
14221e72d8d2Sderaadt 
14231e72d8d2Sderaadt     ulist = ((struct master_lists *) p->data)->ulist;
14241e72d8d2Sderaadt 
14251e72d8d2Sderaadt     got_message = 0;
14261e72d8d2Sderaadt 
14271e72d8d2Sderaadt 
14282e5b458aStholo     Update_Logfile (repository, saved_message, (FILE *) 0, ulist);
14291e72d8d2Sderaadt 
143045381119Stholo     /* Build the administrative files if necessary.  */
143145381119Stholo     {
143245381119Stholo 	char *p;
143345381119Stholo 
143479822b2aStholo 	if (strncmp (current_parsed_root->directory, repository,
143579822b2aStholo 		     strlen (current_parsed_root->directory)) != 0)
14362e5b458aStholo 	    error (0, 0,
14372e5b458aStholo 		 "internal error: repository (%s) doesn't begin with root (%s)",
143879822b2aStholo 		   repository, current_parsed_root->directory);
143979822b2aStholo 	p = repository + strlen (current_parsed_root->directory);
144045381119Stholo 	if (*p == '/')
144145381119Stholo 	    ++p;
14422e5b458aStholo 	if (strcmp ("CVSROOT", p) == 0
14432e5b458aStholo 	    /* Check for subdirectories because people may want to create
14442e5b458aStholo 	       subdirectories and list files therein in checkoutlist.  */
14452e5b458aStholo 	    || strncmp ("CVSROOT/", p, strlen ("CVSROOT/")) == 0
14462e5b458aStholo 	    )
144745381119Stholo 	{
144845381119Stholo 	    /* "Database" might a little bit grandiose and/or vague,
144945381119Stholo 	       but "checked-out copies of administrative files, unless
145045381119Stholo 	       in the case of modules and you are using ndbm in which
145145381119Stholo 	       case modules.{pag,dir,db}" is verbose and excessively
145245381119Stholo 	       focused on how the database is implemented.  */
145345381119Stholo 
14542e5b458aStholo 	    /* mkmodules requires the absolute name of the CVSROOT directory.
14552e5b458aStholo 	       Remove anything after the `CVSROOT' component -- this is
14562e5b458aStholo 	       necessary when committing in a subdirectory of CVSROOT.  */
14572e5b458aStholo 	    char *admin_dir = xstrdup (repository);
1458eea99451Stholo 	    int cvsrootlen = strlen ("CVSROOT");
1459eea99451Stholo 	    assert (admin_dir[p - repository + cvsrootlen] == '\0'
1460eea99451Stholo 		    || admin_dir[p - repository + cvsrootlen] == '/');
1461eea99451Stholo 	    admin_dir[p - repository + cvsrootlen] = '\0';
14622e5b458aStholo 
146345381119Stholo 	    cvs_output (program_name, 0);
146445381119Stholo 	    cvs_output (" ", 1);
146545381119Stholo 	    cvs_output (command_name, 0);
146645381119Stholo 	    cvs_output (": Rebuilding administrative file database\n", 0);
14672e5b458aStholo 	    mkmodules (admin_dir);
14682e5b458aStholo 	    free (admin_dir);
146945381119Stholo 	}
147045381119Stholo     }
147145381119Stholo 
14721e72d8d2Sderaadt     if (err == 0 && run_module_prog)
14731e72d8d2Sderaadt     {
14741e72d8d2Sderaadt 	FILE *fp;
14751e72d8d2Sderaadt 
14767e4d5a85Stholo 	if ((fp = CVS_FOPEN (CVSADM_CIPROG, "r")) != NULL)
14771e72d8d2Sderaadt 	{
1478e5b68881Stholo 	    char *line;
1479e5b68881Stholo 	    int line_length;
1480e5b68881Stholo 	    size_t line_chars_allocated;
14812e5b458aStholo 	    char *repos;
1482e5b68881Stholo 
1483e5b68881Stholo 	    line = NULL;
1484e5b68881Stholo 	    line_chars_allocated = 0;
1485*f9bbbf45Sfgsch 	    line_length = get_line (&line, &line_chars_allocated, fp);
1486e5b68881Stholo 	    if (line_length > 0)
14871e72d8d2Sderaadt 	    {
1488e5b68881Stholo 		/* Remove any trailing newline.  */
1489e5b68881Stholo 		if (line[line_length - 1] == '\n')
1490e5b68881Stholo 		    line[--line_length] = '\0';
14912e5b458aStholo 		repos = Name_Repository ((char *) NULL, update_dir);
1492b2b690deStholo 		run_setup (line);
14932e5b458aStholo 		run_arg (repos);
1494ae5e8a9bStholo 		cvs_output (program_name, 0);
1495ae5e8a9bStholo 		cvs_output (" ", 1);
1496ae5e8a9bStholo 		cvs_output (command_name, 0);
1497ae5e8a9bStholo 		cvs_output (": Executing '", 0);
14981e72d8d2Sderaadt 		run_print (stdout);
1499ae5e8a9bStholo 		cvs_output ("'\n", 0);
1500bde78045Stholo 		cvs_flushout ();
15011e72d8d2Sderaadt 		(void) run_exec (RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL);
15022e5b458aStholo 		free (repos);
15031e72d8d2Sderaadt 	    }
1504e5b68881Stholo 	    else
1505e5b68881Stholo 	    {
1506e5b68881Stholo 		if (ferror (fp))
1507e5b68881Stholo 		    error (0, errno, "warning: error reading %s",
1508e5b68881Stholo 			   CVSADM_CIPROG);
1509e5b68881Stholo 	    }
1510e5b68881Stholo 	    if (line != NULL)
1511e5b68881Stholo 		free (line);
1512e5b68881Stholo 	    if (fclose (fp) < 0)
1513e5b68881Stholo 		error (0, errno, "warning: cannot close %s", CVSADM_CIPROG);
1514e5b68881Stholo 	}
1515e5b68881Stholo 	else
1516e5b68881Stholo 	{
1517e5b68881Stholo 	    if (! existence_error (errno))
1518e5b68881Stholo 		error (0, errno, "warning: cannot open %s", CVSADM_CIPROG);
15191e72d8d2Sderaadt 	}
15201e72d8d2Sderaadt     }
15211e72d8d2Sderaadt 
15221e72d8d2Sderaadt     return (err);
15231e72d8d2Sderaadt }
15241e72d8d2Sderaadt 
15251e72d8d2Sderaadt /*
1526b2b690deStholo  * Get the log message for a dir
15271e72d8d2Sderaadt  */
15281e72d8d2Sderaadt /* ARGSUSED */
15291e72d8d2Sderaadt static Dtype
commit_direntproc(callerdat,dir,repos,update_dir,entries)15307e4d5a85Stholo commit_direntproc (callerdat, dir, repos, update_dir, entries)
15317e4d5a85Stholo     void *callerdat;
15321e72d8d2Sderaadt     char *dir;
15331e72d8d2Sderaadt     char *repos;
15341e72d8d2Sderaadt     char *update_dir;
15357e4d5a85Stholo     List *entries;
15361e72d8d2Sderaadt {
15371e72d8d2Sderaadt     Node *p;
15381e72d8d2Sderaadt     List *ulist;
15391e72d8d2Sderaadt     char *real_repos;
15401e72d8d2Sderaadt 
1541b2b690deStholo     if (!isdir (dir))
1542b2b690deStholo 	return (R_SKIP_ALL);
1543b2b690deStholo 
15441e72d8d2Sderaadt     /* find the update list for this dir */
15451e72d8d2Sderaadt     p = findnode (mulist, update_dir);
15461e72d8d2Sderaadt     if (p != NULL)
15471e72d8d2Sderaadt 	ulist = ((struct master_lists *) p->data)->ulist;
15481e72d8d2Sderaadt     else
15491e72d8d2Sderaadt 	ulist = (List *) NULL;
15501e72d8d2Sderaadt 
15511e72d8d2Sderaadt     /* skip the files as an optimization */
15521e72d8d2Sderaadt     if (ulist == NULL || ulist->list->next == ulist->list)
15531e72d8d2Sderaadt 	return (R_SKIP_FILES);
15541e72d8d2Sderaadt 
15551e72d8d2Sderaadt     /* get commit message */
15561e72d8d2Sderaadt     real_repos = Name_Repository (dir, update_dir);
1557ae5e8a9bStholo     got_message = 1;
1558ae5e8a9bStholo     if (use_editor)
15592e5b458aStholo 	do_editor (update_dir, &saved_message, real_repos, ulist);
15602e5b458aStholo     do_verify (saved_message, real_repos);
15611e72d8d2Sderaadt     free (real_repos);
15621e72d8d2Sderaadt     return (R_PROCESS);
15631e72d8d2Sderaadt }
15641e72d8d2Sderaadt 
15651e72d8d2Sderaadt /*
15661e72d8d2Sderaadt  * Process the post-commit proc if necessary
15671e72d8d2Sderaadt  */
15681e72d8d2Sderaadt /* ARGSUSED */
15691e72d8d2Sderaadt static int
commit_dirleaveproc(callerdat,dir,err,update_dir,entries)15707e4d5a85Stholo commit_dirleaveproc (callerdat, dir, err, update_dir, entries)
15717e4d5a85Stholo     void *callerdat;
15721e72d8d2Sderaadt     char *dir;
15731e72d8d2Sderaadt     int err;
15741e72d8d2Sderaadt     char *update_dir;
15757e4d5a85Stholo     List *entries;
15761e72d8d2Sderaadt {
15771e72d8d2Sderaadt     /* update the per-directory tag info */
1578172be96aStholo     /* FIXME?  Why?  The "commit examples" node of cvs.texinfo briefly
1579172be96aStholo        mentions commit -r being sticky, but apparently in the context of
1580172be96aStholo        this being a confusing feature!  */
15811e72d8d2Sderaadt     if (err == 0 && write_dirtag != NULL)
15821e72d8d2Sderaadt     {
1583bde78045Stholo 	char *repos = Name_Repository (dir, update_dir);
1584172be96aStholo 	WriteTag (NULL, write_dirtag, NULL, write_dirnonbranch,
1585bde78045Stholo 		  update_dir, repos);
1586bde78045Stholo 	free (repos);
15871e72d8d2Sderaadt     }
15881e72d8d2Sderaadt 
15891e72d8d2Sderaadt     return (err);
15901e72d8d2Sderaadt }
15911e72d8d2Sderaadt 
15921e72d8d2Sderaadt /*
15931e72d8d2Sderaadt  * find the maximum major rev number in an entries file
15941e72d8d2Sderaadt  */
15951e72d8d2Sderaadt static int
findmaxrev(p,closure)15961e72d8d2Sderaadt findmaxrev (p, closure)
15971e72d8d2Sderaadt     Node *p;
15981e72d8d2Sderaadt     void *closure;
15991e72d8d2Sderaadt {
16001e72d8d2Sderaadt     int thisrev;
16011e72d8d2Sderaadt     Entnode *entdata;
16021e72d8d2Sderaadt 
16031e72d8d2Sderaadt     entdata = (Entnode *) p->data;
16047e4d5a85Stholo     if (entdata->type != ENT_FILE)
16057e4d5a85Stholo 	return (0);
16061e72d8d2Sderaadt     thisrev = atoi (entdata->version);
16071e72d8d2Sderaadt     if (thisrev > maxrev)
16081e72d8d2Sderaadt 	maxrev = thisrev;
16091e72d8d2Sderaadt     return (0);
16101e72d8d2Sderaadt }
16111e72d8d2Sderaadt 
16121e72d8d2Sderaadt /*
16131e72d8d2Sderaadt  * Actually remove a file by moving it to the attic
16141e72d8d2Sderaadt  * XXX - if removing a ,v file that is a relative symbolic link to
16151e72d8d2Sderaadt  * another ,v file, we probably should add a ".." component to the
16161e72d8d2Sderaadt  * link to keep it relative after we move it into the attic.
16179fe7c2c3Stholo 
16189fe7c2c3Stholo    Return value is 0 on success, or >0 on error (in which case we have
16199fe7c2c3Stholo    printed an error message).  */
16201e72d8d2Sderaadt static int
remove_file(finfo,tag,message)16217e4d5a85Stholo remove_file (finfo, tag, message)
16227e4d5a85Stholo     struct file_info *finfo;
16231e72d8d2Sderaadt     char *tag;
16241e72d8d2Sderaadt     char *message;
16251e72d8d2Sderaadt {
16261e72d8d2Sderaadt     int retcode;
16271e72d8d2Sderaadt 
16281e72d8d2Sderaadt     int branch;
162945381119Stholo     int lockflag;
16301e72d8d2Sderaadt     char *corev;
16311e72d8d2Sderaadt     char *rev;
16321e72d8d2Sderaadt     char *prev_rev;
16337e4d5a85Stholo     char *old_path;
16341e72d8d2Sderaadt 
16351e72d8d2Sderaadt     corev = NULL;
16361e72d8d2Sderaadt     rev = NULL;
16371e72d8d2Sderaadt     prev_rev = NULL;
16381e72d8d2Sderaadt 
16391e72d8d2Sderaadt     retcode = 0;
16401e72d8d2Sderaadt 
16417e4d5a85Stholo     if (finfo->rcs == NULL)
16427e4d5a85Stholo 	error (1, 0, "internal error: no parsed RCS file");
16431e72d8d2Sderaadt 
16441e72d8d2Sderaadt     branch = 0;
1645b2b690deStholo     if (tag && !(branch = RCS_nodeisbranch (finfo->rcs, tag)))
16461e72d8d2Sderaadt     {
16471e72d8d2Sderaadt 	/* a symbolic tag is specified; just remove the tag from the file */
1648b2b690deStholo 	if ((retcode = RCS_deltag (finfo->rcs, tag)) != 0)
16491e72d8d2Sderaadt 	{
16501e72d8d2Sderaadt 	    if (!quiet)
16511e72d8d2Sderaadt 		error (0, retcode == -1 ? errno : 0,
16527e4d5a85Stholo 		       "failed to remove tag `%s' from `%s'", tag,
16537e4d5a85Stholo 		       finfo->fullname);
16541e72d8d2Sderaadt 	    return (1);
16551e72d8d2Sderaadt 	}
1656b2b690deStholo 	RCS_rewrite (finfo->rcs, NULL, NULL);
16577e4d5a85Stholo 	Scratch_Entry (finfo->entries, finfo->file);
16581e72d8d2Sderaadt 	return (0);
16591e72d8d2Sderaadt     }
16601e72d8d2Sderaadt 
16611e72d8d2Sderaadt     /* we are removing the file from either the head or a branch */
16621e72d8d2Sderaadt     /* commit a new, dead revision. */
16631e72d8d2Sderaadt 
16641e72d8d2Sderaadt     /* Print message indicating that file is going to be removed. */
1665ae5e8a9bStholo     cvs_output ("Removing ", 0);
1666ae5e8a9bStholo     cvs_output (finfo->fullname, 0);
1667ae5e8a9bStholo     cvs_output (";\n", 0);
16681e72d8d2Sderaadt 
16691e72d8d2Sderaadt     rev = NULL;
16707e4d5a85Stholo     lockflag = 1;
16711e72d8d2Sderaadt     if (branch)
16721e72d8d2Sderaadt     {
16731e72d8d2Sderaadt 	char *branchname;
16741e72d8d2Sderaadt 
16757e4d5a85Stholo 	rev = RCS_whatbranch (finfo->rcs, tag);
16761e72d8d2Sderaadt 	if (rev == NULL)
16771e72d8d2Sderaadt 	{
16781e72d8d2Sderaadt 	    error (0, 0, "cannot find branch \"%s\".", tag);
16791e72d8d2Sderaadt 	    return (1);
16801e72d8d2Sderaadt 	}
16811e72d8d2Sderaadt 
16827e4d5a85Stholo 	branchname = RCS_getbranch (finfo->rcs, rev, 1);
16831e72d8d2Sderaadt 	if (branchname == NULL)
16841e72d8d2Sderaadt 	{
16851e72d8d2Sderaadt 	    /* no revision exists on this branch.  use the previous
16861e72d8d2Sderaadt 	       revision but do not lock. */
16877e4d5a85Stholo 	    corev = RCS_gettag (finfo->rcs, tag, 1, (int *) NULL);
16881e72d8d2Sderaadt 	    prev_rev = xstrdup(rev);
168945381119Stholo 	    lockflag = 0;
16901e72d8d2Sderaadt 	} else
16911e72d8d2Sderaadt 	{
16921e72d8d2Sderaadt 	    corev = xstrdup (rev);
16931e72d8d2Sderaadt 	    prev_rev = xstrdup(branchname);
16941e72d8d2Sderaadt 	    free (branchname);
16951e72d8d2Sderaadt 	}
16961e72d8d2Sderaadt 
16971e72d8d2Sderaadt     } else  /* Not a branch */
16981e72d8d2Sderaadt     {
16991e72d8d2Sderaadt         /* Get current head revision of file. */
17007e4d5a85Stholo 	prev_rev = RCS_head (finfo->rcs);
17011e72d8d2Sderaadt     }
17021e72d8d2Sderaadt 
17031e72d8d2Sderaadt     /* if removing without a tag or a branch, then make sure the default
17041e72d8d2Sderaadt        branch is the trunk. */
17051e72d8d2Sderaadt     if (!tag && !branch)
17061e72d8d2Sderaadt     {
17077e4d5a85Stholo         if (RCS_setbranch (finfo->rcs, NULL) != 0)
17081e72d8d2Sderaadt 	{
17091e72d8d2Sderaadt 	    error (0, 0, "cannot change branch to default for %s",
17107e4d5a85Stholo 		   finfo->fullname);
17111e72d8d2Sderaadt 	    return (1);
17121e72d8d2Sderaadt 	}
1713b2b690deStholo 	RCS_rewrite (finfo->rcs, NULL, NULL);
17141e72d8d2Sderaadt     }
17151e72d8d2Sderaadt 
17161e72d8d2Sderaadt     /* check something out.  Generally this is the head.  If we have a
17177e4d5a85Stholo        particular rev, then name it.  */
17187e4d5a85Stholo     retcode = RCS_checkout (finfo->rcs, finfo->file, rev ? corev : NULL,
1719172be96aStholo 			    (char *) NULL, (char *) NULL, RUN_TTY,
1720172be96aStholo 			    (RCSCHECKOUTPROC) NULL, (void *) NULL);
172145381119Stholo     if (retcode != 0)
172245381119Stholo     {
172378c87a5cStholo 	error (0, 0,
17247e4d5a85Stholo 	       "failed to check out `%s'", finfo->fullname);
17251e72d8d2Sderaadt 	return (1);
17261e72d8d2Sderaadt     }
17271e72d8d2Sderaadt 
17287e4d5a85Stholo     /* Except when we are creating a branch, lock the revision so that
17297e4d5a85Stholo        we can check in the new revision.  */
17307e4d5a85Stholo     if (lockflag)
1731b2b690deStholo     {
1732b2b690deStholo 	if (RCS_lock (finfo->rcs, rev ? corev : NULL, 1) == 0)
1733b2b690deStholo 	    RCS_rewrite (finfo->rcs, NULL, NULL);
1734b2b690deStholo     }
17357e4d5a85Stholo 
17361e72d8d2Sderaadt     if (corev != NULL)
17371e72d8d2Sderaadt 	free (corev);
17381e72d8d2Sderaadt 
1739b2b690deStholo     retcode = RCS_checkin (finfo->rcs, finfo->file, message, rev,
17407e4d5a85Stholo 			   RCS_FLAGS_DEAD | RCS_FLAGS_QUIET);
174145381119Stholo     if (retcode	!= 0)
174245381119Stholo     {
17431e72d8d2Sderaadt 	if (!quiet)
17441e72d8d2Sderaadt 	    error (0, retcode == -1 ? errno : 0,
17457e4d5a85Stholo 		   "failed to commit dead revision for `%s'", finfo->fullname);
17461e72d8d2Sderaadt 	return (1);
17471e72d8d2Sderaadt     }
1748bde78045Stholo     /* At this point, the file has been committed as removed.  We should
1749bde78045Stholo        probably tell the history file about it  */
1750bde78045Stholo     history_write ('R', NULL, finfo->rcs->head, finfo->file, finfo->repository);
17511e72d8d2Sderaadt 
17521e72d8d2Sderaadt     if (rev != NULL)
17531e72d8d2Sderaadt 	free (rev);
17541e72d8d2Sderaadt 
17559fe7c2c3Stholo     old_path = xstrdup (finfo->rcs->path);
17561e72d8d2Sderaadt     if (!branch)
17579fe7c2c3Stholo 	RCS_setattic (finfo->rcs, 1);
17581e72d8d2Sderaadt 
17591e72d8d2Sderaadt     /* Print message that file was removed. */
1760ae5e8a9bStholo     cvs_output (old_path, 0);
1761ae5e8a9bStholo     cvs_output ("  <--  ", 0);
1762ae5e8a9bStholo     cvs_output (finfo->file, 0);
1763ae5e8a9bStholo     cvs_output ("\nnew revision: delete; previous revision: ", 0);
1764ae5e8a9bStholo     cvs_output (prev_rev, 0);
1765ae5e8a9bStholo     cvs_output ("\ndone\n", 0);
17661e72d8d2Sderaadt     free(prev_rev);
17671e72d8d2Sderaadt 
17687e4d5a85Stholo     free (old_path);
17697e4d5a85Stholo 
17707e4d5a85Stholo     Scratch_Entry (finfo->entries, finfo->file);
17711e72d8d2Sderaadt     return (0);
17721e72d8d2Sderaadt }
17731e72d8d2Sderaadt 
17741e72d8d2Sderaadt /*
17751e72d8d2Sderaadt  * Do the actual checkin for added files
17761e72d8d2Sderaadt  */
17771e72d8d2Sderaadt static int
finaladd(finfo,rev,tag,options)17787e4d5a85Stholo finaladd (finfo, rev, tag, options)
17797e4d5a85Stholo     struct file_info *finfo;
17801e72d8d2Sderaadt     char *rev;
17811e72d8d2Sderaadt     char *tag;
17821e72d8d2Sderaadt     char *options;
17831e72d8d2Sderaadt {
17841e72d8d2Sderaadt     int ret;
1785ae5e8a9bStholo     char *rcs;
17861e72d8d2Sderaadt 
1787ae5e8a9bStholo     rcs = locate_rcs (finfo->file, finfo->repository);
17882e5b458aStholo     ret = Checkin ('A', finfo, rcs, rev, tag, options, saved_message);
17891e72d8d2Sderaadt     if (ret == 0)
17901e72d8d2Sderaadt     {
1791ae5e8a9bStholo 	char *tmp = xmalloc (strlen (finfo->file) + sizeof (CVSADM)
1792ae5e8a9bStholo 			     + sizeof (CVSEXT_LOG) + 10);
17937e4d5a85Stholo 	(void) sprintf (tmp, "%s/%s%s", CVSADM, finfo->file, CVSEXT_LOG);
17949fe7c2c3Stholo 	if (unlink_file (tmp) < 0
17959fe7c2c3Stholo 	    && !existence_error (errno))
17969fe7c2c3Stholo 	    error (0, errno, "cannot remove %s", tmp);
1797ae5e8a9bStholo 	free (tmp);
17981e72d8d2Sderaadt     }
17991e72d8d2Sderaadt     else
18007e4d5a85Stholo 	fixaddfile (finfo->file, finfo->repository);
180145381119Stholo 
180245381119Stholo     (void) time (&last_register_time);
1803ae5e8a9bStholo     free (rcs);
180445381119Stholo 
18051e72d8d2Sderaadt     return (ret);
18061e72d8d2Sderaadt }
18071e72d8d2Sderaadt 
18081e72d8d2Sderaadt /*
18091e72d8d2Sderaadt  * Unlock an rcs file
18101e72d8d2Sderaadt  */
18111e72d8d2Sderaadt static void
unlockrcs(rcs)18127e4d5a85Stholo unlockrcs (rcs)
18137e4d5a85Stholo     RCSNode *rcs;
18141e72d8d2Sderaadt {
18157e4d5a85Stholo     int retcode;
18161e72d8d2Sderaadt 
18171e72d8d2Sderaadt     if ((retcode = RCS_unlock (rcs, NULL, 0)) != 0)
18181e72d8d2Sderaadt 	error (retcode == -1 ? 1 : 0, retcode == -1 ? errno : 0,
18197e4d5a85Stholo 	       "could not unlock %s", rcs->path);
1820b2b690deStholo     else
1821b2b690deStholo 	RCS_rewrite (rcs, NULL, NULL);
18221e72d8d2Sderaadt }
18231e72d8d2Sderaadt 
18241e72d8d2Sderaadt /*
18251e72d8d2Sderaadt  * remove a partially added file.  if we can parse it, leave it alone.
18261e72d8d2Sderaadt  */
18271e72d8d2Sderaadt static void
fixaddfile(file,repository)18281e72d8d2Sderaadt fixaddfile (file, repository)
18291e72d8d2Sderaadt     char *file;
18301e72d8d2Sderaadt     char *repository;
18311e72d8d2Sderaadt {
18321e72d8d2Sderaadt     RCSNode *rcsfile;
1833ae5e8a9bStholo     char *rcs;
18341e72d8d2Sderaadt     int save_really_quiet;
18351e72d8d2Sderaadt 
1836ae5e8a9bStholo     rcs = locate_rcs (file, repository);
18371e72d8d2Sderaadt     save_really_quiet = really_quiet;
18381e72d8d2Sderaadt     really_quiet = 1;
18391e72d8d2Sderaadt     if ((rcsfile = RCS_parsercsfile (rcs)) == NULL)
18409fe7c2c3Stholo     {
18419fe7c2c3Stholo 	if (unlink_file (rcs) < 0)
18429fe7c2c3Stholo 	    error (0, errno, "cannot remove %s", rcs);
18439fe7c2c3Stholo     }
18441e72d8d2Sderaadt     else
18451e72d8d2Sderaadt 	freercsnode (&rcsfile);
18461e72d8d2Sderaadt     really_quiet = save_really_quiet;
1847ae5e8a9bStholo     free (rcs);
18481e72d8d2Sderaadt }
18491e72d8d2Sderaadt 
18501e72d8d2Sderaadt /*
18511e72d8d2Sderaadt  * put the branch back on an rcs file
18521e72d8d2Sderaadt  */
18531e72d8d2Sderaadt static void
fixbranch(rcs,branch)18547e4d5a85Stholo fixbranch (rcs, branch)
18557e4d5a85Stholo     RCSNode *rcs;
18561e72d8d2Sderaadt     char *branch;
18571e72d8d2Sderaadt {
18587e4d5a85Stholo     int retcode;
18591e72d8d2Sderaadt 
1860ae5e8a9bStholo     if (branch != NULL)
18611e72d8d2Sderaadt     {
18621e72d8d2Sderaadt 	if ((retcode = RCS_setbranch (rcs, branch)) != 0)
18631e72d8d2Sderaadt 	    error (retcode == -1 ? 1 : 0, retcode == -1 ? errno : 0,
18647e4d5a85Stholo 		   "cannot restore branch to %s for %s", branch, rcs->path);
1865b2b690deStholo 	RCS_rewrite (rcs, NULL, NULL);
18661e72d8d2Sderaadt     }
18671e72d8d2Sderaadt }
18681e72d8d2Sderaadt 
18691e72d8d2Sderaadt /*
18701e72d8d2Sderaadt  * do the initial part of a file add for the named file.  if adding
18711e72d8d2Sderaadt  * with a tag, put the file in the Attic and point the symbolic tag
18721e72d8d2Sderaadt  * at the committed revision.
18731e72d8d2Sderaadt  */
18741e72d8d2Sderaadt 
18751e72d8d2Sderaadt static int
checkaddfile(file,repository,tag,options,rcsnode)18769d9224ffStholo checkaddfile (file, repository, tag, options, rcsnode)
18771e72d8d2Sderaadt     char *file;
18781e72d8d2Sderaadt     char *repository;
18791e72d8d2Sderaadt     char *tag;
1880e5b68881Stholo     char *options;
18819d9224ffStholo     RCSNode **rcsnode;
18821e72d8d2Sderaadt {
1883ae5e8a9bStholo     char *rcs;
1884ae5e8a9bStholo     char *fname;
18851e72d8d2Sderaadt     mode_t omask;
18861e72d8d2Sderaadt     int retcode = 0;
18871e72d8d2Sderaadt     int newfile = 0;
18887e4d5a85Stholo     RCSNode *rcsfile = NULL;
1889ae5e8a9bStholo     int retval;
18909fe7c2c3Stholo     int adding_on_branch;
18911e72d8d2Sderaadt 
18929fe7c2c3Stholo     /* Callers expect to be able to use either "" or NULL to mean the
18939fe7c2c3Stholo        default keyword expansion.  */
18949fe7c2c3Stholo     if (options != NULL && options[0] == '\0')
18959fe7c2c3Stholo 	options = NULL;
18969fe7c2c3Stholo     if (options != NULL)
18979fe7c2c3Stholo 	assert (options[0] == '-' && options[1] == 'k');
18989fe7c2c3Stholo 
18999fe7c2c3Stholo     /* If numeric, it is on the trunk; check_fileproc enforced
19009fe7c2c3Stholo        this.  */
19019fe7c2c3Stholo     adding_on_branch = tag != NULL && !isdigit ((unsigned char) tag[0]);
19029fe7c2c3Stholo 
19039fe7c2c3Stholo     if (adding_on_branch)
19041e72d8d2Sderaadt     {
1905ae5e8a9bStholo 	rcs = xmalloc (strlen (repository) + strlen (file)
1906ae5e8a9bStholo 		       + sizeof (RCSEXT) + sizeof (CVSATTIC) + 10);
19077e4d5a85Stholo         (void) sprintf (rcs, "%s/%s%s", repository, file, RCSEXT);
19087e4d5a85Stholo 	if (! isreadable (rcs))
19097e4d5a85Stholo 	{
19101e72d8d2Sderaadt 	    (void) sprintf(rcs, "%s/%s", repository, CVSATTIC);
1911e5b68881Stholo 	    omask = umask (cvsumask);
19121e72d8d2Sderaadt 	    if (CVS_MKDIR (rcs, 0777) != 0 && errno != EEXIST)
19131e72d8d2Sderaadt 		error (1, errno, "cannot make directory `%s'", rcs);;
19141e72d8d2Sderaadt 	    (void) umask (omask);
19157e4d5a85Stholo 	    (void) sprintf (rcs, "%s/%s/%s%s", repository, CVSATTIC, file,
19167e4d5a85Stholo 			    RCSEXT);
19177e4d5a85Stholo 	}
19181e72d8d2Sderaadt     }
19191e72d8d2Sderaadt     else
1920ae5e8a9bStholo 	rcs = locate_rcs (file, repository);
19211e72d8d2Sderaadt 
19221e72d8d2Sderaadt     if (isreadable (rcs))
19231e72d8d2Sderaadt     {
19241e72d8d2Sderaadt 	/* file has existed in the past.  Prepare to resurrect. */
19251e72d8d2Sderaadt 	char *rev;
19269fe7c2c3Stholo 	char *oldexpand;
19277e4d5a85Stholo 
19287e4d5a85Stholo 	if ((rcsfile = *rcsnode) == NULL)
19297e4d5a85Stholo 	{
19307e4d5a85Stholo 	    error (0, 0, "could not find parsed rcsfile %s", file);
1931ae5e8a9bStholo 	    retval = 1;
1932ae5e8a9bStholo 	    goto out;
19337e4d5a85Stholo 	}
19341e72d8d2Sderaadt 
19359fe7c2c3Stholo 	oldexpand = RCS_getexpand (rcsfile);
19369fe7c2c3Stholo 	if ((oldexpand != NULL
19379fe7c2c3Stholo 	     && options != NULL
19389fe7c2c3Stholo 	     && strcmp (options + 2, oldexpand) != 0)
19399fe7c2c3Stholo 	    || (oldexpand == NULL && options != NULL))
19401e72d8d2Sderaadt 	{
19419fe7c2c3Stholo 	    /* We tell the user about this, because it means that the
19429fe7c2c3Stholo 	       old revisions will no longer retrieve the way that they
19439fe7c2c3Stholo 	       used to.  */
19449fe7c2c3Stholo 	    error (0, 0, "changing keyword expansion mode to %s", options);
19459fe7c2c3Stholo 	    RCS_setexpand (rcsfile, options + 2);
19469fe7c2c3Stholo 	}
1947ae5e8a9bStholo 
19489fe7c2c3Stholo 	if (!adding_on_branch)
19499fe7c2c3Stholo 	{
19509fe7c2c3Stholo 	    /* We are adding on the trunk, so move the file out of the
19511e72d8d2Sderaadt 	       Attic.  */
19529fe7c2c3Stholo 	    if (!(rcsfile->flags & INATTIC))
1953ae5e8a9bStholo 	    {
195479822b2aStholo 		error (0, 0, "warning: expected %s to be in Attic",
19559fe7c2c3Stholo 		       rcsfile->path);
1956ae5e8a9bStholo 	    }
19579fe7c2c3Stholo 
19589fe7c2c3Stholo 	    sprintf (rcs, "%s/%s%s", repository, file, RCSEXT);
19599fe7c2c3Stholo 
1960bde78045Stholo 	    /* Begin a critical section around the code that spans the
1961bde78045Stholo 	       first commit on the trunk of a file that's already been
1962bde78045Stholo 	       committed on a branch.  */
1963bde78045Stholo 	    SIG_beginCrSect ();
1964bde78045Stholo 
19659fe7c2c3Stholo 	    if (RCS_setattic (rcsfile, 0))
1966ae5e8a9bStholo 	    {
19679fe7c2c3Stholo 		retval = 1;
19689fe7c2c3Stholo 		goto out;
1969ae5e8a9bStholo 	    }
19701e72d8d2Sderaadt 	}
19711e72d8d2Sderaadt 
19727e4d5a85Stholo 	rev = RCS_getversion (rcsfile, tag, NULL, 1, (int *) NULL);
19731e72d8d2Sderaadt 	/* and lock it */
1974ae5e8a9bStholo 	if (lock_RCS (file, rcsfile, rev, repository))
1975ae5e8a9bStholo 	{
19761e72d8d2Sderaadt 	    error (0, 0, "cannot lock `%s'.", rcs);
1977ae5e8a9bStholo 	    if (rev != NULL)
19781e72d8d2Sderaadt 		free (rev);
1979ae5e8a9bStholo 	    retval = 1;
1980ae5e8a9bStholo 	    goto out;
19811e72d8d2Sderaadt 	}
19821e72d8d2Sderaadt 
1983ae5e8a9bStholo 	if (rev != NULL)
19841e72d8d2Sderaadt 	    free (rev);
1985ae5e8a9bStholo     }
1986ae5e8a9bStholo     else
1987ae5e8a9bStholo     {
19881e72d8d2Sderaadt 	/* this is the first time we have ever seen this file; create
19891e72d8d2Sderaadt 	   an rcs file.  */
19901e72d8d2Sderaadt 
1991b2b690deStholo 	char *desc;
1992b2b690deStholo 	size_t descalloc;
1993b2b690deStholo 	size_t desclen;
1994b2b690deStholo 
1995b2b690deStholo 	char *opt;
1996b2b690deStholo 
1997b2b690deStholo 	desc = NULL;
1998b2b690deStholo 	descalloc = 0;
1999b2b690deStholo 	desclen = 0;
2000ae5e8a9bStholo 	fname = xmalloc (strlen (file) + sizeof (CVSADM)
2001ae5e8a9bStholo 			 + sizeof (CVSEXT_LOG) + 10);
20021e72d8d2Sderaadt 	(void) sprintf (fname, "%s/%s%s", CVSADM, file, CVSEXT_LOG);
20031e72d8d2Sderaadt 	/* If the file does not exist, no big deal.  In particular, the
20041e72d8d2Sderaadt 	   server does not (yet at least) create CVSEXT_LOG files.  */
20051e72d8d2Sderaadt 	if (isfile (fname))
2006b2b690deStholo 	    /* FIXME: Should be including update_dir in the appropriate
2007b2b690deStholo 	       place here.  */
2008b2b690deStholo 	    get_file (fname, fname, "r", &desc, &descalloc, &desclen);
2009ae5e8a9bStholo 	free (fname);
20101e72d8d2Sderaadt 
2011b2b690deStholo 	/* From reading the RCS 5.7 source, "rcs -i" adds a newline to the
2012b2b690deStholo 	   end of the log message if the message is nonempty.
2013b2b690deStholo 	   Do it.  RCS also deletes certain whitespace, in cleanlogmsg,
2014b2b690deStholo 	   which we don't try to do here.  */
2015b2b690deStholo 	if (desclen > 0)
2016b2b690deStholo 	{
2017b2b690deStholo 	    expand_string (&desc, &descalloc, desclen + 1);
2018b2b690deStholo 	    desc[desclen++] = '\012';
2019b2b690deStholo 	}
2020b2b690deStholo 
2021e5b68881Stholo 	/* Set RCS keyword expansion options.  */
20229fe7c2c3Stholo 	if (options != NULL)
2023b2b690deStholo 	    opt = options + 2;
2024b2b690deStholo 	else
2025b2b690deStholo 	    opt = NULL;
2026b2b690deStholo 
2027b2b690deStholo 	/* This message is an artifact of the time when this
2028b2b690deStholo 	   was implemented via "rcs -i".  It should be revised at
2029b2b690deStholo 	   some point (does the "initial revision" in the message from
2030b2b690deStholo 	   RCS_checkin indicate that this is a new file?  Or does the
2031b2b690deStholo 	   "RCS file" message serve some function?).  */
2032b2b690deStholo 	cvs_output ("RCS file: ", 0);
2033b2b690deStholo 	cvs_output (rcs, 0);
2034b2b690deStholo 	cvs_output ("\ndone\n", 0);
2035b2b690deStholo 
2036b2b690deStholo 	if (add_rcs_file (NULL, rcs, file, NULL, opt,
2037b2b690deStholo 			  NULL, NULL, 0, NULL,
2038b2b690deStholo 			  desc, desclen, NULL) != 0)
20391e72d8d2Sderaadt 	{
2040ae5e8a9bStholo 	    retval = 1;
2041ae5e8a9bStholo 	    goto out;
20421e72d8d2Sderaadt 	}
2043b2b690deStholo 	rcsfile = RCS_parsercsfile (rcs);
20441e72d8d2Sderaadt 	newfile = 1;
2045b2b690deStholo 	if (desc != NULL)
2046b2b690deStholo 	    free (desc);
2047bde78045Stholo 	if (rcsnode != NULL)
2048bde78045Stholo 	{
2049bde78045Stholo 	    assert (*rcsnode == NULL);
2050bde78045Stholo 	    *rcsnode = rcsfile;
2051bde78045Stholo 	}
20521e72d8d2Sderaadt     }
20531e72d8d2Sderaadt 
20541e72d8d2Sderaadt     /* when adding a file for the first time, and using a tag, we need
20551e72d8d2Sderaadt        to create a dead revision on the trunk.  */
2056bde78045Stholo     if (adding_on_branch)
2057bde78045Stholo     {
2058bde78045Stholo 	if (newfile)
20591e72d8d2Sderaadt 	{
2060e5b68881Stholo 	    char *tmp;
206178c87a5cStholo 	    FILE *fp;
20621e72d8d2Sderaadt 
20631e72d8d2Sderaadt 	    /* move the new file out of the way. */
2064ae5e8a9bStholo 	    fname = xmalloc (strlen (file) + sizeof (CVSADM)
2065ae5e8a9bStholo 			     + sizeof (CVSPREFIX) + 10);
20661e72d8d2Sderaadt 	    (void) sprintf (fname, "%s/%s%s", CVSADM, CVSPREFIX, file);
20671e72d8d2Sderaadt 	    rename_file (file, fname);
206878c87a5cStholo 
206978c87a5cStholo 	    /* Create empty FILE.  Can't use copy_file with a DEVNULL
207078c87a5cStholo 	       argument -- copy_file now ignores device files. */
207178c87a5cStholo 	    fp = fopen (file, "w");
207278c87a5cStholo 	    if (fp == NULL)
207378c87a5cStholo 		error (1, errno, "cannot open %s for writing", file);
207478c87a5cStholo 	    if (fclose (fp) < 0)
207578c87a5cStholo 		error (0, errno, "cannot close %s", file);
20761e72d8d2Sderaadt 
2077e5b68881Stholo 	    tmp = xmalloc (strlen (file) + strlen (tag) + 80);
20781e72d8d2Sderaadt 	    /* commit a dead revision. */
207945381119Stholo 	    (void) sprintf (tmp, "file %s was initially added on branch %s.",
2080e5b68881Stholo 			    file, tag);
2081b2b690deStholo 	    retcode = RCS_checkin (rcsfile, NULL, tmp, NULL,
20827e4d5a85Stholo 				   RCS_FLAGS_DEAD | RCS_FLAGS_QUIET);
2083e5b68881Stholo 	    free (tmp);
208445381119Stholo 	    if (retcode != 0)
20851e72d8d2Sderaadt 	    {
20861e72d8d2Sderaadt 		error (retcode == -1 ? 1 : 0, retcode == -1 ? errno : 0,
20871e72d8d2Sderaadt 		       "could not create initial dead revision %s", rcs);
2088ae5e8a9bStholo 		retval = 1;
2089ae5e8a9bStholo 		goto out;
20901e72d8d2Sderaadt 	    }
20911e72d8d2Sderaadt 
20921e72d8d2Sderaadt 	    /* put the new file back where it was */
20931e72d8d2Sderaadt 	    rename_file (fname, file);
2094ae5e8a9bStholo 	    free (fname);
20951e72d8d2Sderaadt 
2096b2b690deStholo 	    /* double-check that the file was written correctly */
2097b2b690deStholo 	    freercsnode (&rcsfile);
20987e4d5a85Stholo 	    rcsfile = RCS_parse (file, repository);
20997e4d5a85Stholo 	    if (rcsfile == NULL)
21007e4d5a85Stholo 	    {
21017e4d5a85Stholo 		error (0, 0, "could not read %s", rcs);
2102ae5e8a9bStholo 		retval = 1;
2103ae5e8a9bStholo 		goto out;
21047e4d5a85Stholo 	    }
21057e4d5a85Stholo 	    if (rcsnode != NULL)
21067e4d5a85Stholo 		*rcsnode = rcsfile;
21077e4d5a85Stholo 
21081e72d8d2Sderaadt 	    /* and lock it once again. */
2109ae5e8a9bStholo 	    if (lock_RCS (file, rcsfile, NULL, repository))
2110ae5e8a9bStholo 	    {
21111e72d8d2Sderaadt 		error (0, 0, "cannot lock `%s'.", rcs);
2112ae5e8a9bStholo 		retval = 1;
2113ae5e8a9bStholo 		goto out;
21141e72d8d2Sderaadt 	    }
21151e72d8d2Sderaadt 	}
21161e72d8d2Sderaadt 
21171e72d8d2Sderaadt 	/* when adding with a tag, we need to stub a branch, if it
21181e72d8d2Sderaadt 	   doesn't already exist.  */
21191e72d8d2Sderaadt 
21207e4d5a85Stholo 	if (rcsfile == NULL)
21217e4d5a85Stholo 	{
21227e4d5a85Stholo 	    if (rcsnode != NULL && *rcsnode != NULL)
21237e4d5a85Stholo 		rcsfile = *rcsnode;
21247e4d5a85Stholo 	    else
21257e4d5a85Stholo 	    {
21261e72d8d2Sderaadt 		rcsfile = RCS_parse (file, repository);
21271e72d8d2Sderaadt 		if (rcsfile == NULL)
21281e72d8d2Sderaadt 		{
21291e72d8d2Sderaadt 		    error (0, 0, "could not read %s", rcs);
2130ae5e8a9bStholo 		    retval = 1;
2131ae5e8a9bStholo 		    goto out;
21321e72d8d2Sderaadt 		}
21337e4d5a85Stholo 	    }
21347e4d5a85Stholo 	}
21351e72d8d2Sderaadt 
2136ae5e8a9bStholo 	if (!RCS_nodeisbranch (rcsfile, tag))
2137ae5e8a9bStholo 	{
21381e72d8d2Sderaadt 	    /* branch does not exist.  Stub it.  */
21391e72d8d2Sderaadt 	    char *head;
21401e72d8d2Sderaadt 	    char *magicrev;
21411e72d8d2Sderaadt 
21427e4d5a85Stholo 	    head = RCS_getversion (rcsfile, NULL, NULL, 0, (int *) NULL);
21431e72d8d2Sderaadt 	    magicrev = RCS_magicrev (rcsfile, head);
21447e4d5a85Stholo 
21457e4d5a85Stholo 	    retcode = RCS_settag (rcsfile, tag, magicrev);
2146b2b690deStholo 	    RCS_rewrite (rcsfile, NULL, NULL);
21477e4d5a85Stholo 
21487e4d5a85Stholo 	    free (head);
21497e4d5a85Stholo 	    free (magicrev);
21507e4d5a85Stholo 
21517e4d5a85Stholo 	    if (retcode != 0)
21521e72d8d2Sderaadt 	    {
21531e72d8d2Sderaadt 		error (retcode == -1 ? 1 : 0, retcode == -1 ? errno : 0,
21541e72d8d2Sderaadt 		       "could not stub branch %s for %s", tag, rcs);
2155ae5e8a9bStholo 		retval = 1;
2156ae5e8a9bStholo 		goto out;
21571e72d8d2Sderaadt 	    }
21581e72d8d2Sderaadt 	}
21591e72d8d2Sderaadt 	else
21601e72d8d2Sderaadt 	{
21611e72d8d2Sderaadt 	    /* lock the branch. (stubbed branches need not be locked.)  */
2162ae5e8a9bStholo 	    if (lock_RCS (file, rcsfile, NULL, repository))
2163ae5e8a9bStholo 	    {
21641e72d8d2Sderaadt 		error (0, 0, "cannot lock `%s'.", rcs);
2165ae5e8a9bStholo 		retval = 1;
2166ae5e8a9bStholo 		goto out;
21671e72d8d2Sderaadt 	    }
21681e72d8d2Sderaadt 	}
21691e72d8d2Sderaadt 
21707e4d5a85Stholo 	if (rcsnode && *rcsnode != rcsfile)
21717e4d5a85Stholo 	{
21729d9224ffStholo 	    freercsnode(rcsnode);
21739d9224ffStholo 	    *rcsnode = rcsfile;
21741e72d8d2Sderaadt 	}
21757e4d5a85Stholo     }
21761e72d8d2Sderaadt 
2177e5b68881Stholo     fileattr_newfile (file);
2178e5b68881Stholo 
21799fe7c2c3Stholo     /* At this point, we used to set the file mode of the RCS file
21809fe7c2c3Stholo        based on the mode of the file in the working directory.  If we
21819fe7c2c3Stholo        are creating the RCS file for the first time, add_rcs_file does
21829fe7c2c3Stholo        this already.  If we are re-adding the file, then perhaps it is
21839fe7c2c3Stholo        consistent to preserve the old file mode, just as we preserve
21849fe7c2c3Stholo        the old keyword expansion mode.
21859fe7c2c3Stholo 
21869fe7c2c3Stholo        If we decide that we should change the modes, then we can't do
21879fe7c2c3Stholo        it here anyhow.  At this point, the RCS file may be owned by
21889fe7c2c3Stholo        somebody else, so a chmod will fail.  We need to instead do the
21899fe7c2c3Stholo        chmod after rewriting it.
21909fe7c2c3Stholo 
21919fe7c2c3Stholo        FIXME: In general, I think the file mode (and the keyword
21929fe7c2c3Stholo        expansion mode) should be associated with a particular revision
21939fe7c2c3Stholo        of the file, so that it is possible to have different revisions
21949fe7c2c3Stholo        of a file have different modes.  */
2195b2b690deStholo 
2196ae5e8a9bStholo     retval = 0;
2197ae5e8a9bStholo 
2198ae5e8a9bStholo  out:
2199bde78045Stholo     if (retval != 0 && SIG_inCrSect ())
2200bde78045Stholo 	SIG_endCrSect ();
2201ae5e8a9bStholo     free (rcs);
2202ae5e8a9bStholo     return retval;
22031e72d8d2Sderaadt }
22041e72d8d2Sderaadt 
22051e72d8d2Sderaadt /*
22061e72d8d2Sderaadt  * Attempt to place a lock on the RCS file; returns 0 if it could and 1 if it
22071e72d8d2Sderaadt  * couldn't.  If the RCS file currently has a branch as the head, we must
22081e72d8d2Sderaadt  * move the head back to the trunk before locking the file, and be sure to
22091e72d8d2Sderaadt  * put the branch back as the head if there are any errors.
22101e72d8d2Sderaadt  */
22111e72d8d2Sderaadt static int
lock_RCS(user,rcs,rev,repository)22121e72d8d2Sderaadt lock_RCS (user, rcs, rev, repository)
22131e72d8d2Sderaadt     char *user;
22147e4d5a85Stholo     RCSNode *rcs;
22151e72d8d2Sderaadt     char *rev;
22161e72d8d2Sderaadt     char *repository;
22171e72d8d2Sderaadt {
22181e72d8d2Sderaadt     char *branch = NULL;
22191e72d8d2Sderaadt     int err = 0;
22201e72d8d2Sderaadt 
22211e72d8d2Sderaadt     /*
22221e72d8d2Sderaadt      * For a specified, numeric revision of the form "1" or "1.1", (or when
22231e72d8d2Sderaadt      * no revision is specified ""), definitely move the branch to the trunk
22241e72d8d2Sderaadt      * before locking the RCS file.
22251e72d8d2Sderaadt      *
22261e72d8d2Sderaadt      * The assumption is that if there is more than one revision on the trunk,
22271e72d8d2Sderaadt      * the head points to the trunk, not a branch... and as such, it's not
22281e72d8d2Sderaadt      * necessary to move the head in this case.
22291e72d8d2Sderaadt      */
22309fe7c2c3Stholo     if (rev == NULL
22319fe7c2c3Stholo 	|| (rev && isdigit ((unsigned char) *rev) && numdots (rev) < 2))
22321e72d8d2Sderaadt     {
22337e4d5a85Stholo 	branch = xstrdup (rcs->branch);
22341e72d8d2Sderaadt 	if (branch != NULL)
22351e72d8d2Sderaadt 	{
22361e72d8d2Sderaadt 	    if (RCS_setbranch (rcs, NULL) != 0)
22371e72d8d2Sderaadt 	    {
22381e72d8d2Sderaadt 		error (0, 0, "cannot change branch to default for %s",
22397e4d5a85Stholo 		       rcs->path);
22401e72d8d2Sderaadt 		if (branch)
22411e72d8d2Sderaadt 		    free (branch);
22421e72d8d2Sderaadt 		return (1);
22431e72d8d2Sderaadt 	    }
22441e72d8d2Sderaadt 	}
2245b2b690deStholo 	err = RCS_lock(rcs, NULL, 1);
22461e72d8d2Sderaadt     }
22471e72d8d2Sderaadt     else
22481e72d8d2Sderaadt     {
22491e72d8d2Sderaadt 	(void) RCS_lock(rcs, rev, 1);
22501e72d8d2Sderaadt     }
225178c87a5cStholo 
225278c87a5cStholo     /* We used to call RCS_rewrite here, and that might seem
225378c87a5cStholo        appropriate in order to write out the locked revision
225478c87a5cStholo        information.  However, such a call would actually serve no
225578c87a5cStholo        purpose.  CVS locks will prevent any interference from other
225678c87a5cStholo        CVS processes.  The comment above rcs_internal_lockfile
225778c87a5cStholo        explains that it is already unsafe to use RCS and CVS
225878c87a5cStholo        simultaneously.  It follows that writing out the locked
225978c87a5cStholo        revision information here would add no additional security.
226078c87a5cStholo 
226178c87a5cStholo        If we ever do care about it, the proper fix is to create the
226278c87a5cStholo        RCS lock file before calling this function, and maintain it
226378c87a5cStholo        until the checkin is complete.
226478c87a5cStholo 
226578c87a5cStholo        The call to RCS_lock is still required at present, since in
226678c87a5cStholo        some cases RCS_checkin will determine which revision to check
226778c87a5cStholo        in by looking for a lock.  FIXME: This is rather roundabout,
226878c87a5cStholo        and a more straightforward approach would probably be easier to
226978c87a5cStholo        understand.  */
22701e72d8d2Sderaadt 
22711e72d8d2Sderaadt     if (err == 0)
22721e72d8d2Sderaadt     {
2273ae5e8a9bStholo 	if (sbranch != NULL)
2274ae5e8a9bStholo 	    free (sbranch);
2275ae5e8a9bStholo 	sbranch = branch;
22761e72d8d2Sderaadt 	return (0);
22771e72d8d2Sderaadt     }
22781e72d8d2Sderaadt 
22791e72d8d2Sderaadt     /* try to restore the branch if we can on error */
22801e72d8d2Sderaadt     if (branch != NULL)
22817e4d5a85Stholo 	fixbranch (rcs, branch);
22821e72d8d2Sderaadt 
22831e72d8d2Sderaadt     if (branch)
22841e72d8d2Sderaadt 	free (branch);
22851e72d8d2Sderaadt     return (1);
22861e72d8d2Sderaadt }
22871e72d8d2Sderaadt 
22881e72d8d2Sderaadt /*
22897e4d5a85Stholo  * free an UPDATE node's data
22901e72d8d2Sderaadt  */
22911e72d8d2Sderaadt void
update_delproc(p)22921e72d8d2Sderaadt update_delproc (p)
22931e72d8d2Sderaadt     Node *p;
22941e72d8d2Sderaadt {
22957e4d5a85Stholo     struct logfile_info *li;
22967e4d5a85Stholo 
22977e4d5a85Stholo     li = (struct logfile_info *) p->data;
22987e4d5a85Stholo     if (li->tag)
22997e4d5a85Stholo 	free (li->tag);
2300ae5e8a9bStholo     if (li->rev_old)
2301ae5e8a9bStholo 	free (li->rev_old);
2302ae5e8a9bStholo     if (li->rev_new)
2303ae5e8a9bStholo 	free (li->rev_new);
23047e4d5a85Stholo     free (li);
23051e72d8d2Sderaadt }
23061e72d8d2Sderaadt 
23071e72d8d2Sderaadt /*
23081e72d8d2Sderaadt  * Free the commit_info structure in p.
23091e72d8d2Sderaadt  */
23101e72d8d2Sderaadt static void
ci_delproc(p)23111e72d8d2Sderaadt ci_delproc (p)
23121e72d8d2Sderaadt     Node *p;
23131e72d8d2Sderaadt {
23141e72d8d2Sderaadt     struct commit_info *ci;
23151e72d8d2Sderaadt 
23161e72d8d2Sderaadt     ci = (struct commit_info *) p->data;
23171e72d8d2Sderaadt     if (ci->rev)
23181e72d8d2Sderaadt 	free (ci->rev);
23191e72d8d2Sderaadt     if (ci->tag)
23201e72d8d2Sderaadt 	free (ci->tag);
23211e72d8d2Sderaadt     if (ci->options)
23221e72d8d2Sderaadt 	free (ci->options);
23231e72d8d2Sderaadt     free (ci);
23241e72d8d2Sderaadt }
23251e72d8d2Sderaadt 
23261e72d8d2Sderaadt /*
23271e72d8d2Sderaadt  * Free the commit_info structure in p.
23281e72d8d2Sderaadt  */
23291e72d8d2Sderaadt static void
masterlist_delproc(p)23301e72d8d2Sderaadt masterlist_delproc (p)
23311e72d8d2Sderaadt     Node *p;
23321e72d8d2Sderaadt {
23331e72d8d2Sderaadt     struct master_lists *ml;
23341e72d8d2Sderaadt 
23351e72d8d2Sderaadt     ml = (struct master_lists *) p->data;
23361e72d8d2Sderaadt     dellist (&ml->ulist);
23371e72d8d2Sderaadt     dellist (&ml->cilist);
23381e72d8d2Sderaadt     free (ml);
23391e72d8d2Sderaadt }
23401e72d8d2Sderaadt 
2341ae5e8a9bStholo /* Find an RCS file in the repository.  Most parts of CVS will want to
2342ae5e8a9bStholo    rely instead on RCS_parse which performs a similar operation and is
2343ae5e8a9bStholo    called by recurse.c which then puts the result in useful places
2344ae5e8a9bStholo    like the rcs field of struct file_info.
2345ae5e8a9bStholo 
2346ae5e8a9bStholo    REPOSITORY is the repository (including the directory) and FILE is
2347ae5e8a9bStholo    the filename within that directory (without RCSEXT).  Returns a
2348ae5e8a9bStholo    newly-malloc'd array containing the absolute pathname of the RCS
2349ae5e8a9bStholo    file that was found.  */
2350ae5e8a9bStholo static char *
locate_rcs(file,repository)2351ae5e8a9bStholo locate_rcs (file, repository)
23521e72d8d2Sderaadt     char *file;
23531e72d8d2Sderaadt     char *repository;
23541e72d8d2Sderaadt {
2355ae5e8a9bStholo     char *rcs;
2356ae5e8a9bStholo 
2357ae5e8a9bStholo     rcs = xmalloc (strlen (repository) + strlen (file) + sizeof (RCSEXT) + 10);
23581e72d8d2Sderaadt     (void) sprintf (rcs, "%s/%s%s", repository, file, RCSEXT);
23591e72d8d2Sderaadt     if (!isreadable (rcs))
23601e72d8d2Sderaadt     {
23611e72d8d2Sderaadt 	(void) sprintf (rcs, "%s/%s/%s%s", repository, CVSATTIC, file, RCSEXT);
23621e72d8d2Sderaadt 	if (!isreadable (rcs))
23631e72d8d2Sderaadt 	    (void) sprintf (rcs, "%s/%s%s", repository, file, RCSEXT);
23641e72d8d2Sderaadt     }
2365ae5e8a9bStholo     return rcs;
23661e72d8d2Sderaadt }
2367