xref: /openbsd-src/gnu/usr.bin/cvs/src/tag.c (revision 43c1707e6f6829177cb1974ee6615ce6c1307689)
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 
18 static 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));
21 static int check_fileproc PROTO ((void *callerdat, struct file_info *finfo));
22 static int check_filesdoneproc PROTO ((void *callerdat, int err,
23 				       char *repos, char *update_dir,
24 				       List *entries));
25 static int pretag_proc PROTO((char *repository, char *filter));
26 static void masterlist_delproc PROTO((Node *p));
27 static void tag_delproc PROTO((Node *p));
28 static int pretag_list_proc PROTO((Node *p, void *closure));
29 
30 static Dtype tag_dirproc PROTO ((void *callerdat, char *dir,
31 				 char *repos, char *update_dir,
32 				 List *entries));
33 static int rtag_fileproc PROTO ((void *callerdat, struct file_info *finfo));
34 static int rtag_delete PROTO((RCSNode *rcsfile));
35 static int tag_fileproc PROTO ((void *callerdat, struct file_info *finfo));
36 
37 static char *numtag;			/* specific revision to tag */
38 static int numtag_validated = 0;
39 static char *date = NULL;
40 static char *symtag;			/* tag to add or delete */
41 static int delete_flag;			/* adding a tag by default */
42 static int branch_mode;			/* make an automagic "branch" tag */
43 static int local;			/* recursive by default */
44 static int force_tag_match = 1;		/* force tag to match by default */
45 static int force_tag_move;		/* don't force tag to move by default */
46 static int check_uptodate;		/* no uptodate-check by default */
47 static int attic_too;			/* remove tag from Attic files */
48 static int is_rtag;
49 
50 struct tag_info
51 {
52     Ctype status;
53     char *rev;
54     char *tag;
55     char *options;
56 };
57 
58 struct master_lists
59 {
60     List *tlist;
61 };
62 
63 static List *mtlist;
64 static List *tlist;
65 
66 static const char rtag_opts[] = "+abdFflnQqRr:D:";
67 static 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 
84 static const char tag_opts[] = "+bcdFflQqRr:D:";
85 static 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 
101 int
cvstag(argc,argv)102 cvstag (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 */
273 static int
rtag_proc(argc,argv,xwhere,mwhere,mfile,shorten,local_specified,mname,msg)274 rtag_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 
407 static int
check_fileproc(callerdat,finfo)408 check_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 
522 static int
check_filesdoneproc(callerdat,err,repos,update_dir,entries)523 check_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 
554 static int
pretag_proc(repository,filter)555 pretag_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 
588 static void
masterlist_delproc(p)589 masterlist_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 
600 static void
tag_delproc(p)601 tag_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 
612 static int
pretag_list_proc(p,closure)613 pretag_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 */
631 static int
rtag_fileproc(callerdat,finfo)632 rtag_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  */
777 static int
rtag_delete(rcsfile)778 rtag_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 */
817 static int
tag_fileproc(callerdat,finfo)818 tag_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 */
1013 static Dtype
tag_dirproc(callerdat,dir,repos,update_dir,entries)1014 tag_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 
1043 struct val_args {
1044     char *name;
1045     int found;
1046 };
1047 
1048 static int val_fileproc PROTO ((void *callerdat, struct file_info *finfo));
1049 
1050 static int
val_fileproc(callerdat,finfo)1051 val_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 
1074 static Dtype val_direntproc PROTO ((void *, char *, char *, char *, List *));
1075 
1076 static Dtype
val_direntproc(callerdat,dir,repository,update_dir,entries)1077 val_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.  */
1105 void
tag_check_valid(name,argc,argv,local,aflag,repository)1106 tag_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, "\
1131 Numeric 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 
1269 void
tag_check_valid_join(join_tag,argc,argv,local,aflag,repository)1270 tag_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