tag.c revision 26065
117721Speter/*
217721Speter * Copyright (c) 1992, Brian Berliner and Jeff Polk
317721Speter * Copyright (c) 1989-1992, Brian Berliner
417721Speter *
517721Speter * You may distribute under the terms of the GNU General Public License as
617721Speter * specified in the README file that comes with the CVS 1.4 kit.
717721Speter *
817721Speter * Tag
917721Speter *
1017721Speter * Add or delete a symbolic name to an RCS file, or a collection of RCS files.
1117721Speter * Uses the checked out revision in the current directory.
1217721Speter */
1317721Speter
1417721Speter#include "cvs.h"
1517721Speter#include "savecwd.h"
1617721Speter
1725839Speterstatic int check_fileproc PROTO ((void *callerdat, struct file_info *finfo));
1825839Speterstatic int check_filesdoneproc PROTO ((void *callerdat, int err,
1925839Speter				       char *repos, char *update_dir,
2025839Speter				       List *entries));
2117721Speterstatic int pretag_proc PROTO((char *repository, char *filter));
2217721Speterstatic void masterlist_delproc PROTO((Node *p));
2317721Speterstatic void tag_delproc PROTO((Node *p));
2417721Speterstatic int pretag_list_proc PROTO((Node *p, void *closure));
2517721Speter
2625839Speterstatic Dtype tag_dirproc PROTO ((void *callerdat, char *dir,
2725839Speter				 char *repos, char *update_dir,
2825839Speter				 List *entries));
2925839Speterstatic int tag_fileproc PROTO ((void *callerdat, struct file_info *finfo));
3025839Speterstatic int tag_filesdoneproc PROTO ((void *callerdat, int err,
3125839Speter				     char *repos, char *update_dir,
3225839Speter				     List *entries));
3317721Speter
3417721Speterstatic char *numtag;
3517721Speterstatic char *date = NULL;
3617721Speterstatic char *symtag;
3717721Speterstatic int delete_flag;			/* adding a tag by default */
3817721Speterstatic int branch_mode;			/* make an automagic "branch" tag */
3917721Speterstatic int local;			/* recursive by default */
4017721Speterstatic int force_tag_match = 1;         /* force tag to match by default */
4117721Speterstatic int force_tag_move;		/* don't force tag to move by default */
4225839Speterstatic int check_uptodate;		/* no uptodate-check by default */
4317721Speter
4417721Speterstruct tag_info
4517721Speter{
4617721Speter    Ctype status;
4717721Speter    char *rev;
4817721Speter    char *tag;
4917721Speter    char *options;
5017721Speter};
5117721Speter
5217721Speterstruct master_lists
5317721Speter{
5417721Speter    List *tlist;
5517721Speter};
5617721Speter
5717721Speterstatic List *mtlist;
5817721Speterstatic List *tlist;
5917721Speter
6017721Speterstatic const char *const tag_usage[] =
6117721Speter{
6225839Speter    "Usage: %s %s [-lRF] [-b] [-d] [-c] [-r tag|-D date] tag [files...]\n",
6317721Speter    "\t-l\tLocal directory only, not recursive.\n",
6417721Speter    "\t-R\tProcess directories recursively.\n",
6525839Speter    "\t-d\tDelete the given tag.\n",
6617721Speter    "\t-[rD]\tExisting tag or date.\n",
6725839Speter    "\t-f\tForce a head revision if specified tag not found.\n",
6817721Speter    "\t-b\tMake the tag a \"branch\" tag, allowing concurrent development.\n",
6925839Speter    "\t-F\tMove tag if it already exists.\n",
7025839Speter    "\t-c\tCheck that working files are unmodified.\n",
7117721Speter    NULL
7217721Speter};
7317721Speter
7417721Speterint
7525839Spetercvstag (argc, argv)
7617721Speter    int argc;
7717721Speter    char **argv;
7817721Speter{
7917721Speter    int c;
8017721Speter    int err = 0;
8117721Speter
8217721Speter    if (argc == -1)
8317721Speter	usage (tag_usage);
8417721Speter
8526065Speter    optind = 0;
8625839Speter    while ((c = getopt (argc, argv, "+FQqlRcdr:D:bf")) != -1)
8717721Speter    {
8817721Speter	switch (c)
8917721Speter	{
9017721Speter	    case 'Q':
9117721Speter	    case 'q':
9217721Speter#ifdef SERVER_SUPPORT
9317721Speter		/* The CVS 1.5 client sends these options (in addition to
9417721Speter		   Global_option requests), so we must ignore them.  */
9517721Speter		if (!server_active)
9617721Speter#endif
9717721Speter		    error (1, 0,
9817721Speter			   "-q or -Q must be specified before \"%s\"",
9917721Speter			   command_name);
10017721Speter		break;
10117721Speter	    case 'l':
10217721Speter		local = 1;
10317721Speter		break;
10417721Speter	    case 'R':
10517721Speter		local = 0;
10617721Speter		break;
10717721Speter	    case 'd':
10817721Speter		delete_flag = 1;
10917721Speter		break;
11025839Speter	    case 'c':
11125839Speter		check_uptodate = 1;
11225839Speter		break;
11317721Speter            case 'r':
11417721Speter                numtag = optarg;
11517721Speter                break;
11617721Speter            case 'D':
11717721Speter                if (date)
11817721Speter                    free (date);
11917721Speter                date = Make_Date (optarg);
12017721Speter                break;
12117721Speter	    case 'f':
12217721Speter		force_tag_match = 0;
12317721Speter		break;
12417721Speter	    case 'b':
12517721Speter		branch_mode = 1;
12617721Speter		break;
12717721Speter            case 'F':
12817721Speter		force_tag_move = 1;
12917721Speter		break;
13017721Speter	    case '?':
13117721Speter	    default:
13217721Speter		usage (tag_usage);
13317721Speter		break;
13417721Speter	}
13517721Speter    }
13617721Speter    argc -= optind;
13717721Speter    argv += optind;
13817721Speter
13917721Speter    if (argc == 0)
14017721Speter	usage (tag_usage);
14117721Speter    symtag = argv[0];
14217721Speter    argc--;
14317721Speter    argv++;
14417721Speter
14517721Speter    if (date && numtag)
14617721Speter	error (1, 0, "-r and -D options are mutually exclusive");
14717721Speter    if (delete_flag && branch_mode)
14817721Speter	error (0, 0, "warning: -b ignored with -d options");
14917721Speter    RCS_check_tag (symtag);
15017721Speter
15117721Speter#ifdef CLIENT_SUPPORT
15217721Speter    if (client_active)
15317721Speter    {
15417721Speter	/* We're the client side.  Fire up the remote server.  */
15517721Speter	start_server ();
15617721Speter
15717721Speter	ign_setup ();
15817721Speter
15925839Speter	if (!force_tag_match)
16025839Speter	    send_arg ("-f");
16117721Speter	if (local)
16217721Speter	    send_arg("-l");
16317721Speter	if (delete_flag)
16417721Speter	    send_arg("-d");
16525839Speter	if (check_uptodate)
16625839Speter	    send_arg("-c");
16717721Speter	if (branch_mode)
16817721Speter	    send_arg("-b");
16917721Speter	if (force_tag_move)
17017721Speter	    send_arg("-F");
17117721Speter
17217721Speter	if (numtag)
17317721Speter	    option_with_arg ("-r", numtag);
17417721Speter	if (date)
17517721Speter	    client_senddate (date);
17617721Speter
17717721Speter	send_arg (symtag);
17817721Speter
17917721Speter	send_file_names (argc, argv, SEND_EXPAND_WILD);
18025839Speter
18125839Speter	/* SEND_NO_CONTENTS has a mildly bizarre interaction with
18225839Speter	   check_uptodate; if the timestamp is modified but the file
18325839Speter	   is unmodified, the check will fail, only to have "cvs diff"
18425839Speter	   show no differences (and one must do "update" or something to
18525839Speter	   reset the client's notion of the timestamp).  */
18625839Speter
18725839Speter	send_files (argc, argv, local, 0, SEND_NO_CONTENTS);
18817721Speter	send_to_server ("tag\012", 0);
18917721Speter        return get_responses_and_close ();
19017721Speter    }
19117721Speter#endif
19217721Speter
19317721Speter    if (numtag != NULL)
19417721Speter	tag_check_valid (numtag, argc, argv, local, 0, "");
19517721Speter
19617721Speter    /* check to make sure they are authorized to tag all the
19717721Speter       specified files in the repository */
19817721Speter
19917721Speter    mtlist = getlist();
20017721Speter    err = start_recursion (check_fileproc, check_filesdoneproc,
20125839Speter                           (DIRENTPROC) NULL, (DIRLEAVEPROC) NULL, NULL,
20217721Speter                           argc, argv, local, W_LOCAL, 0, 1,
20325839Speter                           (char *) NULL, 1);
20417721Speter
20517721Speter    if (err)
20617721Speter    {
20717721Speter       error (1, 0, "correct the above errors first!");
20817721Speter    }
20917721Speter
21017721Speter    /* start the recursion processor */
21125839Speter    err = start_recursion (tag_fileproc, tag_filesdoneproc, tag_dirproc,
21225839Speter			   (DIRLEAVEPROC) NULL, NULL, argc, argv, local,
21325839Speter			   W_LOCAL, 0, 0, (char *) NULL, 1);
21417721Speter    dellist(&mtlist);
21517721Speter    return (err);
21617721Speter}
21717721Speter
21817721Speter/* check file that is to be tagged */
21917721Speter/* All we do here is add it to our list */
22017721Speter
22117721Speterstatic int
22225839Spetercheck_fileproc (callerdat, finfo)
22325839Speter    void *callerdat;
22417721Speter    struct file_info *finfo;
22517721Speter{
22617721Speter    char *xdir;
22717721Speter    Node *p;
22817721Speter    Vers_TS *vers;
22917721Speter
23025839Speter    if (check_uptodate)
23125839Speter    {
23225839Speter	Ctype status = Classify_File (finfo, (char *) NULL, (char *) NULL,
23325839Speter				      (char *) NULL, 1, 0, &vers, 0);
23425839Speter	if ((status != T_UPTODATE) && (status != T_CHECKOUT))
23525839Speter	{
23625839Speter	    error (0, 0, "%s is locally modified", finfo->fullname);
23725839Speter	    return (1);
23825839Speter	}
23925839Speter    }
24025839Speter
24117721Speter    if (finfo->update_dir[0] == '\0')
24217721Speter	xdir = ".";
24317721Speter    else
24417721Speter	xdir = finfo->update_dir;
24517721Speter    if ((p = findnode (mtlist, xdir)) != NULL)
24617721Speter    {
24717721Speter	tlist = ((struct master_lists *) p->data)->tlist;
24817721Speter    }
24917721Speter    else
25017721Speter    {
25117721Speter	struct master_lists *ml;
25217721Speter
25317721Speter	tlist = getlist ();
25417721Speter	p = getnode ();
25517721Speter	p->key = xstrdup (xdir);
25617721Speter	p->type = UPDATE;
25717721Speter	ml = (struct master_lists *)
25817721Speter	    xmalloc (sizeof (struct master_lists));
25917721Speter	ml->tlist = tlist;
26017721Speter	p->data = (char *) ml;
26117721Speter	p->delproc = masterlist_delproc;
26217721Speter	(void) addnode (mtlist, p);
26317721Speter    }
26417721Speter    /* do tlist */
26517721Speter    p = getnode ();
26617721Speter    p->key = xstrdup (finfo->file);
26717721Speter    p->type = UPDATE;
26817721Speter    p->delproc = tag_delproc;
26925839Speter    vers = Version_TS (finfo, NULL, NULL, NULL, 0, 0);
27017721Speter    if (vers->srcfile == NULL)
27117721Speter    {
27217721Speter        if (!really_quiet)
27317721Speter	    error (0, 0, "nothing known about %s", finfo->file);
27417721Speter	return (1);
27517721Speter    }
27625839Speter    p->data = RCS_getversion(vers->srcfile, numtag, date, force_tag_match,
27725839Speter			     (int *) NULL);
27817721Speter    if (p->data != NULL)
27917721Speter    {
28017721Speter        int addit = 1;
28117721Speter        char *oversion;
28217721Speter
28325839Speter        oversion = RCS_getversion (vers->srcfile, symtag, (char *) NULL, 1,
28425839Speter				   (int *) NULL);
28517721Speter        if (oversion == NULL)
28617721Speter        {
28717721Speter            if (delete_flag)
28817721Speter            {
28917721Speter                addit = 0;
29017721Speter            }
29117721Speter        }
29217721Speter        else if (strcmp(oversion, p->data) == 0)
29317721Speter        {
29417721Speter            addit = 0;
29517721Speter        }
29617721Speter        else if (!force_tag_move)
29717721Speter        {
29817721Speter            addit = 0;
29917721Speter        }
30017721Speter        if (oversion != NULL)
30117721Speter        {
30217721Speter            free(oversion);
30317721Speter        }
30417721Speter        if (!addit)
30517721Speter        {
30617721Speter            free(p->data);
30717721Speter            p->data = NULL;
30817721Speter        }
30917721Speter    }
31017721Speter    freevers_ts(&vers);
31117721Speter    (void) addnode (tlist, p);
31217721Speter    return (0);
31317721Speter}
31417721Speter
31517721Speterstatic int
31625839Spetercheck_filesdoneproc (callerdat, err, repos, update_dir, entries)
31725839Speter    void *callerdat;
31817721Speter    int err;
31917721Speter    char *repos;
32017721Speter    char *update_dir;
32125839Speter    List *entries;
32217721Speter{
32317721Speter    int n;
32417721Speter    Node *p;
32517721Speter
32617721Speter    p = findnode(mtlist, update_dir);
32717721Speter    if (p != NULL)
32817721Speter    {
32917721Speter        tlist = ((struct master_lists *) p->data)->tlist;
33017721Speter    }
33117721Speter    else
33217721Speter    {
33317721Speter        tlist = (List *) NULL;
33417721Speter    }
33517721Speter    if ((tlist == NULL) || (tlist->list->next == tlist->list))
33617721Speter    {
33717721Speter        return (err);
33817721Speter    }
33917721Speter    if ((n = Parse_Info(CVSROOTADM_TAGINFO, repos, pretag_proc, 1)) > 0)
34017721Speter    {
34117721Speter        error (0, 0, "Pre-tag check failed");
34217721Speter        err += n;
34317721Speter    }
34417721Speter    return (err);
34517721Speter}
34617721Speter
34717721Speterstatic int
34817721Speterpretag_proc(repository, filter)
34917721Speter    char *repository;
35017721Speter    char *filter;
35117721Speter{
35217721Speter    if (filter[0] == '/')
35317721Speter    {
35417721Speter        char *s, *cp;
35517721Speter
35617721Speter        s = xstrdup(filter);
35717721Speter        for (cp=s; *cp; cp++)
35817721Speter        {
35917721Speter            if (isspace(*cp))
36017721Speter            {
36117721Speter                *cp = '\0';
36217721Speter                break;
36317721Speter            }
36417721Speter        }
36517721Speter        if (!isfile(s))
36617721Speter        {
36717721Speter            error (0, errno, "cannot find pre-tag filter '%s'", s);
36817721Speter            free(s);
36917721Speter            return (1);
37017721Speter        }
37117721Speter        free(s);
37217721Speter    }
37317721Speter    run_setup("%s %s %s %s",
37417721Speter              filter,
37517721Speter              symtag,
37617721Speter              delete_flag ? "del" : force_tag_move ? "mov" : "add",
37717721Speter              repository);
37817721Speter    walklist(tlist, pretag_list_proc, NULL);
37917721Speter    return (run_exec(RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL|RUN_REALLY));
38017721Speter}
38117721Speter
38217721Speterstatic void
38317721Spetermasterlist_delproc(p)
38417721Speter    Node *p;
38517721Speter{
38617721Speter    struct master_lists *ml;
38717721Speter
38817721Speter    ml = (struct master_lists *)p->data;
38917721Speter    dellist(&ml->tlist);
39017721Speter    free(ml);
39117721Speter    return;
39217721Speter}
39317721Speter
39417721Speterstatic void
39517721Spetertag_delproc(p)
39617721Speter    Node *p;
39717721Speter{
39817721Speter    if (p->data != NULL)
39917721Speter    {
40017721Speter        free(p->data);
40117721Speter        p->data = NULL;
40217721Speter    }
40317721Speter    return;
40417721Speter}
40517721Speter
40617721Speterstatic int
40717721Speterpretag_list_proc(p, closure)
40817721Speter    Node *p;
40917721Speter    void *closure;
41017721Speter{
41117721Speter    if (p->data != NULL)
41217721Speter    {
41317721Speter        run_arg(p->key);
41417721Speter        run_arg(p->data);
41517721Speter    }
41617721Speter    return (0);
41717721Speter}
41817721Speter
41917721Speter
42017721Speter/*
42117721Speter * Called to tag a particular file (the currently checked out version is
42217721Speter * tagged with the specified tag - or the specified tag is deleted).
42317721Speter */
42417721Speter/* ARGSUSED */
42517721Speterstatic int
42625839Spetertag_fileproc (callerdat, finfo)
42725839Speter    void *callerdat;
42817721Speter    struct file_info *finfo;
42917721Speter{
43017721Speter    char *version, *oversion;
43117721Speter    char *nversion = NULL;
43217721Speter    char *rev;
43317721Speter    Vers_TS *vers;
43417721Speter    int retcode = 0;
43517721Speter
43625839Speter    /* Lock the directory if it is not already locked.  We can't rely
43725839Speter       on tag_dirproc because it won't handle the case where the user
43825839Speter       specifies a list of files on the command line.  */
43925839Speter    /* We do not need to acquire a full write lock for the tag operation:
44025839Speter       the revisions are obtained from the working directory, so we do not
44125839Speter       require consistency across the entire repository.  However, we do
44225839Speter       need to prevent simultaneous tag operations from interfering with
44325839Speter       each other.  Therefore, we write lock each directory as we enter
44425839Speter       it, and unlock it as we leave it.  */
44525839Speter    lock_dir_for_write (finfo->repository);
44617721Speter
44725839Speter    vers = Version_TS (finfo, NULL, NULL, NULL, 0, 0);
44825839Speter
44917721Speter    if ((numtag != NULL) || (date != NULL))
45017721Speter    {
45117721Speter        nversion = RCS_getversion(vers->srcfile,
45217721Speter                                  numtag,
45317721Speter                                  date,
45425839Speter                                  force_tag_match,
45525839Speter				  (int *) NULL);
45617721Speter        if (nversion == NULL)
45717721Speter        {
45817721Speter	    freevers_ts (&vers);
45917721Speter            return (0);
46017721Speter        }
46117721Speter    }
46217721Speter    if (delete_flag)
46317721Speter    {
46417721Speter
46517721Speter	/*
46617721Speter	 * If -d is specified, "force_tag_match" is set, so that this call to
46717721Speter	 * RCS_getversion() will return a NULL version string if the symbolic
46817721Speter	 * tag does not exist in the RCS file.
46917721Speter	 *
47017721Speter	 * This is done here because it's MUCH faster than just blindly calling
47117721Speter	 * "rcs" to remove the tag... trust me.
47217721Speter	 */
47317721Speter
47425839Speter	version = RCS_getversion (vers->srcfile, symtag, (char *) NULL, 1,
47525839Speter				  (int *) NULL);
47617721Speter	if (version == NULL || vers->srcfile == NULL)
47717721Speter	{
47817721Speter	    freevers_ts (&vers);
47917721Speter	    return (0);
48017721Speter	}
48117721Speter	free (version);
48217721Speter
48325839Speter	if ((retcode = RCS_deltag(vers->srcfile, symtag, 1)) != 0)
48417721Speter	{
48517721Speter	    if (!quiet)
48617721Speter		error (0, retcode == -1 ? errno : 0,
48717721Speter		       "failed to remove tag %s from %s", symtag,
48817721Speter		       vers->srcfile->path);
48917721Speter	    freevers_ts (&vers);
49017721Speter	    return (1);
49117721Speter	}
49217721Speter
49317721Speter	/* warm fuzzies */
49417721Speter	if (!really_quiet)
49517721Speter	{
49625839Speter	    cvs_output ("D ", 2);
49725839Speter	    cvs_output (finfo->fullname, 0);
49825839Speter	    cvs_output ("\n", 1);
49917721Speter	}
50017721Speter
50117721Speter	freevers_ts (&vers);
50217721Speter	return (0);
50317721Speter    }
50417721Speter
50517721Speter    /*
50617721Speter     * If we are adding a tag, we need to know which version we have checked
50717721Speter     * out and we'll tag that version.
50817721Speter     */
50917721Speter    if (nversion == NULL)
51017721Speter    {
51117721Speter        version = vers->vn_user;
51217721Speter    }
51317721Speter    else
51417721Speter    {
51517721Speter        version = nversion;
51617721Speter    }
51717721Speter    if (version == NULL)
51817721Speter    {
51917721Speter	freevers_ts (&vers);
52017721Speter	return (0);
52117721Speter    }
52217721Speter    else if (strcmp (version, "0") == 0)
52317721Speter    {
52417721Speter	if (!quiet)
52517721Speter	    error (0, 0, "couldn't tag added but un-commited file `%s'", finfo->file);
52617721Speter	freevers_ts (&vers);
52717721Speter	return (0);
52817721Speter    }
52917721Speter    else if (version[0] == '-')
53017721Speter    {
53117721Speter	if (!quiet)
53217721Speter	    error (0, 0, "skipping removed but un-commited file `%s'", finfo->file);
53317721Speter	freevers_ts (&vers);
53417721Speter	return (0);
53517721Speter    }
53617721Speter    else if (vers->srcfile == NULL)
53717721Speter    {
53817721Speter	if (!quiet)
53917721Speter	    error (0, 0, "cannot find revision control file for `%s'", finfo->file);
54017721Speter	freevers_ts (&vers);
54117721Speter	return (0);
54217721Speter    }
54317721Speter
54417721Speter    /*
54517721Speter     * As an enhancement for the case where a tag is being re-applied to a
54617721Speter     * large number of files, make one extra call to RCS_getversion to see
54717721Speter     * if the tag is already set in the RCS file.  If so, check to see if it
54817721Speter     * needs to be moved.  If not, do nothing.  This will likely save a lot of
54917721Speter     * time when simply moving the tag to the "current" head revisions of a
55017721Speter     * module -- which I have found to be a typical tagging operation.
55117721Speter     */
55217721Speter    rev = branch_mode ? RCS_magicrev (vers->srcfile, version) : version;
55325839Speter    oversion = RCS_getversion (vers->srcfile, symtag, (char *) NULL, 1,
55425839Speter			       (int *) NULL);
55517721Speter    if (oversion != NULL)
55617721Speter    {
55717721Speter	int isbranch = RCS_isbranch (finfo->rcs, symtag);
55817721Speter
55917721Speter	/*
56017721Speter	 * if versions the same and neither old or new are branches don't have
56117721Speter	 * to do anything
56217721Speter	 */
56317721Speter	if (strcmp (version, oversion) == 0 && !branch_mode && !isbranch)
56417721Speter	{
56517721Speter	    free (oversion);
56617721Speter	    freevers_ts (&vers);
56717721Speter	    return (0);
56817721Speter	}
56917721Speter
57017721Speter	if (!force_tag_move)
57117721Speter	{
57217721Speter	    /* we're NOT going to move the tag */
57325839Speter	    cvs_output ("W ", 2);
57425839Speter	    cvs_output (finfo->fullname, 0);
57525839Speter	    cvs_output (" : ", 0);
57625839Speter	    cvs_output (symtag, 0);
57725839Speter	    cvs_output (" already exists on ", 0);
57825839Speter	    cvs_output (isbranch ? "branch" : "version", 0);
57925839Speter	    cvs_output (" ", 0);
58025839Speter	    cvs_output (oversion, 0);
58125839Speter	    cvs_output (" : NOT MOVING tag to ", 0);
58225839Speter	    cvs_output (branch_mode ? "branch" : "version", 0);
58325839Speter	    cvs_output (" ", 0);
58425839Speter	    cvs_output (rev, 0);
58525839Speter	    cvs_output ("\n", 1);
58617721Speter	    free (oversion);
58717721Speter	    freevers_ts (&vers);
58817721Speter	    return (0);
58917721Speter	}
59017721Speter	free (oversion);
59117721Speter    }
59217721Speter
59325839Speter    if ((retcode = RCS_settag(vers->srcfile, symtag, rev)) != 0)
59417721Speter    {
59517721Speter	error (1, retcode == -1 ? errno : 0,
59617721Speter	       "failed to set tag %s to revision %s in %s",
59717721Speter	       symtag, rev, vers->srcfile->path);
59817721Speter	freevers_ts (&vers);
59917721Speter	return (1);
60017721Speter    }
60117721Speter
60217721Speter    /* more warm fuzzies */
60317721Speter    if (!really_quiet)
60417721Speter    {
60525839Speter	cvs_output ("T ", 2);
60625839Speter	cvs_output (finfo->fullname, 0);
60725839Speter	cvs_output ("\n", 1);
60817721Speter    }
60917721Speter
61017721Speter    if (nversion != NULL)
61117721Speter    {
61217721Speter        free (nversion);
61317721Speter    }
61417721Speter    freevers_ts (&vers);
61517721Speter    return (0);
61617721Speter}
61717721Speter
61825839Speter/* Clear any lock we may hold on the current directory.  */
61925839Speter
62025839Speterstatic int
62125839Spetertag_filesdoneproc (callerdat, err, repos, update_dir, entries)
62225839Speter    void *callerdat;
62325839Speter    int err;
62425839Speter    char *repos;
62525839Speter    char *update_dir;
62625839Speter    List *entries;
62725839Speter{
62825839Speter    Lock_Cleanup ();
62925839Speter
63025839Speter    return (err);
63125839Speter}
63225839Speter
63317721Speter/*
63417721Speter * Print a warm fuzzy message
63517721Speter */
63617721Speter/* ARGSUSED */
63717721Speterstatic Dtype
63825839Spetertag_dirproc (callerdat, dir, repos, update_dir, entries)
63925839Speter    void *callerdat;
64017721Speter    char *dir;
64117721Speter    char *repos;
64217721Speter    char *update_dir;
64325839Speter    List *entries;
64417721Speter{
64517721Speter    if (!quiet)
64617721Speter	error (0, 0, "%s %s", delete_flag ? "Untagging" : "Tagging", update_dir);
64717721Speter    return (R_PROCESS);
64817721Speter}
64917721Speter
65017721Speter/* Code relating to the val-tags file.  Note that this file has no way
65117721Speter   of knowing when a tag has been deleted.  The problem is that there
65217721Speter   is no way of knowing whether a tag still exists somewhere, when we
65317721Speter   delete it some places.  Using per-directory val-tags files (in
65417721Speter   CVSREP) might be better, but that might slow down the process of
65517721Speter   verifying that a tag is correct (maybe not, for the likely cases,
65617721Speter   if carefully done), and/or be harder to implement correctly.  */
65717721Speter
65817721Speterstruct val_args {
65917721Speter    char *name;
66017721Speter    int found;
66117721Speter};
66217721Speter
66325839Speterstatic int val_fileproc PROTO ((void *callerdat, struct file_info *finfo));
66417721Speter
66517721Speterstatic int
66625839Speterval_fileproc (callerdat, finfo)
66725839Speter    void *callerdat;
66817721Speter    struct file_info *finfo;
66917721Speter{
67017721Speter    RCSNode *rcsdata;
67125839Speter    struct val_args *args = (struct val_args *)callerdat;
67217721Speter    char *tag;
67317721Speter
67417721Speter    if ((rcsdata = finfo->rcs) == NULL)
67517721Speter	/* Not sure this can happen, after all we passed only
67617721Speter	   W_REPOS | W_ATTIC.  */
67717721Speter	return 0;
67817721Speter
67925839Speter    tag = RCS_gettag (rcsdata, args->name, 1, (int *) NULL);
68017721Speter    if (tag != NULL)
68117721Speter    {
68217721Speter	/* FIXME: should find out a way to stop the search at this point.  */
68317721Speter	args->found = 1;
68417721Speter	free (tag);
68517721Speter    }
68617721Speter    return 0;
68717721Speter}
68817721Speter
68925839Speterstatic Dtype val_direntproc PROTO ((void *, char *, char *, char *, List *));
69017721Speter
69117721Speterstatic Dtype
69225839Speterval_direntproc (callerdat, dir, repository, update_dir, entries)
69325839Speter    void *callerdat;
69417721Speter    char *dir;
69517721Speter    char *repository;
69617721Speter    char *update_dir;
69725839Speter    List *entries;
69817721Speter{
69917721Speter    /* This is not quite right--it doesn't get right the case of "cvs
70017721Speter       update -d -r foobar" where foobar is a tag which exists only in
70117721Speter       files in a directory which does not exist yet, but which is
70217721Speter       about to be created.  */
70317721Speter    if (isdir (dir))
70417721Speter	return 0;
70517721Speter    return R_SKIP_ALL;
70617721Speter}
70717721Speter
70817721Speter/* Check to see whether NAME is a valid tag.  If so, return.  If not
70917721Speter   print an error message and exit.  ARGC, ARGV, LOCAL, and AFLAG specify
71017721Speter   which files we will be operating on.
71117721Speter
71217721Speter   REPOSITORY is the repository if we need to cd into it, or NULL if
71317721Speter   we are already there, or "" if we should do a W_LOCAL recursion.
71417721Speter   Sorry for three cases, but the "" case is needed in case the
71517721Speter   working directories come from diverse parts of the repository, the
71617721Speter   NULL case avoids an unneccesary chdir, and the non-NULL, non-""
71717721Speter   case is needed for checkout, where we don't want to chdir if the
71817721Speter   tag is found in CVSROOTADM_VALTAGS, but there is not (yet) any
71917721Speter   local directory.  */
72017721Spetervoid
72117721Spetertag_check_valid (name, argc, argv, local, aflag, repository)
72217721Speter    char *name;
72317721Speter    int argc;
72417721Speter    char **argv;
72517721Speter    int local;
72617721Speter    int aflag;
72717721Speter    char *repository;
72817721Speter{
72917721Speter    DBM *db;
73017721Speter    char *valtags_filename;
73117721Speter    int err;
73217721Speter    datum mytag;
73317721Speter    struct val_args the_val_args;
73417721Speter    struct saved_cwd cwd;
73517721Speter    int which;
73617721Speter
73717721Speter    /* Numeric tags require only a syntactic check.  */
73817721Speter    if (isdigit (name[0]))
73917721Speter    {
74017721Speter	char *p;
74117721Speter	for (p = name; *p != '\0'; ++p)
74217721Speter	{
74317721Speter	    if (!(isdigit (*p) || *p == '.'))
74417721Speter		error (1, 0, "\
74517721SpeterNumeric tag %s contains characters other than digits and '.'", name);
74617721Speter	}
74717721Speter	return;
74817721Speter    }
74917721Speter
75025839Speter    /* Special tags are always valid.  */
75125839Speter    if (strcmp (name, TAG_BASE) == 0
75225839Speter	|| strcmp (name, TAG_HEAD) == 0)
75325839Speter	return;
75425839Speter
75517721Speter    mytag.dptr = name;
75617721Speter    mytag.dsize = strlen (name);
75717721Speter
75825839Speter    valtags_filename = xmalloc (strlen (CVSroot_directory)
75925839Speter				+ sizeof CVSROOTADM
76025839Speter				+ sizeof CVSROOTADM_VALTAGS + 20);
76125839Speter    strcpy (valtags_filename, CVSroot_directory);
76217721Speter    strcat (valtags_filename, "/");
76317721Speter    strcat (valtags_filename, CVSROOTADM);
76417721Speter    strcat (valtags_filename, "/");
76517721Speter    strcat (valtags_filename, CVSROOTADM_VALTAGS);
76617721Speter    db = dbm_open (valtags_filename, O_RDWR, 0666);
76717721Speter    if (db == NULL)
76817721Speter    {
76917721Speter	if (!existence_error (errno))
77017721Speter	    error (1, errno, "cannot read %s", valtags_filename);
77117721Speter
77217721Speter	/* If the file merely fails to exist, we just keep going and create
77317721Speter	   it later if need be.  */
77417721Speter    }
77517721Speter    else
77617721Speter    {
77717721Speter	datum val;
77817721Speter
77917721Speter	val = dbm_fetch (db, mytag);
78017721Speter	if (val.dptr != NULL)
78117721Speter	{
78217721Speter	    /* Found.  The tag is valid.  */
78317721Speter	    dbm_close (db);
78417721Speter	    free (valtags_filename);
78517721Speter	    return;
78617721Speter	}
78717721Speter	/* FIXME: should check errors somehow (add dbm_error to myndbm.c?).  */
78817721Speter    }
78917721Speter
79017721Speter    /* We didn't find the tag in val-tags, so look through all the RCS files
79117721Speter       to see whether it exists there.  Yes, this is expensive, but there
79217721Speter       is no other way to cope with a tag which might have been created
79325839Speter       by an old version of CVS, from before val-tags was invented.
79417721Speter
79525839Speter       Since we need this code anyway, we also use it to create
79625839Speter       entries in val-tags in general (that is, the val-tags entry
79725839Speter       will get created the first time the tag is used, not when the
79825839Speter       tag is created).  */
79925839Speter
80017721Speter    the_val_args.name = name;
80117721Speter    the_val_args.found = 0;
80217721Speter
80317721Speter    which = W_REPOS | W_ATTIC;
80417721Speter
80517721Speter    if (repository != NULL)
80617721Speter    {
80717721Speter	if (repository[0] == '\0')
80817721Speter	    which |= W_LOCAL;
80917721Speter	else
81017721Speter	{
81117721Speter	    if (save_cwd (&cwd))
81225839Speter		error_exit ();
81325839Speter	    if ( CVS_CHDIR (repository) < 0)
81417721Speter		error (1, errno, "cannot change to %s directory", repository);
81517721Speter	}
81617721Speter    }
81717721Speter
81817721Speter    err = start_recursion (val_fileproc, (FILESDONEPROC) NULL,
81917721Speter			   val_direntproc, (DIRLEAVEPROC) NULL,
82025839Speter			   (void *)&the_val_args,
82117721Speter			   argc, argv, local, which, aflag,
82225839Speter			   1, NULL, 1);
82317721Speter    if (repository != NULL && repository[0] != '\0')
82417721Speter    {
82517721Speter	if (restore_cwd (&cwd, NULL))
82617721Speter	    exit (EXIT_FAILURE);
82717721Speter	free_cwd (&cwd);
82817721Speter    }
82917721Speter
83017721Speter    if (!the_val_args.found)
83117721Speter	error (1, 0, "no such tag %s", name);
83217721Speter    else
83317721Speter    {
83417721Speter	/* The tags is valid but not mentioned in val-tags.  Add it.  */
83517721Speter	datum value;
83617721Speter
83717721Speter	if (noexec)
83817721Speter	{
83917721Speter	    if (db != NULL)
84017721Speter		dbm_close (db);
84117721Speter	    free (valtags_filename);
84217721Speter	    return;
84317721Speter	}
84417721Speter
84517721Speter	if (db == NULL)
84617721Speter	{
84717721Speter	    mode_t omask;
84817721Speter	    omask = umask (cvsumask);
84917721Speter	    db = dbm_open (valtags_filename, O_RDWR | O_CREAT | O_TRUNC, 0666);
85017721Speter	    (void) umask (omask);
85117721Speter
85217721Speter	    if (db == NULL)
85317721Speter	    {
85417721Speter		error (0, errno, "cannot create %s", valtags_filename);
85517721Speter		free (valtags_filename);
85617721Speter		return;
85717721Speter	    }
85817721Speter	}
85917721Speter	value.dptr = "y";
86017721Speter	value.dsize = 1;
86117721Speter	if (dbm_store (db, mytag, value, DBM_REPLACE) < 0)
86217721Speter	    error (0, errno, "cannot store %s into %s", name,
86317721Speter		   valtags_filename);
86417721Speter	dbm_close (db);
86517721Speter    }
86617721Speter    free (valtags_filename);
86717721Speter}
86825839Speter
86925839Speter/*
87025839Speter * Check whether a join tag is valid.  This is just like
87125839Speter * tag_check_valid, but we must stop before the colon if there is one.
87225839Speter */
87325839Speter
87425839Spetervoid
87525839Spetertag_check_valid_join (join_tag, argc, argv, local, aflag, repository)
87625839Speter     char *join_tag;
87725839Speter     int argc;
87825839Speter     char **argv;
87925839Speter     int local;
88025839Speter     int aflag;
88125839Speter     char *repository;
88225839Speter{
88325839Speter    char *c, *s;
88425839Speter
88525839Speter    c = xstrdup (join_tag);
88625839Speter    s = strchr (c, ':');
88725839Speter    if (s != NULL)
88825839Speter    {
88925839Speter        if (isdigit (join_tag[0]))
89025839Speter	    error (1, 0,
89125839Speter		   "Numeric join tag %s may not contain a date specifier",
89225839Speter		   join_tag);
89325839Speter
89425839Speter        *s = '\0';
89525839Speter    }
89625839Speter
89725839Speter    tag_check_valid (c, argc, argv, local, aflag, repository);
89825839Speter
89925839Speter    free (c);
90025839Speter}
901