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