add.c revision 44852
1/*
2 * Copyright (c) 1992, Brian Berliner and Jeff Polk
3 * Copyright (c) 1989-1992, Brian Berliner
4 *
5 * You may distribute under the terms of the GNU General Public License as
6 * specified in the README file that comes with the CVS source distribution.
7 *
8 * Add
9 *
10 * Adds a file or directory to the RCS source repository.  For a file,
11 * the entry is marked as "needing to be added" in the user's own CVS
12 * directory, and really added to the repository when it is committed.
13 * For a directory, it is added at the appropriate place in the source
14 * repository and a CVS directory is generated within the directory.
15 *
16 * The -m option is currently the only supported option.  Some may wish to
17 * supply standard "rcs" options here, but I've found that this causes more
18 * trouble than anything else.
19 *
20 * The user files or directories must already exist.  For a directory, it must
21 * not already have a CVS file in it.
22 *
23 * An "add" on a file that has been "remove"d but not committed will cause the
24 * file to be resurrected.
25 */
26
27#include "cvs.h"
28#include "savecwd.h"
29#include "fileattr.h"
30
31static int add_directory PROTO ((struct file_info *finfo));
32static int build_entry PROTO((char *repository, char *user, char *options,
33		        char *message, List * entries, char *tag));
34
35static const char *const add_usage[] =
36{
37    "Usage: %s %s [-k rcs-kflag] [-m message] files...\n",
38    "\t-k\tUse \"rcs-kflag\" to add the file with the specified kflag.\n",
39    "\t-m\tUse \"message\" for the creation log.\n",
40    "(Specify the --help global option for a list of other help options)\n",
41    NULL
42};
43
44int
45add (argc, argv)
46    int argc;
47    char **argv;
48{
49    char *message = NULL;
50    int i;
51    char *repository;
52    int c;
53    int err = 0;
54    int added_files = 0;
55    char *options = NULL;
56    List *entries;
57    Vers_TS *vers;
58    struct saved_cwd cwd;
59    /* Nonzero if we found a slash, and are thus adding files in a
60       subdirectory.  */
61    int found_slash = 0;
62
63    if (argc == 1 || argc == -1)
64	usage (add_usage);
65
66    wrap_setup ();
67
68    /* parse args */
69    optind = 0;
70    while ((c = getopt (argc, argv, "+k:m:")) != -1)
71    {
72	switch (c)
73	{
74	    case 'k':
75		if (options)
76		    free (options);
77		options = RCS_check_kflag (optarg);
78		break;
79
80	    case 'm':
81		message = xstrdup (optarg);
82		break;
83	    case '?':
84	    default:
85		usage (add_usage);
86		break;
87	}
88    }
89    argc -= optind;
90    argv += optind;
91
92    if (argc <= 0)
93	usage (add_usage);
94
95    /* First some sanity checks.  I know that the CVS case is (sort of)
96       also handled by add_directory, but we need to check here so the
97       client won't get all confused in send_file_names.  */
98    for (i = 0; i < argc; i++)
99    {
100	int skip_file = 0;
101
102	/* If it were up to me I'd probably make this a fatal error.
103	   But some people are really fond of their "cvs add *", and
104	   don't seem to object to the warnings.
105	   Whatever.  */
106	strip_trailing_slashes (argv[i]);
107	if (strcmp (argv[i], ".") == 0
108	    || strcmp (argv[i], "..") == 0
109	    || fncmp (argv[i], CVSADM) == 0)
110	{
111	    error (0, 0, "cannot add special file `%s'; skipping", argv[i]);
112	    skip_file = 1;
113	}
114	else
115	{
116	    char *p;
117	    p = argv[i];
118	    while (*p != '\0')
119	    {
120		if (ISDIRSEP (*p))
121		{
122		    found_slash = 1;
123		    break;
124		}
125		++p;
126	    }
127	}
128
129	if (skip_file)
130	{
131	    int j;
132
133	    /* FIXME: We don't do anything about free'ing argv[i].  But
134	       the problem is that it is only sometimes allocated (see
135	       cvsrc.c).  */
136
137	    for (j = i; j < argc - 1; ++j)
138		argv[j] = argv[j + 1];
139	    --argc;
140	    /* Check the new argv[i] again.  */
141	    --i;
142	    ++err;
143	}
144    }
145
146#ifdef CLIENT_SUPPORT
147    if (client_active)
148    {
149	int i;
150
151	if (argc == 0)
152	    /* We snipped out all the arguments in the above sanity
153	       check.  We can just forget the whole thing (and we
154	       better, because if we fired up the server and passed it
155	       nothing, it would spit back a usage message).  */
156	    return err;
157
158	start_server ();
159	ign_setup ();
160	if (options) send_arg(options);
161	option_with_arg ("-m", message);
162
163	/* If !found_slash, refrain from sending "Directory", for
164	   CVS 1.9 compatibility.  If we only tried to deal with servers
165	   which are at least CVS 1.9.26 or so, we wouldn't have to
166	   special-case this.  */
167	if (found_slash)
168	{
169	    repository = Name_Repository (NULL, NULL);
170	    send_a_repository ("", repository, "");
171	    free (repository);
172	}
173
174	for (i = 0; i < argc; ++i)
175	    /* FIXME: Does this erroneously call Create_Admin in error
176	       conditions which are only detected once the server gets its
177	       hands on things?  */
178	    if (isdir (argv[i]))
179	    {
180		char *tag;
181		char *date;
182		int nonbranch;
183		char *rcsdir;
184		char *p;
185		char *update_dir;
186		/* This is some mungeable storage into which we can point
187		   with p and/or update_dir.  */
188		char *filedir;
189
190		if (save_cwd (&cwd))
191		    error_exit ();
192
193		filedir = xstrdup (argv[i]);
194		p = last_component (filedir);
195		if (p == filedir)
196		{
197		    update_dir = "";
198		}
199		else
200		{
201		    p[-1] = '\0';
202		    update_dir = filedir;
203		    if (CVS_CHDIR (update_dir) < 0)
204			error (1, errno,
205			       "could not chdir to %s", update_dir);
206		}
207
208		/* find the repository associated with our current dir */
209		repository = Name_Repository (NULL, update_dir);
210
211		/* before we do anything else, see if we have any
212		   per-directory tags */
213		ParseTag (&tag, &date, &nonbranch);
214
215		rcsdir = xmalloc (strlen (repository) + strlen (p) + 5);
216		sprintf (rcsdir, "%s/%s", repository, p);
217
218		Create_Admin (p, argv[i], rcsdir, tag, date,
219			      nonbranch, 0);
220
221		if (found_slash)
222		    send_a_repository ("", repository, update_dir);
223
224		if (restore_cwd (&cwd, NULL))
225		    error_exit ();
226		free_cwd (&cwd);
227
228		if (tag)
229		    free (tag);
230		if (date)
231		    free (date);
232		free (rcsdir);
233
234		if (p == filedir)
235		    Subdir_Register ((List *) NULL, (char *) NULL, argv[i]);
236		else
237		{
238		    Subdir_Register ((List *) NULL, update_dir, p);
239		}
240		free (repository);
241		free (filedir);
242	    }
243	send_file_names (argc, argv, SEND_EXPAND_WILD);
244	send_files (argc, argv, 0, 0, SEND_BUILD_DIRS | SEND_NO_CONTENTS);
245	send_to_server ("add\012", 0);
246	if (message)
247	    free (message);
248	return err + get_responses_and_close ();
249    }
250#endif
251
252    /* walk the arg list adding files/dirs */
253    for (i = 0; i < argc; i++)
254    {
255	int begin_err = err;
256#ifdef SERVER_SUPPORT
257	int begin_added_files = added_files;
258#endif
259	struct file_info finfo;
260	char *p;
261
262	memset (&finfo, 0, sizeof finfo);
263
264	if (save_cwd (&cwd))
265	    error_exit ();
266
267	finfo.fullname = xstrdup (argv[i]);
268	p = last_component (argv[i]);
269	if (p == argv[i])
270	{
271	    finfo.update_dir = "";
272	    finfo.file = p;
273	}
274	else
275	{
276	    p[-1] = '\0';
277	    finfo.update_dir = argv[i];
278	    finfo.file = p;
279	    if (CVS_CHDIR (finfo.update_dir) < 0)
280		error (1, errno, "could not chdir to %s", finfo.update_dir);
281	}
282
283	/* Add wrappers for this directory.  They exist only until
284	   the next call to wrap_add_file.  */
285	wrap_add_file (CVSDOTWRAPPER, 1);
286
287	finfo.rcs = NULL;
288
289	/* Find the repository associated with our current dir.  */
290	repository = Name_Repository (NULL, finfo.update_dir);
291
292	entries = Entries_Open (0, NULL);
293
294	finfo.repository = repository;
295	finfo.entries = entries;
296
297	/* We pass force_tag_match as 1.  If the directory has a
298           sticky branch tag, and there is already an RCS file which
299           does not have that tag, then the head revision is
300           meaningless to us.  */
301	vers = Version_TS (&finfo, options, NULL, NULL, 1, 0);
302	if (vers->vn_user == NULL)
303	{
304	    /* No entry available, ts_rcs is invalid */
305	    if (vers->vn_rcs == NULL)
306	    {
307		/* There is no RCS file either */
308		if (vers->ts_user == NULL)
309		{
310		    /* There is no user file either */
311		    error (0, 0, "nothing known about %s", finfo.fullname);
312		    err++;
313		}
314		else if (!isdir (finfo.file)
315			 || wrap_name_has (finfo.file, WRAP_TOCVS))
316		{
317		    /*
318		     * See if a directory exists in the repository with
319		     * the same name.  If so, blow this request off.
320		     */
321		    char *dname = xmalloc (strlen (repository)
322					   + strlen (finfo.file)
323					   + 10);
324		    (void) sprintf (dname, "%s/%s", repository, finfo.file);
325		    if (isdir (dname))
326		    {
327			error (0, 0,
328			       "cannot add file `%s' since the directory",
329			       finfo.fullname);
330			error (0, 0, "`%s' already exists in the repository",
331			       dname);
332			error (1, 0, "illegal filename overlap");
333		    }
334		    free (dname);
335
336		    if (vers->options == NULL || *vers->options == '\0')
337		    {
338			/* No options specified on command line (or in
339			   rcs file if it existed, e.g. the file exists
340			   on another branch).  Check for a value from
341			   the wrapper stuff.  */
342			if (wrap_name_has (finfo.file, WRAP_RCSOPTION))
343			{
344			    if (vers->options)
345				free (vers->options);
346			    vers->options = wrap_rcsoption (finfo.file, 1);
347			}
348		    }
349
350		    if (vers->nonbranch)
351		    {
352			error (0, 0,
353			       "cannot add file on non-branch tag %s",
354			       vers->tag);
355			++err;
356		    }
357		    else
358		    {
359			/* There is a user file, so build the entry for it */
360			if (build_entry (repository, finfo.file, vers->options,
361					 message, entries, vers->tag) != 0)
362			    err++;
363			else
364			{
365			    added_files++;
366			    if (!quiet)
367			    {
368				if (vers->tag)
369				    error (0, 0, "\
370scheduling %s `%s' for addition on branch `%s'",
371					   (wrap_name_has (finfo.file,
372							   WRAP_TOCVS)
373					    ? "wrapper"
374					    : "file"),
375					   finfo.fullname, vers->tag);
376				else
377				    error (0, 0,
378					   "scheduling %s `%s' for addition",
379					   (wrap_name_has (finfo.file,
380							   WRAP_TOCVS)
381					    ? "wrapper"
382					    : "file"),
383					   finfo.fullname);
384			    }
385			}
386		    }
387		}
388	    }
389	    else if (RCS_isdead (vers->srcfile, vers->vn_rcs))
390	    {
391		if (isdir (finfo.file)
392		    && !wrap_name_has (finfo.file, WRAP_TOCVS))
393		{
394		    error (0, 0, "\
395the directory `%s' cannot be added because a file of the", finfo.fullname);
396		    error (1, 0, "\
397same name already exists in the repository.");
398		}
399		else
400		{
401		    if (vers->nonbranch)
402		    {
403			error (0, 0,
404			       "cannot add file on non-branch tag %s",
405			       vers->tag);
406			++err;
407		    }
408		    else
409		    {
410			if (vers->tag)
411			    error (0, 0, "\
412file `%s' will be added on branch `%s' from version %s",
413				   finfo.fullname, vers->tag, vers->vn_rcs);
414			else
415			    /* I'm not sure that mentioning
416			       vers->vn_rcs makes any sense here; I
417			       can't think of a way to word the
418			       message which is not confusing.  */
419			    error (0, 0, "\
420re-adding file %s (in place of dead revision %s)",
421				   finfo.fullname, vers->vn_rcs);
422			Register (entries, finfo.file, "0", vers->ts_user,
423				  NULL,
424				  vers->tag, NULL, NULL);
425			++added_files;
426		    }
427		}
428	    }
429	    else
430	    {
431		/*
432		 * There is an RCS file already, so somebody else must've
433		 * added it
434		 */
435		error (0, 0, "%s added independently by second party",
436		       finfo.fullname);
437		err++;
438	    }
439	}
440	else if (vers->vn_user[0] == '0' && vers->vn_user[1] == '\0')
441	{
442
443	    /*
444	     * An entry for a new-born file, ts_rcs is dummy, but that is
445	     * inappropriate here
446	     */
447	    error (0, 0, "%s has already been entered", finfo.fullname);
448	    err++;
449	}
450	else if (vers->vn_user[0] == '-')
451	{
452	    /* An entry for a removed file, ts_rcs is invalid */
453	    if (vers->ts_user == NULL)
454	    {
455		/* There is no user file (as it should be) */
456		if (vers->vn_rcs == NULL)
457		{
458
459		    /*
460		     * There is no RCS file, so somebody else must've removed
461		     * it from under us
462		     */
463		    error (0, 0, "\
464cannot resurrect %s; RCS file removed by second party", finfo.fullname);
465		    err++;
466		}
467		else
468		{
469
470		    /*
471		     * There is an RCS file, so remove the "-" from the
472		     * version number and restore the file
473		     */
474		    char *tmp = xmalloc (strlen (finfo.file) + 50);
475
476		    (void) strcpy (tmp, vers->vn_user + 1);
477		    (void) strcpy (vers->vn_user, tmp);
478		    (void) sprintf (tmp, "Resurrected %s", finfo.file);
479		    Register (entries, finfo.file, vers->vn_user, tmp,
480			      vers->options,
481			      vers->tag, vers->date, vers->ts_conflict);
482		    free (tmp);
483
484		    /* XXX - bugs here; this really resurrect the head */
485		    /* Note that this depends on the Register above actually
486		       having written Entries, or else it won't really
487		       check the file out.  */
488		    if (update (2, argv + i - 1) == 0)
489		    {
490			error (0, 0, "%s, version %s, resurrected",
491			       finfo.fullname,
492			       vers->vn_user);
493		    }
494		    else
495		    {
496			error (0, 0, "could not resurrect %s", finfo.fullname);
497			err++;
498		    }
499		}
500	    }
501	    else
502	    {
503		/* The user file shouldn't be there */
504		error (0, 0, "\
505%s should be removed and is still there (or is back again)", finfo.fullname);
506		err++;
507	    }
508	}
509	else
510	{
511	    /* A normal entry, ts_rcs is valid, so it must already be there */
512	    error (0, 0, "%s already exists, with version number %s",
513		   finfo.fullname,
514		   vers->vn_user);
515	    err++;
516	}
517	freevers_ts (&vers);
518
519	/* passed all the checks.  Go ahead and add it if its a directory */
520	if (begin_err == err
521	    && isdir (finfo.file)
522	    && !wrap_name_has (finfo.file, WRAP_TOCVS))
523	{
524	    err += add_directory (&finfo);
525	}
526	else
527	{
528#ifdef SERVER_SUPPORT
529	    if (server_active && begin_added_files != added_files)
530		server_checked_in (finfo.file, finfo.update_dir, repository);
531#endif
532	}
533	free (repository);
534	Entries_Close (entries);
535
536	if (restore_cwd (&cwd, NULL))
537	    error_exit ();
538	free_cwd (&cwd);
539
540	free (finfo.fullname);
541    }
542    if (added_files)
543	error (0, 0, "use '%s commit' to add %s permanently",
544	       program_name,
545	       (added_files == 1) ? "this file" : "these files");
546
547    if (message)
548	free (message);
549
550    return (err);
551}
552
553/*
554 * The specified user file is really a directory.  So, let's make sure that
555 * it is created in the RCS source repository, and that the user's directory
556 * is updated to include a CVS directory.
557 *
558 * Returns 1 on failure, 0 on success.
559 */
560static int
561add_directory (finfo)
562    struct file_info *finfo;
563{
564    char *repository = finfo->repository;
565    List *entries = finfo->entries;
566    char *dir = finfo->file;
567
568    char *rcsdir = NULL;
569    struct saved_cwd cwd;
570    char *message = NULL;
571    char *tag, *date;
572    int nonbranch;
573    char *attrs;
574
575    if (strchr (dir, '/') != NULL)
576    {
577	/* "Can't happen".  */
578	error (0, 0,
579	       "directory %s not added; must be a direct sub-directory", dir);
580	return (1);
581    }
582    if (fncmp (dir, CVSADM) == 0)
583    {
584	error (0, 0, "cannot add a `%s' directory", CVSADM);
585	return (1);
586    }
587
588    /* before we do anything else, see if we have any per-directory tags */
589    ParseTag (&tag, &date, &nonbranch);
590
591    /* Remember the default attributes from this directory, so we can apply
592       them to the new directory.  */
593    fileattr_startdir (repository);
594    attrs = fileattr_getall (NULL);
595    fileattr_free ();
596
597    /* now, remember where we were, so we can get back */
598    if (save_cwd (&cwd))
599	return (1);
600    if ( CVS_CHDIR (dir) < 0)
601    {
602	error (0, errno, "cannot chdir to %s", finfo->fullname);
603	return (1);
604    }
605#ifdef SERVER_SUPPORT
606    if (!server_active && isfile (CVSADM))
607#else
608    if (isfile (CVSADM))
609#endif
610    {
611	error (0, 0, "%s/%s already exists", finfo->fullname, CVSADM);
612	goto out;
613    }
614
615    rcsdir = xmalloc (strlen (repository) + strlen (dir) + 5);
616    sprintf (rcsdir, "%s/%s", repository, dir);
617    if (isfile (rcsdir) && !isdir (rcsdir))
618    {
619	error (0, 0, "%s is not a directory; %s not added", rcsdir,
620	       finfo->fullname);
621	goto out;
622    }
623
624    /* setup the log message */
625    message = xmalloc (strlen (rcsdir)
626		       + 80
627		       + (tag == NULL ? 0 : strlen (tag) + 80)
628		       + (date == NULL ? 0 : strlen (date) + 80));
629    (void) sprintf (message, "Directory %s added to the repository\n", rcsdir);
630    if (tag)
631    {
632	(void) strcat (message, "--> Using per-directory sticky tag `");
633	(void) strcat (message, tag);
634	(void) strcat (message, "'\n");
635    }
636    if (date)
637    {
638	(void) strcat (message, "--> Using per-directory sticky date `");
639	(void) strcat (message, date);
640	(void) strcat (message, "'\n");
641    }
642
643    if (!isdir (rcsdir))
644    {
645	mode_t omask;
646	Node *p;
647	List *ulist;
648	struct logfile_info *li;
649
650	/* There used to be some code here which would prompt for
651	   whether to add the directory.  The details of that code had
652	   bitrotted, but more to the point it can't work
653	   client/server, doesn't ask in the right way for GUIs, etc.
654	   A better way of making it harder to accidentally add
655	   directories would be to have to add and commit directories
656	   like for files.  The code was #if 0'd at least since CVS 1.5.  */
657
658	if (!noexec)
659	{
660	    omask = umask (cvsumask);
661	    if (CVS_MKDIR (rcsdir, 0777) < 0)
662	    {
663		error (0, errno, "cannot mkdir %s", rcsdir);
664		(void) umask (omask);
665		goto out;
666	    }
667	    (void) umask (omask);
668	}
669
670	/* Now set the default file attributes to the ones we inherited
671	   from the parent directory.  */
672	fileattr_startdir (rcsdir);
673	fileattr_setall (NULL, attrs);
674	fileattr_write ();
675	fileattr_free ();
676	if (attrs != NULL)
677	    free (attrs);
678
679	/*
680	 * Set up an update list with a single title node for Update_Logfile
681	 */
682	ulist = getlist ();
683	p = getnode ();
684	p->type = UPDATE;
685	p->delproc = update_delproc;
686	p->key = xstrdup ("- New directory");
687	li = (struct logfile_info *) xmalloc (sizeof (struct logfile_info));
688	li->type = T_TITLE;
689	li->tag = xstrdup (tag);
690	li->rev_old = li->rev_new = NULL;
691	p->data = (char *) li;
692	(void) addnode (ulist, p);
693	Update_Logfile (rcsdir, message, (FILE *) NULL, ulist);
694	dellist (&ulist);
695    }
696
697#ifdef SERVER_SUPPORT
698    if (!server_active)
699	Create_Admin (".", finfo->fullname, rcsdir, tag, date, nonbranch, 0);
700#else
701    Create_Admin (".", finfo->fullname, rcsdir, tag, date, nonbranch, 0);
702#endif
703    if (tag)
704	free (tag);
705    if (date)
706	free (date);
707
708    if (restore_cwd (&cwd, NULL))
709	error_exit ();
710    free_cwd (&cwd);
711
712    Subdir_Register (entries, (char *) NULL, dir);
713
714    cvs_output (message, 0);
715
716    free (rcsdir);
717    free (message);
718
719    return (0);
720
721out:
722    if (restore_cwd (&cwd, NULL))
723	error_exit ();
724    free_cwd (&cwd);
725    if (rcsdir != NULL)
726	free (rcsdir);
727    return (0);
728}
729
730/*
731 * Builds an entry for a new file and sets up "CVS/file",[pt] by
732 * interrogating the user.  Returns non-zero on error.
733 */
734static int
735build_entry (repository, user, options, message, entries, tag)
736    char *repository;
737    char *user;
738    char *options;
739    char *message;
740    List *entries;
741    char *tag;
742{
743    char *fname;
744    char *line;
745    FILE *fp;
746
747    if (noexec)
748	return (0);
749
750    /*
751     * The requested log is read directly from the user and stored in the
752     * file user,t.  If the "message" argument is set, use it as the
753     * initial creation log (which typically describes the file).
754     */
755    fname = xmalloc (strlen (user) + 80);
756    (void) sprintf (fname, "%s/%s%s", CVSADM, user, CVSEXT_LOG);
757    fp = open_file (fname, "w+");
758    if (message && fputs (message, fp) == EOF)
759	    error (1, errno, "cannot write to %s", fname);
760    if (fclose(fp) == EOF)
761        error(1, errno, "cannot close %s", fname);
762    free (fname);
763
764    /*
765     * Create the entry now, since this allows the user to interrupt us above
766     * without needing to clean anything up (well, we could clean up the
767     * ,t file, but who cares).
768     */
769    line = xmalloc (strlen (user) + 20);
770    (void) sprintf (line, "Initial %s", user);
771    Register (entries, user, "0", line, options, tag, (char *) 0, (char *) 0);
772    free (line);
773    return (0);
774}
775