add.c revision 17721
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 1.4 kit.
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
30static int add_directory PROTO((char *repository, char *dir));
31static int build_entry PROTO((char *repository, char *user, char *options,
32		        char *message, List * entries, char *tag));
33
34static const char *const add_usage[] =
35{
36    "Usage: %s %s [-k rcs-kflag] [-m message] files...\n",
37    "\t-k\tUse \"rcs-kflag\" to add the file with the specified kflag.\n",
38    "\t-m\tUse \"message\" for the creation log.\n",
39    NULL
40};
41
42int
43add (argc, argv)
44    int argc;
45    char **argv;
46{
47    char *message = NULL;
48    char *user;
49    int i;
50    char *repository;
51    int c;
52    int err = 0;
53    int added_files = 0;
54    char *options = NULL;
55    List *entries;
56    Vers_TS *vers;
57
58    if (argc == 1 || argc == -1)
59	usage (add_usage);
60
61    wrap_setup ();
62
63    /* parse args */
64    optind = 1;
65    while ((c = getopt (argc, argv, "k:m:")) != -1)
66    {
67	switch (c)
68	{
69	    case 'k':
70		if (options)
71		    free (options);
72		options = RCS_check_kflag (optarg);
73		break;
74
75	    case 'm':
76		message = xstrdup (optarg);
77		break;
78	    case '?':
79	    default:
80		usage (add_usage);
81		break;
82	}
83    }
84    argc -= optind;
85    argv += optind;
86
87    if (argc <= 0)
88	usage (add_usage);
89
90    /* find the repository associated with our current dir */
91    repository = Name_Repository ((char *) NULL, (char *) NULL);
92
93#ifdef CLIENT_SUPPORT
94    if (client_active)
95      {
96	int i;
97	start_server ();
98	ign_setup ();
99	if (options) send_arg(options);
100	option_with_arg ("-m", message);
101	for (i = 0; i < argc; ++i)
102	  /* FIXME: Does this erroneously call Create_Admin in error
103	     conditions which are only detected once the server gets its
104	     hands on things?  */
105	  if (isdir (argv[i]))
106	    {
107	      char *tag;
108	      char *date;
109	      char *rcsdir = xmalloc (strlen (repository)
110				      + strlen (argv[i]) + 10);
111
112	      /* before we do anything else, see if we have any
113		 per-directory tags */
114	      ParseTag (&tag, &date);
115
116	      sprintf (rcsdir, "%s/%s", repository, argv[i]);
117
118	      Create_Admin (argv[i], argv[i], rcsdir, tag, date);
119
120	      if (tag)
121		free (tag);
122	      if (date)
123		free (date);
124	      free (rcsdir);
125	    }
126	send_file_names (argc, argv, SEND_EXPAND_WILD);
127	send_files (argc, argv, 0, 0);
128	send_to_server ("add\012", 0);
129	return get_responses_and_close ();
130      }
131#endif
132
133    entries = Entries_Open (0);
134
135    /* walk the arg list adding files/dirs */
136    for (i = 0; i < argc; i++)
137    {
138	int begin_err = err;
139	int begin_added_files = added_files;
140
141	user = argv[i];
142	strip_trailing_slashes (user);
143	if (strchr (user, '/') != NULL)
144	{
145	    error (0, 0,
146	     "cannot add files with '/' in their name; %s not added", user);
147	    err++;
148	    continue;
149	}
150
151	vers = Version_TS (repository, options, (char *) NULL, (char *) NULL,
152			   user, 0, 0, entries, (RCSNode *) NULL);
153	if (vers->vn_user == NULL)
154	{
155	    /* No entry available, ts_rcs is invalid */
156	    if (vers->vn_rcs == NULL)
157	    {
158		/* There is no RCS file either */
159		if (vers->ts_user == NULL)
160		{
161		    /* There is no user file either */
162		    error (0, 0, "nothing known about %s", user);
163		    err++;
164		}
165		else if (!isdir (user) || wrap_name_has (user, WRAP_TOCVS))
166		{
167		    /*
168		     * See if a directory exists in the repository with
169		     * the same name.  If so, blow this request off.
170		     */
171		    char dname[PATH_MAX];
172		    (void) sprintf (dname, "%s/%s", repository, user);
173		    if (isdir (dname))
174		    {
175			error (0, 0,
176			       "cannot add file `%s' since the directory",
177			       user);
178			error (0, 0, "`%s' already exists in the repository",
179			       dname);
180			error (1, 0, "illegal filename overlap");
181		    }
182
183		    /* There is a user file, so build the entry for it */
184		    if (build_entry (repository, user, vers->options,
185				     message, entries, vers->tag) != 0)
186			err++;
187		    else
188		    {
189			added_files++;
190			if (!quiet)
191			{
192			    if (vers->tag)
193				error (0, 0, "\
194scheduling %s `%s' for addition on branch `%s'",
195				       (wrap_name_has (user, WRAP_TOCVS)
196					? "wrapper"
197					: "file"),
198				       user, vers->tag);
199			    else
200			    error (0, 0, "scheduling %s `%s' for addition",
201				   (wrap_name_has (user, WRAP_TOCVS)
202				    ? "wrapper"
203				    : "file"),
204				   user);
205			}
206		    }
207		}
208	    }
209	    else if (RCS_isdead (vers->srcfile, vers->vn_rcs))
210	    {
211		if (isdir (user) && !wrap_name_has (user, WRAP_TOCVS))
212		{
213		    error (0, 0, "the directory `%s' cannot be added because a file of the", user);
214		    error (1, 0, "same name already exists in the repository.");
215		}
216		else
217		{
218		    if (vers->tag)
219			error (0, 0, "file `%s' will be added on branch `%s' from version %s",
220			       user, vers->tag, vers->vn_rcs);
221		    else
222			error (0, 0, "version %s of `%s' will be resurrected",
223			       vers->vn_rcs, user);
224		    Register (entries, user, "0", vers->ts_user, NULL,
225			      vers->tag, NULL, NULL);
226		    ++added_files;
227		}
228	    }
229	    else
230	    {
231		/*
232		 * There is an RCS file already, so somebody else must've
233		 * added it
234		 */
235		error (0, 0, "%s added independently by second party", user);
236		err++;
237	    }
238	}
239	else if (vers->vn_user[0] == '0' && vers->vn_user[1] == '\0')
240	{
241
242	    /*
243	     * An entry for a new-born file, ts_rcs is dummy, but that is
244	     * inappropriate here
245	     */
246	    error (0, 0, "%s has already been entered", user);
247	    err++;
248	}
249	else if (vers->vn_user[0] == '-')
250	{
251	    /* An entry for a removed file, ts_rcs is invalid */
252	    if (vers->ts_user == NULL)
253	    {
254		/* There is no user file (as it should be) */
255		if (vers->vn_rcs == NULL)
256		{
257
258		    /*
259		     * There is no RCS file, so somebody else must've removed
260		     * it from under us
261		     */
262		    error (0, 0,
263			   "cannot resurrect %s; RCS file removed by second party", user);
264		    err++;
265		}
266		else
267		{
268
269		    /*
270		     * There is an RCS file, so remove the "-" from the
271		     * version number and restore the file
272		     */
273		    char *tmp = xmalloc (strlen (user) + 50);
274
275		    (void) strcpy (tmp, vers->vn_user + 1);
276		    (void) strcpy (vers->vn_user, tmp);
277		    (void) sprintf (tmp, "Resurrected %s", user);
278		    Register (entries, user, vers->vn_user, tmp, vers->options,
279			      vers->tag, vers->date, vers->ts_conflict);
280		    free (tmp);
281
282		    /* XXX - bugs here; this really resurrect the head */
283		    /* Note that this depends on the Register above actually
284		       having written Entries, or else it won't really
285		       check the file out.  */
286		    if (update (2, argv + i - 1) == 0)
287		    {
288			error (0, 0, "%s, version %s, resurrected", user,
289			       vers->vn_user);
290		    }
291		    else
292		    {
293			error (0, 0, "could not resurrect %s", user);
294			err++;
295		    }
296		}
297	    }
298	    else
299	    {
300		/* The user file shouldn't be there */
301		error (0, 0, "%s should be removed and is still there (or is back again)", user);
302		err++;
303	    }
304	}
305	else
306	{
307	    /* A normal entry, ts_rcs is valid, so it must already be there */
308	    error (0, 0, "%s already exists, with version number %s", user,
309		   vers->vn_user);
310	    err++;
311	}
312	freevers_ts (&vers);
313
314	/* passed all the checks.  Go ahead and add it if its a directory */
315	if (begin_err == err
316	    && isdir (user)
317	    && !wrap_name_has (user, WRAP_TOCVS))
318	{
319	    err += add_directory (repository, user);
320	    continue;
321	}
322#ifdef SERVER_SUPPORT
323	if (server_active && begin_added_files != added_files)
324	    server_checked_in (user, ".", repository);
325#endif
326    }
327    if (added_files)
328	error (0, 0, "use 'cvs commit' to add %s permanently",
329	       (added_files == 1) ? "this file" : "these files");
330
331    Entries_Close (entries);
332
333    if (message)
334	free (message);
335
336    return (err);
337}
338
339/*
340 * The specified user file is really a directory.  So, let's make sure that
341 * it is created in the RCS source repository, and that the user's directory
342 * is updated to include a CVS directory.
343 *
344 * Returns 1 on failure, 0 on success.
345 */
346static int
347add_directory (repository, dir)
348    char *repository;
349    char *dir;
350{
351    char rcsdir[PATH_MAX];
352    struct saved_cwd cwd;
353    char message[PATH_MAX + 100];
354    char *tag, *date;
355
356    if (strchr (dir, '/') != NULL)
357    {
358	error (0, 0,
359	       "directory %s not added; must be a direct sub-directory", dir);
360	return (1);
361    }
362    if (strcmp (dir, CVSADM) == 0)
363    {
364	error (0, 0, "cannot add a `%s' directory", CVSADM);
365	return (1);
366    }
367
368    /* before we do anything else, see if we have any per-directory tags */
369    ParseTag (&tag, &date);
370
371    /* now, remember where we were, so we can get back */
372    if (save_cwd (&cwd))
373	return (1);
374    if (chdir (dir) < 0)
375    {
376	error (0, errno, "cannot chdir to %s", dir);
377	return (1);
378    }
379#ifdef SERVER_SUPPORT
380    if (!server_active && isfile (CVSADM))
381#else
382    if (isfile (CVSADM))
383#endif
384    {
385	error (0, 0, "%s/%s already exists", dir, CVSADM);
386	goto out;
387    }
388
389    (void) sprintf (rcsdir, "%s/%s", repository, dir);
390    if (isfile (rcsdir) && !isdir (rcsdir))
391    {
392	error (0, 0, "%s is not a directory; %s not added", rcsdir, dir);
393	goto out;
394    }
395
396    /* setup the log message */
397    (void) sprintf (message, "Directory %s added to the repository\n", rcsdir);
398    if (tag)
399    {
400	(void) strcat (message, "--> Using per-directory sticky tag `");
401	(void) strcat (message, tag);
402	(void) strcat (message, "'\n");
403    }
404    if (date)
405    {
406	(void) strcat (message, "--> Using per-directory sticky date `");
407	(void) strcat (message, date);
408	(void) strcat (message, "'\n");
409    }
410
411    if (!isdir (rcsdir))
412    {
413	mode_t omask;
414	Node *p;
415	List *ulist;
416
417#if 0
418	char line[MAXLINELEN];
419
420	(void) printf ("Add directory %s to the repository (y/n) [n] ? ",
421		       rcsdir);
422	(void) fflush (stdout);
423	clearerr (stdin);
424	if (fgets (line, sizeof (line), stdin) == NULL ||
425	    (line[0] != 'y' && line[0] != 'Y'))
426	{
427	    error (0, 0, "directory %s not added", rcsdir);
428	    goto out;
429	}
430#endif
431
432	omask = umask (cvsumask);
433	if (CVS_MKDIR (rcsdir, 0777) < 0)
434	{
435	    error (0, errno, "cannot mkdir %s", rcsdir);
436	    (void) umask (omask);
437	    goto out;
438	}
439	(void) umask (omask);
440
441	/*
442	 * Set up an update list with a single title node for Update_Logfile
443	 */
444	ulist = getlist ();
445	p = getnode ();
446	p->type = UPDATE;
447	p->delproc = update_delproc;
448	p->key = xstrdup ("- New directory");
449	p->data = (char *) T_TITLE;
450	(void) addnode (ulist, p);
451	Update_Logfile (rcsdir, message, (char *) NULL, (FILE *) NULL, ulist);
452	dellist (&ulist);
453    }
454
455#ifdef SERVER_SUPPORT
456    if (!server_active)
457	Create_Admin (".", dir, rcsdir, tag, date);
458#else
459    Create_Admin (".", dir, rcsdir, tag, date);
460#endif
461    if (tag)
462	free (tag);
463    if (date)
464	free (date);
465
466    (void) printf ("%s", message);
467out:
468    if (restore_cwd (&cwd, NULL))
469      exit (EXIT_FAILURE);
470    free_cwd (&cwd);
471    return (0);
472}
473
474/*
475 * Builds an entry for a new file and sets up "CVS/file",[pt] by
476 * interrogating the user.  Returns non-zero on error.
477 */
478static int
479build_entry (repository, user, options, message, entries, tag)
480    char *repository;
481    char *user;
482    char *options;
483    char *message;
484    List *entries;
485    char *tag;
486{
487    char fname[PATH_MAX];
488    char line[MAXLINELEN];
489    FILE *fp;
490
491    if (noexec)
492	return (0);
493
494    /*
495     * The requested log is read directly from the user and stored in the
496     * file user,t.  If the "message" argument is set, use it as the
497     * initial creation log (which typically describes the file).
498     */
499    (void) sprintf (fname, "%s/%s%s", CVSADM, user, CVSEXT_LOG);
500    fp = open_file (fname, "w+");
501    if (message && fputs (message, fp) == EOF)
502	    error (1, errno, "cannot write to %s", fname);
503    if (fclose(fp) == EOF)
504        error(1, errno, "cannot close %s", fname);
505
506    /*
507     * Create the entry now, since this allows the user to interrupt us above
508     * without needing to clean anything up (well, we could clean up the
509     * ,t file, but who cares).
510     */
511    (void) sprintf (line, "Initial %s", user);
512    Register (entries, user, "0", line, options, tag, (char *) 0, (char *) 0);
513    return (0);
514}
515