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