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