xref: /dflybsd-src/contrib/cvs-1.12/src/commit.c (revision 86d7f5d305c6adaa56ff4582ece9859d73106103)
1*86d7f5d3SJohn Marino /*
2*86d7f5d3SJohn Marino  * Copyright (C) 1986-2005 The Free Software Foundation, Inc.
3*86d7f5d3SJohn Marino  *
4*86d7f5d3SJohn Marino  * Portions Copyright (C) 1998-2005 Derek Price, Ximbiot <http://ximbiot.com>,
5*86d7f5d3SJohn Marino  *                                  and others.
6*86d7f5d3SJohn Marino  *
7*86d7f5d3SJohn Marino  * Portions Copyright (C) 1992, Brian Berliner and Jeff Polk
8*86d7f5d3SJohn Marino  * Portions Copyright (C) 1989-1992, Brian Berliner
9*86d7f5d3SJohn Marino  *
10*86d7f5d3SJohn Marino  * You may distribute under the terms of the GNU General Public License as
11*86d7f5d3SJohn Marino  * specified in the README file that comes with the CVS source distribution.
12*86d7f5d3SJohn Marino  *
13*86d7f5d3SJohn Marino  * Commit Files
14*86d7f5d3SJohn Marino  *
15*86d7f5d3SJohn Marino  * "commit" commits the present version to the RCS repository, AFTER
16*86d7f5d3SJohn Marino  * having done a test on conflicts.
17*86d7f5d3SJohn Marino  *
18*86d7f5d3SJohn Marino  * The call is: cvs commit [options] files...
19*86d7f5d3SJohn Marino  *
20*86d7f5d3SJohn Marino  */
21*86d7f5d3SJohn Marino 
22*86d7f5d3SJohn Marino #include "cvs.h"
23*86d7f5d3SJohn Marino #include "getline.h"
24*86d7f5d3SJohn Marino #include "edit.h"
25*86d7f5d3SJohn Marino #include "fileattr.h"
26*86d7f5d3SJohn Marino #include "hardlink.h"
27*86d7f5d3SJohn Marino 
28*86d7f5d3SJohn Marino static Dtype check_direntproc (void *callerdat, const char *dir,
29*86d7f5d3SJohn Marino                                const char *repos, const char *update_dir,
30*86d7f5d3SJohn Marino                                List *entries);
31*86d7f5d3SJohn Marino static int check_fileproc (void *callerdat, struct file_info *finfo);
32*86d7f5d3SJohn Marino static int check_filesdoneproc (void *callerdat, int err, const char *repos,
33*86d7f5d3SJohn Marino 				const char *update_dir, List *entries);
34*86d7f5d3SJohn Marino static int checkaddfile (const char *file, const char *repository,
35*86d7f5d3SJohn Marino                          const char *tag, const char *options,
36*86d7f5d3SJohn Marino                          RCSNode **rcsnode);
37*86d7f5d3SJohn Marino static Dtype commit_direntproc (void *callerdat, const char *dir,
38*86d7f5d3SJohn Marino                                 const char *repos, const char *update_dir,
39*86d7f5d3SJohn Marino                                 List *entries);
40*86d7f5d3SJohn Marino static int commit_dirleaveproc (void *callerdat, const char *dir, int err,
41*86d7f5d3SJohn Marino 				const char *update_dir, List *entries);
42*86d7f5d3SJohn Marino static int commit_fileproc (void *callerdat, struct file_info *finfo);
43*86d7f5d3SJohn Marino static int commit_filesdoneproc (void *callerdat, int err,
44*86d7f5d3SJohn Marino                                  const char *repository,
45*86d7f5d3SJohn Marino 				 const char *update_dir, List *entries);
46*86d7f5d3SJohn Marino static int finaladd (struct file_info *finfo, char *revision, char *tag,
47*86d7f5d3SJohn Marino 		     char *options);
48*86d7f5d3SJohn Marino static int findmaxrev (Node * p, void *closure);
49*86d7f5d3SJohn Marino static int lock_RCS (const char *user, RCSNode *rcs, const char *rev,
50*86d7f5d3SJohn Marino                      const char *repository);
51*86d7f5d3SJohn Marino static int precommit_list_to_args_proc (Node * p, void *closure);
52*86d7f5d3SJohn Marino static int precommit_proc (const char *repository, const char *filter,
53*86d7f5d3SJohn Marino                            void *closure);
54*86d7f5d3SJohn Marino static int remove_file (struct file_info *finfo, char *tag,
55*86d7f5d3SJohn Marino 			char *message);
56*86d7f5d3SJohn Marino static void fixaddfile (const char *rcs);
57*86d7f5d3SJohn Marino static void fixbranch (RCSNode *, char *branch);
58*86d7f5d3SJohn Marino static void unlockrcs (RCSNode *rcs);
59*86d7f5d3SJohn Marino static void ci_delproc (Node *p);
60*86d7f5d3SJohn Marino static void masterlist_delproc (Node *p);
61*86d7f5d3SJohn Marino 
62*86d7f5d3SJohn Marino struct commit_info
63*86d7f5d3SJohn Marino {
64*86d7f5d3SJohn Marino     Ctype status;			/* as returned from Classify_File() */
65*86d7f5d3SJohn Marino     char *rev;				/* a numeric rev, if we know it */
66*86d7f5d3SJohn Marino     char *tag;				/* any sticky tag, or -r option */
67*86d7f5d3SJohn Marino     char *options;			/* Any sticky -k option */
68*86d7f5d3SJohn Marino };
69*86d7f5d3SJohn Marino struct master_lists
70*86d7f5d3SJohn Marino {
71*86d7f5d3SJohn Marino     List *ulist;			/* list for Update_Logfile */
72*86d7f5d3SJohn Marino     List *cilist;			/* list with commit_info structs */
73*86d7f5d3SJohn Marino };
74*86d7f5d3SJohn Marino 
75*86d7f5d3SJohn Marino static int check_valid_edit = 0;
76*86d7f5d3SJohn Marino static int force_ci = 0;
77*86d7f5d3SJohn Marino static int got_message;
78*86d7f5d3SJohn Marino static int aflag;
79*86d7f5d3SJohn Marino static char *saved_tag;
80*86d7f5d3SJohn Marino static char *write_dirtag;
81*86d7f5d3SJohn Marino static int write_dirnonbranch;
82*86d7f5d3SJohn Marino static char *logfile;
83*86d7f5d3SJohn Marino static List *mulist;
84*86d7f5d3SJohn Marino static char *saved_message;
85*86d7f5d3SJohn Marino static time_t last_register_time;
86*86d7f5d3SJohn Marino 
87*86d7f5d3SJohn Marino static const char *const commit_usage[] =
88*86d7f5d3SJohn Marino {
89*86d7f5d3SJohn Marino     "Usage: %s %s [-cRlf] [-m msg | -F logfile] [-r rev] files...\n",
90*86d7f5d3SJohn Marino     "    -c          Check for valid edits before committing.\n",
91*86d7f5d3SJohn Marino     "    -R          Process directories recursively.\n",
92*86d7f5d3SJohn Marino     "    -l          Local directory only (not recursive).\n",
93*86d7f5d3SJohn Marino     "    -f          Force the file to be committed; disables recursion.\n",
94*86d7f5d3SJohn Marino     "    -F logfile  Read the log message from file.\n",
95*86d7f5d3SJohn Marino     "    -m msg      Log message.\n",
96*86d7f5d3SJohn Marino     "    -r rev      Commit to this branch or trunk revision.\n",
97*86d7f5d3SJohn Marino     "(Specify the --help global option for a list of other help options)\n",
98*86d7f5d3SJohn Marino     NULL
99*86d7f5d3SJohn Marino };
100*86d7f5d3SJohn Marino 
101*86d7f5d3SJohn Marino #ifdef CLIENT_SUPPORT
102*86d7f5d3SJohn Marino /* Identify a file which needs "? foo" or a Questionable request.  */
103*86d7f5d3SJohn Marino struct question
104*86d7f5d3SJohn Marino {
105*86d7f5d3SJohn Marino     /* The two fields for the Directory request.  */
106*86d7f5d3SJohn Marino     char *dir;
107*86d7f5d3SJohn Marino     char *repos;
108*86d7f5d3SJohn Marino 
109*86d7f5d3SJohn Marino     /* The file name.  */
110*86d7f5d3SJohn Marino     char *file;
111*86d7f5d3SJohn Marino 
112*86d7f5d3SJohn Marino     struct question *next;
113*86d7f5d3SJohn Marino };
114*86d7f5d3SJohn Marino 
115*86d7f5d3SJohn Marino struct find_data
116*86d7f5d3SJohn Marino {
117*86d7f5d3SJohn Marino     List *ulist;
118*86d7f5d3SJohn Marino     int argc;
119*86d7f5d3SJohn Marino     char **argv;
120*86d7f5d3SJohn Marino 
121*86d7f5d3SJohn Marino     /* This is used from dirent to filesdone time, for each directory,
122*86d7f5d3SJohn Marino        to make a list of files we have already seen.  */
123*86d7f5d3SJohn Marino     List *ignlist;
124*86d7f5d3SJohn Marino 
125*86d7f5d3SJohn Marino     /* Linked list of files which need "? foo" or a Questionable request.  */
126*86d7f5d3SJohn Marino     struct question *questionables;
127*86d7f5d3SJohn Marino 
128*86d7f5d3SJohn Marino     /* Only good within functions called from the filesdoneproc.  Stores
129*86d7f5d3SJohn Marino        the repository (pointer into storage managed by the recursion
130*86d7f5d3SJohn Marino        processor.  */
131*86d7f5d3SJohn Marino     const char *repository;
132*86d7f5d3SJohn Marino 
133*86d7f5d3SJohn Marino     /* Non-zero if we should force the commit.  This is enabled by
134*86d7f5d3SJohn Marino        either -f or -r options, unlike force_ci which is just -f.  */
135*86d7f5d3SJohn Marino     int force;
136*86d7f5d3SJohn Marino };
137*86d7f5d3SJohn Marino 
138*86d7f5d3SJohn Marino 
139*86d7f5d3SJohn Marino 
140*86d7f5d3SJohn Marino static Dtype
find_dirent_proc(void * callerdat,const char * dir,const char * repository,const char * update_dir,List * entries)141*86d7f5d3SJohn Marino find_dirent_proc (void *callerdat, const char *dir, const char *repository,
142*86d7f5d3SJohn Marino                   const char *update_dir, List *entries)
143*86d7f5d3SJohn Marino {
144*86d7f5d3SJohn Marino     struct find_data *find_data = callerdat;
145*86d7f5d3SJohn Marino 
146*86d7f5d3SJohn Marino     /* This check seems to slowly be creeping throughout CVS (update
147*86d7f5d3SJohn Marino        and send_dirent_proc by CVS 1.5, diff in 31 Oct 1995.  My guess
148*86d7f5d3SJohn Marino        is that it (or some variant thereof) should go in all the
149*86d7f5d3SJohn Marino        dirent procs.  Unless someone has some better idea...  */
150*86d7f5d3SJohn Marino     if (!isdir (dir))
151*86d7f5d3SJohn Marino 	return R_SKIP_ALL;
152*86d7f5d3SJohn Marino 
153*86d7f5d3SJohn Marino     /* initialize the ignore list for this directory */
154*86d7f5d3SJohn Marino     find_data->ignlist = getlist ();
155*86d7f5d3SJohn Marino 
156*86d7f5d3SJohn Marino     /* Print the same warm fuzzy as in check_direntproc, since that
157*86d7f5d3SJohn Marino        code will never be run during client/server operation and we
158*86d7f5d3SJohn Marino        want the messages to match. */
159*86d7f5d3SJohn Marino     if (!quiet)
160*86d7f5d3SJohn Marino 	error (0, 0, "Examining %s", update_dir);
161*86d7f5d3SJohn Marino 
162*86d7f5d3SJohn Marino     return R_PROCESS;
163*86d7f5d3SJohn Marino }
164*86d7f5d3SJohn Marino 
165*86d7f5d3SJohn Marino 
166*86d7f5d3SJohn Marino 
167*86d7f5d3SJohn Marino /* Here as a static until we get around to fixing ignore_files to pass
168*86d7f5d3SJohn Marino    it along as an argument.  */
169*86d7f5d3SJohn Marino static struct find_data *find_data_static;
170*86d7f5d3SJohn Marino 
171*86d7f5d3SJohn Marino 
172*86d7f5d3SJohn Marino 
173*86d7f5d3SJohn Marino static void
find_ignproc(const char * file,const char * dir)174*86d7f5d3SJohn Marino find_ignproc (const char *file, const char *dir)
175*86d7f5d3SJohn Marino {
176*86d7f5d3SJohn Marino     struct question *p;
177*86d7f5d3SJohn Marino 
178*86d7f5d3SJohn Marino     p = xmalloc (sizeof (struct question));
179*86d7f5d3SJohn Marino     p->dir = xstrdup (dir);
180*86d7f5d3SJohn Marino     p->repos = xstrdup (find_data_static->repository);
181*86d7f5d3SJohn Marino     p->file = xstrdup (file);
182*86d7f5d3SJohn Marino     p->next = find_data_static->questionables;
183*86d7f5d3SJohn Marino     find_data_static->questionables = p;
184*86d7f5d3SJohn Marino }
185*86d7f5d3SJohn Marino 
186*86d7f5d3SJohn Marino 
187*86d7f5d3SJohn Marino 
188*86d7f5d3SJohn Marino static int
find_filesdoneproc(void * callerdat,int err,const char * repository,const char * update_dir,List * entries)189*86d7f5d3SJohn Marino find_filesdoneproc (void *callerdat, int err, const char *repository,
190*86d7f5d3SJohn Marino                     const char *update_dir, List *entries)
191*86d7f5d3SJohn Marino {
192*86d7f5d3SJohn Marino     struct find_data *find_data = callerdat;
193*86d7f5d3SJohn Marino     find_data->repository = repository;
194*86d7f5d3SJohn Marino 
195*86d7f5d3SJohn Marino     /* if this directory has an ignore list, process it then free it */
196*86d7f5d3SJohn Marino     if (find_data->ignlist)
197*86d7f5d3SJohn Marino     {
198*86d7f5d3SJohn Marino 	find_data_static = find_data;
199*86d7f5d3SJohn Marino 	ignore_files (find_data->ignlist, entries, update_dir, find_ignproc);
200*86d7f5d3SJohn Marino 	dellist (&find_data->ignlist);
201*86d7f5d3SJohn Marino     }
202*86d7f5d3SJohn Marino 
203*86d7f5d3SJohn Marino     find_data->repository = NULL;
204*86d7f5d3SJohn Marino 
205*86d7f5d3SJohn Marino     return err;
206*86d7f5d3SJohn Marino }
207*86d7f5d3SJohn Marino 
208*86d7f5d3SJohn Marino 
209*86d7f5d3SJohn Marino 
210*86d7f5d3SJohn Marino /* Machinery to find out what is modified, added, and removed.  It is
211*86d7f5d3SJohn Marino    possible this should be broken out into a new client_classify function;
212*86d7f5d3SJohn Marino    merging it with classify_file is almost sure to be a mess, though,
213*86d7f5d3SJohn Marino    because classify_file has all kinds of repository processing.  */
214*86d7f5d3SJohn Marino static int
find_fileproc(void * callerdat,struct file_info * finfo)215*86d7f5d3SJohn Marino find_fileproc (void *callerdat, struct file_info *finfo)
216*86d7f5d3SJohn Marino {
217*86d7f5d3SJohn Marino     Vers_TS *vers;
218*86d7f5d3SJohn Marino     enum classify_type status;
219*86d7f5d3SJohn Marino     Node *node;
220*86d7f5d3SJohn Marino     struct find_data *args = callerdat;
221*86d7f5d3SJohn Marino     struct logfile_info *data;
222*86d7f5d3SJohn Marino     struct file_info xfinfo;
223*86d7f5d3SJohn Marino 
224*86d7f5d3SJohn Marino     /* if this directory has an ignore list, add this file to it */
225*86d7f5d3SJohn Marino     if (args->ignlist)
226*86d7f5d3SJohn Marino     {
227*86d7f5d3SJohn Marino 	Node *p;
228*86d7f5d3SJohn Marino 
229*86d7f5d3SJohn Marino 	p = getnode ();
230*86d7f5d3SJohn Marino 	p->type = FILES;
231*86d7f5d3SJohn Marino 	p->key = xstrdup (finfo->file);
232*86d7f5d3SJohn Marino 	if (addnode (args->ignlist, p) != 0)
233*86d7f5d3SJohn Marino 	    freenode (p);
234*86d7f5d3SJohn Marino     }
235*86d7f5d3SJohn Marino 
236*86d7f5d3SJohn Marino     xfinfo = *finfo;
237*86d7f5d3SJohn Marino     xfinfo.repository = NULL;
238*86d7f5d3SJohn Marino     xfinfo.rcs = NULL;
239*86d7f5d3SJohn Marino 
240*86d7f5d3SJohn Marino     vers = Version_TS (&xfinfo, NULL, saved_tag, NULL, 0, 0);
241*86d7f5d3SJohn Marino     if (vers->vn_user == NULL)
242*86d7f5d3SJohn Marino     {
243*86d7f5d3SJohn Marino 	if (vers->ts_user == NULL)
244*86d7f5d3SJohn Marino 	    error (0, 0, "nothing known about `%s'", finfo->fullname);
245*86d7f5d3SJohn Marino 	else
246*86d7f5d3SJohn Marino 	    error (0, 0, "use `%s add' to create an entry for `%s'",
247*86d7f5d3SJohn Marino 		   program_name, finfo->fullname);
248*86d7f5d3SJohn Marino 	freevers_ts (&vers);
249*86d7f5d3SJohn Marino 	return 1;
250*86d7f5d3SJohn Marino     }
251*86d7f5d3SJohn Marino     if (vers->vn_user[0] == '-')
252*86d7f5d3SJohn Marino     {
253*86d7f5d3SJohn Marino 	if (vers->ts_user != NULL)
254*86d7f5d3SJohn Marino 	{
255*86d7f5d3SJohn Marino 	    error (0, 0,
256*86d7f5d3SJohn Marino 		   "`%s' should be removed and is still there (or is back"
257*86d7f5d3SJohn Marino 		   " again)", finfo->fullname);
258*86d7f5d3SJohn Marino 	    freevers_ts (&vers);
259*86d7f5d3SJohn Marino 	    return 1;
260*86d7f5d3SJohn Marino 	}
261*86d7f5d3SJohn Marino 	/* else */
262*86d7f5d3SJohn Marino 	status = T_REMOVED;
263*86d7f5d3SJohn Marino     }
264*86d7f5d3SJohn Marino     else if (strcmp (vers->vn_user, "0") == 0)
265*86d7f5d3SJohn Marino     {
266*86d7f5d3SJohn Marino 	if (vers->ts_user == NULL)
267*86d7f5d3SJohn Marino 	{
268*86d7f5d3SJohn Marino 	    /* This happens when one has `cvs add'ed a file, but it no
269*86d7f5d3SJohn Marino 	       longer exists in the working directory at commit time.
270*86d7f5d3SJohn Marino 	       FIXME: What classify_file does in this case is print
271*86d7f5d3SJohn Marino 	       "new-born %s has disappeared" and removes the entry.
272*86d7f5d3SJohn Marino 	       We probably should do the same.  */
273*86d7f5d3SJohn Marino 	    if (!really_quiet)
274*86d7f5d3SJohn Marino 		error (0, 0, "warning: new-born %s has disappeared",
275*86d7f5d3SJohn Marino 		       finfo->fullname);
276*86d7f5d3SJohn Marino 	    status = T_REMOVE_ENTRY;
277*86d7f5d3SJohn Marino 	}
278*86d7f5d3SJohn Marino 	else
279*86d7f5d3SJohn Marino 	    status = T_ADDED;
280*86d7f5d3SJohn Marino     }
281*86d7f5d3SJohn Marino     else if (vers->ts_user == NULL)
282*86d7f5d3SJohn Marino     {
283*86d7f5d3SJohn Marino 	/* FIXME: What classify_file does in this case is print
284*86d7f5d3SJohn Marino 	   "%s was lost".  We probably should do the same.  */
285*86d7f5d3SJohn Marino 	freevers_ts (&vers);
286*86d7f5d3SJohn Marino 	return 0;
287*86d7f5d3SJohn Marino     }
288*86d7f5d3SJohn Marino     else if (vers->ts_rcs != NULL
289*86d7f5d3SJohn Marino 	     && (args->force || strcmp (vers->ts_user, vers->ts_rcs) != 0))
290*86d7f5d3SJohn Marino 	/* If we are forcing commits, pretend that the file is
291*86d7f5d3SJohn Marino            modified.  */
292*86d7f5d3SJohn Marino 	status = T_MODIFIED;
293*86d7f5d3SJohn Marino     else
294*86d7f5d3SJohn Marino     {
295*86d7f5d3SJohn Marino 	/* This covers unmodified files, as well as a variety of other
296*86d7f5d3SJohn Marino 	   cases.  FIXME: we probably should be printing a message and
297*86d7f5d3SJohn Marino 	   returning 1 for many of those cases (but I'm not sure
298*86d7f5d3SJohn Marino 	   exactly which ones).  */
299*86d7f5d3SJohn Marino 	freevers_ts (&vers);
300*86d7f5d3SJohn Marino 	return 0;
301*86d7f5d3SJohn Marino     }
302*86d7f5d3SJohn Marino 
303*86d7f5d3SJohn Marino     node = getnode ();
304*86d7f5d3SJohn Marino     node->key = xstrdup (finfo->fullname);
305*86d7f5d3SJohn Marino 
306*86d7f5d3SJohn Marino     data = xmalloc (sizeof (struct logfile_info));
307*86d7f5d3SJohn Marino     data->type = status;
308*86d7f5d3SJohn Marino     data->tag = xstrdup (vers->tag);
309*86d7f5d3SJohn Marino     data->rev_old = data->rev_new = NULL;
310*86d7f5d3SJohn Marino 
311*86d7f5d3SJohn Marino     node->type = UPDATE;
312*86d7f5d3SJohn Marino     node->delproc = update_delproc;
313*86d7f5d3SJohn Marino     node->data = data;
314*86d7f5d3SJohn Marino     (void)addnode (args->ulist, node);
315*86d7f5d3SJohn Marino 
316*86d7f5d3SJohn Marino     ++args->argc;
317*86d7f5d3SJohn Marino 
318*86d7f5d3SJohn Marino     freevers_ts (&vers);
319*86d7f5d3SJohn Marino     return 0;
320*86d7f5d3SJohn Marino }
321*86d7f5d3SJohn Marino 
322*86d7f5d3SJohn Marino 
323*86d7f5d3SJohn Marino 
324*86d7f5d3SJohn Marino static int
copy_ulist(Node * node,void * data)325*86d7f5d3SJohn Marino copy_ulist (Node *node, void *data)
326*86d7f5d3SJohn Marino {
327*86d7f5d3SJohn Marino     struct find_data *args = data;
328*86d7f5d3SJohn Marino     args->argv[args->argc++] = node->key;
329*86d7f5d3SJohn Marino     return 0;
330*86d7f5d3SJohn Marino }
331*86d7f5d3SJohn Marino #endif /* CLIENT_SUPPORT */
332*86d7f5d3SJohn Marino 
333*86d7f5d3SJohn Marino 
334*86d7f5d3SJohn Marino 
335*86d7f5d3SJohn Marino #ifdef SERVER_SUPPORT
336*86d7f5d3SJohn Marino # define COMMIT_OPTIONS "+cnlRm:fF:r:"
337*86d7f5d3SJohn Marino #else /* !SERVER_SUPPORT */
338*86d7f5d3SJohn Marino # define COMMIT_OPTIONS "+clRm:fF:r:"
339*86d7f5d3SJohn Marino #endif /* SERVER_SUPPORT */
340*86d7f5d3SJohn Marino int
commit(int argc,char ** argv)341*86d7f5d3SJohn Marino commit (int argc, char **argv)
342*86d7f5d3SJohn Marino {
343*86d7f5d3SJohn Marino     int c;
344*86d7f5d3SJohn Marino     int err = 0;
345*86d7f5d3SJohn Marino     int local = 0;
346*86d7f5d3SJohn Marino 
347*86d7f5d3SJohn Marino     if (argc == -1)
348*86d7f5d3SJohn Marino 	usage (commit_usage);
349*86d7f5d3SJohn Marino 
350*86d7f5d3SJohn Marino #ifdef CVS_BADROOT
351*86d7f5d3SJohn Marino     /*
352*86d7f5d3SJohn Marino      * For log purposes, do not allow "root" to commit files.  If you look
353*86d7f5d3SJohn Marino      * like root, but are really logged in as a non-root user, it's OK.
354*86d7f5d3SJohn Marino      */
355*86d7f5d3SJohn Marino     /* FIXME: Shouldn't this check be much more closely related to the
356*86d7f5d3SJohn Marino        readonly user stuff (CVSROOT/readers, &c).  That is, why should
357*86d7f5d3SJohn Marino        root be able to "cvs init", "cvs import", &c, but not "cvs ci"?  */
358*86d7f5d3SJohn Marino     /* Who we are on the client side doesn't affect logging.  */
359*86d7f5d3SJohn Marino     if (geteuid () == (uid_t) 0 && !current_parsed_root->isremote)
360*86d7f5d3SJohn Marino     {
361*86d7f5d3SJohn Marino 	struct passwd *pw;
362*86d7f5d3SJohn Marino 
363*86d7f5d3SJohn Marino 	if ((pw = getpwnam (getcaller ())) == NULL)
364*86d7f5d3SJohn Marino 	    error (1, 0,
365*86d7f5d3SJohn Marino                    "your apparent username (%s) is unknown to this system",
366*86d7f5d3SJohn Marino                    getcaller ());
367*86d7f5d3SJohn Marino 	if (pw->pw_uid == (uid_t) 0)
368*86d7f5d3SJohn Marino 	    error (1, 0, "'root' is not allowed to commit files");
369*86d7f5d3SJohn Marino     }
370*86d7f5d3SJohn Marino #endif /* CVS_BADROOT */
371*86d7f5d3SJohn Marino 
372*86d7f5d3SJohn Marino     optind = 0;
373*86d7f5d3SJohn Marino     while ((c = getopt (argc, argv, COMMIT_OPTIONS)) != -1)
374*86d7f5d3SJohn Marino     {
375*86d7f5d3SJohn Marino 	switch (c)
376*86d7f5d3SJohn Marino 	{
377*86d7f5d3SJohn Marino             case 'c':
378*86d7f5d3SJohn Marino                 check_valid_edit = 1;
379*86d7f5d3SJohn Marino                 break;
380*86d7f5d3SJohn Marino #ifdef SERVER_SUPPORT
381*86d7f5d3SJohn Marino 	    case 'n':
382*86d7f5d3SJohn Marino 		/* Silently ignore -n for compatibility with old
383*86d7f5d3SJohn Marino 		 * clients.
384*86d7f5d3SJohn Marino 		 */
385*86d7f5d3SJohn Marino 		break;
386*86d7f5d3SJohn Marino #endif /* SERVER_SUPPORT */
387*86d7f5d3SJohn Marino 	    case 'm':
388*86d7f5d3SJohn Marino #ifdef FORCE_USE_EDITOR
389*86d7f5d3SJohn Marino 		use_editor = 1;
390*86d7f5d3SJohn Marino #else
391*86d7f5d3SJohn Marino 		use_editor = 0;
392*86d7f5d3SJohn Marino #endif
393*86d7f5d3SJohn Marino 		if (saved_message)
394*86d7f5d3SJohn Marino 		{
395*86d7f5d3SJohn Marino 		    free (saved_message);
396*86d7f5d3SJohn Marino 		    saved_message = NULL;
397*86d7f5d3SJohn Marino 		}
398*86d7f5d3SJohn Marino 
399*86d7f5d3SJohn Marino 		saved_message = xstrdup (optarg);
400*86d7f5d3SJohn Marino 		break;
401*86d7f5d3SJohn Marino 	    case 'r':
402*86d7f5d3SJohn Marino 		if (saved_tag)
403*86d7f5d3SJohn Marino 		    free (saved_tag);
404*86d7f5d3SJohn Marino 		saved_tag = xstrdup (optarg);
405*86d7f5d3SJohn Marino 		break;
406*86d7f5d3SJohn Marino 	    case 'l':
407*86d7f5d3SJohn Marino 		local = 1;
408*86d7f5d3SJohn Marino 		break;
409*86d7f5d3SJohn Marino 	    case 'R':
410*86d7f5d3SJohn Marino 		local = 0;
411*86d7f5d3SJohn Marino 		break;
412*86d7f5d3SJohn Marino 	    case 'f':
413*86d7f5d3SJohn Marino 		force_ci = 1;
414*86d7f5d3SJohn Marino                 check_valid_edit = 0;
415*86d7f5d3SJohn Marino 		local = 1;		/* also disable recursion */
416*86d7f5d3SJohn Marino 		break;
417*86d7f5d3SJohn Marino 	    case 'F':
418*86d7f5d3SJohn Marino #ifdef FORCE_USE_EDITOR
419*86d7f5d3SJohn Marino 		use_editor = 1;
420*86d7f5d3SJohn Marino #else
421*86d7f5d3SJohn Marino 		use_editor = 0;
422*86d7f5d3SJohn Marino #endif
423*86d7f5d3SJohn Marino 		logfile = optarg;
424*86d7f5d3SJohn Marino 		break;
425*86d7f5d3SJohn Marino 	    case '?':
426*86d7f5d3SJohn Marino 	    default:
427*86d7f5d3SJohn Marino 		usage (commit_usage);
428*86d7f5d3SJohn Marino 		break;
429*86d7f5d3SJohn Marino 	}
430*86d7f5d3SJohn Marino     }
431*86d7f5d3SJohn Marino     argc -= optind;
432*86d7f5d3SJohn Marino     argv += optind;
433*86d7f5d3SJohn Marino 
434*86d7f5d3SJohn Marino     /* numeric specified revision means we ignore sticky tags... */
435*86d7f5d3SJohn Marino     if (saved_tag && isdigit ((unsigned char) *saved_tag))
436*86d7f5d3SJohn Marino     {
437*86d7f5d3SJohn Marino 	char *p = saved_tag + strlen (saved_tag);
438*86d7f5d3SJohn Marino 	aflag = 1;
439*86d7f5d3SJohn Marino 	/* strip trailing dots and leading zeros */
440*86d7f5d3SJohn Marino 	while (*--p == '.') ;
441*86d7f5d3SJohn Marino 	p[1] = '\0';
442*86d7f5d3SJohn Marino 	while (saved_tag[0] == '0' && isdigit ((unsigned char) saved_tag[1]))
443*86d7f5d3SJohn Marino 	    ++saved_tag;
444*86d7f5d3SJohn Marino     }
445*86d7f5d3SJohn Marino 
446*86d7f5d3SJohn Marino     /* Check if the user passed -m, but wanted -F */
447*86d7f5d3SJohn Marino     if (saved_message)
448*86d7f5d3SJohn Marino     {
449*86d7f5d3SJohn Marino 	int fd;
450*86d7f5d3SJohn Marino 
451*86d7f5d3SJohn Marino 	fd = open(saved_message, O_RDONLY);
452*86d7f5d3SJohn Marino 	/* valid fd -> possibly wrong flag? */
453*86d7f5d3SJohn Marino 	if (fd >= 0)
454*86d7f5d3SJohn Marino 	{
455*86d7f5d3SJohn Marino 	    char *line = NULL;
456*86d7f5d3SJohn Marino 	    size_t line_alloced = 0;
457*86d7f5d3SJohn Marino 	    int line_len;
458*86d7f5d3SJohn Marino 
459*86d7f5d3SJohn Marino 	    close(fd);
460*86d7f5d3SJohn Marino 
461*86d7f5d3SJohn Marino 	    for (;;) {
462*86d7f5d3SJohn Marino 		printf("The message you passed exists as a file\n");
463*86d7f5d3SJohn Marino 		printf("a)bort, c)ontinue, treat as f)ile name\n");
464*86d7f5d3SJohn Marino 		printf("Action: (abort) ");
465*86d7f5d3SJohn Marino 		fflush(stdout);
466*86d7f5d3SJohn Marino 		line_len = getline(&line, &line_alloced, stdin);
467*86d7f5d3SJohn Marino 		if (line_len < 0)
468*86d7f5d3SJohn Marino 		{
469*86d7f5d3SJohn Marino 		    error(0, errno, "cannot read from stdin");
470*86d7f5d3SJohn Marino 		    error(1, 0, "aborting");
471*86d7f5d3SJohn Marino 		}
472*86d7f5d3SJohn Marino 		else if (line_len == 0 || *line == '\n' || *line == 'a' || *line == 'A')
473*86d7f5d3SJohn Marino 		{
474*86d7f5d3SJohn Marino 		    error(1, 0, "aborted by user");
475*86d7f5d3SJohn Marino 		}
476*86d7f5d3SJohn Marino 		else if (*line == 'c' || *line == 'C')
477*86d7f5d3SJohn Marino 		{
478*86d7f5d3SJohn Marino 		    break;
479*86d7f5d3SJohn Marino 		}
480*86d7f5d3SJohn Marino 		else if (*line == 'f' || *line == 'F')
481*86d7f5d3SJohn Marino 		{
482*86d7f5d3SJohn Marino 		    /*
483*86d7f5d3SJohn Marino 		     * We are leaking the memory for the file name,
484*86d7f5d3SJohn Marino 		     * but who really cares?
485*86d7f5d3SJohn Marino 		     */
486*86d7f5d3SJohn Marino 		    logfile = saved_message;
487*86d7f5d3SJohn Marino 		    saved_message = NULL;
488*86d7f5d3SJohn Marino 		    break;
489*86d7f5d3SJohn Marino 		}
490*86d7f5d3SJohn Marino 		printf("Unknown input\n");
491*86d7f5d3SJohn Marino 	    }
492*86d7f5d3SJohn Marino 
493*86d7f5d3SJohn Marino 	    if (line != NULL)
494*86d7f5d3SJohn Marino 		free(line);
495*86d7f5d3SJohn Marino 	}
496*86d7f5d3SJohn Marino     }
497*86d7f5d3SJohn Marino 
498*86d7f5d3SJohn Marino     /* some checks related to the "-F logfile" option */
499*86d7f5d3SJohn Marino     if (logfile)
500*86d7f5d3SJohn Marino     {
501*86d7f5d3SJohn Marino 	size_t size = 0, len;
502*86d7f5d3SJohn Marino 
503*86d7f5d3SJohn Marino 	if (saved_message)
504*86d7f5d3SJohn Marino 	    error (1, 0, "cannot specify both a message and a log file");
505*86d7f5d3SJohn Marino 
506*86d7f5d3SJohn Marino 	get_file (logfile, logfile, "r", &saved_message, &size, &len);
507*86d7f5d3SJohn Marino     }
508*86d7f5d3SJohn Marino 
509*86d7f5d3SJohn Marino #ifdef CLIENT_SUPPORT
510*86d7f5d3SJohn Marino     if (current_parsed_root->isremote)
511*86d7f5d3SJohn Marino     {
512*86d7f5d3SJohn Marino 	struct find_data find_args;
513*86d7f5d3SJohn Marino 
514*86d7f5d3SJohn Marino 	ign_setup ();
515*86d7f5d3SJohn Marino 
516*86d7f5d3SJohn Marino 	find_args.ulist = getlist ();
517*86d7f5d3SJohn Marino 	find_args.argc = 0;
518*86d7f5d3SJohn Marino 	find_args.questionables = NULL;
519*86d7f5d3SJohn Marino 	find_args.ignlist = NULL;
520*86d7f5d3SJohn Marino 	find_args.repository = NULL;
521*86d7f5d3SJohn Marino 
522*86d7f5d3SJohn Marino 	/* It is possible that only a numeric tag should set this.
523*86d7f5d3SJohn Marino 	   I haven't really thought about it much.
524*86d7f5d3SJohn Marino 	   Anyway, I suspect that setting it unnecessarily only causes
525*86d7f5d3SJohn Marino 	   a little unneeded network traffic.  */
526*86d7f5d3SJohn Marino 	find_args.force = force_ci || saved_tag != NULL;
527*86d7f5d3SJohn Marino 
528*86d7f5d3SJohn Marino 	err = start_recursion
529*86d7f5d3SJohn Marino 	    (find_fileproc, find_filesdoneproc, find_dirent_proc, NULL,
530*86d7f5d3SJohn Marino 	     &find_args, argc, argv, local, W_LOCAL, 0, CVS_LOCK_NONE,
531*86d7f5d3SJohn Marino 	     NULL, 0, NULL );
532*86d7f5d3SJohn Marino 	if (err)
533*86d7f5d3SJohn Marino 	    error (1, 0, "correct above errors first!");
534*86d7f5d3SJohn Marino 
535*86d7f5d3SJohn Marino 	if (find_args.argc == 0)
536*86d7f5d3SJohn Marino 	{
537*86d7f5d3SJohn Marino 	    /* Nothing to commit.  Exit now without contacting the
538*86d7f5d3SJohn Marino 	       server (note that this means that we won't print "?
539*86d7f5d3SJohn Marino 	       foo" for files which merit it, because we don't know
540*86d7f5d3SJohn Marino 	       what is in the CVSROOT/cvsignore file).  */
541*86d7f5d3SJohn Marino 	    dellist (&find_args.ulist);
542*86d7f5d3SJohn Marino 	    return 0;
543*86d7f5d3SJohn Marino 	}
544*86d7f5d3SJohn Marino 
545*86d7f5d3SJohn Marino 	/* Now we keep track of which files we actually are going to
546*86d7f5d3SJohn Marino 	   operate on, and only work with those files in the future.
547*86d7f5d3SJohn Marino 	   This saves time--we don't want to search the file system
548*86d7f5d3SJohn Marino 	   of the working directory twice.  */
549*86d7f5d3SJohn Marino 	if (size_overflow_p (xtimes (find_args.argc, sizeof (char **))))
550*86d7f5d3SJohn Marino 	{
551*86d7f5d3SJohn Marino 	    find_args.argc = 0;
552*86d7f5d3SJohn Marino 	    return 0;
553*86d7f5d3SJohn Marino 	}
554*86d7f5d3SJohn Marino 	find_args.argv = xnmalloc (find_args.argc, sizeof (char **));
555*86d7f5d3SJohn Marino 	find_args.argc = 0;
556*86d7f5d3SJohn Marino 	walklist (find_args.ulist, copy_ulist, &find_args);
557*86d7f5d3SJohn Marino 
558*86d7f5d3SJohn Marino 	/* Do this before calling do_editor; don't ask for a log
559*86d7f5d3SJohn Marino 	   message if we can't talk to the server.  But do it after we
560*86d7f5d3SJohn Marino 	   have made the checks that we can locally (to more quickly
561*86d7f5d3SJohn Marino 	   catch syntax errors, the case where no files are modified,
562*86d7f5d3SJohn Marino 	   added or removed, etc.).
563*86d7f5d3SJohn Marino 
564*86d7f5d3SJohn Marino 	   On the other hand, calling start_server before do_editor
565*86d7f5d3SJohn Marino 	   means that we chew up server resources the whole time that
566*86d7f5d3SJohn Marino 	   the user has the editor open (hours or days if the user
567*86d7f5d3SJohn Marino 	   forgets about it), which seems dubious.  */
568*86d7f5d3SJohn Marino 	start_server ();
569*86d7f5d3SJohn Marino 
570*86d7f5d3SJohn Marino 	/*
571*86d7f5d3SJohn Marino 	 * We do this once, not once for each directory as in normal CVS.
572*86d7f5d3SJohn Marino 	 * The protocol is designed this way.  This is a feature.
573*86d7f5d3SJohn Marino 	 */
574*86d7f5d3SJohn Marino 	if (use_editor)
575*86d7f5d3SJohn Marino 	    do_editor (".", &saved_message, NULL, find_args.ulist);
576*86d7f5d3SJohn Marino 
577*86d7f5d3SJohn Marino 	/* We always send some sort of message, even if empty.  */
578*86d7f5d3SJohn Marino 	option_with_arg ("-m", saved_message ? saved_message : "");
579*86d7f5d3SJohn Marino 
580*86d7f5d3SJohn Marino 	/* OK, now process all the questionable files we have been saving
581*86d7f5d3SJohn Marino 	   up.  */
582*86d7f5d3SJohn Marino 	{
583*86d7f5d3SJohn Marino 	    struct question *p;
584*86d7f5d3SJohn Marino 	    struct question *q;
585*86d7f5d3SJohn Marino 
586*86d7f5d3SJohn Marino 	    p = find_args.questionables;
587*86d7f5d3SJohn Marino 	    while (p != NULL)
588*86d7f5d3SJohn Marino 	    {
589*86d7f5d3SJohn Marino 		if (ign_inhibit_server || !supported_request ("Questionable"))
590*86d7f5d3SJohn Marino 		{
591*86d7f5d3SJohn Marino 		    cvs_output ("? ", 2);
592*86d7f5d3SJohn Marino 		    if (p->dir[0] != '\0')
593*86d7f5d3SJohn Marino 		    {
594*86d7f5d3SJohn Marino 			cvs_output (p->dir, 0);
595*86d7f5d3SJohn Marino 			cvs_output ("/", 1);
596*86d7f5d3SJohn Marino 		    }
597*86d7f5d3SJohn Marino 		    cvs_output (p->file, 0);
598*86d7f5d3SJohn Marino 		    cvs_output ("\n", 1);
599*86d7f5d3SJohn Marino 		}
600*86d7f5d3SJohn Marino 		else
601*86d7f5d3SJohn Marino 		{
602*86d7f5d3SJohn Marino 		    /* This used to send the Directory line of its own accord,
603*86d7f5d3SJohn Marino 		     * but skipped some of the other processing like checking
604*86d7f5d3SJohn Marino 		     * for whether the server would accept "Relative-directory"
605*86d7f5d3SJohn Marino 		     * requests.  Relying on send_a_repository() to do this
606*86d7f5d3SJohn Marino 		     * picks up these checks but also:
607*86d7f5d3SJohn Marino 		     *
608*86d7f5d3SJohn Marino 		     *   1. Causes the "Directory" request to be sent only once
609*86d7f5d3SJohn Marino 		     *      per directory.
610*86d7f5d3SJohn Marino 		     *   2. Causes the global TOPLEVEL_REPOS to be set.
611*86d7f5d3SJohn Marino 		     *   3. Causes "Static-directory" and "Sticky" requests
612*86d7f5d3SJohn Marino 		     *      to sometimes be sent.
613*86d7f5d3SJohn Marino 		     *
614*86d7f5d3SJohn Marino 		     * (1) is almost certainly a plus.  (2) & (3) may or may
615*86d7f5d3SJohn Marino 		     * not be useful sometimes, and will ocassionally cause a
616*86d7f5d3SJohn Marino 		     * little extra network traffic.  The additional network
617*86d7f5d3SJohn Marino 		     * traffic is probably already saved several times over and
618*86d7f5d3SJohn Marino 		     * certainly cancelled out via the multiple "Directory"
619*86d7f5d3SJohn Marino 		     * request suppression of (1).
620*86d7f5d3SJohn Marino 		     */
621*86d7f5d3SJohn Marino 		    send_a_repository (p->dir, p->repos, p->dir);
622*86d7f5d3SJohn Marino 
623*86d7f5d3SJohn Marino 		    send_to_server ("Questionable ", 0);
624*86d7f5d3SJohn Marino 		    send_to_server (p->file, 0);
625*86d7f5d3SJohn Marino 		    send_to_server ("\012", 1);
626*86d7f5d3SJohn Marino 		}
627*86d7f5d3SJohn Marino 		free (p->dir);
628*86d7f5d3SJohn Marino 		free (p->repos);
629*86d7f5d3SJohn Marino 		free (p->file);
630*86d7f5d3SJohn Marino 		q = p->next;
631*86d7f5d3SJohn Marino 		free (p);
632*86d7f5d3SJohn Marino 		p = q;
633*86d7f5d3SJohn Marino 	    }
634*86d7f5d3SJohn Marino 	}
635*86d7f5d3SJohn Marino 
636*86d7f5d3SJohn Marino 	if (local)
637*86d7f5d3SJohn Marino 	    send_arg ("-l");
638*86d7f5d3SJohn Marino         if (check_valid_edit)
639*86d7f5d3SJohn Marino             send_arg ("-c");
640*86d7f5d3SJohn Marino 	if (force_ci)
641*86d7f5d3SJohn Marino 	    send_arg ("-f");
642*86d7f5d3SJohn Marino 	option_with_arg ("-r", saved_tag);
643*86d7f5d3SJohn Marino 	send_arg ("--");
644*86d7f5d3SJohn Marino 
645*86d7f5d3SJohn Marino 	/* FIXME: This whole find_args.force/SEND_FORCE business is a
646*86d7f5d3SJohn Marino 	   kludge.  It would seem to be a server bug that we have to
647*86d7f5d3SJohn Marino 	   say that files are modified when they are not.  This makes
648*86d7f5d3SJohn Marino 	   "cvs commit -r 2" across a whole bunch of files a very slow
649*86d7f5d3SJohn Marino 	   operation (and it isn't documented in cvsclient.texi).  I
650*86d7f5d3SJohn Marino 	   haven't looked at the server code carefully enough to be
651*86d7f5d3SJohn Marino 	   _sure_ why this is needed, but if it is because the "ci"
652*86d7f5d3SJohn Marino 	   program, which we used to call, wanted the file to exist,
653*86d7f5d3SJohn Marino 	   then it would be relatively simple to fix in the server.  */
654*86d7f5d3SJohn Marino 	send_files (find_args.argc, find_args.argv, local, 0,
655*86d7f5d3SJohn Marino 		    find_args.force ? SEND_FORCE : 0);
656*86d7f5d3SJohn Marino 
657*86d7f5d3SJohn Marino 	/* Sending only the names of the files which were modified, added,
658*86d7f5d3SJohn Marino 	   or removed means that the server will only do an up-to-date
659*86d7f5d3SJohn Marino 	   check on those files.  This is different from local CVS and
660*86d7f5d3SJohn Marino 	   previous versions of client/server CVS, but it probably is a Good
661*86d7f5d3SJohn Marino 	   Thing, or at least Not Such A Bad Thing.  */
662*86d7f5d3SJohn Marino 	send_file_names (find_args.argc, find_args.argv, 0);
663*86d7f5d3SJohn Marino 	free (find_args.argv);
664*86d7f5d3SJohn Marino 	dellist (&find_args.ulist);
665*86d7f5d3SJohn Marino 
666*86d7f5d3SJohn Marino 	send_to_server ("ci\012", 0);
667*86d7f5d3SJohn Marino 	err = get_responses_and_close ();
668*86d7f5d3SJohn Marino 	if (err != 0 && use_editor && saved_message != NULL)
669*86d7f5d3SJohn Marino 	{
670*86d7f5d3SJohn Marino 	    /* If there was an error, don't nuke the user's carefully
671*86d7f5d3SJohn Marino 	       constructed prose.  This is something of a kludge; a better
672*86d7f5d3SJohn Marino 	       solution is probably more along the lines of #150 in TODO
673*86d7f5d3SJohn Marino 	       (doing a second up-to-date check before accepting the
674*86d7f5d3SJohn Marino 	       log message has also been suggested, but that seems kind of
675*86d7f5d3SJohn Marino 	       iffy because the real up-to-date check could still fail,
676*86d7f5d3SJohn Marino 	       another error could occur, &c.  Also, a second check would
677*86d7f5d3SJohn Marino 	       slow things down).  */
678*86d7f5d3SJohn Marino 
679*86d7f5d3SJohn Marino 	    char *fname;
680*86d7f5d3SJohn Marino 	    FILE *fp;
681*86d7f5d3SJohn Marino 
682*86d7f5d3SJohn Marino 	    fp = cvs_temp_file (&fname);
683*86d7f5d3SJohn Marino 	    if (fp == NULL)
684*86d7f5d3SJohn Marino 		error (1, 0, "cannot create temporary file %s", fname);
685*86d7f5d3SJohn Marino 	    if (fwrite (saved_message, 1, strlen (saved_message), fp)
686*86d7f5d3SJohn Marino 		!= strlen (saved_message))
687*86d7f5d3SJohn Marino 		error (1, errno, "cannot write temporary file %s", fname);
688*86d7f5d3SJohn Marino 	    if (fclose (fp) < 0)
689*86d7f5d3SJohn Marino 		error (0, errno, "cannot close temporary file %s", fname);
690*86d7f5d3SJohn Marino 	    error (0, 0, "saving log message in %s", fname);
691*86d7f5d3SJohn Marino 	    free (fname);
692*86d7f5d3SJohn Marino 	}
693*86d7f5d3SJohn Marino 	return err;
694*86d7f5d3SJohn Marino     }
695*86d7f5d3SJohn Marino #endif
696*86d7f5d3SJohn Marino 
697*86d7f5d3SJohn Marino     if (saved_tag != NULL)
698*86d7f5d3SJohn Marino 	tag_check_valid (saved_tag, argc, argv, local, aflag, "", false);
699*86d7f5d3SJohn Marino 
700*86d7f5d3SJohn Marino     /* XXX - this is not the perfect check for this */
701*86d7f5d3SJohn Marino     if (argc <= 0)
702*86d7f5d3SJohn Marino 	write_dirtag = saved_tag;
703*86d7f5d3SJohn Marino 
704*86d7f5d3SJohn Marino     wrap_setup ();
705*86d7f5d3SJohn Marino 
706*86d7f5d3SJohn Marino     lock_tree_promotably (argc, argv, local, W_LOCAL, aflag);
707*86d7f5d3SJohn Marino 
708*86d7f5d3SJohn Marino     /*
709*86d7f5d3SJohn Marino      * Set up the master update list and hard link list
710*86d7f5d3SJohn Marino      */
711*86d7f5d3SJohn Marino     mulist = getlist ();
712*86d7f5d3SJohn Marino 
713*86d7f5d3SJohn Marino #ifdef PRESERVE_PERMISSIONS_SUPPORT
714*86d7f5d3SJohn Marino     if (preserve_perms)
715*86d7f5d3SJohn Marino     {
716*86d7f5d3SJohn Marino 	hardlist = getlist ();
717*86d7f5d3SJohn Marino 
718*86d7f5d3SJohn Marino 	/*
719*86d7f5d3SJohn Marino 	 * We need to save the working directory so that
720*86d7f5d3SJohn Marino 	 * check_fileproc can construct a full pathname for each file.
721*86d7f5d3SJohn Marino 	 */
722*86d7f5d3SJohn Marino 	working_dir = xgetcwd ();
723*86d7f5d3SJohn Marino     }
724*86d7f5d3SJohn Marino #endif
725*86d7f5d3SJohn Marino 
726*86d7f5d3SJohn Marino     /*
727*86d7f5d3SJohn Marino      * Run the recursion processor to verify the files are all up-to-date
728*86d7f5d3SJohn Marino      */
729*86d7f5d3SJohn Marino     err = start_recursion (check_fileproc, check_filesdoneproc,
730*86d7f5d3SJohn Marino                            check_direntproc, NULL, NULL, argc, argv, local,
731*86d7f5d3SJohn Marino                            W_LOCAL, aflag, CVS_LOCK_NONE, NULL, 1, NULL);
732*86d7f5d3SJohn Marino     if (err)
733*86d7f5d3SJohn Marino 	error (1, 0, "correct above errors first!");
734*86d7f5d3SJohn Marino 
735*86d7f5d3SJohn Marino     /*
736*86d7f5d3SJohn Marino      * Run the recursion processor to commit the files
737*86d7f5d3SJohn Marino      */
738*86d7f5d3SJohn Marino     write_dirnonbranch = 0;
739*86d7f5d3SJohn Marino     if (noexec == 0)
740*86d7f5d3SJohn Marino 	err = start_recursion (commit_fileproc, commit_filesdoneproc,
741*86d7f5d3SJohn Marino                                commit_direntproc, commit_dirleaveproc, NULL,
742*86d7f5d3SJohn Marino                                argc, argv, local, W_LOCAL, aflag,
743*86d7f5d3SJohn Marino                                CVS_LOCK_WRITE, NULL, 1, NULL);
744*86d7f5d3SJohn Marino 
745*86d7f5d3SJohn Marino     /*
746*86d7f5d3SJohn Marino      * Unlock all the dirs and clean up
747*86d7f5d3SJohn Marino      */
748*86d7f5d3SJohn Marino     Lock_Cleanup ();
749*86d7f5d3SJohn Marino     dellist (&mulist);
750*86d7f5d3SJohn Marino 
751*86d7f5d3SJohn Marino     /* see if we need to sleep before returning to avoid time-stamp races */
752*86d7f5d3SJohn Marino     if (!server_active && last_register_time)
753*86d7f5d3SJohn Marino     {
754*86d7f5d3SJohn Marino 	sleep_past (last_register_time);
755*86d7f5d3SJohn Marino     }
756*86d7f5d3SJohn Marino 
757*86d7f5d3SJohn Marino     return err;
758*86d7f5d3SJohn Marino }
759*86d7f5d3SJohn Marino 
760*86d7f5d3SJohn Marino 
761*86d7f5d3SJohn Marino 
762*86d7f5d3SJohn Marino /* This routine determines the status of a given file and retrieves
763*86d7f5d3SJohn Marino    the version information that is associated with that file. */
764*86d7f5d3SJohn Marino 
765*86d7f5d3SJohn Marino static
766*86d7f5d3SJohn Marino Ctype
classify_file_internal(struct file_info * finfo,Vers_TS ** vers)767*86d7f5d3SJohn Marino classify_file_internal (struct file_info *finfo, Vers_TS **vers)
768*86d7f5d3SJohn Marino {
769*86d7f5d3SJohn Marino     int save_noexec, save_quiet, save_really_quiet;
770*86d7f5d3SJohn Marino     Ctype status;
771*86d7f5d3SJohn Marino 
772*86d7f5d3SJohn Marino     /* FIXME: Do we need to save quiet as well as really_quiet?  Last
773*86d7f5d3SJohn Marino        time I glanced at Classify_File I only saw it looking at really_quiet
774*86d7f5d3SJohn Marino        not quiet.  */
775*86d7f5d3SJohn Marino     save_noexec = noexec;
776*86d7f5d3SJohn Marino     save_quiet = quiet;
777*86d7f5d3SJohn Marino     save_really_quiet = really_quiet;
778*86d7f5d3SJohn Marino     noexec = quiet = really_quiet = 1;
779*86d7f5d3SJohn Marino 
780*86d7f5d3SJohn Marino     /* handle specified numeric revision specially */
781*86d7f5d3SJohn Marino     if (saved_tag && isdigit ((unsigned char) *saved_tag))
782*86d7f5d3SJohn Marino     {
783*86d7f5d3SJohn Marino 	/* If the tag is for the trunk, make sure we're at the head */
784*86d7f5d3SJohn Marino 	if (numdots (saved_tag) < 2)
785*86d7f5d3SJohn Marino 	{
786*86d7f5d3SJohn Marino 	    status = Classify_File (finfo, NULL, NULL,
787*86d7f5d3SJohn Marino 				    NULL, 1, aflag, vers, 0);
788*86d7f5d3SJohn Marino 	    if (status == T_UPTODATE || status == T_MODIFIED ||
789*86d7f5d3SJohn Marino 		status == T_ADDED)
790*86d7f5d3SJohn Marino 	    {
791*86d7f5d3SJohn Marino 		Ctype xstatus;
792*86d7f5d3SJohn Marino 
793*86d7f5d3SJohn Marino 		freevers_ts (vers);
794*86d7f5d3SJohn Marino 		xstatus = Classify_File (finfo, saved_tag, NULL,
795*86d7f5d3SJohn Marino 					 NULL, 1, aflag, vers, 0);
796*86d7f5d3SJohn Marino 		if (xstatus == T_REMOVE_ENTRY)
797*86d7f5d3SJohn Marino 		    status = T_MODIFIED;
798*86d7f5d3SJohn Marino 		else if (status == T_MODIFIED && xstatus == T_CONFLICT)
799*86d7f5d3SJohn Marino 		    status = T_MODIFIED;
800*86d7f5d3SJohn Marino 		else
801*86d7f5d3SJohn Marino 		    status = xstatus;
802*86d7f5d3SJohn Marino 	    }
803*86d7f5d3SJohn Marino 	}
804*86d7f5d3SJohn Marino 	else
805*86d7f5d3SJohn Marino 	{
806*86d7f5d3SJohn Marino 	    char *xtag, *cp;
807*86d7f5d3SJohn Marino 
808*86d7f5d3SJohn Marino 	    /*
809*86d7f5d3SJohn Marino 	     * The revision is off the main trunk; make sure we're
810*86d7f5d3SJohn Marino 	     * up-to-date with the head of the specified branch.
811*86d7f5d3SJohn Marino 	     */
812*86d7f5d3SJohn Marino 	    xtag = xstrdup (saved_tag);
813*86d7f5d3SJohn Marino 	    if ((numdots (xtag) & 1) != 0)
814*86d7f5d3SJohn Marino 	    {
815*86d7f5d3SJohn Marino 		cp = strrchr (xtag, '.');
816*86d7f5d3SJohn Marino 		*cp = '\0';
817*86d7f5d3SJohn Marino 	    }
818*86d7f5d3SJohn Marino 	    status = Classify_File (finfo, xtag, NULL,
819*86d7f5d3SJohn Marino 				    NULL, 1, aflag, vers, 0);
820*86d7f5d3SJohn Marino 	    if ((status == T_REMOVE_ENTRY || status == T_CONFLICT)
821*86d7f5d3SJohn Marino 		&& (cp = strrchr (xtag, '.')) != NULL)
822*86d7f5d3SJohn Marino 	    {
823*86d7f5d3SJohn Marino 		/* pluck one more dot off the revision */
824*86d7f5d3SJohn Marino 		*cp = '\0';
825*86d7f5d3SJohn Marino 		freevers_ts (vers);
826*86d7f5d3SJohn Marino 		status = Classify_File (finfo, xtag, NULL,
827*86d7f5d3SJohn Marino 					NULL, 1, aflag, vers, 0);
828*86d7f5d3SJohn Marino 		if (status == T_UPTODATE || status == T_REMOVE_ENTRY)
829*86d7f5d3SJohn Marino 		    status = T_MODIFIED;
830*86d7f5d3SJohn Marino 	    }
831*86d7f5d3SJohn Marino 	    /* now, muck with vers to make the tag correct */
832*86d7f5d3SJohn Marino 	    free ((*vers)->tag);
833*86d7f5d3SJohn Marino 	    (*vers)->tag = xstrdup (saved_tag);
834*86d7f5d3SJohn Marino 	    free (xtag);
835*86d7f5d3SJohn Marino 	}
836*86d7f5d3SJohn Marino     }
837*86d7f5d3SJohn Marino     else
838*86d7f5d3SJohn Marino 	status = Classify_File (finfo, saved_tag, NULL, NULL, 1, 0, vers, 0);
839*86d7f5d3SJohn Marino     noexec = save_noexec;
840*86d7f5d3SJohn Marino     quiet = save_quiet;
841*86d7f5d3SJohn Marino     really_quiet = save_really_quiet;
842*86d7f5d3SJohn Marino 
843*86d7f5d3SJohn Marino     return status;
844*86d7f5d3SJohn Marino }
845*86d7f5d3SJohn Marino 
846*86d7f5d3SJohn Marino 
847*86d7f5d3SJohn Marino 
848*86d7f5d3SJohn Marino /*
849*86d7f5d3SJohn Marino  * Check to see if a file is ok to commit and make sure all files are
850*86d7f5d3SJohn Marino  * up-to-date
851*86d7f5d3SJohn Marino  */
852*86d7f5d3SJohn Marino /* ARGSUSED */
853*86d7f5d3SJohn Marino static int
check_fileproc(void * callerdat,struct file_info * finfo)854*86d7f5d3SJohn Marino check_fileproc (void *callerdat, struct file_info *finfo)
855*86d7f5d3SJohn Marino {
856*86d7f5d3SJohn Marino     Ctype status;
857*86d7f5d3SJohn Marino     const char *xdir;
858*86d7f5d3SJohn Marino     Node *p;
859*86d7f5d3SJohn Marino     List *ulist, *cilist;
860*86d7f5d3SJohn Marino     Vers_TS *vers;
861*86d7f5d3SJohn Marino     struct commit_info *ci;
862*86d7f5d3SJohn Marino     struct logfile_info *li;
863*86d7f5d3SJohn Marino     int retval = 1;
864*86d7f5d3SJohn Marino 
865*86d7f5d3SJohn Marino     size_t cvsroot_len = strlen (current_parsed_root->directory);
866*86d7f5d3SJohn Marino 
867*86d7f5d3SJohn Marino     if (!finfo->repository)
868*86d7f5d3SJohn Marino     {
869*86d7f5d3SJohn Marino 	error (0, 0, "nothing known about `%s'", finfo->fullname);
870*86d7f5d3SJohn Marino 	return 1;
871*86d7f5d3SJohn Marino     }
872*86d7f5d3SJohn Marino 
873*86d7f5d3SJohn Marino     if (strncmp (finfo->repository, current_parsed_root->directory,
874*86d7f5d3SJohn Marino                  cvsroot_len) == 0
875*86d7f5d3SJohn Marino 	&& ISSLASH (finfo->repository[cvsroot_len])
876*86d7f5d3SJohn Marino 	&& strncmp (finfo->repository + cvsroot_len + 1,
877*86d7f5d3SJohn Marino 		    CVSROOTADM,
878*86d7f5d3SJohn Marino 		    sizeof (CVSROOTADM) - 1) == 0
879*86d7f5d3SJohn Marino 	&& ISSLASH (finfo->repository[cvsroot_len + sizeof (CVSROOTADM)])
880*86d7f5d3SJohn Marino 	&& strcmp (finfo->repository + cvsroot_len + sizeof (CVSROOTADM) + 1,
881*86d7f5d3SJohn Marino 		   CVSNULLREPOS) == 0
882*86d7f5d3SJohn Marino 	)
883*86d7f5d3SJohn Marino 	error (1, 0, "cannot check in to %s", finfo->repository);
884*86d7f5d3SJohn Marino 
885*86d7f5d3SJohn Marino     status = classify_file_internal (finfo, &vers);
886*86d7f5d3SJohn Marino 
887*86d7f5d3SJohn Marino     /*
888*86d7f5d3SJohn Marino      * If the force-commit option is enabled, and the file in question
889*86d7f5d3SJohn Marino      * appears to be up-to-date, just make it look modified so that
890*86d7f5d3SJohn Marino      * it will be committed.
891*86d7f5d3SJohn Marino      */
892*86d7f5d3SJohn Marino     if (force_ci && status == T_UPTODATE)
893*86d7f5d3SJohn Marino 	status = T_MODIFIED;
894*86d7f5d3SJohn Marino 
895*86d7f5d3SJohn Marino     switch (status)
896*86d7f5d3SJohn Marino     {
897*86d7f5d3SJohn Marino 	case T_CHECKOUT:
898*86d7f5d3SJohn Marino 	case T_PATCH:
899*86d7f5d3SJohn Marino 	case T_NEEDS_MERGE:
900*86d7f5d3SJohn Marino 	case T_REMOVE_ENTRY:
901*86d7f5d3SJohn Marino 	    error (0, 0, "Up-to-date check failed for `%s'", finfo->fullname);
902*86d7f5d3SJohn Marino 	    goto out;
903*86d7f5d3SJohn Marino 	case T_CONFLICT:
904*86d7f5d3SJohn Marino 	case T_MODIFIED:
905*86d7f5d3SJohn Marino 	case T_ADDED:
906*86d7f5d3SJohn Marino 	case T_REMOVED:
907*86d7f5d3SJohn Marino         {
908*86d7f5d3SJohn Marino             char *editor;
909*86d7f5d3SJohn Marino 
910*86d7f5d3SJohn Marino 	    /*
911*86d7f5d3SJohn Marino 	     * some quick sanity checks; if no numeric -r option specified:
912*86d7f5d3SJohn Marino 	     *	- can't have a sticky date
913*86d7f5d3SJohn Marino 	     *	- can't have a sticky tag that is not a branch
914*86d7f5d3SJohn Marino 	     * Also,
915*86d7f5d3SJohn Marino 	     *	- if status is T_REMOVED, file must not exist and its entry
916*86d7f5d3SJohn Marino 	     *	  can't have a numeric sticky tag.
917*86d7f5d3SJohn Marino 	     *	- if status is T_ADDED, rcs file must not exist unless on
918*86d7f5d3SJohn Marino 	     *    a branch or head is dead
919*86d7f5d3SJohn Marino 	     *	- if status is T_ADDED, can't have a non-trunk numeric rev
920*86d7f5d3SJohn Marino 	     *	- if status is T_MODIFIED and a Conflict marker exists, don't
921*86d7f5d3SJohn Marino 	     *    allow the commit if timestamp is identical or if we find
922*86d7f5d3SJohn Marino 	     *    an RCS_MERGE_PAT in the file.
923*86d7f5d3SJohn Marino 	     */
924*86d7f5d3SJohn Marino 	    if (!saved_tag || !isdigit ((unsigned char) *saved_tag))
925*86d7f5d3SJohn Marino 	    {
926*86d7f5d3SJohn Marino 		if (vers->date)
927*86d7f5d3SJohn Marino 		{
928*86d7f5d3SJohn Marino 		    error (0, 0,
929*86d7f5d3SJohn Marino 			   "cannot commit with sticky date for file `%s'",
930*86d7f5d3SJohn Marino 			   finfo->fullname);
931*86d7f5d3SJohn Marino 		    goto out;
932*86d7f5d3SJohn Marino 		}
933*86d7f5d3SJohn Marino 		if (status == T_MODIFIED && vers->tag &&
934*86d7f5d3SJohn Marino 		    !RCS_isbranch (finfo->rcs, vers->tag))
935*86d7f5d3SJohn Marino 		{
936*86d7f5d3SJohn Marino 		    error (0, 0,
937*86d7f5d3SJohn Marino 			   "sticky tag `%s' for file `%s' is not a branch",
938*86d7f5d3SJohn Marino 			   vers->tag, finfo->fullname);
939*86d7f5d3SJohn Marino 		    goto out;
940*86d7f5d3SJohn Marino 		}
941*86d7f5d3SJohn Marino 	    }
942*86d7f5d3SJohn Marino 	    if (status == T_CONFLICT && !force_ci)
943*86d7f5d3SJohn Marino 	    {
944*86d7f5d3SJohn Marino 		error (0, 0,
945*86d7f5d3SJohn Marino 		      "file `%s' had a conflict and has not been modified",
946*86d7f5d3SJohn Marino 		       finfo->fullname);
947*86d7f5d3SJohn Marino 		goto out;
948*86d7f5d3SJohn Marino 	    }
949*86d7f5d3SJohn Marino 	    if (status == T_MODIFIED && !force_ci && file_has_markers (finfo))
950*86d7f5d3SJohn Marino 	    {
951*86d7f5d3SJohn Marino 		/* Make this a warning, not an error, because we have
952*86d7f5d3SJohn Marino 		   no way of knowing whether the "conflict indicators"
953*86d7f5d3SJohn Marino 		   are really from a conflict or whether they are part
954*86d7f5d3SJohn Marino 		   of the document itself (cvs.texinfo and sanity.sh in
955*86d7f5d3SJohn Marino 		   CVS itself, for example, tend to want to have strings
956*86d7f5d3SJohn Marino 		   like ">>>>>>>" at the start of a line).  Making people
957*86d7f5d3SJohn Marino 		   kludge this the way they need to kludge keyword
958*86d7f5d3SJohn Marino 		   expansion seems undesirable.  And it is worse than
959*86d7f5d3SJohn Marino 		   keyword expansion, because there is no -ko
960*86d7f5d3SJohn Marino 		   analogue.  */
961*86d7f5d3SJohn Marino 		error (0, 0,
962*86d7f5d3SJohn Marino 		       "\
963*86d7f5d3SJohn Marino warning: file `%s' seems to still contain conflict indicators",
964*86d7f5d3SJohn Marino 		       finfo->fullname);
965*86d7f5d3SJohn Marino 	    }
966*86d7f5d3SJohn Marino 
967*86d7f5d3SJohn Marino 	    if (status == T_REMOVED)
968*86d7f5d3SJohn Marino 	    {
969*86d7f5d3SJohn Marino 		if (vers->ts_user != NULL)
970*86d7f5d3SJohn Marino 		{
971*86d7f5d3SJohn Marino 		    error (0, 0,
972*86d7f5d3SJohn Marino 			   "`%s' should be removed and is still there (or is"
973*86d7f5d3SJohn Marino 			   " back again)", finfo->fullname);
974*86d7f5d3SJohn Marino 		    goto out;
975*86d7f5d3SJohn Marino 		}
976*86d7f5d3SJohn Marino 
977*86d7f5d3SJohn Marino 		if (vers->tag && isdigit ((unsigned char) *vers->tag))
978*86d7f5d3SJohn Marino 		{
979*86d7f5d3SJohn Marino 		    /* Remove also tries to forbid this, but we should check
980*86d7f5d3SJohn Marino 		       here.  I'm only _sure_ about somewhat obscure cases
981*86d7f5d3SJohn Marino 		       (hacking the Entries file, using an old version of
982*86d7f5d3SJohn Marino 		       CVS for the remove and a new one for the commit), but
983*86d7f5d3SJohn Marino 		       there might be other cases.  */
984*86d7f5d3SJohn Marino 		    error (0, 0,
985*86d7f5d3SJohn Marino 			   "cannot remove file `%s' which has a numeric sticky"
986*86d7f5d3SJohn Marino 			   " tag of `%s'", finfo->fullname, vers->tag);
987*86d7f5d3SJohn Marino 		    freevers_ts (&vers);
988*86d7f5d3SJohn Marino 		    goto out;
989*86d7f5d3SJohn Marino 		}
990*86d7f5d3SJohn Marino 	    }
991*86d7f5d3SJohn Marino 	    if (status == T_ADDED)
992*86d7f5d3SJohn Marino 	    {
993*86d7f5d3SJohn Marino 	        if (vers->tag == NULL)
994*86d7f5d3SJohn Marino 		{
995*86d7f5d3SJohn Marino 		    if (finfo->rcs != NULL &&
996*86d7f5d3SJohn Marino 			!RCS_isdead (finfo->rcs, finfo->rcs->head))
997*86d7f5d3SJohn Marino 		    {
998*86d7f5d3SJohn Marino 			error (0, 0,
999*86d7f5d3SJohn Marino 		    "cannot add file `%s' when RCS file `%s' already exists",
1000*86d7f5d3SJohn Marino 			       finfo->fullname, finfo->rcs->path);
1001*86d7f5d3SJohn Marino 			goto out;
1002*86d7f5d3SJohn Marino 		    }
1003*86d7f5d3SJohn Marino 		}
1004*86d7f5d3SJohn Marino 		else if (isdigit ((unsigned char) *vers->tag) &&
1005*86d7f5d3SJohn Marino 		    numdots (vers->tag) > 1)
1006*86d7f5d3SJohn Marino 		{
1007*86d7f5d3SJohn Marino 		    error (0, 0,
1008*86d7f5d3SJohn Marino 		"cannot add file `%s' with revision `%s'; must be on trunk",
1009*86d7f5d3SJohn Marino 			       finfo->fullname, vers->tag);
1010*86d7f5d3SJohn Marino 		    goto out;
1011*86d7f5d3SJohn Marino 		}
1012*86d7f5d3SJohn Marino 	    }
1013*86d7f5d3SJohn Marino 
1014*86d7f5d3SJohn Marino 	    /* done with consistency checks; now, to get on with the commit */
1015*86d7f5d3SJohn Marino 	    if (finfo->update_dir[0] == '\0')
1016*86d7f5d3SJohn Marino 		xdir = ".";
1017*86d7f5d3SJohn Marino 	    else
1018*86d7f5d3SJohn Marino 		xdir = finfo->update_dir;
1019*86d7f5d3SJohn Marino 	    if ((p = findnode (mulist, xdir)) != NULL)
1020*86d7f5d3SJohn Marino 	    {
1021*86d7f5d3SJohn Marino 		ulist = ((struct master_lists *) p->data)->ulist;
1022*86d7f5d3SJohn Marino 		cilist = ((struct master_lists *) p->data)->cilist;
1023*86d7f5d3SJohn Marino 	    }
1024*86d7f5d3SJohn Marino 	    else
1025*86d7f5d3SJohn Marino 	    {
1026*86d7f5d3SJohn Marino 		struct master_lists *ml;
1027*86d7f5d3SJohn Marino 
1028*86d7f5d3SJohn Marino 		ml = xmalloc (sizeof (struct master_lists));
1029*86d7f5d3SJohn Marino 		ulist = ml->ulist = getlist ();
1030*86d7f5d3SJohn Marino 		cilist = ml->cilist = getlist ();
1031*86d7f5d3SJohn Marino 
1032*86d7f5d3SJohn Marino 		p = getnode ();
1033*86d7f5d3SJohn Marino 		p->key = xstrdup (xdir);
1034*86d7f5d3SJohn Marino 		p->type = UPDATE;
1035*86d7f5d3SJohn Marino 		p->data = ml;
1036*86d7f5d3SJohn Marino 		p->delproc = masterlist_delproc;
1037*86d7f5d3SJohn Marino 		(void) addnode (mulist, p);
1038*86d7f5d3SJohn Marino 	    }
1039*86d7f5d3SJohn Marino 
1040*86d7f5d3SJohn Marino 	    /* first do ulist, then cilist */
1041*86d7f5d3SJohn Marino 	    p = getnode ();
1042*86d7f5d3SJohn Marino 	    p->key = xstrdup (finfo->file);
1043*86d7f5d3SJohn Marino 	    p->type = UPDATE;
1044*86d7f5d3SJohn Marino 	    p->delproc = update_delproc;
1045*86d7f5d3SJohn Marino 	    li = xmalloc (sizeof (struct logfile_info));
1046*86d7f5d3SJohn Marino 	    li->type = status;
1047*86d7f5d3SJohn Marino 
1048*86d7f5d3SJohn Marino 	    if (check_valid_edit)
1049*86d7f5d3SJohn Marino             {
1050*86d7f5d3SJohn Marino                 char *editors = NULL;
1051*86d7f5d3SJohn Marino 
1052*86d7f5d3SJohn Marino 		editor = NULL;
1053*86d7f5d3SJohn Marino                 editors = fileattr_get0 (finfo->file, "_editors");
1054*86d7f5d3SJohn Marino                 if (editors != NULL)
1055*86d7f5d3SJohn Marino                 {
1056*86d7f5d3SJohn Marino                     char *caller = getcaller ();
1057*86d7f5d3SJohn Marino                     char *p = NULL;
1058*86d7f5d3SJohn Marino                     char *p0 = NULL;
1059*86d7f5d3SJohn Marino 
1060*86d7f5d3SJohn Marino                     p = editors;
1061*86d7f5d3SJohn Marino                     p0 = p;
1062*86d7f5d3SJohn Marino                     while (*p != '\0')
1063*86d7f5d3SJohn Marino                     {
1064*86d7f5d3SJohn Marino                         p = strchr (p, '>');
1065*86d7f5d3SJohn Marino                         if (p == NULL)
1066*86d7f5d3SJohn Marino                         {
1067*86d7f5d3SJohn Marino                             break;
1068*86d7f5d3SJohn Marino                         }
1069*86d7f5d3SJohn Marino                         *p = '\0';
1070*86d7f5d3SJohn Marino                         if (strcmp (caller, p0) == 0)
1071*86d7f5d3SJohn Marino                         {
1072*86d7f5d3SJohn Marino                             break;
1073*86d7f5d3SJohn Marino                         }
1074*86d7f5d3SJohn Marino                         p = strchr (p + 1, ',');
1075*86d7f5d3SJohn Marino                         if (p == NULL)
1076*86d7f5d3SJohn Marino                         {
1077*86d7f5d3SJohn Marino                             break;
1078*86d7f5d3SJohn Marino                         }
1079*86d7f5d3SJohn Marino                         ++p;
1080*86d7f5d3SJohn Marino                         p0 = p;
1081*86d7f5d3SJohn Marino                     }
1082*86d7f5d3SJohn Marino 
1083*86d7f5d3SJohn Marino                     if (strcmp (caller, p0) == 0)
1084*86d7f5d3SJohn Marino                     {
1085*86d7f5d3SJohn Marino                         editor = caller;
1086*86d7f5d3SJohn Marino                     }
1087*86d7f5d3SJohn Marino 
1088*86d7f5d3SJohn Marino                     free (editors);
1089*86d7f5d3SJohn Marino                 }
1090*86d7f5d3SJohn Marino             }
1091*86d7f5d3SJohn Marino 
1092*86d7f5d3SJohn Marino             if (check_valid_edit && editor == NULL)
1093*86d7f5d3SJohn Marino             {
1094*86d7f5d3SJohn Marino                 error (0, 0, "Valid edit does not exist for %s",
1095*86d7f5d3SJohn Marino                        finfo->fullname);
1096*86d7f5d3SJohn Marino                 freevers_ts (&vers);
1097*86d7f5d3SJohn Marino                 return 1;
1098*86d7f5d3SJohn Marino             }
1099*86d7f5d3SJohn Marino 
1100*86d7f5d3SJohn Marino 	    li->tag = xstrdup (vers->tag);
1101*86d7f5d3SJohn Marino 	    li->rev_old = xstrdup (vers->vn_rcs);
1102*86d7f5d3SJohn Marino 	    li->rev_new = NULL;
1103*86d7f5d3SJohn Marino 	    p->data = li;
1104*86d7f5d3SJohn Marino 	    (void) addnode (ulist, p);
1105*86d7f5d3SJohn Marino 
1106*86d7f5d3SJohn Marino 	    p = getnode ();
1107*86d7f5d3SJohn Marino 	    p->key = xstrdup (finfo->file);
1108*86d7f5d3SJohn Marino 	    p->type = UPDATE;
1109*86d7f5d3SJohn Marino 	    p->delproc = ci_delproc;
1110*86d7f5d3SJohn Marino 	    ci = xmalloc (sizeof (struct commit_info));
1111*86d7f5d3SJohn Marino 	    ci->status = status;
1112*86d7f5d3SJohn Marino 	    if (vers->tag)
1113*86d7f5d3SJohn Marino 		if (isdigit ((unsigned char) *vers->tag))
1114*86d7f5d3SJohn Marino 		    ci->rev = xstrdup (vers->tag);
1115*86d7f5d3SJohn Marino 		else
1116*86d7f5d3SJohn Marino 		    ci->rev = RCS_whatbranch (finfo->rcs, vers->tag);
1117*86d7f5d3SJohn Marino 	    else
1118*86d7f5d3SJohn Marino 		ci->rev = NULL;
1119*86d7f5d3SJohn Marino 	    ci->tag = xstrdup (vers->tag);
1120*86d7f5d3SJohn Marino 	    ci->options = xstrdup (vers->options);
1121*86d7f5d3SJohn Marino 	    p->data = ci;
1122*86d7f5d3SJohn Marino 	    (void) addnode (cilist, p);
1123*86d7f5d3SJohn Marino 
1124*86d7f5d3SJohn Marino #ifdef PRESERVE_PERMISSIONS_SUPPORT
1125*86d7f5d3SJohn Marino 	    if (preserve_perms)
1126*86d7f5d3SJohn Marino 	    {
1127*86d7f5d3SJohn Marino 		/* Add this file to hardlist, indexed on its inode.  When
1128*86d7f5d3SJohn Marino 		   we are done, we can find out what files are hardlinked
1129*86d7f5d3SJohn Marino 		   to a given file by looking up its inode in hardlist. */
1130*86d7f5d3SJohn Marino 		char *fullpath;
1131*86d7f5d3SJohn Marino 		Node *linkp;
1132*86d7f5d3SJohn Marino 		struct hardlink_info *hlinfo;
1133*86d7f5d3SJohn Marino 
1134*86d7f5d3SJohn Marino 		/* Get the full pathname of the current file. */
1135*86d7f5d3SJohn Marino 		fullpath = Xasprintf ("%s/%s", working_dir, finfo->fullname);
1136*86d7f5d3SJohn Marino 
1137*86d7f5d3SJohn Marino 		/* To permit following links in subdirectories, files
1138*86d7f5d3SJohn Marino                    are keyed on finfo->fullname, not on finfo->name. */
1139*86d7f5d3SJohn Marino 		linkp = lookup_file_by_inode (fullpath);
1140*86d7f5d3SJohn Marino 
1141*86d7f5d3SJohn Marino 		/* If linkp is NULL, the file doesn't exist... maybe
1142*86d7f5d3SJohn Marino 		   we're doing a remove operation? */
1143*86d7f5d3SJohn Marino 		if (linkp != NULL)
1144*86d7f5d3SJohn Marino 		{
1145*86d7f5d3SJohn Marino 		    /* Create a new hardlink_info node, which will record
1146*86d7f5d3SJohn Marino 		       the current file's status and the links listed in its
1147*86d7f5d3SJohn Marino 		       `hardlinks' delta field.  We will append this
1148*86d7f5d3SJohn Marino 		       hardlink_info node to the appropriate hardlist entry. */
1149*86d7f5d3SJohn Marino 		    hlinfo = xmalloc (sizeof (struct hardlink_info));
1150*86d7f5d3SJohn Marino 		    hlinfo->status = status;
1151*86d7f5d3SJohn Marino 		    linkp->data = hlinfo;
1152*86d7f5d3SJohn Marino 		}
1153*86d7f5d3SJohn Marino 	    }
1154*86d7f5d3SJohn Marino #endif
1155*86d7f5d3SJohn Marino 
1156*86d7f5d3SJohn Marino 	    break;
1157*86d7f5d3SJohn Marino         }
1158*86d7f5d3SJohn Marino 
1159*86d7f5d3SJohn Marino 	case T_UNKNOWN:
1160*86d7f5d3SJohn Marino 	    error (0, 0, "nothing known about `%s'", finfo->fullname);
1161*86d7f5d3SJohn Marino 	    goto out;
1162*86d7f5d3SJohn Marino 	case T_UPTODATE:
1163*86d7f5d3SJohn Marino 	    break;
1164*86d7f5d3SJohn Marino 	default:
1165*86d7f5d3SJohn Marino 	    error (0, 0, "CVS internal error: unknown status %d", status);
1166*86d7f5d3SJohn Marino 	    break;
1167*86d7f5d3SJohn Marino     }
1168*86d7f5d3SJohn Marino 
1169*86d7f5d3SJohn Marino     retval = 0;
1170*86d7f5d3SJohn Marino 
1171*86d7f5d3SJohn Marino  out:
1172*86d7f5d3SJohn Marino 
1173*86d7f5d3SJohn Marino     freevers_ts (&vers);
1174*86d7f5d3SJohn Marino     return retval;
1175*86d7f5d3SJohn Marino }
1176*86d7f5d3SJohn Marino 
1177*86d7f5d3SJohn Marino 
1178*86d7f5d3SJohn Marino 
1179*86d7f5d3SJohn Marino /*
1180*86d7f5d3SJohn Marino  * By default, return the code that tells do_recursion to examine all
1181*86d7f5d3SJohn Marino  * directories
1182*86d7f5d3SJohn Marino  */
1183*86d7f5d3SJohn Marino /* ARGSUSED */
1184*86d7f5d3SJohn Marino static Dtype
check_direntproc(void * callerdat,const char * dir,const char * repos,const char * update_dir,List * entries)1185*86d7f5d3SJohn Marino check_direntproc (void *callerdat, const char *dir, const char *repos,
1186*86d7f5d3SJohn Marino                   const char *update_dir, List *entries)
1187*86d7f5d3SJohn Marino {
1188*86d7f5d3SJohn Marino     if (!isdir (dir))
1189*86d7f5d3SJohn Marino 	return R_SKIP_ALL;
1190*86d7f5d3SJohn Marino 
1191*86d7f5d3SJohn Marino     if (!quiet)
1192*86d7f5d3SJohn Marino 	error (0, 0, "Examining %s", update_dir);
1193*86d7f5d3SJohn Marino 
1194*86d7f5d3SJohn Marino     return R_PROCESS;
1195*86d7f5d3SJohn Marino }
1196*86d7f5d3SJohn Marino 
1197*86d7f5d3SJohn Marino 
1198*86d7f5d3SJohn Marino 
1199*86d7f5d3SJohn Marino /*
1200*86d7f5d3SJohn Marino  * Walklist proc to generate an arg list from the line in commitinfo
1201*86d7f5d3SJohn Marino  */
1202*86d7f5d3SJohn Marino static int
precommit_list_to_args_proc(p,closure)1203*86d7f5d3SJohn Marino precommit_list_to_args_proc (p, closure)
1204*86d7f5d3SJohn Marino     Node *p;
1205*86d7f5d3SJohn Marino     void *closure;
1206*86d7f5d3SJohn Marino {
1207*86d7f5d3SJohn Marino     struct format_cmdline_walklist_closure *c = closure;
1208*86d7f5d3SJohn Marino     struct logfile_info *li;
1209*86d7f5d3SJohn Marino     char *arg = NULL;
1210*86d7f5d3SJohn Marino     const char *f;
1211*86d7f5d3SJohn Marino     char *d;
1212*86d7f5d3SJohn Marino     size_t doff;
1213*86d7f5d3SJohn Marino 
1214*86d7f5d3SJohn Marino     if (p->data == NULL) return 1;
1215*86d7f5d3SJohn Marino 
1216*86d7f5d3SJohn Marino     f = c->format;
1217*86d7f5d3SJohn Marino     d = *c->d;
1218*86d7f5d3SJohn Marino     /* foreach requested attribute */
1219*86d7f5d3SJohn Marino     while (*f)
1220*86d7f5d3SJohn Marino     {
1221*86d7f5d3SJohn Marino    	switch (*f++)
1222*86d7f5d3SJohn Marino 	{
1223*86d7f5d3SJohn Marino 	    case 's':
1224*86d7f5d3SJohn Marino 		li = p->data;
1225*86d7f5d3SJohn Marino 		if (li->type == T_ADDED
1226*86d7f5d3SJohn Marino 			|| li->type == T_MODIFIED
1227*86d7f5d3SJohn Marino 			|| li->type == T_REMOVED)
1228*86d7f5d3SJohn Marino 		{
1229*86d7f5d3SJohn Marino 		    arg = p->key;
1230*86d7f5d3SJohn Marino 		}
1231*86d7f5d3SJohn Marino 		break;
1232*86d7f5d3SJohn Marino 	    default:
1233*86d7f5d3SJohn Marino 		error (1, 0,
1234*86d7f5d3SJohn Marino 		       "Unknown format character or not a list attribute: %c",
1235*86d7f5d3SJohn Marino 		       f[-1]);
1236*86d7f5d3SJohn Marino 		/* NOTREACHED */
1237*86d7f5d3SJohn Marino 		break;
1238*86d7f5d3SJohn Marino 	}
1239*86d7f5d3SJohn Marino 	/* copy the attribute into an argument */
1240*86d7f5d3SJohn Marino 	if (c->quotes)
1241*86d7f5d3SJohn Marino 	{
1242*86d7f5d3SJohn Marino 	    arg = cmdlineescape (c->quotes, arg);
1243*86d7f5d3SJohn Marino 	}
1244*86d7f5d3SJohn Marino 	else
1245*86d7f5d3SJohn Marino 	{
1246*86d7f5d3SJohn Marino 	    arg = cmdlinequote ('"', arg);
1247*86d7f5d3SJohn Marino 	}
1248*86d7f5d3SJohn Marino 	doff = d - *c->buf;
1249*86d7f5d3SJohn Marino 	expand_string (c->buf, c->length, doff + strlen (arg));
1250*86d7f5d3SJohn Marino 	d = *c->buf + doff;
1251*86d7f5d3SJohn Marino 	strncpy (d, arg, strlen (arg));
1252*86d7f5d3SJohn Marino 	d += strlen (arg);
1253*86d7f5d3SJohn Marino 	free (arg);
1254*86d7f5d3SJohn Marino 
1255*86d7f5d3SJohn Marino 	/* and always put the extra space on.  we'll have to back up a char
1256*86d7f5d3SJohn Marino 	 * when we're done, but that seems most efficient
1257*86d7f5d3SJohn Marino 	 */
1258*86d7f5d3SJohn Marino 	doff = d - *c->buf;
1259*86d7f5d3SJohn Marino 	expand_string (c->buf, c->length, doff + 1);
1260*86d7f5d3SJohn Marino 	d = *c->buf + doff;
1261*86d7f5d3SJohn Marino 	*d++ = ' ';
1262*86d7f5d3SJohn Marino     }
1263*86d7f5d3SJohn Marino     /* correct our original pointer into the buff */
1264*86d7f5d3SJohn Marino     *c->d = d;
1265*86d7f5d3SJohn Marino     return 0;
1266*86d7f5d3SJohn Marino }
1267*86d7f5d3SJohn Marino 
1268*86d7f5d3SJohn Marino 
1269*86d7f5d3SJohn Marino 
1270*86d7f5d3SJohn Marino /*
1271*86d7f5d3SJohn Marino  * Callback proc for pre-commit checking
1272*86d7f5d3SJohn Marino  */
1273*86d7f5d3SJohn Marino static int
precommit_proc(const char * repository,const char * filter,void * closure)1274*86d7f5d3SJohn Marino precommit_proc (const char *repository, const char *filter, void *closure)
1275*86d7f5d3SJohn Marino {
1276*86d7f5d3SJohn Marino     char *newfilter = NULL;
1277*86d7f5d3SJohn Marino     char *cmdline;
1278*86d7f5d3SJohn Marino     const char *srepos = Short_Repository (repository);
1279*86d7f5d3SJohn Marino     List *ulist = closure;
1280*86d7f5d3SJohn Marino 
1281*86d7f5d3SJohn Marino #ifdef SUPPORT_OLD_INFO_FMT_STRINGS
1282*86d7f5d3SJohn Marino     if (!strchr (filter, '%'))
1283*86d7f5d3SJohn Marino     {
1284*86d7f5d3SJohn Marino 	error (0, 0,
1285*86d7f5d3SJohn Marino                "warning: commitinfo line contains no format strings:\n"
1286*86d7f5d3SJohn Marino                "    \"%s\"\n"
1287*86d7f5d3SJohn Marino                "Appending defaults (\" %%r/%%p %%s\"), but please be aware that this usage is\n"
1288*86d7f5d3SJohn Marino                "deprecated.", filter);
1289*86d7f5d3SJohn Marino 	newfilter = Xasprintf ("%s %%r/%%p %%s", filter);
1290*86d7f5d3SJohn Marino 	filter = newfilter;
1291*86d7f5d3SJohn Marino     }
1292*86d7f5d3SJohn Marino #endif /* SUPPORT_OLD_INFO_FMT_STRINGS */
1293*86d7f5d3SJohn Marino 
1294*86d7f5d3SJohn Marino     /*
1295*86d7f5d3SJohn Marino      * Cast any NULL arguments as appropriate pointers as this is an
1296*86d7f5d3SJohn Marino      * stdarg function and we need to be certain the caller gets what
1297*86d7f5d3SJohn Marino      * is expected.
1298*86d7f5d3SJohn Marino      */
1299*86d7f5d3SJohn Marino     cmdline = format_cmdline (
1300*86d7f5d3SJohn Marino #ifdef SUPPORT_OLD_INFO_FMT_STRINGS
1301*86d7f5d3SJohn Marino 			      false, srepos,
1302*86d7f5d3SJohn Marino #endif /* SUPPORT_OLD_INFO_FMT_STRINGS */
1303*86d7f5d3SJohn Marino 			      filter,
1304*86d7f5d3SJohn Marino 			      "c", "s", cvs_cmd_name,
1305*86d7f5d3SJohn Marino #ifdef SERVER_SUPPORT
1306*86d7f5d3SJohn Marino 			      "R", "s", referrer ? referrer->original : "NONE",
1307*86d7f5d3SJohn Marino #endif /* SERVER_SUPPORT */
1308*86d7f5d3SJohn Marino 			      "p", "s", srepos,
1309*86d7f5d3SJohn Marino 			      "r", "s", current_parsed_root->directory,
1310*86d7f5d3SJohn Marino 			      "s", ",", ulist, precommit_list_to_args_proc,
1311*86d7f5d3SJohn Marino 			      (void *) NULL,
1312*86d7f5d3SJohn Marino 			      (char *) NULL);
1313*86d7f5d3SJohn Marino 
1314*86d7f5d3SJohn Marino     if (newfilter) free (newfilter);
1315*86d7f5d3SJohn Marino 
1316*86d7f5d3SJohn Marino     if (!cmdline || !strlen (cmdline))
1317*86d7f5d3SJohn Marino     {
1318*86d7f5d3SJohn Marino 	if (cmdline) free (cmdline);
1319*86d7f5d3SJohn Marino 	error (0, 0, "precommit proc resolved to the empty string!");
1320*86d7f5d3SJohn Marino 	return 1;
1321*86d7f5d3SJohn Marino     }
1322*86d7f5d3SJohn Marino 
1323*86d7f5d3SJohn Marino     run_setup (cmdline);
1324*86d7f5d3SJohn Marino     free (cmdline);
1325*86d7f5d3SJohn Marino 
1326*86d7f5d3SJohn Marino     return run_exec (RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL | RUN_REALLY);
1327*86d7f5d3SJohn Marino }
1328*86d7f5d3SJohn Marino 
1329*86d7f5d3SJohn Marino 
1330*86d7f5d3SJohn Marino 
1331*86d7f5d3SJohn Marino /*
1332*86d7f5d3SJohn Marino  * Run the pre-commit checks for the dir
1333*86d7f5d3SJohn Marino  */
1334*86d7f5d3SJohn Marino /* ARGSUSED */
1335*86d7f5d3SJohn Marino static int
check_filesdoneproc(void * callerdat,int err,const char * repos,const char * update_dir,List * entries)1336*86d7f5d3SJohn Marino check_filesdoneproc (void *callerdat, int err, const char *repos,
1337*86d7f5d3SJohn Marino                      const char *update_dir, List *entries)
1338*86d7f5d3SJohn Marino {
1339*86d7f5d3SJohn Marino     int n;
1340*86d7f5d3SJohn Marino     Node *p;
1341*86d7f5d3SJohn Marino     List *saved_ulist;
1342*86d7f5d3SJohn Marino 
1343*86d7f5d3SJohn Marino     /* find the update list for this dir */
1344*86d7f5d3SJohn Marino     p = findnode (mulist, update_dir);
1345*86d7f5d3SJohn Marino     if (p != NULL)
1346*86d7f5d3SJohn Marino 	saved_ulist = ((struct master_lists *) p->data)->ulist;
1347*86d7f5d3SJohn Marino     else
1348*86d7f5d3SJohn Marino 	saved_ulist = NULL;
1349*86d7f5d3SJohn Marino 
1350*86d7f5d3SJohn Marino     /* skip the checks if there's nothing to do */
1351*86d7f5d3SJohn Marino     if (saved_ulist == NULL || saved_ulist->list->next == saved_ulist->list)
1352*86d7f5d3SJohn Marino 	return err;
1353*86d7f5d3SJohn Marino 
1354*86d7f5d3SJohn Marino     /* run any pre-commit checks */
1355*86d7f5d3SJohn Marino     n = Parse_Info (CVSROOTADM_COMMITINFO, repos, precommit_proc, PIOPT_ALL,
1356*86d7f5d3SJohn Marino                     saved_ulist);
1357*86d7f5d3SJohn Marino     if (n > 0)
1358*86d7f5d3SJohn Marino     {
1359*86d7f5d3SJohn Marino 	error (0, 0, "Pre-commit check failed");
1360*86d7f5d3SJohn Marino 	err += n;
1361*86d7f5d3SJohn Marino     }
1362*86d7f5d3SJohn Marino 
1363*86d7f5d3SJohn Marino     return err;
1364*86d7f5d3SJohn Marino }
1365*86d7f5d3SJohn Marino 
1366*86d7f5d3SJohn Marino 
1367*86d7f5d3SJohn Marino 
1368*86d7f5d3SJohn Marino /*
1369*86d7f5d3SJohn Marino  * Do the work of committing a file
1370*86d7f5d3SJohn Marino  */
1371*86d7f5d3SJohn Marino static int maxrev;
1372*86d7f5d3SJohn Marino static char *sbranch;
1373*86d7f5d3SJohn Marino 
1374*86d7f5d3SJohn Marino /* ARGSUSED */
1375*86d7f5d3SJohn Marino static int
commit_fileproc(void * callerdat,struct file_info * finfo)1376*86d7f5d3SJohn Marino commit_fileproc (void *callerdat, struct file_info *finfo)
1377*86d7f5d3SJohn Marino {
1378*86d7f5d3SJohn Marino     Node *p;
1379*86d7f5d3SJohn Marino     int err = 0;
1380*86d7f5d3SJohn Marino     List *ulist, *cilist;
1381*86d7f5d3SJohn Marino     struct commit_info *ci;
1382*86d7f5d3SJohn Marino 
1383*86d7f5d3SJohn Marino     /* Keep track of whether write_dirtag is a branch tag.
1384*86d7f5d3SJohn Marino        Note that if it is a branch tag in some files and a nonbranch tag
1385*86d7f5d3SJohn Marino        in others, treat it as a nonbranch tag.  It is possible that case
1386*86d7f5d3SJohn Marino        should elicit a warning or an error.  */
1387*86d7f5d3SJohn Marino     if (write_dirtag != NULL
1388*86d7f5d3SJohn Marino 	&& finfo->rcs != NULL)
1389*86d7f5d3SJohn Marino     {
1390*86d7f5d3SJohn Marino 	char *rev = RCS_getversion (finfo->rcs, write_dirtag, NULL, 1, NULL);
1391*86d7f5d3SJohn Marino 	if (rev != NULL
1392*86d7f5d3SJohn Marino 	    && !RCS_nodeisbranch (finfo->rcs, write_dirtag))
1393*86d7f5d3SJohn Marino 	    write_dirnonbranch = 1;
1394*86d7f5d3SJohn Marino 	if (rev != NULL)
1395*86d7f5d3SJohn Marino 	    free (rev);
1396*86d7f5d3SJohn Marino     }
1397*86d7f5d3SJohn Marino 
1398*86d7f5d3SJohn Marino     if (finfo->update_dir[0] == '\0')
1399*86d7f5d3SJohn Marino 	p = findnode (mulist, ".");
1400*86d7f5d3SJohn Marino     else
1401*86d7f5d3SJohn Marino 	p = findnode (mulist, finfo->update_dir);
1402*86d7f5d3SJohn Marino 
1403*86d7f5d3SJohn Marino     /*
1404*86d7f5d3SJohn Marino      * if p is null, there were file type command line args which were
1405*86d7f5d3SJohn Marino      * all up-to-date so nothing really needs to be done
1406*86d7f5d3SJohn Marino      */
1407*86d7f5d3SJohn Marino     if (p == NULL)
1408*86d7f5d3SJohn Marino 	return 0;
1409*86d7f5d3SJohn Marino     ulist = ((struct master_lists *) p->data)->ulist;
1410*86d7f5d3SJohn Marino     cilist = ((struct master_lists *) p->data)->cilist;
1411*86d7f5d3SJohn Marino 
1412*86d7f5d3SJohn Marino     /*
1413*86d7f5d3SJohn Marino      * At this point, we should have the commit message unless we were called
1414*86d7f5d3SJohn Marino      * with files as args from the command line.  In that latter case, we
1415*86d7f5d3SJohn Marino      * need to get the commit message ourselves
1416*86d7f5d3SJohn Marino      */
1417*86d7f5d3SJohn Marino     if (!got_message)
1418*86d7f5d3SJohn Marino     {
1419*86d7f5d3SJohn Marino 	got_message = 1;
1420*86d7f5d3SJohn Marino 	if (!server_active && use_editor)
1421*86d7f5d3SJohn Marino 	    do_editor (finfo->update_dir, &saved_message,
1422*86d7f5d3SJohn Marino 		       finfo->repository, ulist);
1423*86d7f5d3SJohn Marino 	do_verify (&saved_message, finfo->repository, ulist);
1424*86d7f5d3SJohn Marino     }
1425*86d7f5d3SJohn Marino 
1426*86d7f5d3SJohn Marino     p = findnode (cilist, finfo->file);
1427*86d7f5d3SJohn Marino     if (p == NULL)
1428*86d7f5d3SJohn Marino 	return 0;
1429*86d7f5d3SJohn Marino 
1430*86d7f5d3SJohn Marino     ci = p->data;
1431*86d7f5d3SJohn Marino     if (ci->status == T_MODIFIED)
1432*86d7f5d3SJohn Marino     {
1433*86d7f5d3SJohn Marino 	if (finfo->rcs == NULL)
1434*86d7f5d3SJohn Marino 	    error (1, 0, "internal error: no parsed RCS file");
1435*86d7f5d3SJohn Marino 	if (lock_RCS (finfo->file, finfo->rcs, ci->rev,
1436*86d7f5d3SJohn Marino 		      finfo->repository) != 0)
1437*86d7f5d3SJohn Marino 	{
1438*86d7f5d3SJohn Marino 	    unlockrcs (finfo->rcs);
1439*86d7f5d3SJohn Marino 	    err = 1;
1440*86d7f5d3SJohn Marino 	    goto out;
1441*86d7f5d3SJohn Marino 	}
1442*86d7f5d3SJohn Marino     }
1443*86d7f5d3SJohn Marino     else if (ci->status == T_ADDED)
1444*86d7f5d3SJohn Marino     {
1445*86d7f5d3SJohn Marino 	if (checkaddfile (finfo->file, finfo->repository, ci->tag, ci->options,
1446*86d7f5d3SJohn Marino 			  &finfo->rcs) != 0)
1447*86d7f5d3SJohn Marino 	{
1448*86d7f5d3SJohn Marino 	    if (finfo->rcs != NULL)
1449*86d7f5d3SJohn Marino 		fixaddfile (finfo->rcs->path);
1450*86d7f5d3SJohn Marino 	    err = 1;
1451*86d7f5d3SJohn Marino 	    goto out;
1452*86d7f5d3SJohn Marino 	}
1453*86d7f5d3SJohn Marino 
1454*86d7f5d3SJohn Marino 	/* adding files with a tag, now means adding them on a branch.
1455*86d7f5d3SJohn Marino 	   Since the branch test was done in check_fileproc for
1456*86d7f5d3SJohn Marino 	   modified files, we need to stub it in again here. */
1457*86d7f5d3SJohn Marino 
1458*86d7f5d3SJohn Marino 	if (ci->tag
1459*86d7f5d3SJohn Marino 
1460*86d7f5d3SJohn Marino 	    /* If numeric, it is on the trunk; check_fileproc enforced
1461*86d7f5d3SJohn Marino 	       this.  */
1462*86d7f5d3SJohn Marino 	    && !isdigit ((unsigned char) ci->tag[0]))
1463*86d7f5d3SJohn Marino 	{
1464*86d7f5d3SJohn Marino 	    if (finfo->rcs == NULL)
1465*86d7f5d3SJohn Marino 		error (1, 0, "internal error: no parsed RCS file");
1466*86d7f5d3SJohn Marino 	    if (ci->rev)
1467*86d7f5d3SJohn Marino 		free (ci->rev);
1468*86d7f5d3SJohn Marino 	    ci->rev = RCS_whatbranch (finfo->rcs, ci->tag);
1469*86d7f5d3SJohn Marino 	    err = Checkin ('A', finfo, ci->rev,
1470*86d7f5d3SJohn Marino 			   ci->tag, ci->options, saved_message);
1471*86d7f5d3SJohn Marino 	    if (err != 0)
1472*86d7f5d3SJohn Marino 	    {
1473*86d7f5d3SJohn Marino 		unlockrcs (finfo->rcs);
1474*86d7f5d3SJohn Marino 		fixbranch (finfo->rcs, sbranch);
1475*86d7f5d3SJohn Marino 	    }
1476*86d7f5d3SJohn Marino 
1477*86d7f5d3SJohn Marino 	    (void) time (&last_register_time);
1478*86d7f5d3SJohn Marino 
1479*86d7f5d3SJohn Marino 	    ci->status = T_UPTODATE;
1480*86d7f5d3SJohn Marino 	}
1481*86d7f5d3SJohn Marino     }
1482*86d7f5d3SJohn Marino 
1483*86d7f5d3SJohn Marino     /*
1484*86d7f5d3SJohn Marino      * Add the file for real
1485*86d7f5d3SJohn Marino      */
1486*86d7f5d3SJohn Marino     if (ci->status == T_ADDED)
1487*86d7f5d3SJohn Marino     {
1488*86d7f5d3SJohn Marino 	char *xrev = NULL;
1489*86d7f5d3SJohn Marino 
1490*86d7f5d3SJohn Marino 	if (ci->rev == NULL)
1491*86d7f5d3SJohn Marino 	{
1492*86d7f5d3SJohn Marino 	    /* find the max major rev number in this directory */
1493*86d7f5d3SJohn Marino 	    maxrev = 0;
1494*86d7f5d3SJohn Marino 	    (void) walklist (finfo->entries, findmaxrev, NULL);
1495*86d7f5d3SJohn Marino 	    if (finfo->rcs->head)
1496*86d7f5d3SJohn Marino 	    {
1497*86d7f5d3SJohn Marino 		/* resurrecting: include dead revision */
1498*86d7f5d3SJohn Marino 		int thisrev = atoi (finfo->rcs->head);
1499*86d7f5d3SJohn Marino 		if (thisrev > maxrev)
1500*86d7f5d3SJohn Marino 		    maxrev = thisrev;
1501*86d7f5d3SJohn Marino 	    }
1502*86d7f5d3SJohn Marino 	    if (maxrev == 0)
1503*86d7f5d3SJohn Marino 		maxrev = 1;
1504*86d7f5d3SJohn Marino 	    xrev = Xasprintf ("%d", maxrev);
1505*86d7f5d3SJohn Marino 	}
1506*86d7f5d3SJohn Marino 
1507*86d7f5d3SJohn Marino 	/* XXX - an added file with symbolic -r should add tag as well */
1508*86d7f5d3SJohn Marino 	err = finaladd (finfo, ci->rev ? ci->rev : xrev, ci->tag, ci->options);
1509*86d7f5d3SJohn Marino 	if (xrev)
1510*86d7f5d3SJohn Marino 	    free (xrev);
1511*86d7f5d3SJohn Marino     }
1512*86d7f5d3SJohn Marino     else if (ci->status == T_MODIFIED)
1513*86d7f5d3SJohn Marino     {
1514*86d7f5d3SJohn Marino 	err = Checkin ('M', finfo, ci->rev, ci->tag,
1515*86d7f5d3SJohn Marino 		       ci->options, saved_message);
1516*86d7f5d3SJohn Marino 
1517*86d7f5d3SJohn Marino 	(void) time (&last_register_time);
1518*86d7f5d3SJohn Marino 
1519*86d7f5d3SJohn Marino 	if (err != 0)
1520*86d7f5d3SJohn Marino 	{
1521*86d7f5d3SJohn Marino 	    unlockrcs (finfo->rcs);
1522*86d7f5d3SJohn Marino 	    fixbranch (finfo->rcs, sbranch);
1523*86d7f5d3SJohn Marino 	}
1524*86d7f5d3SJohn Marino     }
1525*86d7f5d3SJohn Marino     else if (ci->status == T_REMOVED)
1526*86d7f5d3SJohn Marino     {
1527*86d7f5d3SJohn Marino 	err = remove_file (finfo, ci->tag, saved_message);
1528*86d7f5d3SJohn Marino #ifdef SERVER_SUPPORT
1529*86d7f5d3SJohn Marino 	if (server_active)
1530*86d7f5d3SJohn Marino 	{
1531*86d7f5d3SJohn Marino 	    server_scratch_entry_only ();
1532*86d7f5d3SJohn Marino 	    server_updated (finfo,
1533*86d7f5d3SJohn Marino 			    NULL,
1534*86d7f5d3SJohn Marino 
1535*86d7f5d3SJohn Marino 			    /* Doesn't matter, it won't get checked.  */
1536*86d7f5d3SJohn Marino 			    SERVER_UPDATED,
1537*86d7f5d3SJohn Marino 
1538*86d7f5d3SJohn Marino 			    (mode_t) -1,
1539*86d7f5d3SJohn Marino 			    NULL,
1540*86d7f5d3SJohn Marino 			    NULL);
1541*86d7f5d3SJohn Marino 	}
1542*86d7f5d3SJohn Marino #endif
1543*86d7f5d3SJohn Marino     }
1544*86d7f5d3SJohn Marino 
1545*86d7f5d3SJohn Marino     /* Clearly this is right for T_MODIFIED.  I haven't thought so much
1546*86d7f5d3SJohn Marino        about T_ADDED or T_REMOVED.  */
1547*86d7f5d3SJohn Marino     notify_do ('C', finfo->file, finfo->update_dir, getcaller (), NULL, NULL,
1548*86d7f5d3SJohn Marino 	       finfo->repository);
1549*86d7f5d3SJohn Marino 
1550*86d7f5d3SJohn Marino out:
1551*86d7f5d3SJohn Marino     if (err != 0)
1552*86d7f5d3SJohn Marino     {
1553*86d7f5d3SJohn Marino 	/* on failure, remove the file from ulist */
1554*86d7f5d3SJohn Marino 	p = findnode (ulist, finfo->file);
1555*86d7f5d3SJohn Marino 	if (p)
1556*86d7f5d3SJohn Marino 	    delnode (p);
1557*86d7f5d3SJohn Marino     }
1558*86d7f5d3SJohn Marino     else
1559*86d7f5d3SJohn Marino     {
1560*86d7f5d3SJohn Marino 	/* On success, retrieve the new version number of the file and
1561*86d7f5d3SJohn Marino            copy it into the log information (see logmsg.c
1562*86d7f5d3SJohn Marino            (logfile_write) for more details).  We should only update
1563*86d7f5d3SJohn Marino            the version number for files that have been added or
1564*86d7f5d3SJohn Marino            modified but not removed since classify_file_internal
1565*86d7f5d3SJohn Marino            will return the version number of a file even after it has
1566*86d7f5d3SJohn Marino            been removed from the archive, which is not the behavior we
1567*86d7f5d3SJohn Marino            want for our commitlog messages; we want the old version
1568*86d7f5d3SJohn Marino            number and then "NONE." */
1569*86d7f5d3SJohn Marino 
1570*86d7f5d3SJohn Marino 	if (ci->status != T_REMOVED)
1571*86d7f5d3SJohn Marino 	{
1572*86d7f5d3SJohn Marino 	    p = findnode (ulist, finfo->file);
1573*86d7f5d3SJohn Marino 	    if (p)
1574*86d7f5d3SJohn Marino 	    {
1575*86d7f5d3SJohn Marino 		Vers_TS *vers;
1576*86d7f5d3SJohn Marino 		struct logfile_info *li;
1577*86d7f5d3SJohn Marino 
1578*86d7f5d3SJohn Marino 		(void) classify_file_internal (finfo, &vers);
1579*86d7f5d3SJohn Marino 		li = p->data;
1580*86d7f5d3SJohn Marino 		li->rev_new = xstrdup (vers->vn_rcs);
1581*86d7f5d3SJohn Marino 		freevers_ts (&vers);
1582*86d7f5d3SJohn Marino 	    }
1583*86d7f5d3SJohn Marino 	}
1584*86d7f5d3SJohn Marino     }
1585*86d7f5d3SJohn Marino     if (SIG_inCrSect ())
1586*86d7f5d3SJohn Marino 	SIG_endCrSect ();
1587*86d7f5d3SJohn Marino 
1588*86d7f5d3SJohn Marino     return err;
1589*86d7f5d3SJohn Marino }
1590*86d7f5d3SJohn Marino 
1591*86d7f5d3SJohn Marino 
1592*86d7f5d3SJohn Marino 
1593*86d7f5d3SJohn Marino /*
1594*86d7f5d3SJohn Marino  * Log the commit and clean up the update list
1595*86d7f5d3SJohn Marino  */
1596*86d7f5d3SJohn Marino /* ARGSUSED */
1597*86d7f5d3SJohn Marino static int
commit_filesdoneproc(void * callerdat,int err,const char * repository,const char * update_dir,List * entries)1598*86d7f5d3SJohn Marino commit_filesdoneproc (void *callerdat, int err, const char *repository,
1599*86d7f5d3SJohn Marino                       const char *update_dir, List *entries)
1600*86d7f5d3SJohn Marino {
1601*86d7f5d3SJohn Marino     Node *p;
1602*86d7f5d3SJohn Marino     List *ulist;
1603*86d7f5d3SJohn Marino 
1604*86d7f5d3SJohn Marino     assert (repository);
1605*86d7f5d3SJohn Marino 
1606*86d7f5d3SJohn Marino     p = findnode (mulist, update_dir);
1607*86d7f5d3SJohn Marino     if (p == NULL)
1608*86d7f5d3SJohn Marino 	return err;
1609*86d7f5d3SJohn Marino 
1610*86d7f5d3SJohn Marino     ulist = ((struct master_lists *) p->data)->ulist;
1611*86d7f5d3SJohn Marino 
1612*86d7f5d3SJohn Marino     got_message = 0;
1613*86d7f5d3SJohn Marino 
1614*86d7f5d3SJohn Marino     /* Build the administrative files if necessary.  */
1615*86d7f5d3SJohn Marino     {
1616*86d7f5d3SJohn Marino 	const char *p;
1617*86d7f5d3SJohn Marino 
1618*86d7f5d3SJohn Marino 	if (strncmp (current_parsed_root->directory, repository,
1619*86d7f5d3SJohn Marino 		     strlen (current_parsed_root->directory)) != 0)
1620*86d7f5d3SJohn Marino 	    error (0, 0,
1621*86d7f5d3SJohn Marino 		 "internal error: repository (%s) doesn't begin with root (%s)",
1622*86d7f5d3SJohn Marino 		   repository, current_parsed_root->directory);
1623*86d7f5d3SJohn Marino 	p = repository + strlen (current_parsed_root->directory);
1624*86d7f5d3SJohn Marino 	if (*p == '/')
1625*86d7f5d3SJohn Marino 	    ++p;
1626*86d7f5d3SJohn Marino 	if (strcmp ("CVSROOT", p) == 0
1627*86d7f5d3SJohn Marino 	    /* Check for subdirectories because people may want to create
1628*86d7f5d3SJohn Marino 	       subdirectories and list files therein in checkoutlist.  */
1629*86d7f5d3SJohn Marino 	    || strncmp ("CVSROOT/", p, strlen ("CVSROOT/")) == 0
1630*86d7f5d3SJohn Marino 	    )
1631*86d7f5d3SJohn Marino 	{
1632*86d7f5d3SJohn Marino 	    /* "Database" might a little bit grandiose and/or vague,
1633*86d7f5d3SJohn Marino 	       but "checked-out copies of administrative files, unless
1634*86d7f5d3SJohn Marino 	       in the case of modules and you are using ndbm in which
1635*86d7f5d3SJohn Marino 	       case modules.{pag,dir,db}" is verbose and excessively
1636*86d7f5d3SJohn Marino 	       focused on how the database is implemented.  */
1637*86d7f5d3SJohn Marino 
1638*86d7f5d3SJohn Marino 	    /* mkmodules requires the absolute name of the CVSROOT directory.
1639*86d7f5d3SJohn Marino 	       Remove anything after the `CVSROOT' component -- this is
1640*86d7f5d3SJohn Marino 	       necessary when committing in a subdirectory of CVSROOT.  */
1641*86d7f5d3SJohn Marino 	    char *admin_dir = xstrdup (repository);
1642*86d7f5d3SJohn Marino 	    int cvsrootlen = strlen ("CVSROOT");
1643*86d7f5d3SJohn Marino 	    assert (admin_dir[p - repository + cvsrootlen] == '\0'
1644*86d7f5d3SJohn Marino 		    || admin_dir[p - repository + cvsrootlen] == '/');
1645*86d7f5d3SJohn Marino 	    admin_dir[p - repository + cvsrootlen] = '\0';
1646*86d7f5d3SJohn Marino 
1647*86d7f5d3SJohn Marino 	    if (!really_quiet)
1648*86d7f5d3SJohn Marino 	    {
1649*86d7f5d3SJohn Marino 		cvs_output (program_name, 0);
1650*86d7f5d3SJohn Marino 		cvs_output (" ", 1);
1651*86d7f5d3SJohn Marino 		cvs_output (cvs_cmd_name, 0);
1652*86d7f5d3SJohn Marino 		cvs_output (": Rebuilding administrative file database\n", 0);
1653*86d7f5d3SJohn Marino 	    }
1654*86d7f5d3SJohn Marino 	    mkmodules (admin_dir);
1655*86d7f5d3SJohn Marino 	    free (admin_dir);
1656*86d7f5d3SJohn Marino 	    WriteTemplate (".", 1, repository);
1657*86d7f5d3SJohn Marino 	}
1658*86d7f5d3SJohn Marino     }
1659*86d7f5d3SJohn Marino 
1660*86d7f5d3SJohn Marino     /* FIXME: This used to be above the block above.  The advantage of being
1661*86d7f5d3SJohn Marino      * here is that it is not called until after all possible writes from this
1662*86d7f5d3SJohn Marino      * process are complete.  The disadvantage is that a fatal error during
1663*86d7f5d3SJohn Marino      * update of CVSROOT can prevent the loginfo script from being called.
1664*86d7f5d3SJohn Marino      *
1665*86d7f5d3SJohn Marino      * A more general solution I have been considering is calling a generic
1666*86d7f5d3SJohn Marino      * "postwrite" hook from the remove write lock routine.
1667*86d7f5d3SJohn Marino      */
1668*86d7f5d3SJohn Marino     Update_Logfile (repository, saved_message, NULL, ulist);
1669*86d7f5d3SJohn Marino 
1670*86d7f5d3SJohn Marino     return err;
1671*86d7f5d3SJohn Marino }
1672*86d7f5d3SJohn Marino 
1673*86d7f5d3SJohn Marino 
1674*86d7f5d3SJohn Marino 
1675*86d7f5d3SJohn Marino /*
1676*86d7f5d3SJohn Marino  * Get the log message for a dir
1677*86d7f5d3SJohn Marino  */
1678*86d7f5d3SJohn Marino /* ARGSUSED */
1679*86d7f5d3SJohn Marino static Dtype
commit_direntproc(void * callerdat,const char * dir,const char * repos,const char * update_dir,List * entries)1680*86d7f5d3SJohn Marino commit_direntproc (void *callerdat, const char *dir, const char *repos,
1681*86d7f5d3SJohn Marino                    const char *update_dir, List *entries)
1682*86d7f5d3SJohn Marino {
1683*86d7f5d3SJohn Marino     Node *p;
1684*86d7f5d3SJohn Marino     List *ulist;
1685*86d7f5d3SJohn Marino     char *real_repos;
1686*86d7f5d3SJohn Marino 
1687*86d7f5d3SJohn Marino     if (!isdir (dir))
1688*86d7f5d3SJohn Marino 	return R_SKIP_ALL;
1689*86d7f5d3SJohn Marino 
1690*86d7f5d3SJohn Marino     /* find the update list for this dir */
1691*86d7f5d3SJohn Marino     p = findnode (mulist, update_dir);
1692*86d7f5d3SJohn Marino     if (p != NULL)
1693*86d7f5d3SJohn Marino 	ulist = ((struct master_lists *) p->data)->ulist;
1694*86d7f5d3SJohn Marino     else
1695*86d7f5d3SJohn Marino 	ulist = NULL;
1696*86d7f5d3SJohn Marino 
1697*86d7f5d3SJohn Marino     /* skip the files as an optimization */
1698*86d7f5d3SJohn Marino     if (ulist == NULL || ulist->list->next == ulist->list)
1699*86d7f5d3SJohn Marino 	return R_SKIP_FILES;
1700*86d7f5d3SJohn Marino 
1701*86d7f5d3SJohn Marino     /* get commit message */
1702*86d7f5d3SJohn Marino     got_message = 1;
1703*86d7f5d3SJohn Marino     real_repos = Name_Repository (dir, update_dir);
1704*86d7f5d3SJohn Marino     if (!server_active && use_editor)
1705*86d7f5d3SJohn Marino 	do_editor (update_dir, &saved_message, real_repos, ulist);
1706*86d7f5d3SJohn Marino     do_verify (&saved_message, real_repos, ulist);
1707*86d7f5d3SJohn Marino     free (real_repos);
1708*86d7f5d3SJohn Marino     return R_PROCESS;
1709*86d7f5d3SJohn Marino }
1710*86d7f5d3SJohn Marino 
1711*86d7f5d3SJohn Marino 
1712*86d7f5d3SJohn Marino 
1713*86d7f5d3SJohn Marino /*
1714*86d7f5d3SJohn Marino  * Process the post-commit proc if necessary
1715*86d7f5d3SJohn Marino  */
1716*86d7f5d3SJohn Marino /* ARGSUSED */
1717*86d7f5d3SJohn Marino static int
commit_dirleaveproc(void * callerdat,const char * dir,int err,const char * update_dir,List * entries)1718*86d7f5d3SJohn Marino commit_dirleaveproc (void *callerdat, const char *dir, int err,
1719*86d7f5d3SJohn Marino                      const char *update_dir, List *entries)
1720*86d7f5d3SJohn Marino {
1721*86d7f5d3SJohn Marino     /* update the per-directory tag info */
1722*86d7f5d3SJohn Marino     /* FIXME?  Why?  The "commit examples" node of cvs.texinfo briefly
1723*86d7f5d3SJohn Marino        mentions commit -r being sticky, but apparently in the context of
1724*86d7f5d3SJohn Marino        this being a confusing feature!  */
1725*86d7f5d3SJohn Marino     if (err == 0 && write_dirtag != NULL)
1726*86d7f5d3SJohn Marino     {
1727*86d7f5d3SJohn Marino 	char *repos = Name_Repository (NULL, update_dir);
1728*86d7f5d3SJohn Marino 	WriteTag (NULL, write_dirtag, NULL, write_dirnonbranch,
1729*86d7f5d3SJohn Marino 		  update_dir, repos);
1730*86d7f5d3SJohn Marino 	free (repos);
1731*86d7f5d3SJohn Marino     }
1732*86d7f5d3SJohn Marino 
1733*86d7f5d3SJohn Marino     return err;
1734*86d7f5d3SJohn Marino }
1735*86d7f5d3SJohn Marino 
1736*86d7f5d3SJohn Marino 
1737*86d7f5d3SJohn Marino 
1738*86d7f5d3SJohn Marino /*
1739*86d7f5d3SJohn Marino  * find the maximum major rev number in an entries file
1740*86d7f5d3SJohn Marino  */
1741*86d7f5d3SJohn Marino static int
findmaxrev(Node * p,void * closure)1742*86d7f5d3SJohn Marino findmaxrev (Node *p, void *closure)
1743*86d7f5d3SJohn Marino {
1744*86d7f5d3SJohn Marino     int thisrev;
1745*86d7f5d3SJohn Marino     Entnode *entdata = p->data;
1746*86d7f5d3SJohn Marino 
1747*86d7f5d3SJohn Marino     if (entdata->type != ENT_FILE)
1748*86d7f5d3SJohn Marino 	return 0;
1749*86d7f5d3SJohn Marino     thisrev = atoi (entdata->version);
1750*86d7f5d3SJohn Marino     if (thisrev > maxrev)
1751*86d7f5d3SJohn Marino 	maxrev = thisrev;
1752*86d7f5d3SJohn Marino     return 0;
1753*86d7f5d3SJohn Marino }
1754*86d7f5d3SJohn Marino 
1755*86d7f5d3SJohn Marino /*
1756*86d7f5d3SJohn Marino  * Actually remove a file by moving it to the attic
1757*86d7f5d3SJohn Marino  * XXX - if removing a ,v file that is a relative symbolic link to
1758*86d7f5d3SJohn Marino  * another ,v file, we probably should add a ".." component to the
1759*86d7f5d3SJohn Marino  * link to keep it relative after we move it into the attic.
1760*86d7f5d3SJohn Marino 
1761*86d7f5d3SJohn Marino    Return value is 0 on success, or >0 on error (in which case we have
1762*86d7f5d3SJohn Marino    printed an error message).  */
1763*86d7f5d3SJohn Marino static int
remove_file(struct file_info * finfo,char * tag,char * message)1764*86d7f5d3SJohn Marino remove_file (struct file_info *finfo, char *tag, char *message)
1765*86d7f5d3SJohn Marino {
1766*86d7f5d3SJohn Marino     int retcode;
1767*86d7f5d3SJohn Marino 
1768*86d7f5d3SJohn Marino     int branch;
1769*86d7f5d3SJohn Marino     int lockflag;
1770*86d7f5d3SJohn Marino     char *corev;
1771*86d7f5d3SJohn Marino     char *rev;
1772*86d7f5d3SJohn Marino     char *prev_rev;
1773*86d7f5d3SJohn Marino     char *old_path;
1774*86d7f5d3SJohn Marino 
1775*86d7f5d3SJohn Marino     corev = NULL;
1776*86d7f5d3SJohn Marino     rev = NULL;
1777*86d7f5d3SJohn Marino     prev_rev = NULL;
1778*86d7f5d3SJohn Marino 
1779*86d7f5d3SJohn Marino     retcode = 0;
1780*86d7f5d3SJohn Marino 
1781*86d7f5d3SJohn Marino     if (finfo->rcs == NULL)
1782*86d7f5d3SJohn Marino 	error (1, 0, "internal error: no parsed RCS file");
1783*86d7f5d3SJohn Marino 
1784*86d7f5d3SJohn Marino     branch = 0;
1785*86d7f5d3SJohn Marino     if (tag && !(branch = RCS_nodeisbranch (finfo->rcs, tag)))
1786*86d7f5d3SJohn Marino     {
1787*86d7f5d3SJohn Marino 	/* a symbolic tag is specified; just remove the tag from the file */
1788*86d7f5d3SJohn Marino 	if ((retcode = RCS_deltag (finfo->rcs, tag)) != 0)
1789*86d7f5d3SJohn Marino 	{
1790*86d7f5d3SJohn Marino 	    if (!quiet)
1791*86d7f5d3SJohn Marino 		error (0, retcode == -1 ? errno : 0,
1792*86d7f5d3SJohn Marino 		       "failed to remove tag `%s' from `%s'", tag,
1793*86d7f5d3SJohn Marino 		       finfo->fullname);
1794*86d7f5d3SJohn Marino 	    return 1;
1795*86d7f5d3SJohn Marino 	}
1796*86d7f5d3SJohn Marino 	RCS_rewrite (finfo->rcs, NULL, NULL);
1797*86d7f5d3SJohn Marino 	Scratch_Entry (finfo->entries, finfo->file);
1798*86d7f5d3SJohn Marino 	return 0;
1799*86d7f5d3SJohn Marino     }
1800*86d7f5d3SJohn Marino 
1801*86d7f5d3SJohn Marino     /* we are removing the file from either the head or a branch */
1802*86d7f5d3SJohn Marino     /* commit a new, dead revision. */
1803*86d7f5d3SJohn Marino 
1804*86d7f5d3SJohn Marino     rev = NULL;
1805*86d7f5d3SJohn Marino     lockflag = 1;
1806*86d7f5d3SJohn Marino     if (branch)
1807*86d7f5d3SJohn Marino     {
1808*86d7f5d3SJohn Marino 	char *branchname;
1809*86d7f5d3SJohn Marino 
1810*86d7f5d3SJohn Marino 	rev = RCS_whatbranch (finfo->rcs, tag);
1811*86d7f5d3SJohn Marino 	if (rev == NULL)
1812*86d7f5d3SJohn Marino 	{
1813*86d7f5d3SJohn Marino 	    error (0, 0, "cannot find branch \"%s\".", tag);
1814*86d7f5d3SJohn Marino 	    return 1;
1815*86d7f5d3SJohn Marino 	}
1816*86d7f5d3SJohn Marino 
1817*86d7f5d3SJohn Marino 	branchname = RCS_getbranch (finfo->rcs, rev, 1);
1818*86d7f5d3SJohn Marino 	if (branchname == NULL)
1819*86d7f5d3SJohn Marino 	{
1820*86d7f5d3SJohn Marino 	    /* no revision exists on this branch.  use the previous
1821*86d7f5d3SJohn Marino 	       revision but do not lock. */
1822*86d7f5d3SJohn Marino 	    corev = RCS_gettag (finfo->rcs, tag, 1, NULL);
1823*86d7f5d3SJohn Marino 	    prev_rev = xstrdup (corev);
1824*86d7f5d3SJohn Marino 	    lockflag = 0;
1825*86d7f5d3SJohn Marino 	} else
1826*86d7f5d3SJohn Marino 	{
1827*86d7f5d3SJohn Marino 	    corev = xstrdup (rev);
1828*86d7f5d3SJohn Marino 	    prev_rev = xstrdup (branchname);
1829*86d7f5d3SJohn Marino 	    free (branchname);
1830*86d7f5d3SJohn Marino 	}
1831*86d7f5d3SJohn Marino 
1832*86d7f5d3SJohn Marino     } else  /* Not a branch */
1833*86d7f5d3SJohn Marino     {
1834*86d7f5d3SJohn Marino         /* Get current head revision of file. */
1835*86d7f5d3SJohn Marino 	prev_rev = RCS_head (finfo->rcs);
1836*86d7f5d3SJohn Marino     }
1837*86d7f5d3SJohn Marino 
1838*86d7f5d3SJohn Marino     /* if removing without a tag or a branch, then make sure the default
1839*86d7f5d3SJohn Marino        branch is the trunk. */
1840*86d7f5d3SJohn Marino     if (!tag && !branch)
1841*86d7f5d3SJohn Marino     {
1842*86d7f5d3SJohn Marino         if (RCS_setbranch (finfo->rcs, NULL) != 0)
1843*86d7f5d3SJohn Marino 	{
1844*86d7f5d3SJohn Marino 	    error (0, 0, "cannot change branch to default for %s",
1845*86d7f5d3SJohn Marino 		   finfo->fullname);
1846*86d7f5d3SJohn Marino 	    return 1;
1847*86d7f5d3SJohn Marino 	}
1848*86d7f5d3SJohn Marino 	RCS_rewrite (finfo->rcs, NULL, NULL);
1849*86d7f5d3SJohn Marino     }
1850*86d7f5d3SJohn Marino 
1851*86d7f5d3SJohn Marino     /* check something out.  Generally this is the head.  If we have a
1852*86d7f5d3SJohn Marino        particular rev, then name it.  */
1853*86d7f5d3SJohn Marino     retcode = RCS_checkout (finfo->rcs, finfo->file, rev ? corev : NULL,
1854*86d7f5d3SJohn Marino 			    NULL, NULL, RUN_TTY, NULL, NULL);
1855*86d7f5d3SJohn Marino     if (retcode != 0)
1856*86d7f5d3SJohn Marino     {
1857*86d7f5d3SJohn Marino 	error (0, 0,
1858*86d7f5d3SJohn Marino 	       "failed to check out `%s'", finfo->fullname);
1859*86d7f5d3SJohn Marino 	return 1;
1860*86d7f5d3SJohn Marino     }
1861*86d7f5d3SJohn Marino 
1862*86d7f5d3SJohn Marino     /* Except when we are creating a branch, lock the revision so that
1863*86d7f5d3SJohn Marino        we can check in the new revision.  */
1864*86d7f5d3SJohn Marino     if (lockflag)
1865*86d7f5d3SJohn Marino     {
1866*86d7f5d3SJohn Marino 	if (RCS_lock (finfo->rcs, rev ? corev : NULL, 1) == 0)
1867*86d7f5d3SJohn Marino 	    RCS_rewrite (finfo->rcs, NULL, NULL);
1868*86d7f5d3SJohn Marino     }
1869*86d7f5d3SJohn Marino 
1870*86d7f5d3SJohn Marino     if (corev != NULL)
1871*86d7f5d3SJohn Marino 	free (corev);
1872*86d7f5d3SJohn Marino 
1873*86d7f5d3SJohn Marino     retcode = RCS_checkin (finfo->rcs, NULL, finfo->file, message,
1874*86d7f5d3SJohn Marino 			   rev, 0, RCS_FLAGS_DEAD | RCS_FLAGS_QUIET);
1875*86d7f5d3SJohn Marino     if (retcode	!= 0)
1876*86d7f5d3SJohn Marino     {
1877*86d7f5d3SJohn Marino 	if (!quiet)
1878*86d7f5d3SJohn Marino 	    error (0, retcode == -1 ? errno : 0,
1879*86d7f5d3SJohn Marino 		   "failed to commit dead revision for `%s'", finfo->fullname);
1880*86d7f5d3SJohn Marino 	return 1;
1881*86d7f5d3SJohn Marino     }
1882*86d7f5d3SJohn Marino     /* At this point, the file has been committed as removed.  We should
1883*86d7f5d3SJohn Marino        probably tell the history file about it  */
1884*86d7f5d3SJohn Marino     history_write ('R', NULL, finfo->rcs->head, finfo->file, finfo->repository);
1885*86d7f5d3SJohn Marino 
1886*86d7f5d3SJohn Marino     if (rev != NULL)
1887*86d7f5d3SJohn Marino 	free (rev);
1888*86d7f5d3SJohn Marino 
1889*86d7f5d3SJohn Marino     old_path = xstrdup (finfo->rcs->path);
1890*86d7f5d3SJohn Marino     if (!branch)
1891*86d7f5d3SJohn Marino 	RCS_setattic (finfo->rcs, 1);
1892*86d7f5d3SJohn Marino 
1893*86d7f5d3SJohn Marino     /* Print message that file was removed. */
1894*86d7f5d3SJohn Marino     if (!really_quiet)
1895*86d7f5d3SJohn Marino     {
1896*86d7f5d3SJohn Marino 	cvs_output (old_path, 0);
1897*86d7f5d3SJohn Marino 	cvs_output ("  <--  ", 0);
1898*86d7f5d3SJohn Marino 	if (finfo->update_dir && strlen (finfo->update_dir))
1899*86d7f5d3SJohn Marino 	{
1900*86d7f5d3SJohn Marino 	    cvs_output (finfo->update_dir, 0);
1901*86d7f5d3SJohn Marino 	    cvs_output ("/", 1);
1902*86d7f5d3SJohn Marino 	}
1903*86d7f5d3SJohn Marino 	cvs_output (finfo->file, 0);
1904*86d7f5d3SJohn Marino 	cvs_output ("\nnew revision: delete; previous revision: ", 0);
1905*86d7f5d3SJohn Marino 	cvs_output (prev_rev, 0);
1906*86d7f5d3SJohn Marino 	cvs_output ("\n", 0);
1907*86d7f5d3SJohn Marino     }
1908*86d7f5d3SJohn Marino 
1909*86d7f5d3SJohn Marino     free (prev_rev);
1910*86d7f5d3SJohn Marino 
1911*86d7f5d3SJohn Marino     free (old_path);
1912*86d7f5d3SJohn Marino 
1913*86d7f5d3SJohn Marino     Scratch_Entry (finfo->entries, finfo->file);
1914*86d7f5d3SJohn Marino     return 0;
1915*86d7f5d3SJohn Marino }
1916*86d7f5d3SJohn Marino 
1917*86d7f5d3SJohn Marino 
1918*86d7f5d3SJohn Marino 
1919*86d7f5d3SJohn Marino /*
1920*86d7f5d3SJohn Marino  * Do the actual checkin for added files
1921*86d7f5d3SJohn Marino  */
1922*86d7f5d3SJohn Marino static int
finaladd(struct file_info * finfo,char * rev,char * tag,char * options)1923*86d7f5d3SJohn Marino finaladd (struct file_info *finfo, char *rev, char *tag, char *options)
1924*86d7f5d3SJohn Marino {
1925*86d7f5d3SJohn Marino     int ret;
1926*86d7f5d3SJohn Marino 
1927*86d7f5d3SJohn Marino     ret = Checkin ('A', finfo, rev, tag, options, saved_message);
1928*86d7f5d3SJohn Marino     if (ret == 0)
1929*86d7f5d3SJohn Marino     {
1930*86d7f5d3SJohn Marino 	char *tmp = Xasprintf ("%s/%s%s", CVSADM, finfo->file, CVSEXT_LOG);
1931*86d7f5d3SJohn Marino 	if (unlink_file (tmp) < 0
1932*86d7f5d3SJohn Marino 	    && !existence_error (errno))
1933*86d7f5d3SJohn Marino 	    error (0, errno, "cannot remove %s", tmp);
1934*86d7f5d3SJohn Marino 	free (tmp);
1935*86d7f5d3SJohn Marino     }
1936*86d7f5d3SJohn Marino     else if (finfo->rcs != NULL)
1937*86d7f5d3SJohn Marino 	fixaddfile (finfo->rcs->path);
1938*86d7f5d3SJohn Marino 
1939*86d7f5d3SJohn Marino     (void) time (&last_register_time);
1940*86d7f5d3SJohn Marino 
1941*86d7f5d3SJohn Marino     return ret;
1942*86d7f5d3SJohn Marino }
1943*86d7f5d3SJohn Marino 
1944*86d7f5d3SJohn Marino 
1945*86d7f5d3SJohn Marino 
1946*86d7f5d3SJohn Marino /*
1947*86d7f5d3SJohn Marino  * Unlock an rcs file
1948*86d7f5d3SJohn Marino  */
1949*86d7f5d3SJohn Marino static void
unlockrcs(RCSNode * rcs)1950*86d7f5d3SJohn Marino unlockrcs (RCSNode *rcs)
1951*86d7f5d3SJohn Marino {
1952*86d7f5d3SJohn Marino     int retcode;
1953*86d7f5d3SJohn Marino 
1954*86d7f5d3SJohn Marino     if ((retcode = RCS_unlock (rcs, NULL, 1)) != 0)
1955*86d7f5d3SJohn Marino 	error (retcode == -1 ? 1 : 0, retcode == -1 ? errno : 0,
1956*86d7f5d3SJohn Marino 	       "could not unlock %s", rcs->path);
1957*86d7f5d3SJohn Marino     else
1958*86d7f5d3SJohn Marino 	RCS_rewrite (rcs, NULL, NULL);
1959*86d7f5d3SJohn Marino }
1960*86d7f5d3SJohn Marino 
1961*86d7f5d3SJohn Marino 
1962*86d7f5d3SJohn Marino 
1963*86d7f5d3SJohn Marino /*
1964*86d7f5d3SJohn Marino  * remove a partially added file.  if we can parse it, leave it alone.
1965*86d7f5d3SJohn Marino  *
1966*86d7f5d3SJohn Marino  * FIXME: Every caller that calls this function can access finfo->rcs (the
1967*86d7f5d3SJohn Marino  * parsed RCSNode data), so we should be able to detect that the file needs
1968*86d7f5d3SJohn Marino  * to be removed without reparsing the file as we do below.
1969*86d7f5d3SJohn Marino  */
1970*86d7f5d3SJohn Marino static void
fixaddfile(const char * rcs)1971*86d7f5d3SJohn Marino fixaddfile (const char *rcs)
1972*86d7f5d3SJohn Marino {
1973*86d7f5d3SJohn Marino     RCSNode *rcsfile;
1974*86d7f5d3SJohn Marino     int save_really_quiet;
1975*86d7f5d3SJohn Marino 
1976*86d7f5d3SJohn Marino     save_really_quiet = really_quiet;
1977*86d7f5d3SJohn Marino     really_quiet = 1;
1978*86d7f5d3SJohn Marino     if ((rcsfile = RCS_parsercsfile (rcs)) == NULL)
1979*86d7f5d3SJohn Marino     {
1980*86d7f5d3SJohn Marino 	if (unlink_file (rcs) < 0)
1981*86d7f5d3SJohn Marino 	    error (0, errno, "cannot remove %s", rcs);
1982*86d7f5d3SJohn Marino     }
1983*86d7f5d3SJohn Marino     else
1984*86d7f5d3SJohn Marino 	freercsnode (&rcsfile);
1985*86d7f5d3SJohn Marino     really_quiet = save_really_quiet;
1986*86d7f5d3SJohn Marino }
1987*86d7f5d3SJohn Marino 
1988*86d7f5d3SJohn Marino 
1989*86d7f5d3SJohn Marino 
1990*86d7f5d3SJohn Marino /*
1991*86d7f5d3SJohn Marino  * put the branch back on an rcs file
1992*86d7f5d3SJohn Marino  */
1993*86d7f5d3SJohn Marino static void
fixbranch(RCSNode * rcs,char * branch)1994*86d7f5d3SJohn Marino fixbranch (RCSNode *rcs, char *branch)
1995*86d7f5d3SJohn Marino {
1996*86d7f5d3SJohn Marino     int retcode;
1997*86d7f5d3SJohn Marino 
1998*86d7f5d3SJohn Marino     if (branch != NULL)
1999*86d7f5d3SJohn Marino     {
2000*86d7f5d3SJohn Marino 	if ((retcode = RCS_setbranch (rcs, branch)) != 0)
2001*86d7f5d3SJohn Marino 	    error (retcode == -1 ? 1 : 0, retcode == -1 ? errno : 0,
2002*86d7f5d3SJohn Marino 		   "cannot restore branch to %s for %s", branch, rcs->path);
2003*86d7f5d3SJohn Marino 	RCS_rewrite (rcs, NULL, NULL);
2004*86d7f5d3SJohn Marino     }
2005*86d7f5d3SJohn Marino }
2006*86d7f5d3SJohn Marino 
2007*86d7f5d3SJohn Marino 
2008*86d7f5d3SJohn Marino 
2009*86d7f5d3SJohn Marino /*
2010*86d7f5d3SJohn Marino  * do the initial part of a file add for the named file.  if adding
2011*86d7f5d3SJohn Marino  * with a tag, put the file in the Attic and point the symbolic tag
2012*86d7f5d3SJohn Marino  * at the committed revision.
2013*86d7f5d3SJohn Marino  *
2014*86d7f5d3SJohn Marino  * INPUTS
2015*86d7f5d3SJohn Marino  *   file	The name of the file in the workspace.
2016*86d7f5d3SJohn Marino  *   repository	The repository directory to expect to find FILE,v in.
2017*86d7f5d3SJohn Marino  *   tag	The name or rev num of the branch being added to, if any.
2018*86d7f5d3SJohn Marino  *   options	Any RCS keyword expansion options specified by the user.
2019*86d7f5d3SJohn Marino  *   rcsnode	A pointer to the pre-parsed RCSNode for this file, if the file
2020*86d7f5d3SJohn Marino  *		exists in the repository.  If this is NULL, assume the file
2021*86d7f5d3SJohn Marino  *		does not yet exist.
2022*86d7f5d3SJohn Marino  *
2023*86d7f5d3SJohn Marino  * RETURNS
2024*86d7f5d3SJohn Marino  *   0 on success.
2025*86d7f5d3SJohn Marino  *   1 on errors, after printing any appropriate error messages.
2026*86d7f5d3SJohn Marino  *
2027*86d7f5d3SJohn Marino  * ERRORS
2028*86d7f5d3SJohn Marino  *   This function will return an error when any of the following functions do:
2029*86d7f5d3SJohn Marino  *     add_rcs_file
2030*86d7f5d3SJohn Marino  *     RCS_setattic
2031*86d7f5d3SJohn Marino  *     lock_RCS
2032*86d7f5d3SJohn Marino  *     RCS_checkin
2033*86d7f5d3SJohn Marino  *     RCS_parse (called to verify the newly created archive file)
2034*86d7f5d3SJohn Marino  *     RCS_settag
2035*86d7f5d3SJohn Marino  */
2036*86d7f5d3SJohn Marino 
2037*86d7f5d3SJohn Marino static int
checkaddfile(const char * file,const char * repository,const char * tag,const char * options,RCSNode ** rcsnode)2038*86d7f5d3SJohn Marino checkaddfile (const char *file, const char *repository, const char *tag,
2039*86d7f5d3SJohn Marino               const char *options, RCSNode **rcsnode)
2040*86d7f5d3SJohn Marino {
2041*86d7f5d3SJohn Marino     RCSNode *rcs;
2042*86d7f5d3SJohn Marino     char *fname;
2043*86d7f5d3SJohn Marino     int newfile = 0;		/* Set to 1 if we created a new RCS archive. */
2044*86d7f5d3SJohn Marino     int retval = 1;
2045*86d7f5d3SJohn Marino     int adding_on_branch;
2046*86d7f5d3SJohn Marino 
2047*86d7f5d3SJohn Marino     assert (rcsnode != NULL);
2048*86d7f5d3SJohn Marino 
2049*86d7f5d3SJohn Marino     /* Callers expect to be able to use either "" or NULL to mean the
2050*86d7f5d3SJohn Marino        default keyword expansion.  */
2051*86d7f5d3SJohn Marino     if (options != NULL && options[0] == '\0')
2052*86d7f5d3SJohn Marino 	options = NULL;
2053*86d7f5d3SJohn Marino     if (options != NULL)
2054*86d7f5d3SJohn Marino 	assert (options[0] == '-' && options[1] == 'k');
2055*86d7f5d3SJohn Marino 
2056*86d7f5d3SJohn Marino     /* If numeric, it is on the trunk; check_fileproc enforced
2057*86d7f5d3SJohn Marino        this.  */
2058*86d7f5d3SJohn Marino     adding_on_branch = tag != NULL && !isdigit ((unsigned char) tag[0]);
2059*86d7f5d3SJohn Marino 
2060*86d7f5d3SJohn Marino     if (*rcsnode == NULL)
2061*86d7f5d3SJohn Marino     {
2062*86d7f5d3SJohn Marino 	char *rcsname;
2063*86d7f5d3SJohn Marino 	char *desc = NULL;
2064*86d7f5d3SJohn Marino 	size_t descalloc = 0;
2065*86d7f5d3SJohn Marino 	size_t desclen = 0;
2066*86d7f5d3SJohn Marino 	const char *opt;
2067*86d7f5d3SJohn Marino 
2068*86d7f5d3SJohn Marino 	if (adding_on_branch)
2069*86d7f5d3SJohn Marino 	{
2070*86d7f5d3SJohn Marino 	    mode_t omask;
2071*86d7f5d3SJohn Marino 	    rcsname = xmalloc (strlen (repository)
2072*86d7f5d3SJohn Marino 			       + sizeof (CVSATTIC)
2073*86d7f5d3SJohn Marino 			       + strlen (file)
2074*86d7f5d3SJohn Marino 			       + sizeof (RCSEXT)
2075*86d7f5d3SJohn Marino 			       + 3);
2076*86d7f5d3SJohn Marino 	    (void) sprintf (rcsname, "%s/%s", repository, CVSATTIC);
2077*86d7f5d3SJohn Marino 	    omask = umask (cvsumask);
2078*86d7f5d3SJohn Marino 	    if (CVS_MKDIR (rcsname, 0777) != 0 && errno != EEXIST)
2079*86d7f5d3SJohn Marino 		error (1, errno, "cannot make directory `%s'", rcsname);
2080*86d7f5d3SJohn Marino 	    (void) umask (omask);
2081*86d7f5d3SJohn Marino 	    (void) sprintf (rcsname,
2082*86d7f5d3SJohn Marino 			    "%s/%s/%s%s",
2083*86d7f5d3SJohn Marino 			    repository,
2084*86d7f5d3SJohn Marino 			    CVSATTIC,
2085*86d7f5d3SJohn Marino 			    file,
2086*86d7f5d3SJohn Marino 			    RCSEXT);
2087*86d7f5d3SJohn Marino 	}
2088*86d7f5d3SJohn Marino 	else
2089*86d7f5d3SJohn Marino 	    rcsname = Xasprintf ("%s/%s%s", repository, file, RCSEXT);
2090*86d7f5d3SJohn Marino 
2091*86d7f5d3SJohn Marino 	/* this is the first time we have ever seen this file; create
2092*86d7f5d3SJohn Marino 	   an RCS file.  */
2093*86d7f5d3SJohn Marino 	fname = Xasprintf ("%s/%s%s", CVSADM, file, CVSEXT_LOG);
2094*86d7f5d3SJohn Marino 	/* If the file does not exist, no big deal.  In particular, the
2095*86d7f5d3SJohn Marino 	   server does not (yet at least) create CVSEXT_LOG files.  */
2096*86d7f5d3SJohn Marino 	if (isfile (fname))
2097*86d7f5d3SJohn Marino 	    /* FIXME: Should be including update_dir in the appropriate
2098*86d7f5d3SJohn Marino 	       place here.  */
2099*86d7f5d3SJohn Marino 	    get_file (fname, fname, "r", &desc, &descalloc, &desclen);
2100*86d7f5d3SJohn Marino 	free (fname);
2101*86d7f5d3SJohn Marino 
2102*86d7f5d3SJohn Marino 	/* From reading the RCS 5.7 source, "rcs -i" adds a newline to the
2103*86d7f5d3SJohn Marino 	   end of the log message if the message is nonempty.
2104*86d7f5d3SJohn Marino 	   Do it.  RCS also deletes certain whitespace, in cleanlogmsg,
2105*86d7f5d3SJohn Marino 	   which we don't try to do here.  */
2106*86d7f5d3SJohn Marino 	if (desclen > 0)
2107*86d7f5d3SJohn Marino 	{
2108*86d7f5d3SJohn Marino 	    expand_string (&desc, &descalloc, desclen + 1);
2109*86d7f5d3SJohn Marino 	    desc[desclen++] = '\012';
2110*86d7f5d3SJohn Marino 	}
2111*86d7f5d3SJohn Marino 
2112*86d7f5d3SJohn Marino 	/* Set RCS keyword expansion options.  */
2113*86d7f5d3SJohn Marino 	if (options != NULL)
2114*86d7f5d3SJohn Marino 	    opt = options + 2;
2115*86d7f5d3SJohn Marino 	else
2116*86d7f5d3SJohn Marino 	    opt = NULL;
2117*86d7f5d3SJohn Marino 
2118*86d7f5d3SJohn Marino 	if (add_rcs_file (NULL, rcsname, file, NULL, opt,
2119*86d7f5d3SJohn Marino 			  NULL, NULL, 0, NULL,
2120*86d7f5d3SJohn Marino 			  desc, desclen, NULL, 0) != 0)
2121*86d7f5d3SJohn Marino 	{
2122*86d7f5d3SJohn Marino 	    if (rcsname != NULL)
2123*86d7f5d3SJohn Marino 	        free (rcsname);
2124*86d7f5d3SJohn Marino 	    goto out;
2125*86d7f5d3SJohn Marino 	}
2126*86d7f5d3SJohn Marino 	rcs = RCS_parsercsfile (rcsname);
2127*86d7f5d3SJohn Marino 	newfile = 1;
2128*86d7f5d3SJohn Marino 	if (rcsname != NULL)
2129*86d7f5d3SJohn Marino 	    free (rcsname);
2130*86d7f5d3SJohn Marino 	if (desc != NULL)
2131*86d7f5d3SJohn Marino 	    free (desc);
2132*86d7f5d3SJohn Marino 	*rcsnode = rcs;
2133*86d7f5d3SJohn Marino     }
2134*86d7f5d3SJohn Marino     else
2135*86d7f5d3SJohn Marino     {
2136*86d7f5d3SJohn Marino 	/* file has existed in the past.  Prepare to resurrect. */
2137*86d7f5d3SJohn Marino 	char *rev;
2138*86d7f5d3SJohn Marino 	char *oldexpand;
2139*86d7f5d3SJohn Marino 
2140*86d7f5d3SJohn Marino 	rcs = *rcsnode;
2141*86d7f5d3SJohn Marino 
2142*86d7f5d3SJohn Marino 	oldexpand = RCS_getexpand (rcs);
2143*86d7f5d3SJohn Marino 	if ((oldexpand != NULL
2144*86d7f5d3SJohn Marino 	     && options != NULL
2145*86d7f5d3SJohn Marino 	     && strcmp (options + 2, oldexpand) != 0)
2146*86d7f5d3SJohn Marino 	    || (oldexpand == NULL && options != NULL))
2147*86d7f5d3SJohn Marino 	{
2148*86d7f5d3SJohn Marino 	    /* We tell the user about this, because it means that the
2149*86d7f5d3SJohn Marino 	       old revisions will no longer retrieve the way that they
2150*86d7f5d3SJohn Marino 	       used to.  */
2151*86d7f5d3SJohn Marino 	    error (0, 0, "changing keyword expansion mode to %s", options);
2152*86d7f5d3SJohn Marino 	    RCS_setexpand (rcs, options + 2);
2153*86d7f5d3SJohn Marino 	}
2154*86d7f5d3SJohn Marino 
2155*86d7f5d3SJohn Marino 	if (!adding_on_branch)
2156*86d7f5d3SJohn Marino 	{
2157*86d7f5d3SJohn Marino 	    /* We are adding on the trunk, so move the file out of the
2158*86d7f5d3SJohn Marino 	       Attic.  */
2159*86d7f5d3SJohn Marino 	    if (!(rcs->flags & INATTIC))
2160*86d7f5d3SJohn Marino 	    {
2161*86d7f5d3SJohn Marino 		error (0, 0, "warning: expected %s to be in Attic",
2162*86d7f5d3SJohn Marino 		       rcs->path);
2163*86d7f5d3SJohn Marino 	    }
2164*86d7f5d3SJohn Marino 
2165*86d7f5d3SJohn Marino 	    /* Begin a critical section around the code that spans the
2166*86d7f5d3SJohn Marino 	       first commit on the trunk of a file that's already been
2167*86d7f5d3SJohn Marino 	       committed on a branch.  */
2168*86d7f5d3SJohn Marino 	    SIG_beginCrSect ();
2169*86d7f5d3SJohn Marino 
2170*86d7f5d3SJohn Marino 	    if (RCS_setattic (rcs, 0))
2171*86d7f5d3SJohn Marino 	    {
2172*86d7f5d3SJohn Marino 		goto out;
2173*86d7f5d3SJohn Marino 	    }
2174*86d7f5d3SJohn Marino 	}
2175*86d7f5d3SJohn Marino 
2176*86d7f5d3SJohn Marino 	rev = RCS_getversion (rcs, tag, NULL, 1, NULL);
2177*86d7f5d3SJohn Marino 	/* and lock it */
2178*86d7f5d3SJohn Marino 	if (lock_RCS (file, rcs, rev, repository))
2179*86d7f5d3SJohn Marino 	{
2180*86d7f5d3SJohn Marino 	    error (0, 0, "cannot lock revision %s in `%s'.",
2181*86d7f5d3SJohn Marino 		   rev ? rev : tag ? tag : "HEAD", rcs->path);
2182*86d7f5d3SJohn Marino 	    if (rev != NULL)
2183*86d7f5d3SJohn Marino 		free (rev);
2184*86d7f5d3SJohn Marino 	    goto out;
2185*86d7f5d3SJohn Marino 	}
2186*86d7f5d3SJohn Marino 
2187*86d7f5d3SJohn Marino 	if (rev != NULL)
2188*86d7f5d3SJohn Marino 	    free (rev);
2189*86d7f5d3SJohn Marino     }
2190*86d7f5d3SJohn Marino 
2191*86d7f5d3SJohn Marino     /* when adding a file for the first time, and using a tag, we need
2192*86d7f5d3SJohn Marino        to create a dead revision on the trunk.  */
2193*86d7f5d3SJohn Marino     if (adding_on_branch)
2194*86d7f5d3SJohn Marino     {
2195*86d7f5d3SJohn Marino 	if (newfile)
2196*86d7f5d3SJohn Marino 	{
2197*86d7f5d3SJohn Marino 	    char *tmp;
2198*86d7f5d3SJohn Marino 	    FILE *fp;
2199*86d7f5d3SJohn Marino 	    int retcode;
2200*86d7f5d3SJohn Marino 
2201*86d7f5d3SJohn Marino 	    /* move the new file out of the way. */
2202*86d7f5d3SJohn Marino 	    fname = Xasprintf ("%s/%s%s", CVSADM, CVSPREFIX, file);
2203*86d7f5d3SJohn Marino 	    rename_file (file, fname);
2204*86d7f5d3SJohn Marino 
2205*86d7f5d3SJohn Marino 	    /* Create empty FILE.  Can't use copy_file with a DEVNULL
2206*86d7f5d3SJohn Marino 	       argument -- copy_file now ignores device files. */
2207*86d7f5d3SJohn Marino 	    fp = fopen (file, "w");
2208*86d7f5d3SJohn Marino 	    if (fp == NULL)
2209*86d7f5d3SJohn Marino 		error (1, errno, "cannot open %s for writing", file);
2210*86d7f5d3SJohn Marino 	    if (fclose (fp) < 0)
2211*86d7f5d3SJohn Marino 		error (0, errno, "cannot close %s", file);
2212*86d7f5d3SJohn Marino 
2213*86d7f5d3SJohn Marino 	    tmp = Xasprintf ("file %s was initially added on branch %s.",
2214*86d7f5d3SJohn Marino 			     file, tag);
2215*86d7f5d3SJohn Marino 	    /* commit a dead revision. */
2216*86d7f5d3SJohn Marino 	    retcode = RCS_checkin (rcs, NULL, NULL, tmp, NULL, 0,
2217*86d7f5d3SJohn Marino 				   RCS_FLAGS_DEAD | RCS_FLAGS_QUIET);
2218*86d7f5d3SJohn Marino 	    free (tmp);
2219*86d7f5d3SJohn Marino 	    if (retcode != 0)
2220*86d7f5d3SJohn Marino 	    {
2221*86d7f5d3SJohn Marino 		error (retcode == -1 ? 1 : 0, retcode == -1 ? errno : 0,
2222*86d7f5d3SJohn Marino 		       "could not create initial dead revision %s", rcs->path);
2223*86d7f5d3SJohn Marino 		free (fname);
2224*86d7f5d3SJohn Marino 		goto out;
2225*86d7f5d3SJohn Marino 	    }
2226*86d7f5d3SJohn Marino 
2227*86d7f5d3SJohn Marino 	    /* put the new file back where it was */
2228*86d7f5d3SJohn Marino 	    rename_file (fname, file);
2229*86d7f5d3SJohn Marino 	    free (fname);
2230*86d7f5d3SJohn Marino 
2231*86d7f5d3SJohn Marino 	    /* double-check that the file was written correctly */
2232*86d7f5d3SJohn Marino 	    freercsnode (&rcs);
2233*86d7f5d3SJohn Marino 	    rcs = RCS_parse (file, repository);
2234*86d7f5d3SJohn Marino 	    if (rcs == NULL)
2235*86d7f5d3SJohn Marino 	    {
2236*86d7f5d3SJohn Marino 		error (0, 0, "could not read %s", rcs->path);
2237*86d7f5d3SJohn Marino 		goto out;
2238*86d7f5d3SJohn Marino 	    }
2239*86d7f5d3SJohn Marino 	    *rcsnode = rcs;
2240*86d7f5d3SJohn Marino 
2241*86d7f5d3SJohn Marino 	    /* and lock it once again. */
2242*86d7f5d3SJohn Marino 	    if (lock_RCS (file, rcs, NULL, repository))
2243*86d7f5d3SJohn Marino 	    {
2244*86d7f5d3SJohn Marino 		error (0, 0, "cannot lock initial revision in `%s'.",
2245*86d7f5d3SJohn Marino 		       rcs->path);
2246*86d7f5d3SJohn Marino 		goto out;
2247*86d7f5d3SJohn Marino 	    }
2248*86d7f5d3SJohn Marino 	}
2249*86d7f5d3SJohn Marino 
2250*86d7f5d3SJohn Marino 	/* when adding with a tag, we need to stub a branch, if it
2251*86d7f5d3SJohn Marino 	   doesn't already exist.  */
2252*86d7f5d3SJohn Marino 	if (!RCS_nodeisbranch (rcs, tag))
2253*86d7f5d3SJohn Marino 	{
2254*86d7f5d3SJohn Marino 	    /* branch does not exist.  Stub it.  */
2255*86d7f5d3SJohn Marino 	    char *head;
2256*86d7f5d3SJohn Marino 	    char *magicrev;
2257*86d7f5d3SJohn Marino 	    int retcode;
2258*86d7f5d3SJohn Marino 	    time_t headtime = -1;
2259*86d7f5d3SJohn Marino 	    char *revnum, *tmp;
2260*86d7f5d3SJohn Marino 	    FILE *fp;
2261*86d7f5d3SJohn Marino 	    time_t t = -1;
2262*86d7f5d3SJohn Marino 	    struct tm *ct;
2263*86d7f5d3SJohn Marino 
2264*86d7f5d3SJohn Marino 	    fixbranch (rcs, sbranch);
2265*86d7f5d3SJohn Marino 
2266*86d7f5d3SJohn Marino 	    head = RCS_getversion (rcs, NULL, NULL, 0, NULL);
2267*86d7f5d3SJohn Marino 	    if (!head)
2268*86d7f5d3SJohn Marino 		error (1, 0, "No head revision in archive file `%s'.",
2269*86d7f5d3SJohn Marino 		       rcs->print_path);
2270*86d7f5d3SJohn Marino 	    magicrev = RCS_magicrev (rcs, head);
2271*86d7f5d3SJohn Marino 
2272*86d7f5d3SJohn Marino 	    /* If this is not a new branch, then we will want a dead
2273*86d7f5d3SJohn Marino 	       version created before this one. */
2274*86d7f5d3SJohn Marino 	    if (!newfile)
2275*86d7f5d3SJohn Marino 		headtime = RCS_getrevtime (rcs, head, 0, 0);
2276*86d7f5d3SJohn Marino 
2277*86d7f5d3SJohn Marino 	    retcode = RCS_settag (rcs, tag, magicrev);
2278*86d7f5d3SJohn Marino 	    RCS_rewrite (rcs, NULL, NULL);
2279*86d7f5d3SJohn Marino 
2280*86d7f5d3SJohn Marino 	    free (head);
2281*86d7f5d3SJohn Marino 	    free (magicrev);
2282*86d7f5d3SJohn Marino 
2283*86d7f5d3SJohn Marino 	    if (retcode != 0)
2284*86d7f5d3SJohn Marino 	    {
2285*86d7f5d3SJohn Marino 		error (retcode == -1 ? 1 : 0, retcode == -1 ? errno : 0,
2286*86d7f5d3SJohn Marino 		       "could not stub branch %s for %s", tag, rcs->path);
2287*86d7f5d3SJohn Marino 		goto out;
2288*86d7f5d3SJohn Marino 	    }
2289*86d7f5d3SJohn Marino 	    /* We need to add a dead version here to avoid -rtag -Dtime
2290*86d7f5d3SJohn Marino 	       checkout problems between when the head version was
2291*86d7f5d3SJohn Marino 	       created and now. */
2292*86d7f5d3SJohn Marino 	    if (!newfile && headtime != -1)
2293*86d7f5d3SJohn Marino 	    {
2294*86d7f5d3SJohn Marino 		/* move the new file out of the way. */
2295*86d7f5d3SJohn Marino 		fname = Xasprintf ("%s/%s%s", CVSADM, CVSPREFIX, file);
2296*86d7f5d3SJohn Marino 		rename_file (file, fname);
2297*86d7f5d3SJohn Marino 
2298*86d7f5d3SJohn Marino 		/* Create empty FILE.  Can't use copy_file with a DEVNULL
2299*86d7f5d3SJohn Marino 		   argument -- copy_file now ignores device files. */
2300*86d7f5d3SJohn Marino 		fp = fopen (file, "w");
2301*86d7f5d3SJohn Marino 		if (fp == NULL)
2302*86d7f5d3SJohn Marino 		    error (1, errno, "cannot open %s for writing", file);
2303*86d7f5d3SJohn Marino 		if (fclose (fp) < 0)
2304*86d7f5d3SJohn Marino 		    error (0, errno, "cannot close %s", file);
2305*86d7f5d3SJohn Marino 
2306*86d7f5d3SJohn Marino 		/* As we will be hacking the delta date, put the time
2307*86d7f5d3SJohn Marino 		   this was added into the log message. */
2308*86d7f5d3SJohn Marino 		t = time (NULL);
2309*86d7f5d3SJohn Marino 		ct = gmtime (&t);
2310*86d7f5d3SJohn Marino 		tmp = Xasprintf ("file %s was added on branch %s on %d-%02d-%02d %02d:%02d:%02d +0000",
2311*86d7f5d3SJohn Marino 				 file, tag,
2312*86d7f5d3SJohn Marino 				 ct->tm_year + (ct->tm_year < 100 ? 0 : 1900),
2313*86d7f5d3SJohn Marino 				 ct->tm_mon + 1, ct->tm_mday,
2314*86d7f5d3SJohn Marino 				 ct->tm_hour, ct->tm_min, ct->tm_sec);
2315*86d7f5d3SJohn Marino 
2316*86d7f5d3SJohn Marino 		/* commit a dead revision. */
2317*86d7f5d3SJohn Marino 		revnum = RCS_whatbranch (rcs, tag);
2318*86d7f5d3SJohn Marino 		retcode = RCS_checkin (rcs, NULL, NULL, tmp, revnum, headtime,
2319*86d7f5d3SJohn Marino 				       RCS_FLAGS_DEAD |
2320*86d7f5d3SJohn Marino 				       RCS_FLAGS_QUIET |
2321*86d7f5d3SJohn Marino 				       RCS_FLAGS_USETIME);
2322*86d7f5d3SJohn Marino 		free (revnum);
2323*86d7f5d3SJohn Marino 		free (tmp);
2324*86d7f5d3SJohn Marino 
2325*86d7f5d3SJohn Marino 		if (retcode != 0)
2326*86d7f5d3SJohn Marino 		{
2327*86d7f5d3SJohn Marino 		    error (retcode == -1 ? 1 : 0, retcode == -1 ? errno : 0,
2328*86d7f5d3SJohn Marino 			   "could not created dead stub %s for %s", tag,
2329*86d7f5d3SJohn Marino 			   rcs->path);
2330*86d7f5d3SJohn Marino 		    goto out;
2331*86d7f5d3SJohn Marino 		}
2332*86d7f5d3SJohn Marino 
2333*86d7f5d3SJohn Marino 		/* put the new file back where it was */
2334*86d7f5d3SJohn Marino 		rename_file (fname, file);
2335*86d7f5d3SJohn Marino 		free (fname);
2336*86d7f5d3SJohn Marino 
2337*86d7f5d3SJohn Marino 		/* double-check that the file was written correctly */
2338*86d7f5d3SJohn Marino 		freercsnode (&rcs);
2339*86d7f5d3SJohn Marino 		rcs = RCS_parse (file, repository);
2340*86d7f5d3SJohn Marino 		if (rcs == NULL)
2341*86d7f5d3SJohn Marino 		{
2342*86d7f5d3SJohn Marino 		    error (0, 0, "could not read %s", rcs->path);
2343*86d7f5d3SJohn Marino 		    goto out;
2344*86d7f5d3SJohn Marino 		}
2345*86d7f5d3SJohn Marino 		*rcsnode = rcs;
2346*86d7f5d3SJohn Marino 	    }
2347*86d7f5d3SJohn Marino 	}
2348*86d7f5d3SJohn Marino 	else
2349*86d7f5d3SJohn Marino 	{
2350*86d7f5d3SJohn Marino 	    /* lock the branch. (stubbed branches need not be locked.)  */
2351*86d7f5d3SJohn Marino 	    if (lock_RCS (file, rcs, NULL, repository))
2352*86d7f5d3SJohn Marino 	    {
2353*86d7f5d3SJohn Marino 		error (0, 0, "cannot lock head revision in `%s'.", rcs->path);
2354*86d7f5d3SJohn Marino 		goto out;
2355*86d7f5d3SJohn Marino 	    }
2356*86d7f5d3SJohn Marino 	}
2357*86d7f5d3SJohn Marino 
2358*86d7f5d3SJohn Marino 	if (*rcsnode != rcs)
2359*86d7f5d3SJohn Marino 	{
2360*86d7f5d3SJohn Marino 	    freercsnode (rcsnode);
2361*86d7f5d3SJohn Marino 	    *rcsnode = rcs;
2362*86d7f5d3SJohn Marino 	}
2363*86d7f5d3SJohn Marino     }
2364*86d7f5d3SJohn Marino 
2365*86d7f5d3SJohn Marino     fileattr_newfile (file);
2366*86d7f5d3SJohn Marino 
2367*86d7f5d3SJohn Marino     /* At this point, we used to set the file mode of the RCS file
2368*86d7f5d3SJohn Marino        based on the mode of the file in the working directory.  If we
2369*86d7f5d3SJohn Marino        are creating the RCS file for the first time, add_rcs_file does
2370*86d7f5d3SJohn Marino        this already.  If we are re-adding the file, then perhaps it is
2371*86d7f5d3SJohn Marino        consistent to preserve the old file mode, just as we preserve
2372*86d7f5d3SJohn Marino        the old keyword expansion mode.
2373*86d7f5d3SJohn Marino 
2374*86d7f5d3SJohn Marino        If we decide that we should change the modes, then we can't do
2375*86d7f5d3SJohn Marino        it here anyhow.  At this point, the RCS file may be owned by
2376*86d7f5d3SJohn Marino        somebody else, so a chmod will fail.  We need to instead do the
2377*86d7f5d3SJohn Marino        chmod after rewriting it.
2378*86d7f5d3SJohn Marino 
2379*86d7f5d3SJohn Marino        FIXME: In general, I think the file mode (and the keyword
2380*86d7f5d3SJohn Marino        expansion mode) should be associated with a particular revision
2381*86d7f5d3SJohn Marino        of the file, so that it is possible to have different revisions
2382*86d7f5d3SJohn Marino        of a file have different modes.  */
2383*86d7f5d3SJohn Marino 
2384*86d7f5d3SJohn Marino     retval = 0;
2385*86d7f5d3SJohn Marino 
2386*86d7f5d3SJohn Marino  out:
2387*86d7f5d3SJohn Marino     if (retval != 0 && SIG_inCrSect ())
2388*86d7f5d3SJohn Marino 	SIG_endCrSect ();
2389*86d7f5d3SJohn Marino     return retval;
2390*86d7f5d3SJohn Marino }
2391*86d7f5d3SJohn Marino 
2392*86d7f5d3SJohn Marino 
2393*86d7f5d3SJohn Marino 
2394*86d7f5d3SJohn Marino /*
2395*86d7f5d3SJohn Marino  * Attempt to place a lock on the RCS file; returns 0 if it could and 1 if it
2396*86d7f5d3SJohn Marino  * couldn't.  If the RCS file currently has a branch as the head, we must
2397*86d7f5d3SJohn Marino  * move the head back to the trunk before locking the file, and be sure to
2398*86d7f5d3SJohn Marino  * put the branch back as the head if there are any errors.
2399*86d7f5d3SJohn Marino  */
2400*86d7f5d3SJohn Marino static int
lock_RCS(const char * user,RCSNode * rcs,const char * rev,const char * repository)2401*86d7f5d3SJohn Marino lock_RCS (const char *user, RCSNode *rcs, const char *rev,
2402*86d7f5d3SJohn Marino           const char *repository)
2403*86d7f5d3SJohn Marino {
2404*86d7f5d3SJohn Marino     char *branch = NULL;
2405*86d7f5d3SJohn Marino     int err = 0;
2406*86d7f5d3SJohn Marino 
2407*86d7f5d3SJohn Marino     /*
2408*86d7f5d3SJohn Marino      * For a specified, numeric revision of the form "1" or "1.1", (or when
2409*86d7f5d3SJohn Marino      * no revision is specified ""), definitely move the branch to the trunk
2410*86d7f5d3SJohn Marino      * before locking the RCS file.
2411*86d7f5d3SJohn Marino      *
2412*86d7f5d3SJohn Marino      * The assumption is that if there is more than one revision on the trunk,
2413*86d7f5d3SJohn Marino      * the head points to the trunk, not a branch... and as such, it's not
2414*86d7f5d3SJohn Marino      * necessary to move the head in this case.
2415*86d7f5d3SJohn Marino      */
2416*86d7f5d3SJohn Marino     if (rev == NULL
2417*86d7f5d3SJohn Marino 	|| (rev && isdigit ((unsigned char) *rev) && numdots (rev) < 2))
2418*86d7f5d3SJohn Marino     {
2419*86d7f5d3SJohn Marino 	branch = xstrdup (rcs->branch);
2420*86d7f5d3SJohn Marino 	if (branch != NULL)
2421*86d7f5d3SJohn Marino 	{
2422*86d7f5d3SJohn Marino 	    if (RCS_setbranch (rcs, NULL) != 0)
2423*86d7f5d3SJohn Marino 	    {
2424*86d7f5d3SJohn Marino 		error (0, 0, "cannot change branch to default for %s",
2425*86d7f5d3SJohn Marino 		       rcs->path);
2426*86d7f5d3SJohn Marino 		if (branch)
2427*86d7f5d3SJohn Marino 		    free (branch);
2428*86d7f5d3SJohn Marino 		return 1;
2429*86d7f5d3SJohn Marino 	    }
2430*86d7f5d3SJohn Marino 	}
2431*86d7f5d3SJohn Marino 	err = RCS_lock (rcs, NULL, 1);
2432*86d7f5d3SJohn Marino     }
2433*86d7f5d3SJohn Marino     else
2434*86d7f5d3SJohn Marino     {
2435*86d7f5d3SJohn Marino 	RCS_lock (rcs, rev, 1);
2436*86d7f5d3SJohn Marino     }
2437*86d7f5d3SJohn Marino 
2438*86d7f5d3SJohn Marino     /* We used to call RCS_rewrite here, and that might seem
2439*86d7f5d3SJohn Marino        appropriate in order to write out the locked revision
2440*86d7f5d3SJohn Marino        information.  However, such a call would actually serve no
2441*86d7f5d3SJohn Marino        purpose.  CVS locks will prevent any interference from other
2442*86d7f5d3SJohn Marino        CVS processes.  The comment above rcs_internal_lockfile
2443*86d7f5d3SJohn Marino        explains that it is already unsafe to use RCS and CVS
2444*86d7f5d3SJohn Marino        simultaneously.  It follows that writing out the locked
2445*86d7f5d3SJohn Marino        revision information here would add no additional security.
2446*86d7f5d3SJohn Marino 
2447*86d7f5d3SJohn Marino        If we ever do care about it, the proper fix is to create the
2448*86d7f5d3SJohn Marino        RCS lock file before calling this function, and maintain it
2449*86d7f5d3SJohn Marino        until the checkin is complete.
2450*86d7f5d3SJohn Marino 
2451*86d7f5d3SJohn Marino        The call to RCS_lock is still required at present, since in
2452*86d7f5d3SJohn Marino        some cases RCS_checkin will determine which revision to check
2453*86d7f5d3SJohn Marino        in by looking for a lock.  FIXME: This is rather roundabout,
2454*86d7f5d3SJohn Marino        and a more straightforward approach would probably be easier to
2455*86d7f5d3SJohn Marino        understand.  */
2456*86d7f5d3SJohn Marino 
2457*86d7f5d3SJohn Marino     if (err == 0)
2458*86d7f5d3SJohn Marino     {
2459*86d7f5d3SJohn Marino 	if (sbranch != NULL)
2460*86d7f5d3SJohn Marino 	    free (sbranch);
2461*86d7f5d3SJohn Marino 	sbranch = branch;
2462*86d7f5d3SJohn Marino 	return 0;
2463*86d7f5d3SJohn Marino     }
2464*86d7f5d3SJohn Marino 
2465*86d7f5d3SJohn Marino     /* try to restore the branch if we can on error */
2466*86d7f5d3SJohn Marino     if (branch != NULL)
2467*86d7f5d3SJohn Marino 	fixbranch (rcs, branch);
2468*86d7f5d3SJohn Marino 
2469*86d7f5d3SJohn Marino     if (branch)
2470*86d7f5d3SJohn Marino 	free (branch);
2471*86d7f5d3SJohn Marino     return 1;
2472*86d7f5d3SJohn Marino }
2473*86d7f5d3SJohn Marino 
2474*86d7f5d3SJohn Marino 
2475*86d7f5d3SJohn Marino 
2476*86d7f5d3SJohn Marino /*
2477*86d7f5d3SJohn Marino  * free an UPDATE node's data
2478*86d7f5d3SJohn Marino  */
2479*86d7f5d3SJohn Marino void
update_delproc(Node * p)2480*86d7f5d3SJohn Marino update_delproc (Node *p)
2481*86d7f5d3SJohn Marino {
2482*86d7f5d3SJohn Marino     struct logfile_info *li = p->data;
2483*86d7f5d3SJohn Marino 
2484*86d7f5d3SJohn Marino     if (li->tag)
2485*86d7f5d3SJohn Marino 	free (li->tag);
2486*86d7f5d3SJohn Marino     if (li->rev_old)
2487*86d7f5d3SJohn Marino 	free (li->rev_old);
2488*86d7f5d3SJohn Marino     if (li->rev_new)
2489*86d7f5d3SJohn Marino 	free (li->rev_new);
2490*86d7f5d3SJohn Marino     free (li);
2491*86d7f5d3SJohn Marino }
2492*86d7f5d3SJohn Marino 
2493*86d7f5d3SJohn Marino /*
2494*86d7f5d3SJohn Marino  * Free the commit_info structure in p.
2495*86d7f5d3SJohn Marino  */
2496*86d7f5d3SJohn Marino static void
ci_delproc(Node * p)2497*86d7f5d3SJohn Marino ci_delproc (Node *p)
2498*86d7f5d3SJohn Marino {
2499*86d7f5d3SJohn Marino     struct commit_info *ci = p->data;
2500*86d7f5d3SJohn Marino 
2501*86d7f5d3SJohn Marino     if (ci->rev)
2502*86d7f5d3SJohn Marino 	free (ci->rev);
2503*86d7f5d3SJohn Marino     if (ci->tag)
2504*86d7f5d3SJohn Marino 	free (ci->tag);
2505*86d7f5d3SJohn Marino     if (ci->options)
2506*86d7f5d3SJohn Marino 	free (ci->options);
2507*86d7f5d3SJohn Marino     free (ci);
2508*86d7f5d3SJohn Marino }
2509*86d7f5d3SJohn Marino 
2510*86d7f5d3SJohn Marino /*
2511*86d7f5d3SJohn Marino  * Free the commit_info structure in p.
2512*86d7f5d3SJohn Marino  */
2513*86d7f5d3SJohn Marino static void
masterlist_delproc(Node * p)2514*86d7f5d3SJohn Marino masterlist_delproc (Node *p)
2515*86d7f5d3SJohn Marino {
2516*86d7f5d3SJohn Marino     struct master_lists *ml = p->data;
2517*86d7f5d3SJohn Marino 
2518*86d7f5d3SJohn Marino     dellist (&ml->ulist);
2519*86d7f5d3SJohn Marino     dellist (&ml->cilist);
2520*86d7f5d3SJohn Marino     free (ml);
2521*86d7f5d3SJohn Marino }
2522