tag.c revision 54427
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
9 *
10 * Add or delete a symbolic name to an RCS file, or a collection of RCS files.
11 * Uses the checked out revision in the current directory.
12 */
13
14#include "cvs.h"
15#include "savecwd.h"
16
17static int check_fileproc PROTO ((void *callerdat, struct file_info *finfo));
18static int check_filesdoneproc PROTO ((void *callerdat, int err,
19				       char *repos, char *update_dir,
20				       List *entries));
21static int pretag_proc PROTO((char *repository, char *filter));
22static void masterlist_delproc PROTO((Node *p));
23static void tag_delproc PROTO((Node *p));
24static int pretag_list_proc PROTO((Node *p, void *closure));
25
26static Dtype tag_dirproc PROTO ((void *callerdat, char *dir,
27				 char *repos, char *update_dir,
28				 List *entries));
29static int tag_fileproc PROTO ((void *callerdat, struct file_info *finfo));
30static int tag_filesdoneproc PROTO ((void *callerdat, int err,
31				     char *repos, char *update_dir,
32				     List *entries));
33
34static char *numtag;
35static char *date = NULL;
36static char *symtag;
37static int delete_flag;			/* adding a tag by default */
38static int branch_mode;			/* make an automagic "branch" tag */
39static int local;			/* recursive by default */
40static int force_tag_match = 1;         /* force tag to match by default */
41static int force_tag_move;		/* don't force tag to move by default */
42static int check_uptodate;		/* no uptodate-check by default */
43
44struct tag_info
45{
46    Ctype status;
47    char *rev;
48    char *tag;
49    char *options;
50};
51
52struct master_lists
53{
54    List *tlist;
55};
56
57static List *mtlist;
58static List *tlist;
59
60static const char *const tag_usage[] =
61{
62    "Usage: %s %s [-lRF] [-b] [-d] [-c] [-r rev|-D date] tag [files...]\n",
63    "\t-l\tLocal directory only, not recursive.\n",
64    "\t-R\tProcess directories recursively.\n",
65    "\t-d\tDelete the given tag.\n",
66    "\t-r rev\tExisting revision/tag.\n",
67    "\t-D\tExisting date.\n",
68    "\t-f\tForce a head revision if specified tag not found.\n",
69    "\t-b\tMake the tag a \"branch\" tag, allowing concurrent development.\n",
70    "\t-F\tMove tag if it already exists.\n",
71    "\t-c\tCheck that working files are unmodified.\n",
72    "(Specify the --help global option for a list of other help options)\n",
73    NULL
74};
75
76int
77cvstag (argc, argv)
78    int argc;
79    char **argv;
80{
81    int c;
82    int err = 0;
83
84    if (argc == -1)
85	usage (tag_usage);
86
87    optind = 0;
88    while ((c = getopt (argc, argv, "+FQqlRcdr:D:bf")) != -1)
89    {
90	switch (c)
91	{
92	    case 'Q':
93	    case 'q':
94#ifdef SERVER_SUPPORT
95		/* The CVS 1.5 client sends these options (in addition to
96		   Global_option requests), so we must ignore them.  */
97		if (!server_active)
98#endif
99		    error (1, 0,
100			   "-q or -Q must be specified before \"%s\"",
101			   command_name);
102		break;
103	    case 'l':
104		local = 1;
105		break;
106	    case 'R':
107		local = 0;
108		break;
109	    case 'd':
110		delete_flag = 1;
111		break;
112	    case 'c':
113		check_uptodate = 1;
114		break;
115            case 'r':
116                numtag = optarg;
117                break;
118            case 'D':
119                if (date)
120                    free (date);
121                date = Make_Date (optarg);
122                break;
123	    case 'f':
124		force_tag_match = 0;
125		break;
126	    case 'b':
127		branch_mode = 1;
128		break;
129            case 'F':
130		force_tag_move = 1;
131		break;
132	    case '?':
133	    default:
134		usage (tag_usage);
135		break;
136	}
137    }
138    argc -= optind;
139    argv += optind;
140
141    if (argc == 0)
142	usage (tag_usage);
143    symtag = argv[0];
144    argc--;
145    argv++;
146
147    if (date && numtag)
148	error (1, 0, "-r and -D options are mutually exclusive");
149    if (delete_flag && branch_mode)
150	error (0, 0, "warning: -b ignored with -d options");
151    RCS_check_tag (symtag);
152
153#ifdef CLIENT_SUPPORT
154    if (client_active)
155    {
156	/* We're the client side.  Fire up the remote server.  */
157	start_server ();
158
159	ign_setup ();
160
161	if (!force_tag_match)
162	    send_arg ("-f");
163	if (local)
164	    send_arg("-l");
165	if (delete_flag)
166	    send_arg("-d");
167	if (check_uptodate)
168	    send_arg("-c");
169	if (branch_mode)
170	    send_arg("-b");
171	if (force_tag_move)
172	    send_arg("-F");
173
174	if (numtag)
175	    option_with_arg ("-r", numtag);
176	if (date)
177	    client_senddate (date);
178
179	send_arg (symtag);
180
181	send_files (argc, argv, local, 0,
182
183		    /* I think the -c case is like "cvs status", in
184		       which we really better be correct rather than
185		       being fast; it is just too confusing otherwise.  */
186		    check_uptodate ? 0 : SEND_NO_CONTENTS);
187	send_file_names (argc, argv, SEND_EXPAND_WILD);
188	send_to_server ("tag\012", 0);
189        return get_responses_and_close ();
190    }
191#endif
192
193    if (numtag != NULL)
194	tag_check_valid (numtag, argc, argv, local, 0, "");
195
196    /* check to make sure they are authorized to tag all the
197       specified files in the repository */
198
199    mtlist = getlist();
200    err = start_recursion (check_fileproc, check_filesdoneproc,
201                           (DIRENTPROC) NULL, (DIRLEAVEPROC) NULL, NULL,
202                           argc, argv, local, W_LOCAL, 0, 1,
203                           (char *) NULL, 1);
204
205    if (err)
206    {
207       error (1, 0, "correct the above errors first!");
208    }
209
210    /* start the recursion processor */
211    err = start_recursion (tag_fileproc, tag_filesdoneproc, tag_dirproc,
212			   (DIRLEAVEPROC) NULL, NULL, argc, argv, local,
213			   W_LOCAL, 0, 0, (char *) NULL, 1);
214    dellist(&mtlist);
215    return (err);
216}
217
218/* check file that is to be tagged */
219/* All we do here is add it to our list */
220
221static int
222check_fileproc (callerdat, finfo)
223    void *callerdat;
224    struct file_info *finfo;
225{
226    char *xdir;
227    Node *p;
228    Vers_TS *vers;
229
230    if (check_uptodate)
231    {
232	Ctype status = Classify_File (finfo, (char *) NULL, (char *) NULL,
233				      (char *) NULL, 1, 0, &vers, 0);
234	if ((status != T_UPTODATE) && (status != T_CHECKOUT))
235	{
236	    error (0, 0, "%s is locally modified", finfo->fullname);
237	    return (1);
238	}
239    }
240
241    if (finfo->update_dir[0] == '\0')
242	xdir = ".";
243    else
244	xdir = finfo->update_dir;
245    if ((p = findnode (mtlist, xdir)) != NULL)
246    {
247	tlist = ((struct master_lists *) p->data)->tlist;
248    }
249    else
250    {
251	struct master_lists *ml;
252
253	tlist = getlist ();
254	p = getnode ();
255	p->key = xstrdup (xdir);
256	p->type = UPDATE;
257	ml = (struct master_lists *)
258	    xmalloc (sizeof (struct master_lists));
259	ml->tlist = tlist;
260	p->data = (char *) ml;
261	p->delproc = masterlist_delproc;
262	(void) addnode (mtlist, p);
263    }
264    /* do tlist */
265    p = getnode ();
266    p->key = xstrdup (finfo->file);
267    p->type = UPDATE;
268    p->delproc = tag_delproc;
269    vers = Version_TS (finfo, NULL, NULL, NULL, 0, 0);
270    if (vers->srcfile == NULL)
271    {
272        if (!really_quiet)
273	    error (0, 0, "nothing known about %s", finfo->file);
274	return (1);
275    }
276
277    /* Here we duplicate the calculation in tag_fileproc about which
278       version we are going to tag.  There probably are some subtle races
279       (e.g. numtag is "foo" which gets moved between here and
280       tag_fileproc).  */
281    if (numtag == NULL && date == NULL)
282	p->data = xstrdup (vers->vn_user);
283    else
284	p->data = RCS_getversion (vers->srcfile, numtag, date,
285				  force_tag_match, NULL);
286
287    if (p->data != NULL)
288    {
289        int addit = 1;
290        char *oversion;
291
292        oversion = RCS_getversion (vers->srcfile, symtag, (char *) NULL, 1,
293				   (int *) NULL);
294        if (oversion == NULL)
295        {
296            if (delete_flag)
297            {
298		/* Deleting a tag which did not exist is a noop and
299		   should not be logged.  */
300                addit = 0;
301            }
302        }
303	else if (delete_flag)
304	{
305	    free (p->data);
306	    p->data = xstrdup (oversion);
307	}
308        else if (strcmp(oversion, p->data) == 0)
309        {
310            addit = 0;
311        }
312        else if (!force_tag_move)
313        {
314            addit = 0;
315        }
316        if (oversion != NULL)
317        {
318            free(oversion);
319        }
320        if (!addit)
321        {
322            free(p->data);
323            p->data = NULL;
324        }
325    }
326    freevers_ts(&vers);
327    (void) addnode (tlist, p);
328    return (0);
329}
330
331static int
332check_filesdoneproc (callerdat, err, repos, update_dir, entries)
333    void *callerdat;
334    int err;
335    char *repos;
336    char *update_dir;
337    List *entries;
338{
339    int n;
340    Node *p;
341
342    p = findnode(mtlist, update_dir);
343    if (p != NULL)
344    {
345        tlist = ((struct master_lists *) p->data)->tlist;
346    }
347    else
348    {
349        tlist = (List *) NULL;
350    }
351    if ((tlist == NULL) || (tlist->list->next == tlist->list))
352    {
353        return (err);
354    }
355    if ((n = Parse_Info(CVSROOTADM_TAGINFO, repos, pretag_proc, 1)) > 0)
356    {
357        error (0, 0, "Pre-tag check failed");
358        err += n;
359    }
360    return (err);
361}
362
363static int
364pretag_proc(repository, filter)
365    char *repository;
366    char *filter;
367{
368    if (filter[0] == '/')
369    {
370        char *s, *cp;
371
372        s = xstrdup(filter);
373        for (cp=s; *cp; cp++)
374        {
375            if (isspace ((unsigned char) *cp))
376            {
377                *cp = '\0';
378                break;
379            }
380        }
381        if (!isfile(s))
382        {
383            error (0, errno, "cannot find pre-tag filter '%s'", s);
384            free(s);
385            return (1);
386        }
387        free(s);
388    }
389    run_setup (filter);
390    run_arg (symtag);
391    run_arg (delete_flag ? "del" : force_tag_move ? "mov" : "add");
392    run_arg (repository);
393    walklist(tlist, pretag_list_proc, NULL);
394    return (run_exec (RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL));
395}
396
397static void
398masterlist_delproc(p)
399    Node *p;
400{
401    struct master_lists *ml;
402
403    ml = (struct master_lists *)p->data;
404    dellist(&ml->tlist);
405    free(ml);
406    return;
407}
408
409static void
410tag_delproc(p)
411    Node *p;
412{
413    if (p->data != NULL)
414    {
415        free(p->data);
416        p->data = NULL;
417    }
418    return;
419}
420
421static int
422pretag_list_proc(p, closure)
423    Node *p;
424    void *closure;
425{
426    if (p->data != NULL)
427    {
428        run_arg(p->key);
429        run_arg(p->data);
430    }
431    return (0);
432}
433
434
435/*
436 * Called to tag a particular file (the currently checked out version is
437 * tagged with the specified tag - or the specified tag is deleted).
438 */
439/* ARGSUSED */
440static int
441tag_fileproc (callerdat, finfo)
442    void *callerdat;
443    struct file_info *finfo;
444{
445    char *version, *oversion;
446    char *nversion = NULL;
447    char *rev;
448    Vers_TS *vers;
449    int retcode = 0;
450
451    /* Lock the directory if it is not already locked.  We can't rely
452       on tag_dirproc because it won't handle the case where the user
453       specifies a list of files on the command line.  */
454    /* We do not need to acquire a full write lock for the tag operation:
455       the revisions are obtained from the working directory, so we do not
456       require consistency across the entire repository.  However, we do
457       need to prevent simultaneous tag operations from interfering with
458       each other.  Therefore, we write lock each directory as we enter
459       it, and unlock it as we leave it.  */
460    lock_dir_for_write (finfo->repository);
461
462    vers = Version_TS (finfo, NULL, NULL, NULL, 0, 0);
463
464    if ((numtag != NULL) || (date != NULL))
465    {
466        nversion = RCS_getversion(vers->srcfile,
467                                  numtag,
468                                  date,
469                                  force_tag_match,
470				  (int *) NULL);
471        if (nversion == NULL)
472        {
473	    freevers_ts (&vers);
474            return (0);
475        }
476    }
477    if (delete_flag)
478    {
479
480	/*
481	 * If -d is specified, "force_tag_match" is set, so that this call to
482	 * RCS_getversion() will return a NULL version string if the symbolic
483	 * tag does not exist in the RCS file.
484	 *
485	 * This is done here because it's MUCH faster than just blindly calling
486	 * "rcs" to remove the tag... trust me.
487	 */
488
489	version = RCS_getversion (vers->srcfile, symtag, (char *) NULL, 1,
490				  (int *) NULL);
491	if (version == NULL || vers->srcfile == NULL)
492	{
493	    freevers_ts (&vers);
494	    return (0);
495	}
496	free (version);
497
498	if ((retcode = RCS_deltag(vers->srcfile, symtag)) != 0)
499	{
500	    if (!quiet)
501		error (0, retcode == -1 ? errno : 0,
502		       "failed to remove tag %s from %s", symtag,
503		       vers->srcfile->path);
504	    freevers_ts (&vers);
505	    return (1);
506	}
507	RCS_rewrite (vers->srcfile, NULL, NULL);
508
509	/* warm fuzzies */
510	if (!really_quiet)
511	{
512	    cvs_output ("D ", 2);
513	    cvs_output (finfo->fullname, 0);
514	    cvs_output ("\n", 1);
515	}
516
517	freevers_ts (&vers);
518	return (0);
519    }
520
521    /*
522     * If we are adding a tag, we need to know which version we have checked
523     * out and we'll tag that version.
524     */
525    if (nversion == NULL)
526    {
527        version = vers->vn_user;
528    }
529    else
530    {
531        version = nversion;
532    }
533    if (version == NULL)
534    {
535	freevers_ts (&vers);
536	return (0);
537    }
538    else if (strcmp (version, "0") == 0)
539    {
540	if (!quiet)
541	    error (0, 0, "couldn't tag added but un-commited file `%s'", finfo->file);
542	freevers_ts (&vers);
543	return (0);
544    }
545    else if (version[0] == '-')
546    {
547	if (!quiet)
548	    error (0, 0, "skipping removed but un-commited file `%s'", finfo->file);
549	freevers_ts (&vers);
550	return (0);
551    }
552    else if (vers->srcfile == NULL)
553    {
554	if (!quiet)
555	    error (0, 0, "cannot find revision control file for `%s'", finfo->file);
556	freevers_ts (&vers);
557	return (0);
558    }
559
560    /*
561     * As an enhancement for the case where a tag is being re-applied to a
562     * large number of files, make one extra call to RCS_getversion to see
563     * if the tag is already set in the RCS file.  If so, check to see if it
564     * needs to be moved.  If not, do nothing.  This will likely save a lot of
565     * time when simply moving the tag to the "current" head revisions of a
566     * module -- which I have found to be a typical tagging operation.
567     */
568    rev = branch_mode ? RCS_magicrev (vers->srcfile, version) : version;
569    oversion = RCS_getversion (vers->srcfile, symtag, (char *) NULL, 1,
570			       (int *) NULL);
571    if (oversion != NULL)
572    {
573	int isbranch = RCS_nodeisbranch (finfo->rcs, symtag);
574
575	/*
576	 * if versions the same and neither old or new are branches don't have
577	 * to do anything
578	 */
579	if (strcmp (version, oversion) == 0 && !branch_mode && !isbranch)
580	{
581	    free (oversion);
582	    freevers_ts (&vers);
583	    return (0);
584	}
585
586	if (!force_tag_move)
587	{
588	    /* we're NOT going to move the tag */
589	    cvs_output ("W ", 2);
590	    cvs_output (finfo->fullname, 0);
591	    cvs_output (" : ", 0);
592	    cvs_output (symtag, 0);
593	    cvs_output (" already exists on ", 0);
594	    cvs_output (isbranch ? "branch" : "version", 0);
595	    cvs_output (" ", 0);
596	    cvs_output (oversion, 0);
597	    cvs_output (" : NOT MOVING tag to ", 0);
598	    cvs_output (branch_mode ? "branch" : "version", 0);
599	    cvs_output (" ", 0);
600	    cvs_output (rev, 0);
601	    cvs_output ("\n", 1);
602	    free (oversion);
603	    freevers_ts (&vers);
604	    return (0);
605	}
606	free (oversion);
607    }
608
609    if ((retcode = RCS_settag(vers->srcfile, symtag, rev)) != 0)
610    {
611	error (1, retcode == -1 ? errno : 0,
612	       "failed to set tag %s to revision %s in %s",
613	       symtag, rev, vers->srcfile->path);
614	freevers_ts (&vers);
615	return (1);
616    }
617    RCS_rewrite (vers->srcfile, NULL, NULL);
618
619    /* more warm fuzzies */
620    if (!really_quiet)
621    {
622	cvs_output ("T ", 2);
623	cvs_output (finfo->fullname, 0);
624	cvs_output ("\n", 1);
625    }
626
627    if (nversion != NULL)
628    {
629        free (nversion);
630    }
631    freevers_ts (&vers);
632    return (0);
633}
634
635/* Clear any lock we may hold on the current directory.  */
636
637static int
638tag_filesdoneproc (callerdat, err, repos, update_dir, entries)
639    void *callerdat;
640    int err;
641    char *repos;
642    char *update_dir;
643    List *entries;
644{
645    Lock_Cleanup ();
646
647    return (err);
648}
649
650/*
651 * Print a warm fuzzy message
652 */
653/* ARGSUSED */
654static Dtype
655tag_dirproc (callerdat, dir, repos, update_dir, entries)
656    void *callerdat;
657    char *dir;
658    char *repos;
659    char *update_dir;
660    List *entries;
661{
662    if (!quiet)
663	error (0, 0, "%s %s", delete_flag ? "Untagging" : "Tagging", update_dir);
664    return (R_PROCESS);
665}
666
667/* Code relating to the val-tags file.  Note that this file has no way
668   of knowing when a tag has been deleted.  The problem is that there
669   is no way of knowing whether a tag still exists somewhere, when we
670   delete it some places.  Using per-directory val-tags files (in
671   CVSREP) might be better, but that might slow down the process of
672   verifying that a tag is correct (maybe not, for the likely cases,
673   if carefully done), and/or be harder to implement correctly.  */
674
675struct val_args {
676    char *name;
677    int found;
678};
679
680static int val_fileproc PROTO ((void *callerdat, struct file_info *finfo));
681
682static int
683val_fileproc (callerdat, finfo)
684    void *callerdat;
685    struct file_info *finfo;
686{
687    RCSNode *rcsdata;
688    struct val_args *args = (struct val_args *)callerdat;
689    char *tag;
690
691    if ((rcsdata = finfo->rcs) == NULL)
692	/* Not sure this can happen, after all we passed only
693	   W_REPOS | W_ATTIC.  */
694	return 0;
695
696    tag = RCS_gettag (rcsdata, args->name, 1, (int *) NULL);
697    if (tag != NULL)
698    {
699	/* FIXME: should find out a way to stop the search at this point.  */
700	args->found = 1;
701	free (tag);
702    }
703    return 0;
704}
705
706static Dtype val_direntproc PROTO ((void *, char *, char *, char *, List *));
707
708static Dtype
709val_direntproc (callerdat, dir, repository, update_dir, entries)
710    void *callerdat;
711    char *dir;
712    char *repository;
713    char *update_dir;
714    List *entries;
715{
716    /* This is not quite right--it doesn't get right the case of "cvs
717       update -d -r foobar" where foobar is a tag which exists only in
718       files in a directory which does not exist yet, but which is
719       about to be created.  */
720    if (isdir (dir))
721	return 0;
722    return R_SKIP_ALL;
723}
724
725/* Check to see whether NAME is a valid tag.  If so, return.  If not
726   print an error message and exit.  ARGC, ARGV, LOCAL, and AFLAG specify
727   which files we will be operating on.
728
729   REPOSITORY is the repository if we need to cd into it, or NULL if
730   we are already there, or "" if we should do a W_LOCAL recursion.
731   Sorry for three cases, but the "" case is needed in case the
732   working directories come from diverse parts of the repository, the
733   NULL case avoids an unneccesary chdir, and the non-NULL, non-""
734   case is needed for checkout, where we don't want to chdir if the
735   tag is found in CVSROOTADM_VALTAGS, but there is not (yet) any
736   local directory.  */
737void
738tag_check_valid (name, argc, argv, local, aflag, repository)
739    char *name;
740    int argc;
741    char **argv;
742    int local;
743    int aflag;
744    char *repository;
745{
746    DBM *db;
747    char *valtags_filename;
748    int err;
749    datum mytag;
750    struct val_args the_val_args;
751    struct saved_cwd cwd;
752    int which;
753
754    /* Numeric tags require only a syntactic check.  */
755    if (isdigit ((unsigned char) name[0]))
756    {
757	char *p;
758	for (p = name; *p != '\0'; ++p)
759	{
760	    if (!(isdigit ((unsigned char) *p) || *p == '.'))
761		error (1, 0, "\
762Numeric tag %s contains characters other than digits and '.'", name);
763	}
764	return;
765    }
766
767    /* Special tags are always valid.  */
768    if (strcmp (name, TAG_BASE) == 0
769	|| strcmp (name, TAG_HEAD) == 0)
770	return;
771
772    /* FIXME: This routine doesn't seem to do any locking whatsoever
773       (and it is called from places which don't have locks in place).
774       If two processes try to write val-tags at the same time, it would
775       seem like we are in trouble.  */
776
777    mytag.dptr = name;
778    mytag.dsize = strlen (name);
779
780    valtags_filename = xmalloc (strlen (CVSroot_directory)
781				+ sizeof CVSROOTADM
782				+ sizeof CVSROOTADM_VALTAGS + 20);
783    strcpy (valtags_filename, CVSroot_directory);
784    strcat (valtags_filename, "/");
785    strcat (valtags_filename, CVSROOTADM);
786    strcat (valtags_filename, "/");
787    strcat (valtags_filename, CVSROOTADM_VALTAGS);
788    db = dbm_open (valtags_filename, O_RDWR, 0666);
789    if (db == NULL)
790    {
791	if (!existence_error (errno))
792	    error (1, errno, "cannot read %s", valtags_filename);
793
794	/* If the file merely fails to exist, we just keep going and create
795	   it later if need be.  */
796    }
797    else
798    {
799	datum val;
800
801	val = dbm_fetch (db, mytag);
802	if (val.dptr != NULL)
803	{
804	    /* Found.  The tag is valid.  */
805	    dbm_close (db);
806	    free (valtags_filename);
807	    return;
808	}
809	/* FIXME: should check errors somehow (add dbm_error to myndbm.c?).  */
810    }
811
812    /* We didn't find the tag in val-tags, so look through all the RCS files
813       to see whether it exists there.  Yes, this is expensive, but there
814       is no other way to cope with a tag which might have been created
815       by an old version of CVS, from before val-tags was invented.
816
817       Since we need this code anyway, we also use it to create
818       entries in val-tags in general (that is, the val-tags entry
819       will get created the first time the tag is used, not when the
820       tag is created).  */
821
822    the_val_args.name = name;
823    the_val_args.found = 0;
824
825    which = W_REPOS | W_ATTIC;
826
827    if (repository != NULL)
828    {
829	if (repository[0] == '\0')
830	    which |= W_LOCAL;
831	else
832	{
833	    if (save_cwd (&cwd))
834		error_exit ();
835	    if ( CVS_CHDIR (repository) < 0)
836		error (1, errno, "cannot change to %s directory", repository);
837	}
838    }
839
840    err = start_recursion (val_fileproc, (FILESDONEPROC) NULL,
841			   val_direntproc, (DIRLEAVEPROC) NULL,
842			   (void *)&the_val_args,
843			   argc, argv, local, which, aflag,
844			   1, NULL, 1);
845    if (repository != NULL && repository[0] != '\0')
846    {
847	if (restore_cwd (&cwd, NULL))
848	    exit (EXIT_FAILURE);
849	free_cwd (&cwd);
850    }
851
852    if (!the_val_args.found)
853	error (1, 0, "no such tag %s", name);
854    else
855    {
856	/* The tags is valid but not mentioned in val-tags.  Add it.  */
857	datum value;
858
859	if (noexec)
860	{
861	    if (db != NULL)
862		dbm_close (db);
863	    free (valtags_filename);
864	    return;
865	}
866
867	if (db == NULL)
868	{
869	    mode_t omask;
870	    omask = umask (cvsumask);
871	    db = dbm_open (valtags_filename, O_RDWR | O_CREAT | O_TRUNC, 0666);
872	    (void) umask (omask);
873
874	    if (db == NULL)
875	    {
876		error (0, errno, "cannot create %s", valtags_filename);
877		free (valtags_filename);
878		return;
879	    }
880	}
881	value.dptr = "y";
882	value.dsize = 1;
883	if (dbm_store (db, mytag, value, DBM_REPLACE) < 0)
884	    error (0, errno, "cannot store %s into %s", name,
885		   valtags_filename);
886	dbm_close (db);
887    }
888    free (valtags_filename);
889}
890
891/*
892 * Check whether a join tag is valid.  This is just like
893 * tag_check_valid, but we must stop before the colon if there is one.
894 */
895
896void
897tag_check_valid_join (join_tag, argc, argv, local, aflag, repository)
898     char *join_tag;
899     int argc;
900     char **argv;
901     int local;
902     int aflag;
903     char *repository;
904{
905    char *c, *s;
906
907    c = xstrdup (join_tag);
908    s = strchr (c, ':');
909    if (s != NULL)
910    {
911        if (isdigit ((unsigned char) join_tag[0]))
912	    error (1, 0,
913		   "Numeric join tag %s may not contain a date specifier",
914		   join_tag);
915
916        *s = '\0';
917	/* hmmm...  I think it makes sense to allow -j:<date>, but
918	 * for now this fixes a bug where CVS just spins and spins (I
919	 * think in the RCS code) looking for a zero length tag.
920	 */
921	if (!*c)
922	    error (1, 0,
923		   "argument to join may not contain a date specifier without a tag");
924    }
925
926    tag_check_valid (c, argc, argv, local, aflag, repository);
927
928    free (c);
929}
930