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 * Tag and Rtag
9 *
10 * Add or delete a symbolic name to an RCS file, or a collection of RCS files.
11 * Tag uses the checked out revision in the current directory, rtag uses
12 * the modules database, if necessary.
13 */
14
15#include "cvs.h"
16#include "savecwd.h"
17
18static int rtag_proc PROTO((int argc, char **argv, char *xwhere,
19		      char *mwhere, char *mfile, int shorten,
20		      int local_specified, char *mname, char *msg));
21static int check_fileproc PROTO ((void *callerdat, struct file_info *finfo));
22static int check_filesdoneproc PROTO ((void *callerdat, int err,
23				       char *repos, char *update_dir,
24				       List *entries));
25static int pretag_proc PROTO((char *repository, char *filter));
26static void masterlist_delproc PROTO((Node *p));
27static void tag_delproc PROTO((Node *p));
28static int pretag_list_proc PROTO((Node *p, void *closure));
29
30static Dtype tag_dirproc PROTO ((void *callerdat, char *dir,
31				 char *repos, char *update_dir,
32				 List *entries));
33static int rtag_fileproc PROTO ((void *callerdat, struct file_info *finfo));
34static int rtag_delete PROTO((RCSNode *rcsfile));
35static int tag_fileproc PROTO ((void *callerdat, struct file_info *finfo));
36
37static char *numtag;			/* specific revision to tag */
38static int numtag_validated = 0;
39static char *date = NULL;
40static char *symtag;			/* tag to add or delete */
41static int delete_flag;			/* adding a tag by default */
42static int branch_mode;			/* make an automagic "branch" tag */
43static int local;			/* recursive by default */
44static int force_tag_match = 1;		/* force tag to match by default */
45static int force_tag_move;		/* don't force tag to move by default */
46static int check_uptodate;		/* no uptodate-check by default */
47static int attic_too;			/* remove tag from Attic files */
48static int is_rtag;
49
50struct tag_info
51{
52    Ctype status;
53    char *rev;
54    char *tag;
55    char *options;
56};
57
58struct master_lists
59{
60    List *tlist;
61};
62
63static List *mtlist;
64static List *tlist;
65
66static const char rtag_opts[] = "+abdFflnQqRr:D:";
67static const char *const rtag_usage[] =
68{
69    "Usage: %s %s [-abdFflnR] [-r rev|-D date] tag modules...\n",
70    "\t-a\tClear tag from removed files that would not otherwise be tagged.\n",
71    "\t-b\tMake the tag a \"branch\" tag, allowing concurrent development.\n",
72    "\t-d\tDelete the given tag.\n",
73    "\t-F\tMove tag if it already exists.\n",
74    "\t-f\tForce a head revision match if tag/date not found.\n",
75    "\t-l\tLocal directory only, not recursive.\n",
76    "\t-n\tNo execution of 'tag program'.\n",
77    "\t-R\tProcess directories recursively.\n",
78    "\t-r rev\tExisting revision/tag.\n",
79    "\t-D\tExisting date.\n",
80    "(Specify the --help global option for a list of other help options)\n",
81    NULL
82};
83
84static const char tag_opts[] = "+bcdFflQqRr:D:";
85static const char *const tag_usage[] =
86{
87    "Usage: %s %s [-bcdFflR] [-r rev|-D date] tag [files...]\n",
88    "\t-b\tMake the tag a \"branch\" tag, allowing concurrent development.\n",
89    "\t-c\tCheck that working files are unmodified.\n",
90    "\t-d\tDelete the given tag.\n",
91    "\t-F\tMove tag if it already exists.\n",
92    "\t-f\tForce a head revision match if tag/date not found.\n",
93    "\t-l\tLocal directory only, not recursive.\n",
94    "\t-R\tProcess directories recursively.\n",
95    "\t-r rev\tExisting revision/tag.\n",
96    "\t-D\tExisting date.\n",
97    "(Specify the --help global option for a list of other help options)\n",
98    NULL
99};
100
101int
102cvstag (argc, argv)
103    int argc;
104    char **argv;
105{
106    int c;
107    int err = 0;
108    int run_module_prog = 1;
109
110    is_rtag = (strcmp (command_name, "rtag") == 0);
111
112    if (argc == -1)
113	usage (is_rtag ? rtag_usage : tag_usage);
114
115    optind = 0;
116    while ((c = getopt (argc, argv, is_rtag ? rtag_opts : tag_opts)) != -1)
117    {
118	switch (c)
119	{
120	    case 'a':
121		attic_too = 1;
122		break;
123	    case 'b':
124		branch_mode = 1;
125		break;
126	    case 'c':
127		check_uptodate = 1;
128		break;
129	    case 'd':
130		delete_flag = 1;
131		break;
132            case 'F':
133		force_tag_move = 1;
134		break;
135	    case 'f':
136		force_tag_match = 0;
137		break;
138	    case 'l':
139		local = 1;
140		break;
141	    case 'n':
142		run_module_prog = 0;
143		break;
144	    case 'Q':
145	    case 'q':
146#ifdef SERVER_SUPPORT
147		/* The CVS 1.5 client sends these options (in addition to
148		   Global_option requests), so we must ignore them.  */
149		if (!server_active)
150#endif
151		    error (1, 0,
152			   "-q or -Q must be specified before \"%s\"",
153			   command_name);
154		break;
155	    case 'R':
156		local = 0;
157		break;
158            case 'r':
159                numtag = optarg;
160                break;
161            case 'D':
162                if (date)
163                    free (date);
164                date = Make_Date (optarg);
165                break;
166	    case '?':
167	    default:
168		usage (is_rtag ? rtag_usage : tag_usage);
169		break;
170	}
171    }
172    argc -= optind;
173    argv += optind;
174
175    if (argc < (is_rtag ? 2 : 1))
176	usage (is_rtag ? rtag_usage : tag_usage);
177    symtag = argv[0];
178    argc--;
179    argv++;
180
181    if (date && numtag)
182	error (1, 0, "-r and -D options are mutually exclusive");
183    if (delete_flag && branch_mode)
184	error (0, 0, "warning: -b ignored with -d options");
185    RCS_check_tag (symtag);
186
187#ifdef CLIENT_SUPPORT
188    if (current_parsed_root->isremote)
189    {
190	/* We're the client side.  Fire up the remote server.  */
191	start_server ();
192
193	ign_setup ();
194
195	if (attic_too)
196	    send_arg("-a");
197	if (branch_mode)
198	    send_arg("-b");
199	if (check_uptodate)
200	    send_arg("-c");
201	if (delete_flag)
202	    send_arg("-d");
203	if (force_tag_move)
204	    send_arg("-F");
205	if (!force_tag_match)
206	    send_arg ("-f");
207	if (local)
208	    send_arg("-l");
209	if (!run_module_prog)
210	    send_arg("-n");
211
212	if (numtag)
213	    option_with_arg ("-r", numtag);
214	if (date)
215	    client_senddate (date);
216
217	send_arg (symtag);
218
219	if (is_rtag)
220	{
221	    int i;
222	    for (i = 0; i < argc; ++i)
223		send_arg (argv[i]);
224	    send_to_server ("rtag\012", 0);
225	}
226	else
227	{
228
229	    send_files (argc, argv, local, 0,
230
231		    /* I think the -c case is like "cvs status", in
232		       which we really better be correct rather than
233		       being fast; it is just too confusing otherwise.  */
234			check_uptodate ? 0 : SEND_NO_CONTENTS);
235	    send_file_names (argc, argv, SEND_EXPAND_WILD);
236	    send_to_server ("tag\012", 0);
237	}
238
239        return get_responses_and_close ();
240    }
241#endif
242
243    if (is_rtag)
244    {
245	DBM *db;
246	int i;
247	db = open_module ();
248	for (i = 0; i < argc; i++)
249	{
250	    /* XXX last arg should be repository, but doesn't make sense here */
251	    history_write ('T', (delete_flag ? "D" : (numtag ? numtag :
252			   (date ? date : "A"))), symtag, argv[i], "");
253	    err += do_module (db, argv[i], TAG,
254			      delete_flag ? "Untagging" : "Tagging",
255			      rtag_proc, (char *) NULL, 0, 0, run_module_prog,
256			      0, symtag);
257	}
258	close_module (db);
259    }
260    else
261    {
262	err = rtag_proc (argc + 1, argv - 1, NULL, NULL, NULL, 0, 0, NULL,
263			 NULL);
264    }
265
266    return (err);
267}
268
269/*
270 * callback proc for doing the real work of tagging
271 */
272/* ARGSUSED */
273static int
274rtag_proc (argc, argv, xwhere, mwhere, mfile, shorten, local_specified,
275	   mname, msg)
276    int argc;
277    char **argv;
278    char *xwhere;
279    char *mwhere;
280    char *mfile;
281    int shorten;
282    int local_specified;
283    char *mname;
284    char *msg;
285{
286    /* Begin section which is identical to patch_proc--should this
287       be abstracted out somehow?  */
288    char *myargv[2];
289    int err = 0;
290    int which;
291    char *repository;
292    char *where;
293
294    if (is_rtag)
295    {
296	repository = xmalloc (strlen (current_parsed_root->directory) + strlen (argv[0])
297			      + (mfile == NULL ? 0 : strlen (mfile) + 1) + 2);
298	(void) sprintf (repository, "%s/%s", current_parsed_root->directory, argv[0]);
299	where = xmalloc (strlen (argv[0]) + (mfile == NULL ? 0 : strlen (mfile) + 1)
300			 + 1);
301	(void) strcpy (where, argv[0]);
302
303	/* if mfile isn't null, we need to set up to do only part of the module */
304	if (mfile != NULL)
305	{
306	    char *cp;
307	    char *path;
308
309	    /* if the portion of the module is a path, put the dir part on repos */
310	    if ((cp = strrchr (mfile, '/')) != NULL)
311	    {
312		*cp = '\0';
313		(void) strcat (repository, "/");
314		(void) strcat (repository, mfile);
315		(void) strcat (where, "/");
316		(void) strcat (where, mfile);
317		mfile = cp + 1;
318	    }
319
320	    /* take care of the rest */
321	    path = xmalloc (strlen (repository) + strlen (mfile) + 5);
322	    (void) sprintf (path, "%s/%s", repository, mfile);
323	    if (isdir (path))
324	    {
325		/* directory means repository gets the dir tacked on */
326		(void) strcpy (repository, path);
327		(void) strcat (where, "/");
328		(void) strcat (where, mfile);
329	    }
330	    else
331	    {
332		myargv[0] = argv[0];
333		myargv[1] = mfile;
334		argc = 2;
335		argv = myargv;
336	    }
337	    free (path);
338	}
339
340	/* cd to the starting repository */
341	if ( CVS_CHDIR (repository) < 0)
342	{
343	    error (0, errno, "cannot chdir to %s", repository);
344	    free (repository);
345	    return (1);
346	}
347	free (repository);
348	/* End section which is identical to patch_proc.  */
349
350	if (delete_flag || attic_too || (force_tag_match && numtag))
351	    which = W_REPOS | W_ATTIC;
352	else
353	    which = W_REPOS;
354	repository = NULL;
355    }
356    else
357    {
358        where = NULL;
359        which = W_LOCAL;
360        repository = "";
361    }
362
363    if (numtag != NULL && !numtag_validated)
364    {
365	tag_check_valid (numtag, argc - 1, argv + 1, local, 0, repository);
366	numtag_validated = 1;
367    }
368
369    /* check to make sure they are authorized to tag all the
370       specified files in the repository */
371
372    mtlist = getlist();
373    err = start_recursion (check_fileproc, check_filesdoneproc,
374                           (DIRENTPROC) NULL, (DIRLEAVEPROC) NULL, NULL,
375                           argc - 1, argv + 1, local, which, 0, 1,
376                           where, 1);
377
378    if (err)
379    {
380       error (1, 0, "correct the above errors first!");
381    }
382
383    /* It would be nice to provide consistency with respect to
384       commits; however CVS lacks the infrastructure to do that (see
385       Concurrency in cvs.texinfo and comment in do_recursion).  We
386       do need to ensure that the RCS file info that gets read and
387       cached in do_recursion isn't stale by the time we get around
388       to using it to rewrite the RCS file in the callback, and this
389       is the easiest way to accomplish that.  */
390    lock_tree_for_write (argc - 1, argv + 1, local, which, 0);
391
392    /* start the recursion processor */
393    err = start_recursion (is_rtag ? rtag_fileproc : tag_fileproc,
394			   (FILESDONEPROC) NULL, tag_dirproc,
395			   (DIRLEAVEPROC) NULL, NULL, argc - 1, argv + 1,
396			   local, which, 0, 0, where, 1);
397    Lock_Cleanup ();
398    dellist (&mtlist);
399    if (where != NULL)
400	free (where);
401    return (err);
402}
403
404/* check file that is to be tagged */
405/* All we do here is add it to our list */
406
407static int
408check_fileproc (callerdat, finfo)
409    void *callerdat;
410    struct file_info *finfo;
411{
412    char *xdir;
413    Node *p;
414    Vers_TS *vers;
415
416    if (check_uptodate)
417    {
418	Ctype status = Classify_File (finfo, (char *) NULL, (char *) NULL,
419				      (char *) NULL, 1, 0, &vers, 0);
420	if ((status != T_UPTODATE) && (status != T_CHECKOUT) &&
421	    (status != T_PATCH))
422	{
423	    error (0, 0, "%s is locally modified", finfo->fullname);
424	    freevers_ts (&vers);
425	    return (1);
426	}
427    }
428    else
429	vers = Version_TS (finfo, NULL, NULL, NULL, 0, 0);
430
431    if (finfo->update_dir[0] == '\0')
432	xdir = ".";
433    else
434	xdir = finfo->update_dir;
435    if ((p = findnode (mtlist, xdir)) != NULL)
436    {
437	tlist = ((struct master_lists *) p->data)->tlist;
438    }
439    else
440    {
441	struct master_lists *ml;
442
443	tlist = getlist ();
444	p = getnode ();
445	p->key = xstrdup (xdir);
446	p->type = UPDATE;
447	ml = (struct master_lists *)
448	    xmalloc (sizeof (struct master_lists));
449	ml->tlist = tlist;
450	p->data = (char *) ml;
451	p->delproc = masterlist_delproc;
452	(void) addnode (mtlist, p);
453    }
454    /* do tlist */
455    p = getnode ();
456    p->key = xstrdup (finfo->file);
457    p->type = UPDATE;
458    p->delproc = tag_delproc;
459    if (vers->srcfile == NULL)
460    {
461        if (!really_quiet)
462	    error (0, 0, "nothing known about %s", finfo->file);
463	freevers_ts (&vers);
464	freenode (p);
465	return (1);
466    }
467
468    /* Here we duplicate the calculation in tag_fileproc about which
469       version we are going to tag.  There probably are some subtle races
470       (e.g. numtag is "foo" which gets moved between here and
471       tag_fileproc).  */
472    if (!is_rtag && numtag == NULL && date == NULL)
473	p->data = xstrdup (vers->vn_user);
474    else
475	p->data = RCS_getversion (vers->srcfile, numtag, date,
476				  force_tag_match, NULL);
477
478    if (p->data != NULL)
479    {
480        int addit = 1;
481        char *oversion;
482
483        oversion = RCS_getversion (vers->srcfile, symtag, (char *) NULL, 1,
484				   (int *) NULL);
485        if (oversion == NULL)
486        {
487            if (delete_flag)
488            {
489		/* Deleting a tag which did not exist is a noop and
490		   should not be logged.  */
491                addit = 0;
492            }
493        }
494	else if (delete_flag)
495	{
496	    free (p->data);
497	    p->data = xstrdup (oversion);
498	}
499        else if (strcmp(oversion, p->data) == 0)
500        {
501            addit = 0;
502        }
503        else if (!force_tag_move)
504        {
505            addit = 0;
506        }
507        if (oversion != NULL)
508        {
509            free(oversion);
510        }
511        if (!addit)
512        {
513            free(p->data);
514            p->data = NULL;
515        }
516    }
517    freevers_ts (&vers);
518    (void) addnode (tlist, p);
519    return (0);
520}
521
522static int
523check_filesdoneproc (callerdat, err, repos, update_dir, entries)
524    void *callerdat;
525    int err;
526    char *repos;
527    char *update_dir;
528    List *entries;
529{
530    int n;
531    Node *p;
532
533    p = findnode(mtlist, update_dir);
534    if (p != NULL)
535    {
536        tlist = ((struct master_lists *) p->data)->tlist;
537    }
538    else
539    {
540        tlist = (List *) NULL;
541    }
542    if ((tlist == NULL) || (tlist->list->next == tlist->list))
543    {
544        return (err);
545    }
546    if ((n = Parse_Info(CVSROOTADM_TAGINFO, repos, pretag_proc, 1)) > 0)
547    {
548        error (0, 0, "Pre-tag check failed");
549        err += n;
550    }
551    return (err);
552}
553
554static int
555pretag_proc(repository, filter)
556    char *repository;
557    char *filter;
558{
559    if (filter[0] == '/')
560    {
561        char *s, *cp;
562
563        s = xstrdup(filter);
564        for (cp=s; *cp; cp++)
565        {
566            if (isspace ((unsigned char) *cp))
567            {
568                *cp = '\0';
569                break;
570            }
571        }
572        if (!isfile(s))
573        {
574            error (0, errno, "cannot find pre-tag filter '%s'", s);
575            free(s);
576            return (1);
577        }
578        free(s);
579    }
580    run_setup (filter);
581    run_arg (symtag);
582    run_arg (delete_flag ? "del" : force_tag_move ? "mov" : "add");
583    run_arg (repository);
584    walklist(tlist, pretag_list_proc, NULL);
585    return (run_exec (RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL));
586}
587
588static void
589masterlist_delproc(p)
590    Node *p;
591{
592    struct master_lists *ml;
593
594    ml = (struct master_lists *)p->data;
595    dellist(&ml->tlist);
596    free(ml);
597    return;
598}
599
600static void
601tag_delproc(p)
602    Node *p;
603{
604    if (p->data != NULL)
605    {
606        free(p->data);
607        p->data = NULL;
608    }
609    return;
610}
611
612static int
613pretag_list_proc(p, closure)
614    Node *p;
615    void *closure;
616{
617    if (p->data != NULL)
618    {
619        run_arg(p->key);
620        run_arg(p->data);
621    }
622    return (0);
623}
624
625
626/*
627 * Called to rtag a particular file, as appropriate with the options that were
628 * set above.
629 */
630/* ARGSUSED */
631static int
632rtag_fileproc (callerdat, finfo)
633    void *callerdat;
634    struct file_info *finfo;
635{
636    RCSNode *rcsfile;
637    char *version, *rev;
638    int retcode = 0;
639
640    /* find the parsed RCS data */
641    if ((rcsfile = finfo->rcs) == NULL)
642	return (1);
643
644    /*
645     * For tagging an RCS file which is a symbolic link, you'd best be
646     * running with RCS 5.6, since it knows how to handle symbolic links
647     * correctly without breaking your link!
648     */
649
650    if (delete_flag)
651	return (rtag_delete (rcsfile));
652
653    /*
654     * If we get here, we are adding a tag.  But, if -a was specified, we
655     * need to check to see if a -r or -D option was specified.  If neither
656     * was specified and the file is in the Attic, remove the tag.
657     */
658    if (attic_too && (!numtag && !date))
659    {
660	if ((rcsfile->flags & VALID) && (rcsfile->flags & INATTIC))
661	    return (rtag_delete (rcsfile));
662    }
663
664    version = RCS_getversion (rcsfile, numtag, date, force_tag_match,
665			      (int *) NULL);
666    if (version == NULL)
667    {
668	/* If -a specified, clean up any old tags */
669	if (attic_too)
670	    (void) rtag_delete (rcsfile);
671
672	if (!quiet && !force_tag_match)
673	{
674	    error (0, 0, "cannot find tag `%s' in `%s'",
675		   numtag ? numtag : "head", rcsfile->path);
676	    return (1);
677	}
678	return (0);
679    }
680    if (numtag
681	&& isdigit ((unsigned char) *numtag)
682	&& strcmp (numtag, version) != 0)
683    {
684
685	/*
686	 * We didn't find a match for the numeric tag that was specified, but
687	 * that's OK.  just pass the numeric tag on to rcs, to be tagged as
688	 * specified.  Could get here if one tried to tag "1.1.1" and there
689	 * was a 1.1.1 branch with some head revision.  In this case, we want
690	 * the tag to reference "1.1.1" and not the revision at the head of
691	 * the branch.  Use a symbolic tag for that.
692	 */
693	rev = branch_mode ? RCS_magicrev (rcsfile, version) : numtag;
694	retcode = RCS_settag(rcsfile, symtag, numtag);
695	if (retcode == 0)
696	    RCS_rewrite (rcsfile, NULL, NULL);
697    }
698    else
699    {
700	char *oversion;
701
702	/*
703	 * As an enhancement for the case where a tag is being re-applied to
704	 * a large body of a module, make one extra call to RCS_getversion to
705	 * see if the tag is already set in the RCS file.  If so, check to
706	 * see if it needs to be moved.  If not, do nothing.  This will
707	 * likely save a lot of time when simply moving the tag to the
708	 * "current" head revisions of a module -- which I have found to be a
709	 * typical tagging operation.
710	 */
711	rev = branch_mode ? RCS_magicrev (rcsfile, version) : version;
712	oversion = RCS_getversion (rcsfile, symtag, (char *) NULL, 1,
713				   (int *) NULL);
714	if (oversion != NULL)
715	{
716	    int isbranch = RCS_nodeisbranch (finfo->rcs, symtag);
717
718	    /*
719	     * if versions the same and neither old or new are branches don't
720	     * have to do anything
721	     */
722	    if (strcmp (version, oversion) == 0 && !branch_mode && !isbranch)
723	    {
724		free (oversion);
725		free (version);
726		return (0);
727	    }
728
729	    if (!force_tag_move)
730	    {
731		/* we're NOT going to move the tag */
732		(void) printf ("W %s", finfo->fullname);
733
734		(void) printf (" : %s already exists on %s %s",
735			       symtag, isbranch ? "branch" : "version",
736			       oversion);
737		(void) printf (" : NOT MOVING tag to %s %s\n",
738			       branch_mode ? "branch" : "version", rev);
739		free (oversion);
740		free (version);
741		return (0);
742	    }
743	    free (oversion);
744	}
745	retcode = RCS_settag(rcsfile, symtag, rev);
746	if (retcode == 0)
747	    RCS_rewrite (rcsfile, NULL, NULL);
748    }
749
750    if (retcode != 0)
751    {
752	error (1, retcode == -1 ? errno : 0,
753	       "failed to set tag `%s' to revision `%s' in `%s'",
754	       symtag, rev, rcsfile->path);
755        if (branch_mode)
756	    free (rev);
757        free (version);
758        return (1);
759    }
760    if (branch_mode)
761	free (rev);
762    free (version);
763    return (0);
764}
765
766/*
767 * If -d is specified, "force_tag_match" is set, so that this call to
768 * RCS_getversion() will return a NULL version string if the symbolic
769 * tag does not exist in the RCS file.
770 *
771 * If the -r flag was used, numtag is set, and we only delete the
772 * symtag from files that have numtag.
773 *
774 * This is done here because it's MUCH faster than just blindly calling
775 * "rcs" to remove the tag... trust me.
776 */
777static int
778rtag_delete (rcsfile)
779    RCSNode *rcsfile;
780{
781    char *version;
782    int retcode;
783
784    if (numtag)
785    {
786	version = RCS_getversion (rcsfile, numtag, (char *) NULL, 1,
787				  (int *) NULL);
788	if (version == NULL)
789	    return (0);
790	free (version);
791    }
792
793    version = RCS_getversion (rcsfile, symtag, (char *) NULL, 1,
794			      (int *) NULL);
795    if (version == NULL)
796	return (0);
797    free (version);
798
799    if ((retcode = RCS_deltag(rcsfile, symtag)) != 0)
800    {
801	if (!quiet)
802	    error (0, retcode == -1 ? errno : 0,
803		   "failed to remove tag `%s' from `%s'", symtag,
804		   rcsfile->path);
805	return (1);
806    }
807    RCS_rewrite (rcsfile, NULL, NULL);
808    return (0);
809}
810
811
812/*
813 * Called to tag a particular file (the currently checked out version is
814 * tagged with the specified tag - or the specified tag is deleted).
815 */
816/* ARGSUSED */
817static int
818tag_fileproc (callerdat, finfo)
819    void *callerdat;
820    struct file_info *finfo;
821{
822    char *version, *oversion;
823    char *nversion = NULL;
824    char *rev;
825    Vers_TS *vers;
826    int retcode = 0;
827
828    vers = Version_TS (finfo, NULL, NULL, NULL, 0, 0);
829
830    if ((numtag != NULL) || (date != NULL))
831    {
832        nversion = RCS_getversion(vers->srcfile,
833                                  numtag,
834                                  date,
835                                  force_tag_match,
836				  (int *) NULL);
837        if (nversion == NULL)
838        {
839	    freevers_ts (&vers);
840            return (0);
841        }
842    }
843    if (delete_flag)
844    {
845
846	/*
847	 * If -d is specified, "force_tag_match" is set, so that this call to
848	 * RCS_getversion() will return a NULL version string if the symbolic
849	 * tag does not exist in the RCS file.
850	 *
851	 * This is done here because it's MUCH faster than just blindly calling
852	 * "rcs" to remove the tag... trust me.
853	 */
854
855	version = RCS_getversion (vers->srcfile, symtag, (char *) NULL, 1,
856				  (int *) NULL);
857	if (version == NULL || vers->srcfile == NULL)
858	{
859	    freevers_ts (&vers);
860	    return (0);
861	}
862	free (version);
863
864	if ((retcode = RCS_deltag(vers->srcfile, symtag)) != 0)
865	{
866	    if (!quiet)
867		error (0, retcode == -1 ? errno : 0,
868		       "failed to remove tag %s from %s", symtag,
869		       vers->srcfile->path);
870	    freevers_ts (&vers);
871	    return (1);
872	}
873	RCS_rewrite (vers->srcfile, NULL, NULL);
874
875	/* warm fuzzies */
876	if (!really_quiet)
877	{
878	    cvs_output ("D ", 2);
879	    cvs_output (finfo->fullname, 0);
880	    cvs_output ("\n", 1);
881	}
882
883	freevers_ts (&vers);
884	return (0);
885    }
886
887    /*
888     * If we are adding a tag, we need to know which version we have checked
889     * out and we'll tag that version.
890     */
891    if (nversion == NULL)
892    {
893        version = vers->vn_user;
894    }
895    else
896    {
897        version = nversion;
898    }
899    if (version == NULL)
900    {
901	freevers_ts (&vers);
902	return (0);
903    }
904    else if (strcmp (version, "0") == 0)
905    {
906	if (!quiet)
907	    error (0, 0, "couldn't tag added but un-commited file `%s'", finfo->file);
908	freevers_ts (&vers);
909	return (0);
910    }
911    else if (version[0] == '-')
912    {
913	if (!quiet)
914	    error (0, 0, "skipping removed but un-commited file `%s'", finfo->file);
915	freevers_ts (&vers);
916	return (0);
917    }
918    else if (vers->srcfile == NULL)
919    {
920	if (!quiet)
921	    error (0, 0, "cannot find revision control file for `%s'", finfo->file);
922	freevers_ts (&vers);
923	return (0);
924    }
925
926    /*
927     * As an enhancement for the case where a tag is being re-applied to a
928     * large number of files, make one extra call to RCS_getversion to see
929     * if the tag is already set in the RCS file.  If so, check to see if it
930     * needs to be moved.  If not, do nothing.  This will likely save a lot of
931     * time when simply moving the tag to the "current" head revisions of a
932     * module -- which I have found to be a typical tagging operation.
933     */
934    rev = branch_mode ? RCS_magicrev (vers->srcfile, version) : version;
935    oversion = RCS_getversion (vers->srcfile, symtag, (char *) NULL, 1,
936			       (int *) NULL);
937    if (oversion != NULL)
938    {
939	int isbranch = RCS_nodeisbranch (finfo->rcs, symtag);
940
941	/*
942	 * if versions the same and neither old or new are branches don't have
943	 * to do anything
944	 */
945	if (strcmp (version, oversion) == 0 && !branch_mode && !isbranch)
946	{
947	    free (oversion);
948	    if (branch_mode)
949		free (rev);
950	    freevers_ts (&vers);
951	    return (0);
952	}
953
954	if (!force_tag_move)
955	{
956	    /* we're NOT going to move the tag */
957	    cvs_output ("W ", 2);
958	    cvs_output (finfo->fullname, 0);
959	    cvs_output (" : ", 0);
960	    cvs_output (symtag, 0);
961	    cvs_output (" already exists on ", 0);
962	    cvs_output (isbranch ? "branch" : "version", 0);
963	    cvs_output (" ", 0);
964	    cvs_output (oversion, 0);
965	    cvs_output (" : NOT MOVING tag to ", 0);
966	    cvs_output (branch_mode ? "branch" : "version", 0);
967	    cvs_output (" ", 0);
968	    cvs_output (rev, 0);
969	    cvs_output ("\n", 1);
970	    free (oversion);
971	    if (branch_mode)
972		free (rev);
973	    freevers_ts (&vers);
974	    return (0);
975	}
976	free (oversion);
977    }
978
979    if ((retcode = RCS_settag(vers->srcfile, symtag, rev)) != 0)
980    {
981	error (1, retcode == -1 ? errno : 0,
982	       "failed to set tag %s to revision %s in %s",
983	       symtag, rev, vers->srcfile->path);
984	if (branch_mode)
985	    free (rev);
986	freevers_ts (&vers);
987	return (1);
988    }
989    if (branch_mode)
990	free (rev);
991    RCS_rewrite (vers->srcfile, NULL, NULL);
992
993    /* more warm fuzzies */
994    if (!really_quiet)
995    {
996	cvs_output ("T ", 2);
997	cvs_output (finfo->fullname, 0);
998	cvs_output ("\n", 1);
999    }
1000
1001    if (nversion != NULL)
1002    {
1003        free (nversion);
1004    }
1005    freevers_ts (&vers);
1006    return (0);
1007}
1008
1009/*
1010 * Print a warm fuzzy message
1011 */
1012/* ARGSUSED */
1013static Dtype
1014tag_dirproc (callerdat, dir, repos, update_dir, entries)
1015    void *callerdat;
1016    char *dir;
1017    char *repos;
1018    char *update_dir;
1019    List *entries;
1020{
1021
1022    if (ignore_directory (update_dir))
1023    {
1024	/* print the warm fuzzy message */
1025	if (!quiet)
1026	  error (0, 0, "Ignoring %s", update_dir);
1027        return R_SKIP_ALL;
1028    }
1029
1030    if (!quiet)
1031	error (0, 0, "%s %s", delete_flag ? "Untagging" : "Tagging", update_dir);
1032    return (R_PROCESS);
1033}
1034
1035/* Code relating to the val-tags file.  Note that this file has no way
1036   of knowing when a tag has been deleted.  The problem is that there
1037   is no way of knowing whether a tag still exists somewhere, when we
1038   delete it some places.  Using per-directory val-tags files (in
1039   CVSREP) might be better, but that might slow down the process of
1040   verifying that a tag is correct (maybe not, for the likely cases,
1041   if carefully done), and/or be harder to implement correctly.  */
1042
1043struct val_args {
1044    char *name;
1045    int found;
1046};
1047
1048static int val_fileproc PROTO ((void *callerdat, struct file_info *finfo));
1049
1050static int
1051val_fileproc (callerdat, finfo)
1052    void *callerdat;
1053    struct file_info *finfo;
1054{
1055    RCSNode *rcsdata;
1056    struct val_args *args = (struct val_args *)callerdat;
1057    char *tag;
1058
1059    if ((rcsdata = finfo->rcs) == NULL)
1060	/* Not sure this can happen, after all we passed only
1061	   W_REPOS | W_ATTIC.  */
1062	return 0;
1063
1064    tag = RCS_gettag (rcsdata, args->name, 1, (int *) NULL);
1065    if (tag != NULL)
1066    {
1067	/* FIXME: should find out a way to stop the search at this point.  */
1068	args->found = 1;
1069	free (tag);
1070    }
1071    return 0;
1072}
1073
1074static Dtype val_direntproc PROTO ((void *, char *, char *, char *, List *));
1075
1076static Dtype
1077val_direntproc (callerdat, dir, repository, update_dir, entries)
1078    void *callerdat;
1079    char *dir;
1080    char *repository;
1081    char *update_dir;
1082    List *entries;
1083{
1084    /* This is not quite right--it doesn't get right the case of "cvs
1085       update -d -r foobar" where foobar is a tag which exists only in
1086       files in a directory which does not exist yet, but which is
1087       about to be created.  */
1088    if (isdir (dir))
1089	return R_PROCESS;
1090    return R_SKIP_ALL;
1091}
1092
1093/* Check to see whether NAME is a valid tag.  If so, return.  If not
1094   print an error message and exit.  ARGC, ARGV, LOCAL, and AFLAG specify
1095   which files we will be operating on.
1096
1097   REPOSITORY is the repository if we need to cd into it, or NULL if
1098   we are already there, or "" if we should do a W_LOCAL recursion.
1099   Sorry for three cases, but the "" case is needed in case the
1100   working directories come from diverse parts of the repository, the
1101   NULL case avoids an unneccesary chdir, and the non-NULL, non-""
1102   case is needed for checkout, where we don't want to chdir if the
1103   tag is found in CVSROOTADM_VALTAGS, but there is not (yet) any
1104   local directory.  */
1105void
1106tag_check_valid (name, argc, argv, local, aflag, repository)
1107    char *name;
1108    int argc;
1109    char **argv;
1110    int local;
1111    int aflag;
1112    char *repository;
1113{
1114    DBM *db;
1115    char *valtags_filename;
1116    int err;
1117    int nowrite = 0;
1118    datum mytag;
1119    struct val_args the_val_args;
1120    struct saved_cwd cwd;
1121    int which;
1122
1123    /* Numeric tags require only a syntactic check.  */
1124    if (isdigit ((unsigned char) name[0]))
1125    {
1126	char *p;
1127	for (p = name; *p != '\0'; ++p)
1128	{
1129	    if (!(isdigit ((unsigned char) *p) || *p == '.'))
1130		error (1, 0, "\
1131Numeric tag %s contains characters other than digits and '.'", name);
1132	}
1133	return;
1134    }
1135
1136    /* Special tags are always valid.  */
1137    if (strcmp (name, TAG_BASE) == 0
1138	|| strcmp (name, TAG_HEAD) == 0)
1139	return;
1140
1141    /* FIXME: This routine doesn't seem to do any locking whatsoever
1142       (and it is called from places which don't have locks in place).
1143       If two processes try to write val-tags at the same time, it would
1144       seem like we are in trouble.  */
1145
1146    mytag.dptr = name;
1147    mytag.dsize = strlen (name);
1148
1149    valtags_filename = xmalloc (strlen (current_parsed_root->directory)
1150				+ sizeof CVSROOTADM
1151				+ sizeof CVSROOTADM_VALTAGS + 3);
1152    sprintf (valtags_filename, "%s/%s/%s", current_parsed_root->directory,
1153					   CVSROOTADM, CVSROOTADM_VALTAGS);
1154    db = dbm_open (valtags_filename, O_RDWR, 0666);
1155    if (db == NULL)
1156    {
1157	if (!existence_error (errno))
1158	{
1159	    error (0, errno, "warning: cannot open %s read/write",
1160		   valtags_filename);
1161	    db = dbm_open (valtags_filename, O_RDONLY, 0666);
1162	    if (db != NULL)
1163		nowrite = 1;
1164	    else if (!existence_error (errno))
1165		error (1, errno, "cannot read %s", valtags_filename);
1166	}
1167	/* If the file merely fails to exist, we just keep going and create
1168	   it later if need be.  */
1169    }
1170    if (db != NULL)
1171    {
1172	datum val;
1173
1174	val = dbm_fetch (db, mytag);
1175	if (val.dptr != NULL)
1176	{
1177	    /* Found.  The tag is valid.  */
1178	    dbm_close (db);
1179	    free (valtags_filename);
1180	    return;
1181	}
1182	/* FIXME: should check errors somehow (add dbm_error to myndbm.c?).  */
1183    }
1184
1185    /* We didn't find the tag in val-tags, so look through all the RCS files
1186       to see whether it exists there.  Yes, this is expensive, but there
1187       is no other way to cope with a tag which might have been created
1188       by an old version of CVS, from before val-tags was invented.
1189
1190       Since we need this code anyway, we also use it to create
1191       entries in val-tags in general (that is, the val-tags entry
1192       will get created the first time the tag is used, not when the
1193       tag is created).  */
1194
1195    the_val_args.name = name;
1196    the_val_args.found = 0;
1197
1198    which = W_REPOS | W_ATTIC;
1199
1200    if (repository != NULL)
1201    {
1202	if (repository[0] == '\0')
1203	    which |= W_LOCAL;
1204	else
1205	{
1206	    if (save_cwd (&cwd))
1207		error_exit ();
1208	    if ( CVS_CHDIR (repository) < 0)
1209		error (1, errno, "cannot change to %s directory", repository);
1210	}
1211    }
1212
1213    err = start_recursion (val_fileproc, (FILESDONEPROC) NULL,
1214			   val_direntproc, (DIRLEAVEPROC) NULL,
1215			   (void *)&the_val_args,
1216			   argc, argv, local, which, aflag,
1217			   1, NULL, 1);
1218    if (repository != NULL && repository[0] != '\0')
1219    {
1220	if (restore_cwd (&cwd, NULL))
1221	    exit (EXIT_FAILURE);
1222	free_cwd (&cwd);
1223    }
1224
1225    if (!the_val_args.found)
1226	error (1, 0, "no such tag %s", name);
1227    else
1228    {
1229	/* The tags is valid but not mentioned in val-tags.  Add it.  */
1230	datum value;
1231
1232	if (noexec || nowrite)
1233	{
1234	    if (db != NULL)
1235		dbm_close (db);
1236	    free (valtags_filename);
1237	    return;
1238	}
1239
1240	if (db == NULL)
1241	{
1242	    mode_t omask;
1243	    omask = umask (cvsumask);
1244	    db = dbm_open (valtags_filename, O_RDWR | O_CREAT | O_TRUNC, 0666);
1245	    (void) umask (omask);
1246
1247	    if (db == NULL)
1248	    {
1249		error (0, errno, "warning: cannot create %s", valtags_filename);
1250		free (valtags_filename);
1251		return;
1252	    }
1253	}
1254	value.dptr = "y";
1255	value.dsize = 1;
1256	if (dbm_store (db, mytag, value, DBM_REPLACE) < 0)
1257	    error (0, errno, "cannot store %s into %s", name,
1258		   valtags_filename);
1259	dbm_close (db);
1260    }
1261    free (valtags_filename);
1262}
1263
1264/*
1265 * Check whether a join tag is valid.  This is just like
1266 * tag_check_valid, but we must stop before the colon if there is one.
1267 */
1268
1269void
1270tag_check_valid_join (join_tag, argc, argv, local, aflag, repository)
1271     char *join_tag;
1272     int argc;
1273     char **argv;
1274     int local;
1275     int aflag;
1276     char *repository;
1277{
1278    char *c, *s;
1279
1280    c = xstrdup (join_tag);
1281    s = strchr (c, ':');
1282    if (s != NULL)
1283    {
1284        if (isdigit ((unsigned char) join_tag[0]))
1285	    error (1, 0,
1286		   "Numeric join tag %s may not contain a date specifier",
1287		   join_tag);
1288
1289        *s = '\0';
1290	/* hmmm...  I think it makes sense to allow -j:<date>, but
1291	 * for now this fixes a bug where CVS just spins and spins (I
1292	 * think in the RCS code) looking for a zero length tag.
1293	 */
1294	if (!*c)
1295	    error (1, 0,
1296		   "argument to join may not contain a date specifier without a tag");
1297    }
1298
1299    tag_check_valid (c, argc, argv, local, aflag, repository);
1300
1301    free (c);
1302}
1303