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