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