tag.c revision 17721
1193323Sed/*
2193323Sed * Copyright (c) 1992, Brian Berliner and Jeff Polk
3193323Sed * Copyright (c) 1989-1992, Brian Berliner
4193323Sed *
5193323Sed * You may distribute under the terms of the GNU General Public License as
6193323Sed * specified in the README file that comes with the CVS 1.4 kit.
7193323Sed *
8193323Sed * Tag
9193323Sed *
10193323Sed * Add or delete a symbolic name to an RCS file, or a collection of RCS files.
11193323Sed * Uses the checked out revision in the current directory.
12193323Sed */
13193323Sed
14193323Sed#include "cvs.h"
15193323Sed#include "savecwd.h"
16193323Sed
17193323Sedstatic int check_fileproc PROTO((struct file_info *finfo));
18193323Sedstatic int check_filesdoneproc PROTO((int err, char *repos, char *update_dir));
19198090Srdivackystatic int pretag_proc PROTO((char *repository, char *filter));
20193323Sedstatic void masterlist_delproc PROTO((Node *p));
21193323Sedstatic void tag_delproc PROTO((Node *p));
22193323Sedstatic int pretag_list_proc PROTO((Node *p, void *closure));
23193323Sed
24198090Srdivackystatic Dtype tag_dirproc PROTO((char *dir, char *repos, char *update_dir));
25193323Sedstatic int tag_fileproc PROTO((struct file_info *finfo));
26193323Sed
27193323Sedstatic char *numtag;
28198090Srdivackystatic char *date = NULL;
29193323Sedstatic char *symtag;
30193323Sedstatic int delete_flag;			/* adding a tag by default */
31193323Sedstatic int branch_mode;			/* make an automagic "branch" tag */
32193323Sedstatic int local;			/* recursive by default */
33193323Sedstatic int force_tag_match = 1;         /* force tag to match by default */
34193323Sedstatic int force_tag_move;		/* don't force tag to move by default */
35195340Sed
36193323Sedstruct tag_info
37193323Sed{
38193323Sed    Ctype status;
39195340Sed    char *rev;
40193323Sed    char *tag;
41193323Sed    char *options;
42193323Sed};
43198090Srdivacky
44198090Srdivackystruct master_lists
45198090Srdivacky{
46198090Srdivacky    List *tlist;
47198090Srdivacky};
48198090Srdivacky
49198090Srdivackystatic List *mtlist;
50198090Srdivackystatic List *tlist;
51193323Sed
52198090Srdivackystatic const char *const tag_usage[] =
53198090Srdivacky{
54198090Srdivacky    "Usage: %s %s [-lRF] [-b] [-d] [-r tag|-D date] tag [files...]\n",
55198090Srdivacky    "\t-l\tLocal directory only, not recursive.\n",
56198090Srdivacky    "\t-R\tProcess directories recursively.\n",
57198090Srdivacky    "\t-d\tDelete the given Tag.\n",
58198090Srdivacky    "\t-[rD]\tExisting tag or date.\n",
59198090Srdivacky    "\t-f\tForce a head revision if tag etc not found.\n",
60198090Srdivacky    "\t-b\tMake the tag a \"branch\" tag, allowing concurrent development.\n",
61198090Srdivacky    "\t-F\tMove tag if it already exists\n",
62198090Srdivacky    NULL
63198090Srdivacky};
64198090Srdivacky
65198090Srdivackyint
66198090Srdivackytag (argc, argv)
67207618Srdivacky    int argc;
68198090Srdivacky    char **argv;
69198090Srdivacky{
70198090Srdivacky    int c;
71198090Srdivacky    int err = 0;
72198090Srdivacky
73198090Srdivacky    if (argc == -1)
74198090Srdivacky	usage (tag_usage);
75193323Sed
76193323Sed    optind = 1;
77193323Sed    while ((c = getopt (argc, argv, "FQqlRdr:D:bf")) != -1)
78193323Sed    {
79193323Sed	switch (c)
80193323Sed	{
81193323Sed	    case 'Q':
82193323Sed	    case 'q':
83193323Sed#ifdef SERVER_SUPPORT
84193323Sed		/* The CVS 1.5 client sends these options (in addition to
85193323Sed		   Global_option requests), so we must ignore them.  */
86193323Sed		if (!server_active)
87198090Srdivacky#endif
88193323Sed		    error (1, 0,
89193323Sed			   "-q or -Q must be specified before \"%s\"",
90193323Sed			   command_name);
91193323Sed		break;
92193323Sed	    case 'l':
93193323Sed		local = 1;
94193323Sed		break;
95193323Sed	    case 'R':
96193323Sed		local = 0;
97193323Sed		break;
98193323Sed	    case 'd':
99193323Sed		delete_flag = 1;
100193323Sed		break;
101193323Sed            case 'r':
102193323Sed                numtag = optarg;
103199481Srdivacky                break;
104199481Srdivacky            case 'D':
105193323Sed                if (date)
106193323Sed                    free (date);
107193323Sed                date = Make_Date (optarg);
108193323Sed                break;
109198090Srdivacky	    case 'f':
110198090Srdivacky		force_tag_match = 0;
111198090Srdivacky		break;
112198090Srdivacky	    case 'b':
113193323Sed		branch_mode = 1;
114193323Sed		break;
115198090Srdivacky            case 'F':
116198090Srdivacky		force_tag_move = 1;
117198090Srdivacky		break;
118198090Srdivacky	    case '?':
119193323Sed	    default:
120193323Sed		usage (tag_usage);
121193323Sed		break;
122193323Sed	}
123193323Sed    }
124193323Sed    argc -= optind;
125193323Sed    argv += optind;
126193323Sed
127193323Sed    if (argc == 0)
128200581Srdivacky	usage (tag_usage);
129193323Sed    symtag = argv[0];
130193323Sed    argc--;
131193323Sed    argv++;
132193323Sed
133193323Sed    if (date && numtag)
134193323Sed	error (1, 0, "-r and -D options are mutually exclusive");
135193323Sed    if (delete_flag && branch_mode)
136193323Sed	error (0, 0, "warning: -b ignored with -d options");
137193323Sed    RCS_check_tag (symtag);
138193323Sed
139207618Srdivacky#ifdef CLIENT_SUPPORT
140207618Srdivacky    if (client_active)
141207618Srdivacky    {
142198090Srdivacky	/* We're the client side.  Fire up the remote server.  */
143198090Srdivacky	start_server ();
144210299Sed
145210299Sed	ign_setup ();
146210299Sed
147210299Sed	if (local)
148198090Srdivacky	    send_arg("-l");
149198090Srdivacky	if (delete_flag)
150198090Srdivacky	    send_arg("-d");
151198090Srdivacky	if (branch_mode)
152193323Sed	    send_arg("-b");
153193323Sed	if (force_tag_move)
154198090Srdivacky	    send_arg("-F");
155193323Sed
156193323Sed	if (numtag)
157198090Srdivacky	    option_with_arg ("-r", numtag);
158195340Sed	if (date)
159193323Sed	    client_senddate (date);
160198090Srdivacky
161198090Srdivacky	send_arg (symtag);
162210299Sed
163198090Srdivacky	send_file_names (argc, argv, SEND_EXPAND_WILD);
164198090Srdivacky	/* FIXME:  We shouldn't have to send current files, but I'm not sure
165198090Srdivacky	   whether it works.  So send the files --
166210299Sed	   it's slower but it works.  */
167198090Srdivacky	send_files (argc, argv, local, 0);
168198090Srdivacky	send_to_server ("tag\012", 0);
169198090Srdivacky        return get_responses_and_close ();
170198090Srdivacky    }
171198090Srdivacky#endif
172193323Sed
173193323Sed    if (numtag != NULL)
174193323Sed	tag_check_valid (numtag, argc, argv, local, 0, "");
175198090Srdivacky
176193323Sed    /* check to make sure they are authorized to tag all the
177193323Sed       specified files in the repository */
178198090Srdivacky
179193323Sed    mtlist = getlist();
180198090Srdivacky    err = start_recursion (check_fileproc, check_filesdoneproc,
181198090Srdivacky                           (DIRENTPROC) NULL, (DIRLEAVEPROC) NULL,
182195340Sed                           argc, argv, local, W_LOCAL, 0, 1,
183198090Srdivacky                           (char *) NULL, 1, 0);
184198892Srdivacky
185198892Srdivacky    if (err)
186198892Srdivacky    {
187198892Srdivacky       error (1, 0, "correct the above errors first!");
188198892Srdivacky    }
189198892Srdivacky
190198892Srdivacky    /* start the recursion processor */
191198892Srdivacky    err = start_recursion (tag_fileproc, (FILESDONEPROC) NULL, tag_dirproc,
192198892Srdivacky			   (DIRLEAVEPROC) NULL, argc, argv, local,
193198892Srdivacky			   W_LOCAL, 0, 1, (char *) NULL, 1, 0);
194198892Srdivacky    dellist(&mtlist);
195198892Srdivacky    return (err);
196198892Srdivacky}
197198892Srdivacky
198198892Srdivacky/* check file that is to be tagged */
199198892Srdivacky/* All we do here is add it to our list */
200198892Srdivacky
201198892Srdivackystatic int
202198090Srdivackycheck_fileproc (finfo)
203198090Srdivacky    struct file_info *finfo;
204198090Srdivacky{
205198090Srdivacky    char *xdir;
206198090Srdivacky    Node *p;
207198090Srdivacky    Vers_TS *vers;
208198090Srdivacky
209198090Srdivacky    if (finfo->update_dir[0] == '\0')
210198090Srdivacky	xdir = ".";
211198090Srdivacky    else
212198090Srdivacky	xdir = finfo->update_dir;
213198090Srdivacky    if ((p = findnode (mtlist, xdir)) != NULL)
214193323Sed    {
215193323Sed	tlist = ((struct master_lists *) p->data)->tlist;
216198090Srdivacky    }
217198090Srdivacky    else
218198090Srdivacky    {
219198090Srdivacky	struct master_lists *ml;
220193323Sed
221193323Sed	tlist = getlist ();
222193323Sed	p = getnode ();
223193323Sed	p->key = xstrdup (xdir);
224193323Sed	p->type = UPDATE;
225193323Sed	ml = (struct master_lists *)
226193323Sed	    xmalloc (sizeof (struct master_lists));
227193323Sed	ml->tlist = tlist;
228193323Sed	p->data = (char *) ml;
229193323Sed	p->delproc = masterlist_delproc;
230193323Sed	(void) addnode (mtlist, p);
231193323Sed    }
232193323Sed    /* do tlist */
233193323Sed    p = getnode ();
234193323Sed    p->key = xstrdup (finfo->file);
235193323Sed    p->type = UPDATE;
236193323Sed    p->delproc = tag_delproc;
237198090Srdivacky    vers = Version_TS (finfo->repository, (char *) NULL, (char *) NULL,
238198090Srdivacky		       (char *) NULL, finfo->file, 0, 0,
239198090Srdivacky		       finfo->entries, finfo->rcs);
240198090Srdivacky    if (vers->srcfile == NULL)
241198090Srdivacky    {
242198090Srdivacky        if (!really_quiet)
243198090Srdivacky	    error (0, 0, "nothing known about %s", finfo->file);
244193323Sed	return (1);
245210299Sed    }
246210299Sed    p->data = RCS_getversion(vers->srcfile, numtag, date, force_tag_match, 0);
247210299Sed    if (p->data != NULL)
248210299Sed    {
249210299Sed        int addit = 1;
250210299Sed        char *oversion;
251210299Sed
252210299Sed        oversion = RCS_getversion (vers->srcfile, symtag, (char *) NULL, 1, 0);
253210299Sed        if (oversion == NULL)
254210299Sed        {
255198090Srdivacky            if (delete_flag)
256210299Sed            {
257198090Srdivacky                addit = 0;
258198090Srdivacky            }
259198090Srdivacky        }
260198090Srdivacky        else if (strcmp(oversion, p->data) == 0)
261198892Srdivacky        {
262198090Srdivacky            addit = 0;
263198090Srdivacky        }
264198090Srdivacky        else if (!force_tag_move)
265198090Srdivacky        {
266198090Srdivacky            addit = 0;
267193323Sed        }
268198090Srdivacky        if (oversion != NULL)
269198892Srdivacky        {
270198892Srdivacky            free(oversion);
271198892Srdivacky        }
272193323Sed        if (!addit)
273198892Srdivacky        {
274198090Srdivacky            free(p->data);
275210299Sed            p->data = NULL;
276210299Sed        }
277210299Sed    }
278210299Sed    freevers_ts(&vers);
279198892Srdivacky    (void) addnode (tlist, p);
280198892Srdivacky    return (0);
281198090Srdivacky}
282198090Srdivacky
283198090Srdivackystatic int
284198892Srdivackycheck_filesdoneproc(err, repos, update_dir)
285198090Srdivacky    int err;
286198892Srdivacky    char *repos;
287198892Srdivacky    char *update_dir;
288198892Srdivacky{
289198892Srdivacky    int n;
290198892Srdivacky    Node *p;
291198892Srdivacky
292198892Srdivacky    p = findnode(mtlist, update_dir);
293198090Srdivacky    if (p != NULL)
294198090Srdivacky    {
295198090Srdivacky        tlist = ((struct master_lists *) p->data)->tlist;
296198090Srdivacky    }
297198892Srdivacky    else
298198892Srdivacky    {
299198892Srdivacky        tlist = (List *) NULL;
300193323Sed    }
301198892Srdivacky    if ((tlist == NULL) || (tlist->list->next == tlist->list))
302198892Srdivacky    {
303198892Srdivacky        return (err);
304198892Srdivacky    }
305198090Srdivacky    if ((n = Parse_Info(CVSROOTADM_TAGINFO, repos, pretag_proc, 1)) > 0)
306198090Srdivacky    {
307198090Srdivacky        error (0, 0, "Pre-tag check failed");
308193323Sed        err += n;
309198090Srdivacky    }
310198090Srdivacky    return (err);
311198090Srdivacky}
312193323Sed
313198090Srdivackystatic int
314193323Sedpretag_proc(repository, filter)
315198892Srdivacky    char *repository;
316198892Srdivacky    char *filter;
317198892Srdivacky{
318198892Srdivacky    if (filter[0] == '/')
319198090Srdivacky    {
320198090Srdivacky        char *s, *cp;
321198892Srdivacky
322198090Srdivacky        s = xstrdup(filter);
323193323Sed        for (cp=s; *cp; cp++)
324193323Sed        {
325193323Sed            if (isspace(*cp))
326193323Sed            {
327193323Sed                *cp = '\0';
328193323Sed                break;
329193323Sed            }
330193323Sed        }
331198090Srdivacky        if (!isfile(s))
332198090Srdivacky        {
333193323Sed            error (0, errno, "cannot find pre-tag filter '%s'", s);
334193323Sed            free(s);
335193323Sed            return (1);
336193323Sed        }
337198090Srdivacky        free(s);
338198090Srdivacky    }
339193323Sed    run_setup("%s %s %s %s",
340193323Sed              filter,
341193323Sed              symtag,
342210299Sed              delete_flag ? "del" : force_tag_move ? "mov" : "add",
343210299Sed              repository);
344210299Sed    walklist(tlist, pretag_list_proc, NULL);
345210299Sed    return (run_exec(RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL|RUN_REALLY));
346210299Sed}
347193323Sed
348198090Srdivackystatic void
349198090Srdivackymasterlist_delproc(p)
350193323Sed    Node *p;
351210299Sed{
352198090Srdivacky    struct master_lists *ml;
353198090Srdivacky
354193323Sed    ml = (struct master_lists *)p->data;
355198090Srdivacky    dellist(&ml->tlist);
356198090Srdivacky    free(ml);
357193323Sed    return;
358198090Srdivacky}
359193323Sed
360198090Srdivackystatic void
361198090Srdivackytag_delproc(p)
362198090Srdivacky    Node *p;
363198396Srdivacky{
364198090Srdivacky    if (p->data != NULL)
365198090Srdivacky    {
366198090Srdivacky        free(p->data);
367208599Srdivacky        p->data = NULL;
368198090Srdivacky    }
369198090Srdivacky    return;
370198090Srdivacky}
371198090Srdivacky
372208599Srdivackystatic int
373198396Srdivackypretag_list_proc(p, closure)
374198396Srdivacky    Node *p;
375198396Srdivacky    void *closure;
376198090Srdivacky{
377198090Srdivacky    if (p->data != NULL)
378198090Srdivacky    {
379198090Srdivacky        run_arg(p->key);
380198090Srdivacky        run_arg(p->data);
381193323Sed    }
382193323Sed    return (0);
383193323Sed}
384193323Sed
385
386/*
387 * Called to tag a particular file (the currently checked out version is
388 * tagged with the specified tag - or the specified tag is deleted).
389 */
390/* ARGSUSED */
391static int
392tag_fileproc (finfo)
393    struct file_info *finfo;
394{
395    char *version, *oversion;
396    char *nversion = NULL;
397    char *rev;
398    Vers_TS *vers;
399    int retcode = 0;
400
401    vers = Version_TS (finfo->repository, (char *) NULL, (char *) NULL, (char *) NULL,
402		       finfo->file, 0, 0, finfo->entries, finfo->rcs);
403
404    if ((numtag != NULL) || (date != NULL))
405    {
406        nversion = RCS_getversion(vers->srcfile,
407                                  numtag,
408                                  date,
409                                  force_tag_match, 0);
410        if (nversion == NULL)
411        {
412	    freevers_ts (&vers);
413            return (0);
414        }
415    }
416    if (delete_flag)
417    {
418
419	/*
420	 * If -d is specified, "force_tag_match" is set, so that this call to
421	 * RCS_getversion() will return a NULL version string if the symbolic
422	 * tag does not exist in the RCS file.
423	 *
424	 * This is done here because it's MUCH faster than just blindly calling
425	 * "rcs" to remove the tag... trust me.
426	 */
427
428	version = RCS_getversion (vers->srcfile, symtag, (char *) NULL, 1, 0);
429	if (version == NULL || vers->srcfile == NULL)
430	{
431	    freevers_ts (&vers);
432	    return (0);
433	}
434	free (version);
435
436	if ((retcode = RCS_deltag(vers->srcfile->path, symtag, 1)) != 0)
437	{
438	    if (!quiet)
439		error (0, retcode == -1 ? errno : 0,
440		       "failed to remove tag %s from %s", symtag,
441		       vers->srcfile->path);
442	    freevers_ts (&vers);
443	    return (1);
444	}
445
446	/* warm fuzzies */
447	if (!really_quiet)
448	{
449	    (void) printf ("D %s\n", finfo->fullname);
450	}
451
452	freevers_ts (&vers);
453	return (0);
454    }
455
456    /*
457     * If we are adding a tag, we need to know which version we have checked
458     * out and we'll tag that version.
459     */
460    if (nversion == NULL)
461    {
462        version = vers->vn_user;
463    }
464    else
465    {
466        version = nversion;
467    }
468    if (version == NULL)
469    {
470	freevers_ts (&vers);
471	return (0);
472    }
473    else if (strcmp (version, "0") == 0)
474    {
475	if (!quiet)
476	    error (0, 0, "couldn't tag added but un-commited file `%s'", finfo->file);
477	freevers_ts (&vers);
478	return (0);
479    }
480    else if (version[0] == '-')
481    {
482	if (!quiet)
483	    error (0, 0, "skipping removed but un-commited file `%s'", finfo->file);
484	freevers_ts (&vers);
485	return (0);
486    }
487    else if (vers->srcfile == NULL)
488    {
489	if (!quiet)
490	    error (0, 0, "cannot find revision control file for `%s'", finfo->file);
491	freevers_ts (&vers);
492	return (0);
493    }
494
495    /*
496     * As an enhancement for the case where a tag is being re-applied to a
497     * large number of files, make one extra call to RCS_getversion to see
498     * if the tag is already set in the RCS file.  If so, check to see if it
499     * needs to be moved.  If not, do nothing.  This will likely save a lot of
500     * time when simply moving the tag to the "current" head revisions of a
501     * module -- which I have found to be a typical tagging operation.
502     */
503    rev = branch_mode ? RCS_magicrev (vers->srcfile, version) : version;
504    oversion = RCS_getversion (vers->srcfile, symtag, (char *) NULL, 1, 0);
505    if (oversion != NULL)
506    {
507	int isbranch = RCS_isbranch (finfo->rcs, symtag);
508
509	/*
510	 * if versions the same and neither old or new are branches don't have
511	 * to do anything
512	 */
513	if (strcmp (version, oversion) == 0 && !branch_mode && !isbranch)
514	{
515	    free (oversion);
516	    freevers_ts (&vers);
517	    return (0);
518	}
519
520	if (!force_tag_move)
521	{
522	    /* we're NOT going to move the tag */
523	    (void) printf ("W %s", finfo->fullname);
524
525	    (void) printf (" : %s already exists on %s %s",
526			   symtag, isbranch ? "branch" : "version", oversion);
527	    (void) printf (" : NOT MOVING tag to %s %s\n",
528			   branch_mode ? "branch" : "version", rev);
529	    free (oversion);
530	    freevers_ts (&vers);
531	    return (0);
532	}
533	free (oversion);
534    }
535
536    if ((retcode = RCS_settag(vers->srcfile->path, symtag, rev)) != 0)
537    {
538	error (1, retcode == -1 ? errno : 0,
539	       "failed to set tag %s to revision %s in %s",
540	       symtag, rev, vers->srcfile->path);
541	freevers_ts (&vers);
542	return (1);
543    }
544
545    /* more warm fuzzies */
546    if (!really_quiet)
547    {
548	(void) printf ("T %s\n", finfo->fullname);
549    }
550
551    if (nversion != NULL)
552    {
553        free (nversion);
554    }
555    freevers_ts (&vers);
556    return (0);
557}
558
559/*
560 * Print a warm fuzzy message
561 */
562/* ARGSUSED */
563static Dtype
564tag_dirproc (dir, repos, update_dir)
565    char *dir;
566    char *repos;
567    char *update_dir;
568{
569    if (!quiet)
570	error (0, 0, "%s %s", delete_flag ? "Untagging" : "Tagging", update_dir);
571    return (R_PROCESS);
572}
573
574/* Code relating to the val-tags file.  Note that this file has no way
575   of knowing when a tag has been deleted.  The problem is that there
576   is no way of knowing whether a tag still exists somewhere, when we
577   delete it some places.  Using per-directory val-tags files (in
578   CVSREP) might be better, but that might slow down the process of
579   verifying that a tag is correct (maybe not, for the likely cases,
580   if carefully done), and/or be harder to implement correctly.  */
581
582struct val_args {
583    char *name;
584    int found;
585};
586
587/* Pass as a static until we get around to fixing start_recursion to pass along
588   a void * where we can stash it.  */
589static struct val_args *val_args_static;
590
591static int val_fileproc PROTO ((struct file_info *finfo));
592
593static int
594val_fileproc (finfo)
595    struct file_info *finfo;
596{
597    RCSNode *rcsdata;
598    struct val_args *args = val_args_static;
599    char *tag;
600
601    if ((rcsdata = finfo->rcs) == NULL)
602	/* Not sure this can happen, after all we passed only
603	   W_REPOS | W_ATTIC.  */
604	return 0;
605
606    tag = RCS_gettag (rcsdata, args->name, 1, 0);
607    if (tag != NULL)
608    {
609	/* FIXME: should find out a way to stop the search at this point.  */
610	args->found = 1;
611	free (tag);
612    }
613    return 0;
614}
615
616static Dtype val_direntproc PROTO ((char *, char *, char *));
617
618static Dtype
619val_direntproc (dir, repository, update_dir)
620    char *dir;
621    char *repository;
622    char *update_dir;
623{
624    /* This is not quite right--it doesn't get right the case of "cvs
625       update -d -r foobar" where foobar is a tag which exists only in
626       files in a directory which does not exist yet, but which is
627       about to be created.  */
628    if (isdir (dir))
629	return 0;
630    return R_SKIP_ALL;
631}
632
633/* Check to see whether NAME is a valid tag.  If so, return.  If not
634   print an error message and exit.  ARGC, ARGV, LOCAL, and AFLAG specify
635   which files we will be operating on.
636
637   REPOSITORY is the repository if we need to cd into it, or NULL if
638   we are already there, or "" if we should do a W_LOCAL recursion.
639   Sorry for three cases, but the "" case is needed in case the
640   working directories come from diverse parts of the repository, the
641   NULL case avoids an unneccesary chdir, and the non-NULL, non-""
642   case is needed for checkout, where we don't want to chdir if the
643   tag is found in CVSROOTADM_VALTAGS, but there is not (yet) any
644   local directory.  */
645void
646tag_check_valid (name, argc, argv, local, aflag, repository)
647    char *name;
648    int argc;
649    char **argv;
650    int local;
651    int aflag;
652    char *repository;
653{
654    DBM *db;
655    char *valtags_filename;
656    int err;
657    datum mytag;
658    struct val_args the_val_args;
659    struct saved_cwd cwd;
660    int which;
661
662    /* Numeric tags require only a syntactic check.  */
663    if (isdigit (name[0]))
664    {
665	char *p;
666	for (p = name; *p != '\0'; ++p)
667	{
668	    if (!(isdigit (*p) || *p == '.'))
669		error (1, 0, "\
670Numeric tag %s contains characters other than digits and '.'", name);
671	}
672	return;
673    }
674
675    mytag.dptr = name;
676    mytag.dsize = strlen (name);
677
678    valtags_filename = xmalloc (strlen (CVSroot) + sizeof CVSROOTADM
679				+ sizeof CVSROOTADM_HISTORY + 20);
680    strcpy (valtags_filename, CVSroot);
681    strcat (valtags_filename, "/");
682    strcat (valtags_filename, CVSROOTADM);
683    strcat (valtags_filename, "/");
684    strcat (valtags_filename, CVSROOTADM_VALTAGS);
685    db = dbm_open (valtags_filename, O_RDWR, 0666);
686    if (db == NULL)
687    {
688	if (!existence_error (errno))
689	    error (1, errno, "cannot read %s", valtags_filename);
690
691	/* If the file merely fails to exist, we just keep going and create
692	   it later if need be.  */
693    }
694    else
695    {
696	datum val;
697
698	val = dbm_fetch (db, mytag);
699	if (val.dptr != NULL)
700	{
701	    /* Found.  The tag is valid.  */
702	    dbm_close (db);
703	    free (valtags_filename);
704	    return;
705	}
706	/* FIXME: should check errors somehow (add dbm_error to myndbm.c?).  */
707    }
708
709    /* We didn't find the tag in val-tags, so look through all the RCS files
710       to see whether it exists there.  Yes, this is expensive, but there
711       is no other way to cope with a tag which might have been created
712       by an old version of CVS, from before val-tags was invented.  */
713
714    the_val_args.name = name;
715    the_val_args.found = 0;
716    val_args_static = &the_val_args;
717
718    which = W_REPOS | W_ATTIC;
719
720    if (repository != NULL)
721    {
722	if (repository[0] == '\0')
723	    which |= W_LOCAL;
724	else
725	{
726	    if (save_cwd (&cwd))
727		exit (EXIT_FAILURE);
728	    if (chdir (repository) < 0)
729		error (1, errno, "cannot change to %s directory", repository);
730	}
731    }
732
733    err = start_recursion (val_fileproc, (FILESDONEPROC) NULL,
734			   val_direntproc, (DIRLEAVEPROC) NULL,
735			   argc, argv, local, which, aflag,
736			   1, NULL, 1, 0);
737    if (repository != NULL && repository[0] != '\0')
738    {
739	if (restore_cwd (&cwd, NULL))
740	    exit (EXIT_FAILURE);
741	free_cwd (&cwd);
742    }
743
744    if (!the_val_args.found)
745	error (1, 0, "no such tag %s", name);
746    else
747    {
748	/* The tags is valid but not mentioned in val-tags.  Add it.  */
749	datum value;
750
751	if (noexec)
752	{
753	    if (db != NULL)
754		dbm_close (db);
755	    free (valtags_filename);
756	    return;
757	}
758
759	if (db == NULL)
760	{
761	    mode_t omask;
762	    omask = umask (cvsumask);
763	    db = dbm_open (valtags_filename, O_RDWR | O_CREAT | O_TRUNC, 0666);
764	    (void) umask (omask);
765
766	    if (db == NULL)
767	    {
768		error (0, errno, "cannot create %s", valtags_filename);
769		free (valtags_filename);
770		return;
771	    }
772	}
773	value.dptr = "y";
774	value.dsize = 1;
775	if (dbm_store (db, mytag, value, DBM_REPLACE) < 0)
776	    error (0, errno, "cannot store %s into %s", name,
777		   valtags_filename);
778	dbm_close (db);
779    }
780    free (valtags_filename);
781}
782