117721Speter/*
2175270Sobrien * Copyright (C) 1986-2005 The Free Software Foundation, Inc.
3175270Sobrien *
4175270Sobrien * Portions Copyright (C) 1998-2005 Derek Price, Ximbiot <http://ximbiot.com>,
5175270Sobrien *                                  and others.
6175270Sobrien *
7175270Sobrien * Portions Copyright (C) 1992, Brian Berliner and Jeff Polk
8175270Sobrien * Portions Copyright (C) 1989-1992, Brian Berliner
917721Speter *
1017721Speter * You may distribute under the terms of the GNU General Public License as
1132785Speter * specified in the README file that comes with the CVS source distribution.
1217721Speter *
1381404Speter * Tag and Rtag
1417721Speter *
1517721Speter * Add or delete a symbolic name to an RCS file, or a collection of RCS files.
1681404Speter * Tag uses the checked out revision in the current directory, rtag uses
1781404Speter * the modules database, if necessary.
18133180Sdes *
19133180Sdes * $FreeBSD$
2017721Speter */
2117721Speter
2217721Speter#include "cvs.h"
2317721Speter#include "savecwd.h"
2417721Speter
2581404Speterstatic int rtag_proc PROTO((int argc, char **argv, char *xwhere,
2681404Speter		      char *mwhere, char *mfile, int shorten,
2781404Speter		      int local_specified, char *mname, char *msg));
2825839Speterstatic int check_fileproc PROTO ((void *callerdat, struct file_info *finfo));
2925839Speterstatic int check_filesdoneproc PROTO ((void *callerdat, int err,
30128266Speter                                       const char *repos,
31128266Speter                                       const char *update_dir,
32128266Speter                                       List *entries));
33128266Speterstatic int pretag_proc PROTO((const char *repository, const char *filter));
3417721Speterstatic void masterlist_delproc PROTO((Node *p));
3517721Speterstatic void tag_delproc PROTO((Node *p));
3617721Speterstatic int pretag_list_proc PROTO((Node *p, void *closure));
3717721Speter
38128266Speterstatic Dtype tag_dirproc PROTO ((void *callerdat, const char *dir,
39128266Speter                                 const char *repos, const char *update_dir,
40128266Speter                                 List *entries));
4181404Speterstatic int rtag_fileproc PROTO ((void *callerdat, struct file_info *finfo));
4281404Speterstatic int rtag_delete PROTO((RCSNode *rcsfile));
4325839Speterstatic int tag_fileproc PROTO ((void *callerdat, struct file_info *finfo));
4417721Speter
4581404Speterstatic char *numtag;			/* specific revision to tag */
4681404Speterstatic int numtag_validated = 0;
4717721Speterstatic char *date = NULL;
4881404Speterstatic char *symtag;			/* tag to add or delete */
4917721Speterstatic int delete_flag;			/* adding a tag by default */
5017721Speterstatic int branch_mode;			/* make an automagic "branch" tag */
51102840Speterstatic int disturb_branch_tags = 0;	/* allow -F,-d to disturb branch tags */
5281404Speterstatic int force_tag_match = 1;		/* force tag to match by default */
5317721Speterstatic int force_tag_move;		/* don't force tag to move by default */
5425839Speterstatic int check_uptodate;		/* no uptodate-check by default */
5581404Speterstatic int attic_too;			/* remove tag from Attic files */
5681404Speterstatic int is_rtag;
5717721Speter
5817721Speterstruct tag_info
5917721Speter{
6017721Speter    Ctype status;
6117721Speter    char *rev;
6217721Speter    char *tag;
6317721Speter    char *options;
6417721Speter};
6517721Speter
6617721Speterstruct master_lists
6717721Speter{
6817721Speter    List *tlist;
6917721Speter};
7017721Speter
7117721Speterstatic List *mtlist;
7217721Speterstatic List *tlist;
7317721Speter
74102840Speterstatic const char rtag_opts[] = "+aBbdFflnQqRr:D:";
7581404Speterstatic const char *const rtag_usage[] =
7617721Speter{
7781404Speter    "Usage: %s %s [-abdFflnR] [-r rev|-D date] tag modules...\n",
7881404Speter    "\t-a\tClear tag from removed files that would not otherwise be tagged.\n",
7981404Speter    "\t-b\tMake the tag a \"branch\" tag, allowing concurrent development.\n",
80102840Speter    "\t-B\tAllows -F and -d to disturb branch tags.  Use with extreme care.\n",
8181404Speter    "\t-d\tDelete the given tag.\n",
8281404Speter    "\t-F\tMove tag if it already exists.\n",
8381404Speter    "\t-f\tForce a head revision match if tag/date not found.\n",
8417721Speter    "\t-l\tLocal directory only, not recursive.\n",
8581404Speter    "\t-n\tNo execution of 'tag program'.\n",
8617721Speter    "\t-R\tProcess directories recursively.\n",
8732785Speter    "\t-r rev\tExisting revision/tag.\n",
8832785Speter    "\t-D\tExisting date.\n",
8981404Speter    "(Specify the --help global option for a list of other help options)\n",
9081404Speter    NULL
9181404Speter};
9281404Speter
93102840Speterstatic const char tag_opts[] = "+BbcdFflQqRr:D:";
9481404Speterstatic const char *const tag_usage[] =
9581404Speter{
9681404Speter    "Usage: %s %s [-bcdFflR] [-r rev|-D date] tag [files...]\n",
9717721Speter    "\t-b\tMake the tag a \"branch\" tag, allowing concurrent development.\n",
98102840Speter    "\t-B\tAllows -F and -d to disturb branch tags.  Use with extreme care.\n",
9981404Speter    "\t-c\tCheck that working files are unmodified.\n",
10081404Speter    "\t-d\tDelete the given tag.\n",
10125839Speter    "\t-F\tMove tag if it already exists.\n",
10281404Speter    "\t-f\tForce a head revision match if tag/date not found.\n",
10381404Speter    "\t-l\tLocal directory only, not recursive.\n",
10481404Speter    "\t-R\tProcess directories recursively.\n",
10581404Speter    "\t-r rev\tExisting revision/tag.\n",
10681404Speter    "\t-D\tExisting date.\n",
10732785Speter    "(Specify the --help global option for a list of other help options)\n",
10817721Speter    NULL
10917721Speter};
11017721Speter
11117721Speterint
11225839Spetercvstag (argc, argv)
11317721Speter    int argc;
11417721Speter    char **argv;
11517721Speter{
116102840Speter    int local = 0;			/* recursive by default */
11717721Speter    int c;
11817721Speter    int err = 0;
11981404Speter    int run_module_prog = 1;
12017721Speter
121128266Speter    is_rtag = (strcmp (cvs_cmd_name, "rtag") == 0);
12281404Speter
12317721Speter    if (argc == -1)
12481404Speter	usage (is_rtag ? rtag_usage : tag_usage);
12517721Speter
12626065Speter    optind = 0;
12781404Speter    while ((c = getopt (argc, argv, is_rtag ? rtag_opts : tag_opts)) != -1)
12817721Speter    {
12917721Speter	switch (c)
13017721Speter	{
13181404Speter	    case 'a':
13281404Speter		attic_too = 1;
13381404Speter		break;
13481404Speter	    case 'b':
13581404Speter		branch_mode = 1;
13681404Speter		break;
137102840Speter	    case 'B':
138102840Speter		disturb_branch_tags = 1;
139102840Speter		break;
14081404Speter	    case 'c':
14181404Speter		check_uptodate = 1;
14281404Speter		break;
14381404Speter	    case 'd':
14481404Speter		delete_flag = 1;
14581404Speter		break;
14681404Speter            case 'F':
14781404Speter		force_tag_move = 1;
14881404Speter		break;
14981404Speter	    case 'f':
15081404Speter		force_tag_match = 0;
15181404Speter		break;
15281404Speter	    case 'l':
15381404Speter		local = 1;
15481404Speter		break;
15581404Speter	    case 'n':
15681404Speter		run_module_prog = 0;
15781404Speter		break;
15817721Speter	    case 'Q':
15917721Speter	    case 'q':
16017721Speter		/* The CVS 1.5 client sends these options (in addition to
16117721Speter		   Global_option requests), so we must ignore them.  */
16217721Speter		if (!server_active)
16317721Speter		    error (1, 0,
16417721Speter			   "-q or -Q must be specified before \"%s\"",
165128266Speter			   cvs_cmd_name);
16617721Speter		break;
16717721Speter	    case 'R':
16817721Speter		local = 0;
16917721Speter		break;
17017721Speter            case 'r':
17117721Speter                numtag = optarg;
17217721Speter                break;
17317721Speter            case 'D':
17417721Speter                if (date)
17517721Speter                    free (date);
17617721Speter                date = Make_Date (optarg);
17717721Speter                break;
17817721Speter	    case '?':
17917721Speter	    default:
18081404Speter		usage (is_rtag ? rtag_usage : tag_usage);
18117721Speter		break;
18217721Speter	}
18317721Speter    }
18417721Speter    argc -= optind;
18517721Speter    argv += optind;
18617721Speter
18781404Speter    if (argc < (is_rtag ? 2 : 1))
18881404Speter	usage (is_rtag ? rtag_usage : tag_usage);
18917721Speter    symtag = argv[0];
19017721Speter    argc--;
19117721Speter    argv++;
19217721Speter
19317721Speter    if (date && numtag)
19417721Speter	error (1, 0, "-r and -D options are mutually exclusive");
19517721Speter    if (delete_flag && branch_mode)
19617721Speter	error (0, 0, "warning: -b ignored with -d options");
19717721Speter    RCS_check_tag (symtag);
19817721Speter
19917721Speter#ifdef CLIENT_SUPPORT
20081404Speter    if (current_parsed_root->isremote)
20117721Speter    {
20217721Speter	/* We're the client side.  Fire up the remote server.  */
20317721Speter	start_server ();
20417721Speter
20517721Speter	ign_setup ();
20617721Speter
20781404Speter	if (attic_too)
20881404Speter	    send_arg("-a");
20981404Speter	if (branch_mode)
21081404Speter	    send_arg("-b");
211102840Speter	if (disturb_branch_tags)
212102840Speter	    send_arg("-B");
21381404Speter	if (check_uptodate)
21481404Speter	    send_arg("-c");
21581404Speter	if (delete_flag)
21681404Speter	    send_arg("-d");
21781404Speter	if (force_tag_move)
21881404Speter	    send_arg("-F");
21925839Speter	if (!force_tag_match)
22025839Speter	    send_arg ("-f");
22117721Speter	if (local)
22217721Speter	    send_arg("-l");
22381404Speter	if (!run_module_prog)
22481404Speter	    send_arg("-n");
22517721Speter
22617721Speter	if (numtag)
22717721Speter	    option_with_arg ("-r", numtag);
22817721Speter	if (date)
22917721Speter	    client_senddate (date);
23017721Speter
231107484Speter	send_arg ("--");
232107484Speter
23317721Speter	send_arg (symtag);
23417721Speter
23581404Speter	if (is_rtag)
23681404Speter	{
23781404Speter	    int i;
23881404Speter	    for (i = 0; i < argc; ++i)
23981404Speter		send_arg (argv[i]);
24081404Speter	    send_to_server ("rtag\012", 0);
24181404Speter	}
24281404Speter	else
24381404Speter	{
24481404Speter	    send_files (argc, argv, local, 0,
24581404Speter
24654427Speter		    /* I think the -c case is like "cvs status", in
24754427Speter		       which we really better be correct rather than
24854427Speter		       being fast; it is just too confusing otherwise.  */
24981404Speter			check_uptodate ? 0 : SEND_NO_CONTENTS);
25081404Speter	    send_file_names (argc, argv, SEND_EXPAND_WILD);
25181404Speter	    send_to_server ("tag\012", 0);
25281404Speter	}
25381404Speter
25417721Speter        return get_responses_and_close ();
25517721Speter    }
25617721Speter#endif
25717721Speter
25881404Speter    if (is_rtag)
25981404Speter    {
26081404Speter	DBM *db;
26181404Speter	int i;
26281404Speter	db = open_module ();
26381404Speter	for (i = 0; i < argc; i++)
26481404Speter	{
26581404Speter	    /* XXX last arg should be repository, but doesn't make sense here */
26681404Speter	    history_write ('T', (delete_flag ? "D" : (numtag ? numtag :
26781404Speter			   (date ? date : "A"))), symtag, argv[i], "");
26881404Speter	    err += do_module (db, argv[i], TAG,
26981404Speter			      delete_flag ? "Untagging" : "Tagging",
270102840Speter			      rtag_proc, (char *) NULL, 0, local, run_module_prog,
27181404Speter			      0, symtag);
27281404Speter	}
27381404Speter	close_module (db);
27481404Speter    }
27581404Speter    else
27681404Speter    {
277102840Speter	err = rtag_proc (argc + 1, argv - 1, NULL, NULL, NULL, 0, local, NULL,
27881404Speter			 NULL);
27981404Speter    }
28017721Speter
28181404Speter    return (err);
28281404Speter}
28381404Speter
28481404Speter/*
28581404Speter * callback proc for doing the real work of tagging
28681404Speter */
28781404Speter/* ARGSUSED */
28881404Speterstatic int
28981404Speterrtag_proc (argc, argv, xwhere, mwhere, mfile, shorten, local_specified,
29081404Speter	   mname, msg)
29181404Speter    int argc;
29281404Speter    char **argv;
29381404Speter    char *xwhere;
29481404Speter    char *mwhere;
29581404Speter    char *mfile;
29681404Speter    int shorten;
29781404Speter    int local_specified;
29881404Speter    char *mname;
29981404Speter    char *msg;
30081404Speter{
30181404Speter    /* Begin section which is identical to patch_proc--should this
30281404Speter       be abstracted out somehow?  */
30381404Speter    char *myargv[2];
30481404Speter    int err = 0;
30581404Speter    int which;
30681404Speter    char *repository;
30781404Speter    char *where;
30881404Speter
30981404Speter    if (is_rtag)
31081404Speter    {
31181404Speter	repository = xmalloc (strlen (current_parsed_root->directory) + strlen (argv[0])
31281404Speter			      + (mfile == NULL ? 0 : strlen (mfile) + 1) + 2);
31381404Speter	(void) sprintf (repository, "%s/%s", current_parsed_root->directory, argv[0]);
31481404Speter	where = xmalloc (strlen (argv[0]) + (mfile == NULL ? 0 : strlen (mfile) + 1)
31581404Speter			 + 1);
31681404Speter	(void) strcpy (where, argv[0]);
31781404Speter
31881404Speter	/* if mfile isn't null, we need to set up to do only part of the module */
31981404Speter	if (mfile != NULL)
32081404Speter	{
32181404Speter	    char *cp;
32281404Speter	    char *path;
32381404Speter
32481404Speter	    /* if the portion of the module is a path, put the dir part on repos */
32581404Speter	    if ((cp = strrchr (mfile, '/')) != NULL)
32681404Speter	    {
32781404Speter		*cp = '\0';
32881404Speter		(void) strcat (repository, "/");
32981404Speter		(void) strcat (repository, mfile);
33081404Speter		(void) strcat (where, "/");
33181404Speter		(void) strcat (where, mfile);
33281404Speter		mfile = cp + 1;
33381404Speter	    }
33481404Speter
33581404Speter	    /* take care of the rest */
33681404Speter	    path = xmalloc (strlen (repository) + strlen (mfile) + 5);
33781404Speter	    (void) sprintf (path, "%s/%s", repository, mfile);
33881404Speter	    if (isdir (path))
33981404Speter	    {
34081404Speter		/* directory means repository gets the dir tacked on */
34181404Speter		(void) strcpy (repository, path);
34281404Speter		(void) strcat (where, "/");
34381404Speter		(void) strcat (where, mfile);
34481404Speter	    }
34581404Speter	    else
34681404Speter	    {
34781404Speter		myargv[0] = argv[0];
34881404Speter		myargv[1] = mfile;
34981404Speter		argc = 2;
35081404Speter		argv = myargv;
35181404Speter	    }
35281404Speter	    free (path);
35381404Speter	}
35481404Speter
35581404Speter	/* cd to the starting repository */
35681404Speter	if ( CVS_CHDIR (repository) < 0)
35781404Speter	{
35881404Speter	    error (0, errno, "cannot chdir to %s", repository);
35981404Speter	    free (repository);
360175270Sobrien	    free (where);
36181404Speter	    return (1);
36281404Speter	}
36381404Speter	/* End section which is identical to patch_proc.  */
36481404Speter
365175270Sobrien	if (delete_flag || force_tag_move || attic_too || numtag)
36681404Speter	    which = W_REPOS | W_ATTIC;
36781404Speter	else
36881404Speter	    which = W_REPOS;
36981404Speter    }
37081404Speter    else
37181404Speter    {
37281404Speter        where = NULL;
37381404Speter        which = W_LOCAL;
37481404Speter        repository = "";
37581404Speter    }
37681404Speter
37781404Speter    if (numtag != NULL && !numtag_validated)
37881404Speter    {
379102840Speter	tag_check_valid (numtag, argc - 1, argv + 1, local_specified, 0, repository);
38081404Speter	numtag_validated = 1;
38181404Speter    }
38281404Speter
38317721Speter    /* check to make sure they are authorized to tag all the
38417721Speter       specified files in the repository */
38517721Speter
38617721Speter    mtlist = getlist();
38717721Speter    err = start_recursion (check_fileproc, check_filesdoneproc,
38825839Speter                           (DIRENTPROC) NULL, (DIRLEAVEPROC) NULL, NULL,
389107484Speter			   argc - 1, argv + 1, local_specified, which, 0,
390128266Speter			   CVS_LOCK_READ, where, 1, repository);
39117721Speter
39217721Speter    if (err)
39317721Speter    {
39417721Speter       error (1, 0, "correct the above errors first!");
39517721Speter    }
39617721Speter
39781404Speter    /* It would be nice to provide consistency with respect to
39881404Speter       commits; however CVS lacks the infrastructure to do that (see
399107484Speter       Concurrency in cvs.texinfo and comment in do_recursion).  */
40081404Speter
40117721Speter    /* start the recursion processor */
40281404Speter    err = start_recursion (is_rtag ? rtag_fileproc : tag_fileproc,
40381404Speter			   (FILESDONEPROC) NULL, tag_dirproc,
40481404Speter			   (DIRLEAVEPROC) NULL, NULL, argc - 1, argv + 1,
405128266Speter			   local_specified, which, 0, CVS_LOCK_WRITE, where, 1,
406128266Speter			   repository);
407128266Speter    if ( which & W_REPOS ) free ( repository );
40881404Speter    dellist (&mtlist);
40981404Speter    if (where != NULL)
41081404Speter	free (where);
41117721Speter    return (err);
41217721Speter}
41317721Speter
41417721Speter/* check file that is to be tagged */
41517721Speter/* All we do here is add it to our list */
41617721Speter
41717721Speterstatic int
41825839Spetercheck_fileproc (callerdat, finfo)
41925839Speter    void *callerdat;
42017721Speter    struct file_info *finfo;
42117721Speter{
422128266Speter    const char *xdir;
42317721Speter    Node *p;
42417721Speter    Vers_TS *vers;
42517721Speter
42625839Speter    if (check_uptodate)
42725839Speter    {
428107484Speter	switch (Classify_File (finfo, (char *) NULL, (char *) NULL,
429107484Speter				      (char *) NULL, 1, 0, &vers, 0))
43025839Speter	{
431107484Speter	case T_UPTODATE:
432107484Speter	case T_CHECKOUT:
433107484Speter	case T_PATCH:
434107484Speter	case T_REMOVE_ENTRY:
435107484Speter	    break;
436107484Speter	case T_UNKNOWN:
437107484Speter	case T_CONFLICT:
438107484Speter	case T_NEEDS_MERGE:
439107484Speter	case T_MODIFIED:
440107484Speter	case T_ADDED:
441107484Speter	case T_REMOVED:
442107484Speter	default:
44325839Speter	    error (0, 0, "%s is locally modified", finfo->fullname);
44466525Speter	    freevers_ts (&vers);
44525839Speter	    return (1);
44625839Speter	}
44725839Speter    }
44866525Speter    else
44966525Speter	vers = Version_TS (finfo, NULL, NULL, NULL, 0, 0);
45025839Speter
45117721Speter    if (finfo->update_dir[0] == '\0')
45217721Speter	xdir = ".";
45317721Speter    else
45417721Speter	xdir = finfo->update_dir;
45517721Speter    if ((p = findnode (mtlist, xdir)) != NULL)
45617721Speter    {
45717721Speter	tlist = ((struct master_lists *) p->data)->tlist;
45817721Speter    }
45917721Speter    else
46017721Speter    {
46117721Speter	struct master_lists *ml;
46217721Speter
46317721Speter	tlist = getlist ();
46417721Speter	p = getnode ();
46517721Speter	p->key = xstrdup (xdir);
46617721Speter	p->type = UPDATE;
46717721Speter	ml = (struct master_lists *)
46817721Speter	    xmalloc (sizeof (struct master_lists));
46917721Speter	ml->tlist = tlist;
470128266Speter	p->data = ml;
47117721Speter	p->delproc = masterlist_delproc;
47217721Speter	(void) addnode (mtlist, p);
47317721Speter    }
47417721Speter    /* do tlist */
47517721Speter    p = getnode ();
47617721Speter    p->key = xstrdup (finfo->file);
47717721Speter    p->type = UPDATE;
47817721Speter    p->delproc = tag_delproc;
47917721Speter    if (vers->srcfile == NULL)
48017721Speter    {
48117721Speter        if (!really_quiet)
48217721Speter	    error (0, 0, "nothing known about %s", finfo->file);
48366525Speter	freevers_ts (&vers);
48466525Speter	freenode (p);
48517721Speter	return (1);
48617721Speter    }
48744852Speter
48844852Speter    /* Here we duplicate the calculation in tag_fileproc about which
48944852Speter       version we are going to tag.  There probably are some subtle races
49044852Speter       (e.g. numtag is "foo" which gets moved between here and
49144852Speter       tag_fileproc).  */
49281404Speter    if (!is_rtag && numtag == NULL && date == NULL)
49344852Speter	p->data = xstrdup (vers->vn_user);
49444852Speter    else
49544852Speter	p->data = RCS_getversion (vers->srcfile, numtag, date,
49644852Speter				  force_tag_match, NULL);
49744852Speter
49817721Speter    if (p->data != NULL)
49917721Speter    {
50017721Speter        int addit = 1;
50117721Speter        char *oversion;
50217721Speter
50325839Speter        oversion = RCS_getversion (vers->srcfile, symtag, (char *) NULL, 1,
50425839Speter				   (int *) NULL);
50517721Speter        if (oversion == NULL)
50617721Speter        {
50717721Speter            if (delete_flag)
50817721Speter            {
50954427Speter		/* Deleting a tag which did not exist is a noop and
51054427Speter		   should not be logged.  */
51117721Speter                addit = 0;
51217721Speter            }
51317721Speter        }
51454427Speter	else if (delete_flag)
51554427Speter	{
51654427Speter	    free (p->data);
51754427Speter	    p->data = xstrdup (oversion);
51854427Speter	}
51917721Speter        else if (strcmp(oversion, p->data) == 0)
52017721Speter        {
52117721Speter            addit = 0;
52217721Speter        }
52317721Speter        else if (!force_tag_move)
52417721Speter        {
52517721Speter            addit = 0;
52617721Speter        }
52717721Speter        if (oversion != NULL)
52817721Speter        {
52917721Speter            free(oversion);
53017721Speter        }
53117721Speter        if (!addit)
53217721Speter        {
53317721Speter            free(p->data);
53417721Speter            p->data = NULL;
53517721Speter        }
53617721Speter    }
53781404Speter    freevers_ts (&vers);
53817721Speter    (void) addnode (tlist, p);
53917721Speter    return (0);
54017721Speter}
54117721Speter
54217721Speterstatic int
54325839Spetercheck_filesdoneproc (callerdat, err, repos, update_dir, entries)
54425839Speter    void *callerdat;
54517721Speter    int err;
546128266Speter    const char *repos;
547128266Speter    const char *update_dir;
54825839Speter    List *entries;
54917721Speter{
55017721Speter    int n;
55117721Speter    Node *p;
55217721Speter
55317721Speter    p = findnode(mtlist, update_dir);
55417721Speter    if (p != NULL)
55517721Speter    {
55617721Speter        tlist = ((struct master_lists *) p->data)->tlist;
55717721Speter    }
55817721Speter    else
55917721Speter    {
56017721Speter        tlist = (List *) NULL;
56117721Speter    }
56217721Speter    if ((tlist == NULL) || (tlist->list->next == tlist->list))
56317721Speter    {
56417721Speter        return (err);
56517721Speter    }
56617721Speter    if ((n = Parse_Info(CVSROOTADM_TAGINFO, repos, pretag_proc, 1)) > 0)
56717721Speter    {
56817721Speter        error (0, 0, "Pre-tag check failed");
56917721Speter        err += n;
57017721Speter    }
57117721Speter    return (err);
57217721Speter}
57317721Speter
57417721Speterstatic int
575128266Speterpretag_proc (repository, filter)
576128266Speter    const char *repository;
577128266Speter    const char *filter;
57817721Speter{
57917721Speter    if (filter[0] == '/')
58017721Speter    {
58117721Speter        char *s, *cp;
58217721Speter
58317721Speter        s = xstrdup(filter);
58417721Speter        for (cp=s; *cp; cp++)
58517721Speter        {
58654427Speter            if (isspace ((unsigned char) *cp))
58717721Speter            {
58817721Speter                *cp = '\0';
58917721Speter                break;
59017721Speter            }
59117721Speter        }
59217721Speter        if (!isfile(s))
59317721Speter        {
59417721Speter            error (0, errno, "cannot find pre-tag filter '%s'", s);
59517721Speter            free(s);
59617721Speter            return (1);
59717721Speter        }
59817721Speter        free(s);
59917721Speter    }
60032785Speter    run_setup (filter);
60132785Speter    run_arg (symtag);
60232785Speter    run_arg (delete_flag ? "del" : force_tag_move ? "mov" : "add");
60332785Speter    run_arg (repository);
60417721Speter    walklist(tlist, pretag_list_proc, NULL);
60544852Speter    return (run_exec (RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL));
60617721Speter}
60717721Speter
60817721Speterstatic void
60917721Spetermasterlist_delproc(p)
61017721Speter    Node *p;
61117721Speter{
612128266Speter    struct master_lists *ml = p->data;
61317721Speter
61417721Speter    dellist(&ml->tlist);
61517721Speter    free(ml);
61617721Speter    return;
61717721Speter}
61817721Speter
61917721Speterstatic void
62017721Spetertag_delproc(p)
62117721Speter    Node *p;
62217721Speter{
62317721Speter    if (p->data != NULL)
62417721Speter    {
62517721Speter        free(p->data);
62617721Speter        p->data = NULL;
62717721Speter    }
62817721Speter    return;
62917721Speter}
63017721Speter
63117721Speterstatic int
63217721Speterpretag_list_proc(p, closure)
63317721Speter    Node *p;
63417721Speter    void *closure;
63517721Speter{
63617721Speter    if (p->data != NULL)
63717721Speter    {
63817721Speter        run_arg(p->key);
63917721Speter        run_arg(p->data);
64017721Speter    }
64117721Speter    return (0);
64217721Speter}
64317721Speter
64417721Speter
64517721Speter/*
64681404Speter * Called to rtag a particular file, as appropriate with the options that were
64781404Speter * set above.
64881404Speter */
64981404Speter/* ARGSUSED */
65081404Speterstatic int
65181404Speterrtag_fileproc (callerdat, finfo)
65281404Speter    void *callerdat;
65381404Speter    struct file_info *finfo;
65481404Speter{
65581404Speter    RCSNode *rcsfile;
65681404Speter    char *version, *rev;
65781404Speter    int retcode = 0;
65881404Speter
65981404Speter    /* find the parsed RCS data */
66081404Speter    if ((rcsfile = finfo->rcs) == NULL)
66181404Speter	return (1);
66281404Speter
66381404Speter    /*
66481404Speter     * For tagging an RCS file which is a symbolic link, you'd best be
66581404Speter     * running with RCS 5.6, since it knows how to handle symbolic links
66681404Speter     * correctly without breaking your link!
66781404Speter     */
66881404Speter
66981404Speter    if (delete_flag)
67081404Speter	return (rtag_delete (rcsfile));
67181404Speter
67281404Speter    /*
67381404Speter     * If we get here, we are adding a tag.  But, if -a was specified, we
67481404Speter     * need to check to see if a -r or -D option was specified.  If neither
67581404Speter     * was specified and the file is in the Attic, remove the tag.
67681404Speter     */
67781404Speter    if (attic_too && (!numtag && !date))
67881404Speter    {
67981404Speter	if ((rcsfile->flags & VALID) && (rcsfile->flags & INATTIC))
68081404Speter	    return (rtag_delete (rcsfile));
68181404Speter    }
68281404Speter
68381404Speter    version = RCS_getversion (rcsfile, numtag, date, force_tag_match,
68481404Speter			      (int *) NULL);
68581404Speter    if (version == NULL)
68681404Speter    {
68781404Speter	/* If -a specified, clean up any old tags */
68881404Speter	if (attic_too)
68981404Speter	    (void) rtag_delete (rcsfile);
69081404Speter
69181404Speter	if (!quiet && !force_tag_match)
69281404Speter	{
69381404Speter	    error (0, 0, "cannot find tag `%s' in `%s'",
69481404Speter		   numtag ? numtag : "head", rcsfile->path);
69581404Speter	    return (1);
69681404Speter	}
69781404Speter	return (0);
69881404Speter    }
69981404Speter    if (numtag
70081404Speter	&& isdigit ((unsigned char) *numtag)
70181404Speter	&& strcmp (numtag, version) != 0)
70281404Speter    {
70381404Speter
70481404Speter	/*
70581404Speter	 * We didn't find a match for the numeric tag that was specified, but
70681404Speter	 * that's OK.  just pass the numeric tag on to rcs, to be tagged as
70781404Speter	 * specified.  Could get here if one tried to tag "1.1.1" and there
70881404Speter	 * was a 1.1.1 branch with some head revision.  In this case, we want
70981404Speter	 * the tag to reference "1.1.1" and not the revision at the head of
71081404Speter	 * the branch.  Use a symbolic tag for that.
71181404Speter	 */
71281404Speter	rev = branch_mode ? RCS_magicrev (rcsfile, version) : numtag;
71381404Speter	retcode = RCS_settag(rcsfile, symtag, numtag);
71481404Speter	if (retcode == 0)
71581404Speter	    RCS_rewrite (rcsfile, NULL, NULL);
71681404Speter    }
71781404Speter    else
71881404Speter    {
71981404Speter	char *oversion;
72081404Speter
72181404Speter	/*
72281404Speter	 * As an enhancement for the case where a tag is being re-applied to
72381404Speter	 * a large body of a module, make one extra call to RCS_getversion to
72481404Speter	 * see if the tag is already set in the RCS file.  If so, check to
72581404Speter	 * see if it needs to be moved.  If not, do nothing.  This will
72681404Speter	 * likely save a lot of time when simply moving the tag to the
72781404Speter	 * "current" head revisions of a module -- which I have found to be a
72881404Speter	 * typical tagging operation.
72981404Speter	 */
73081404Speter	rev = branch_mode ? RCS_magicrev (rcsfile, version) : version;
73181404Speter	oversion = RCS_getversion (rcsfile, symtag, (char *) NULL, 1,
73281404Speter				   (int *) NULL);
73381404Speter	if (oversion != NULL)
73481404Speter	{
73581404Speter	    int isbranch = RCS_nodeisbranch (finfo->rcs, symtag);
73681404Speter
73781404Speter	    /*
73881404Speter	     * if versions the same and neither old or new are branches don't
73981404Speter	     * have to do anything
74081404Speter	     */
74181404Speter	    if (strcmp (version, oversion) == 0 && !branch_mode && !isbranch)
74281404Speter	    {
74381404Speter		free (oversion);
74481404Speter		free (version);
74581404Speter		return (0);
74681404Speter	    }
74781404Speter
74881404Speter	    if (!force_tag_move)
74981404Speter	    {
75081404Speter		/* we're NOT going to move the tag */
75181404Speter		(void) printf ("W %s", finfo->fullname);
75281404Speter
75381404Speter		(void) printf (" : %s already exists on %s %s",
75481404Speter			       symtag, isbranch ? "branch" : "version",
75581404Speter			       oversion);
75681404Speter		(void) printf (" : NOT MOVING tag to %s %s\n",
75781404Speter			       branch_mode ? "branch" : "version", rev);
75881404Speter		free (oversion);
75981404Speter		free (version);
760102840Speter		if (branch_mode) free(rev);
76181404Speter		return (0);
76281404Speter	    }
763102840Speter	    else /* force_tag_move is set and... */
764102840Speter		if ((isbranch && !disturb_branch_tags) ||
765102840Speter		    (!isbranch && disturb_branch_tags))
766102840Speter	    {
767102840Speter	        error(0,0, "%s: Not moving %s tag `%s' from %s to %s%s.",
768102840Speter			finfo->fullname,
769102840Speter			isbranch ? "branch" : "non-branch",
770102840Speter			symtag, oversion, rev,
771102840Speter			isbranch ? "" : " due to `-B' option");
772102840Speter		if (branch_mode) free(rev);
773102840Speter		free (oversion);
774102840Speter		free (version);
775102840Speter		return (0);
776102840Speter	    }
77781404Speter	    free (oversion);
77881404Speter	}
77981404Speter	retcode = RCS_settag(rcsfile, symtag, rev);
78081404Speter	if (retcode == 0)
78181404Speter	    RCS_rewrite (rcsfile, NULL, NULL);
78281404Speter    }
78381404Speter
78481404Speter    if (retcode != 0)
78581404Speter    {
78681404Speter	error (1, retcode == -1 ? errno : 0,
78781404Speter	       "failed to set tag `%s' to revision `%s' in `%s'",
78881404Speter	       symtag, rev, rcsfile->path);
78981404Speter        if (branch_mode)
79081404Speter	    free (rev);
79181404Speter        free (version);
79281404Speter        return (1);
79381404Speter    }
79481404Speter    if (branch_mode)
79581404Speter	free (rev);
79681404Speter    free (version);
79781404Speter    return (0);
79881404Speter}
79981404Speter
80081404Speter/*
80181404Speter * If -d is specified, "force_tag_match" is set, so that this call to
80281404Speter * RCS_getversion() will return a NULL version string if the symbolic
80381404Speter * tag does not exist in the RCS file.
80481404Speter *
80581404Speter * If the -r flag was used, numtag is set, and we only delete the
80681404Speter * symtag from files that have numtag.
80781404Speter *
80881404Speter * This is done here because it's MUCH faster than just blindly calling
80981404Speter * "rcs" to remove the tag... trust me.
81081404Speter */
81181404Speterstatic int
81281404Speterrtag_delete (rcsfile)
81381404Speter    RCSNode *rcsfile;
81481404Speter{
81581404Speter    char *version;
816102840Speter    int retcode, isbranch;
81781404Speter
81881404Speter    if (numtag)
81981404Speter    {
82081404Speter	version = RCS_getversion (rcsfile, numtag, (char *) NULL, 1,
82181404Speter				  (int *) NULL);
82281404Speter	if (version == NULL)
82381404Speter	    return (0);
82481404Speter	free (version);
82581404Speter    }
82681404Speter
82781404Speter    version = RCS_getversion (rcsfile, symtag, (char *) NULL, 1,
82881404Speter			      (int *) NULL);
82981404Speter    if (version == NULL)
83081404Speter	return (0);
83181404Speter    free (version);
83281404Speter
833102840Speter
834102840Speter    isbranch = RCS_nodeisbranch (rcsfile, symtag);
835102840Speter    if ((isbranch && !disturb_branch_tags) ||
836102840Speter	(!isbranch && disturb_branch_tags))
837102840Speter    {
838102840Speter	if (!quiet)
839102840Speter	    error(0, 0,
840102840Speter		"Not removing %s tag `%s' from `%s'%s.",
841102840Speter		isbranch ? "branch" : "non-branch",
842102840Speter		symtag, rcsfile->path,
843102840Speter		isbranch ? "" : " due to `-B' option");
844102840Speter	return (1);
845102840Speter    }
846102840Speter
84781404Speter    if ((retcode = RCS_deltag(rcsfile, symtag)) != 0)
84881404Speter    {
84981404Speter	if (!quiet)
85081404Speter	    error (0, retcode == -1 ? errno : 0,
85181404Speter		   "failed to remove tag `%s' from `%s'", symtag,
85281404Speter		   rcsfile->path);
85381404Speter	return (1);
85481404Speter    }
85581404Speter    RCS_rewrite (rcsfile, NULL, NULL);
85681404Speter    return (0);
85781404Speter}
85881404Speter
85981404Speter
86081404Speter/*
86117721Speter * Called to tag a particular file (the currently checked out version is
86217721Speter * tagged with the specified tag - or the specified tag is deleted).
86317721Speter */
86417721Speter/* ARGSUSED */
86517721Speterstatic int
86625839Spetertag_fileproc (callerdat, finfo)
86725839Speter    void *callerdat;
86817721Speter    struct file_info *finfo;
86917721Speter{
87017721Speter    char *version, *oversion;
87117721Speter    char *nversion = NULL;
87217721Speter    char *rev;
87317721Speter    Vers_TS *vers;
87417721Speter    int retcode = 0;
875128266Speter    int retval = 0;
87617721Speter
87725839Speter    vers = Version_TS (finfo, NULL, NULL, NULL, 0, 0);
87825839Speter
87917721Speter    if ((numtag != NULL) || (date != NULL))
88017721Speter    {
88117721Speter        nversion = RCS_getversion(vers->srcfile,
88217721Speter                                  numtag,
88317721Speter                                  date,
88425839Speter                                  force_tag_match,
88525839Speter				  (int *) NULL);
88617721Speter        if (nversion == NULL)
887128266Speter	    goto free_vars_and_return;
88817721Speter    }
88917721Speter    if (delete_flag)
89017721Speter    {
89117721Speter
892102840Speter	int isbranch;
89317721Speter	/*
89417721Speter	 * If -d is specified, "force_tag_match" is set, so that this call to
89517721Speter	 * RCS_getversion() will return a NULL version string if the symbolic
89617721Speter	 * tag does not exist in the RCS file.
89717721Speter	 *
89817721Speter	 * This is done here because it's MUCH faster than just blindly calling
89917721Speter	 * "rcs" to remove the tag... trust me.
90017721Speter	 */
90117721Speter
90225839Speter	version = RCS_getversion (vers->srcfile, symtag, (char *) NULL, 1,
90325839Speter				  (int *) NULL);
90417721Speter	if (version == NULL || vers->srcfile == NULL)
905128266Speter	    goto free_vars_and_return;
906128266Speter
90717721Speter	free (version);
90817721Speter
909102840Speter	isbranch = RCS_nodeisbranch (finfo->rcs, symtag);
910102840Speter	if ((isbranch && !disturb_branch_tags) ||
911102840Speter	    (!isbranch && disturb_branch_tags))
912102840Speter	{
913102840Speter	    if (!quiet)
914102840Speter		error(0, 0,
915102840Speter		       "Not removing %s tag `%s' from `%s'%s.",
916102840Speter			isbranch ? "branch" : "non-branch",
917102840Speter			symtag, vers->srcfile->path,
918102840Speter			isbranch ? "" : " due to `-B' option");
919128266Speter	    retval = 1;
920128266Speter	    goto free_vars_and_return;
921102840Speter	}
922102840Speter
92332785Speter	if ((retcode = RCS_deltag(vers->srcfile, symtag)) != 0)
92417721Speter	{
92517721Speter	    if (!quiet)
92617721Speter		error (0, retcode == -1 ? errno : 0,
92717721Speter		       "failed to remove tag %s from %s", symtag,
92817721Speter		       vers->srcfile->path);
929128266Speter	    retval = 1;
930128266Speter	    goto free_vars_and_return;
93117721Speter	}
93232785Speter	RCS_rewrite (vers->srcfile, NULL, NULL);
93317721Speter
93417721Speter	/* warm fuzzies */
93517721Speter	if (!really_quiet)
93617721Speter	{
93725839Speter	    cvs_output ("D ", 2);
93825839Speter	    cvs_output (finfo->fullname, 0);
93925839Speter	    cvs_output ("\n", 1);
94017721Speter	}
94117721Speter
942128266Speter	goto free_vars_and_return;
94317721Speter    }
94417721Speter
94517721Speter    /*
94617721Speter     * If we are adding a tag, we need to know which version we have checked
94717721Speter     * out and we'll tag that version.
94817721Speter     */
94917721Speter    if (nversion == NULL)
95017721Speter    {
95117721Speter        version = vers->vn_user;
95217721Speter    }
95317721Speter    else
95417721Speter    {
95517721Speter        version = nversion;
95617721Speter    }
95717721Speter    if (version == NULL)
95817721Speter    {
959128266Speter	goto free_vars_and_return;
96017721Speter    }
96117721Speter    else if (strcmp (version, "0") == 0)
96217721Speter    {
96317721Speter	if (!quiet)
96417721Speter	    error (0, 0, "couldn't tag added but un-commited file `%s'", finfo->file);
965128266Speter	goto free_vars_and_return;
96617721Speter    }
96717721Speter    else if (version[0] == '-')
96817721Speter    {
96917721Speter	if (!quiet)
97017721Speter	    error (0, 0, "skipping removed but un-commited file `%s'", finfo->file);
971128266Speter	goto free_vars_and_return;
97217721Speter    }
97317721Speter    else if (vers->srcfile == NULL)
97417721Speter    {
97517721Speter	if (!quiet)
97617721Speter	    error (0, 0, "cannot find revision control file for `%s'", finfo->file);
977128266Speter	goto free_vars_and_return;
97817721Speter    }
97917721Speter
98017721Speter    /*
98117721Speter     * As an enhancement for the case where a tag is being re-applied to a
98217721Speter     * large number of files, make one extra call to RCS_getversion to see
98317721Speter     * if the tag is already set in the RCS file.  If so, check to see if it
98417721Speter     * needs to be moved.  If not, do nothing.  This will likely save a lot of
98517721Speter     * time when simply moving the tag to the "current" head revisions of a
98617721Speter     * module -- which I have found to be a typical tagging operation.
98717721Speter     */
98817721Speter    rev = branch_mode ? RCS_magicrev (vers->srcfile, version) : version;
98925839Speter    oversion = RCS_getversion (vers->srcfile, symtag, (char *) NULL, 1,
99025839Speter			       (int *) NULL);
99117721Speter    if (oversion != NULL)
99217721Speter    {
99332785Speter	int isbranch = RCS_nodeisbranch (finfo->rcs, symtag);
99417721Speter
99517721Speter	/*
99617721Speter	 * if versions the same and neither old or new are branches don't have
99717721Speter	 * to do anything
99817721Speter	 */
99917721Speter	if (strcmp (version, oversion) == 0 && !branch_mode && !isbranch)
100017721Speter	{
100117721Speter	    free (oversion);
100266525Speter	    if (branch_mode)
100366525Speter		free (rev);
1004128266Speter	    goto free_vars_and_return;
100517721Speter	}
100617721Speter
100717721Speter	if (!force_tag_move)
100817721Speter	{
100917721Speter	    /* we're NOT going to move the tag */
101025839Speter	    cvs_output ("W ", 2);
101125839Speter	    cvs_output (finfo->fullname, 0);
101225839Speter	    cvs_output (" : ", 0);
101325839Speter	    cvs_output (symtag, 0);
101425839Speter	    cvs_output (" already exists on ", 0);
101525839Speter	    cvs_output (isbranch ? "branch" : "version", 0);
101625839Speter	    cvs_output (" ", 0);
101725839Speter	    cvs_output (oversion, 0);
101825839Speter	    cvs_output (" : NOT MOVING tag to ", 0);
101925839Speter	    cvs_output (branch_mode ? "branch" : "version", 0);
102025839Speter	    cvs_output (" ", 0);
102125839Speter	    cvs_output (rev, 0);
102225839Speter	    cvs_output ("\n", 1);
102317721Speter	    free (oversion);
102466525Speter	    if (branch_mode)
102566525Speter		free (rev);
1026128266Speter	    goto free_vars_and_return;
102717721Speter	}
1028102840Speter	else 	/* force_tag_move == 1 and... */
1029102840Speter		if ((isbranch && !disturb_branch_tags) ||
1030102840Speter		    (!isbranch && disturb_branch_tags))
1031102840Speter	{
1032102840Speter	    error(0,0, "%s: Not moving %s tag `%s' from %s to %s%s.",
1033102840Speter		    finfo->fullname,
1034102840Speter		    isbranch ? "branch" : "non-branch",
1035102840Speter		    symtag, oversion, rev,
1036102840Speter		    isbranch ? "" : " due to `-B' option");
1037102840Speter	    free (oversion);
1038128266Speter	    if (branch_mode)
1039128266Speter		free (rev);
1040128266Speter	    goto free_vars_and_return;
1041102840Speter	}
104217721Speter	free (oversion);
104317721Speter    }
104417721Speter
104525839Speter    if ((retcode = RCS_settag(vers->srcfile, symtag, rev)) != 0)
104617721Speter    {
104717721Speter	error (1, retcode == -1 ? errno : 0,
104817721Speter	       "failed to set tag %s to revision %s in %s",
104917721Speter	       symtag, rev, vers->srcfile->path);
105066525Speter	if (branch_mode)
105166525Speter	    free (rev);
1052128266Speter	retval = 1;
1053128266Speter	goto free_vars_and_return;
105417721Speter    }
105566525Speter    if (branch_mode)
105666525Speter	free (rev);
105732785Speter    RCS_rewrite (vers->srcfile, NULL, NULL);
105817721Speter
105917721Speter    /* more warm fuzzies */
106017721Speter    if (!really_quiet)
106117721Speter    {
106225839Speter	cvs_output ("T ", 2);
106325839Speter	cvs_output (finfo->fullname, 0);
106425839Speter	cvs_output ("\n", 1);
106517721Speter    }
106617721Speter
1067128266Speter free_vars_and_return:
106817721Speter    if (nversion != NULL)
106917721Speter        free (nversion);
107017721Speter    freevers_ts (&vers);
1071128266Speter    return (retval);
107217721Speter}
107317721Speter
107417721Speter/*
107517721Speter * Print a warm fuzzy message
107617721Speter */
107717721Speter/* ARGSUSED */
107817721Speterstatic Dtype
107925839Spetertag_dirproc (callerdat, dir, repos, update_dir, entries)
108025839Speter    void *callerdat;
1081128266Speter    const char *dir;
1082128266Speter    const char *repos;
1083128266Speter    const char *update_dir;
108425839Speter    List *entries;
108517721Speter{
108681404Speter
108781404Speter    if (ignore_directory (update_dir))
108881404Speter    {
108981404Speter	/* print the warm fuzzy message */
109081404Speter	if (!quiet)
109181404Speter	  error (0, 0, "Ignoring %s", update_dir);
109281404Speter        return R_SKIP_ALL;
109381404Speter    }
109481404Speter
109517721Speter    if (!quiet)
109617721Speter	error (0, 0, "%s %s", delete_flag ? "Untagging" : "Tagging", update_dir);
109717721Speter    return (R_PROCESS);
109817721Speter}
109917721Speter
110017721Speter/* Code relating to the val-tags file.  Note that this file has no way
110117721Speter   of knowing when a tag has been deleted.  The problem is that there
110217721Speter   is no way of knowing whether a tag still exists somewhere, when we
110317721Speter   delete it some places.  Using per-directory val-tags files (in
110417721Speter   CVSREP) might be better, but that might slow down the process of
110517721Speter   verifying that a tag is correct (maybe not, for the likely cases,
110617721Speter   if carefully done), and/or be harder to implement correctly.  */
110717721Speter
110817721Speterstruct val_args {
110917721Speter    char *name;
111017721Speter    int found;
111117721Speter};
111217721Speter
111325839Speterstatic int val_fileproc PROTO ((void *callerdat, struct file_info *finfo));
111417721Speter
111517721Speterstatic int
111625839Speterval_fileproc (callerdat, finfo)
111725839Speter    void *callerdat;
111817721Speter    struct file_info *finfo;
111917721Speter{
112017721Speter    RCSNode *rcsdata;
112125839Speter    struct val_args *args = (struct val_args *)callerdat;
112217721Speter    char *tag;
112317721Speter
112417721Speter    if ((rcsdata = finfo->rcs) == NULL)
112517721Speter	/* Not sure this can happen, after all we passed only
112617721Speter	   W_REPOS | W_ATTIC.  */
112717721Speter	return 0;
112817721Speter
112925839Speter    tag = RCS_gettag (rcsdata, args->name, 1, (int *) NULL);
113017721Speter    if (tag != NULL)
113117721Speter    {
113217721Speter	/* FIXME: should find out a way to stop the search at this point.  */
113317721Speter	args->found = 1;
113417721Speter	free (tag);
113517721Speter    }
113617721Speter    return 0;
113717721Speter}
113817721Speter
113917721Speter
1140128266Speter
1141175270Sobrien/* This routine determines whether a tag appears in CVSROOT/val-tags.
1142175270Sobrien *
1143175270Sobrien * The val-tags file will be open read-only when IDB is NULL.  Since writes to
1144175270Sobrien * val-tags always append to it, the lack of locking is okay.  The worst case
1145175270Sobrien * race condition might misinterpret a partially written "foobar" matched, for
1146175270Sobrien * instance,  a request for "f", "foo", of "foob".  Such a mismatch would be
1147175270Sobrien * caught harmlessly later.
1148175270Sobrien *
1149175270Sobrien * Before CVS adds a tag to val-tags, it will lock val-tags for write and
1150175270Sobrien * verify that the tag is still not present to avoid adding it twice.
1151175270Sobrien *
1152175270Sobrien * NOTES
1153175270Sobrien *   This function expects its parent to handle any necessary locking of the
1154175270Sobrien *   val-tags file.
1155175270Sobrien *
1156175270Sobrien * INPUTS
1157175270Sobrien *   idb	When this value is NULL, the val-tags file is opened in
1158175270Sobrien *   		in read-only mode.  When present, the val-tags file is opened
1159175270Sobrien *   		in read-write mode and the DBM handle is stored in *IDB.
1160175270Sobrien *   name	The tag to search for.
1161175270Sobrien *
1162175270Sobrien * OUTPUTS
1163175270Sobrien *   *idb	The val-tags file opened for read/write, or NULL if it couldn't
1164175270Sobrien *   		be opened.
1165175270Sobrien *
1166175270Sobrien * ERRORS
1167175270Sobrien *   Exits with an error message if the val-tags file cannot be opened for
1168175270Sobrien *   read (failure to open val-tags read/write is harmless - see below).
1169175270Sobrien *
1170175270Sobrien * RETURNS
1171175270Sobrien *   true	1. If NAME exists in val-tags.
1172175270Sobrien *   		2. If IDB is non-NULL and val-tags cannot be opened for write.
1173175270Sobrien *   		   This allows callers to ignore the harmless inability to
1174175270Sobrien *   		   update the val-tags cache.
1175175270Sobrien *   false	If the file could be opened and the tag is not present.
1176175270Sobrien */
1177175270Sobrienstatic int is_in_val_tags PROTO((DBM **idb, const char *name));
1178175270Sobrienstatic int
1179175270Sobrienis_in_val_tags (idb, name)
1180175270Sobrien    DBM **idb;
1181175270Sobrien    const char *name;
1182175270Sobrien{
1183175270Sobrien    DBM *db = NULL;
1184175270Sobrien    char *valtags_filename;
1185175270Sobrien    datum mytag;
1186175270Sobrien    int status;
1187175270Sobrien
1188175270Sobrien    /* Casting out const should be safe here - input datums are not
1189175270Sobrien     * written to by the myndbm functions.
1190175270Sobrien     */
1191175270Sobrien    mytag.dptr = (char *)name;
1192175270Sobrien    mytag.dsize = strlen (name);
1193175270Sobrien
1194175270Sobrien    valtags_filename = xmalloc (strlen (current_parsed_root->directory)
1195175270Sobrien				+ sizeof CVSROOTADM
1196175270Sobrien				+ sizeof CVSROOTADM_VALTAGS + 3);
1197175270Sobrien    sprintf (valtags_filename, "%s/%s/%s", current_parsed_root->directory,
1198175270Sobrien					   CVSROOTADM, CVSROOTADM_VALTAGS);
1199175270Sobrien
1200175270Sobrien    if (idb)
1201175270Sobrien    {
1202175270Sobrien	db = dbm_open (valtags_filename, O_RDWR, 0666);
1203175270Sobrien	if (!db)
1204175270Sobrien	{
1205175270Sobrien	    mode_t omask;
1206175270Sobrien
1207175270Sobrien	    if (!existence_error (errno))
1208175270Sobrien	    {
1209175270Sobrien		error (0, errno, "warning: cannot open %s read/write",
1210175270Sobrien		       valtags_filename);
1211175270Sobrien		*idb = NULL;
1212175270Sobrien		return 1;
1213175270Sobrien	    }
1214175270Sobrien
1215175270Sobrien	    omask = umask (cvsumask);
1216175270Sobrien	    db = dbm_open (valtags_filename, O_RDWR | O_CREAT | O_TRUNC, 0666);
1217175270Sobrien	    umask (omask);
1218175270Sobrien	    if (!db)
1219175270Sobrien	    {
1220175270Sobrien		error (0, errno, "warning: cannot create %s",
1221175270Sobrien		       valtags_filename);
1222175270Sobrien		*idb = NULL;
1223175270Sobrien		return 1;
1224175270Sobrien	    }
1225175270Sobrien
1226175270Sobrien	    *idb = db;
1227175270Sobrien	    return 0;
1228175270Sobrien	}
1229175270Sobrien
1230175270Sobrien	*idb = db;
1231175270Sobrien    }
1232175270Sobrien    else
1233175270Sobrien    {
1234175270Sobrien	db = dbm_open (valtags_filename, O_RDONLY, 0444);
1235175270Sobrien	if (!db && !existence_error (errno))
1236175270Sobrien	    error (1, errno, "cannot read %s", valtags_filename);
1237175270Sobrien    }
1238175270Sobrien
1239175270Sobrien    /* If the file merely fails to exist, we just keep going and create
1240175270Sobrien       it later if need be.  */
1241175270Sobrien
1242175270Sobrien    status = 0;
1243175270Sobrien    if (db)
1244175270Sobrien    {
1245175270Sobrien	datum val;
1246175270Sobrien
1247175270Sobrien	val = dbm_fetch (db, mytag);
1248175270Sobrien	if (val.dptr != NULL)
1249175270Sobrien	    /* Found.  The tag is valid.  */
1250175270Sobrien	    status = 1;
1251175270Sobrien
1252175270Sobrien	/* FIXME: should check errors somehow (add dbm_error to myndbm.c?).  */
1253175270Sobrien
1254175270Sobrien	if (!idb) dbm_close (db);
1255175270Sobrien    }
1256175270Sobrien
1257175270Sobrien    free (valtags_filename);
1258175270Sobrien    return status;
1259175270Sobrien}
1260175270Sobrien
1261175270Sobrien
1262175270Sobrien
1263175270Sobrien/* Add a tag to the CVSROOT/val-tags cache.  Establishes a write lock and
1264175270Sobrien * reverifies that the tag does not exist before adding it.
1265175270Sobrien */
1266175270Sobrienstatic void add_to_val_tags PROTO((const char *name));
1267175270Sobrienstatic void
1268175270Sobrienadd_to_val_tags (name)
1269175270Sobrien    const char *name;
1270175270Sobrien{
1271175270Sobrien    DBM *db;
1272175270Sobrien    datum mytag;
1273175270Sobrien    datum value;
1274175270Sobrien
1275175270Sobrien    if (noexec) return;
1276175270Sobrien
1277175270Sobrien    val_tags_lock (current_parsed_root->directory);
1278175270Sobrien
1279175270Sobrien    /* Check for presence again since we have a lock now.  */
1280175270Sobrien    if (is_in_val_tags (&db, name))
1281175270Sobrien    {
1282175270Sobrien	clear_val_tags_lock ();
1283175270Sobrien	if (db)
1284175270Sobrien	    dbm_close (db);
1285175270Sobrien	return;
1286175270Sobrien    }
1287175270Sobrien
1288175270Sobrien    /* Casting out const should be safe here - input datums are not
1289175270Sobrien     * written to by the myndbm functions.
1290175270Sobrien     */
1291175270Sobrien    mytag.dptr = (char *)name;
1292175270Sobrien    mytag.dsize = strlen (name);
1293175270Sobrien    value.dptr = "y";
1294175270Sobrien    value.dsize = 1;
1295175270Sobrien
1296175270Sobrien    if (dbm_store (db, mytag, value, DBM_REPLACE) < 0)
1297175270Sobrien	error (0, errno, "failed to store %s into val-tags", name);
1298175270Sobrien    dbm_close (db);
1299175270Sobrien
1300175270Sobrien    clear_val_tags_lock ();
1301175270Sobrien}
1302175270Sobrien
1303175270Sobrien
1304175270Sobrien
1305128266Speterstatic Dtype val_direntproc PROTO ((void *, const char *, const char *,
1306128266Speter                                    const char *, List *));
1307128266Speter
130817721Speterstatic Dtype
130925839Speterval_direntproc (callerdat, dir, repository, update_dir, entries)
131025839Speter    void *callerdat;
1311128266Speter    const char *dir;
1312128266Speter    const char *repository;
1313128266Speter    const char *update_dir;
131425839Speter    List *entries;
131517721Speter{
131617721Speter    /* This is not quite right--it doesn't get right the case of "cvs
131717721Speter       update -d -r foobar" where foobar is a tag which exists only in
131817721Speter       files in a directory which does not exist yet, but which is
131917721Speter       about to be created.  */
132017721Speter    if (isdir (dir))
132166525Speter	return R_PROCESS;
132217721Speter    return R_SKIP_ALL;
132317721Speter}
132417721Speter
132517721Speter/* Check to see whether NAME is a valid tag.  If so, return.  If not
132617721Speter   print an error message and exit.  ARGC, ARGV, LOCAL, and AFLAG specify
132717721Speter   which files we will be operating on.
132817721Speter
132917721Speter   REPOSITORY is the repository if we need to cd into it, or NULL if
133017721Speter   we are already there, or "" if we should do a W_LOCAL recursion.
133117721Speter   Sorry for three cases, but the "" case is needed in case the
133217721Speter   working directories come from diverse parts of the repository, the
133317721Speter   NULL case avoids an unneccesary chdir, and the non-NULL, non-""
133417721Speter   case is needed for checkout, where we don't want to chdir if the
133517721Speter   tag is found in CVSROOTADM_VALTAGS, but there is not (yet) any
133617721Speter   local directory.  */
133717721Spetervoid
133817721Spetertag_check_valid (name, argc, argv, local, aflag, repository)
133917721Speter    char *name;
134017721Speter    int argc;
134117721Speter    char **argv;
134217721Speter    int local;
134317721Speter    int aflag;
134417721Speter    char *repository;
134517721Speter{
134617721Speter    struct val_args the_val_args;
134717721Speter    struct saved_cwd cwd;
134817721Speter    int which;
134917721Speter
135017721Speter    /* Numeric tags require only a syntactic check.  */
135154427Speter    if (isdigit ((unsigned char) name[0]))
135217721Speter    {
135317721Speter	char *p;
135417721Speter	for (p = name; *p != '\0'; ++p)
135517721Speter	{
135654427Speter	    if (!(isdigit ((unsigned char) *p) || *p == '.'))
135717721Speter		error (1, 0, "\
135817721SpeterNumeric tag %s contains characters other than digits and '.'", name);
135917721Speter	}
136017721Speter	return;
136117721Speter    }
136217721Speter
136325839Speter    /* Special tags are always valid.  */
136425839Speter    if (strcmp (name, TAG_BASE) == 0
136525839Speter	|| strcmp (name, TAG_HEAD) == 0)
136625839Speter	return;
136725839Speter
1368133180Sdes    if (readonlyfs)
1369133180Sdes	return;
1370133180Sdes
1371175270Sobrien    /* Verify that the tag is valid syntactically.  Some later code once made
1372175270Sobrien     * assumptions about this.
1373175270Sobrien     */
1374175270Sobrien    RCS_check_tag (name);
137532785Speter
1376175270Sobrien    if (is_in_val_tags (NULL, name)) return;
137717721Speter
137817721Speter    /* We didn't find the tag in val-tags, so look through all the RCS files
137917721Speter       to see whether it exists there.  Yes, this is expensive, but there
138017721Speter       is no other way to cope with a tag which might have been created
138125839Speter       by an old version of CVS, from before val-tags was invented.
138217721Speter
138325839Speter       Since we need this code anyway, we also use it to create
138425839Speter       entries in val-tags in general (that is, the val-tags entry
138525839Speter       will get created the first time the tag is used, not when the
138625839Speter       tag is created).  */
138725839Speter
138817721Speter    the_val_args.name = name;
138917721Speter    the_val_args.found = 0;
139017721Speter
139117721Speter    which = W_REPOS | W_ATTIC;
139217721Speter
139317721Speter    if (repository != NULL)
139417721Speter    {
139517721Speter	if (repository[0] == '\0')
139617721Speter	    which |= W_LOCAL;
139717721Speter	else
139817721Speter	{
139917721Speter	    if (save_cwd (&cwd))
140025839Speter		error_exit ();
1401128266Speter	    if (CVS_CHDIR (repository) < 0)
140217721Speter		error (1, errno, "cannot change to %s directory", repository);
140317721Speter	}
140417721Speter    }
140517721Speter
1406128266Speter    start_recursion (val_fileproc, (FILESDONEPROC) NULL,
1407128266Speter		     val_direntproc, (DIRLEAVEPROC) NULL,
1408128266Speter		     (void *)&the_val_args,
1409128266Speter		     argc, argv, local, which, aflag,
1410128266Speter		     CVS_LOCK_READ, NULL, 1, repository);
141117721Speter    if (repository != NULL && repository[0] != '\0')
141217721Speter    {
141317721Speter	if (restore_cwd (&cwd, NULL))
1414107484Speter	    error_exit ();
141517721Speter	free_cwd (&cwd);
141617721Speter    }
141717721Speter
141817721Speter    if (!the_val_args.found)
141917721Speter	error (1, 0, "no such tag %s", name);
142017721Speter    else
142117721Speter	/* The tags is valid but not mentioned in val-tags.  Add it.  */
1422175270Sobrien	add_to_val_tags (name);
1423175270Sobrien}
142417721Speter
142517721Speter
142617721Speter
142725839Speter/*
142825839Speter * Check whether a join tag is valid.  This is just like
142925839Speter * tag_check_valid, but we must stop before the colon if there is one.
143025839Speter */
143125839Speter
143225839Spetervoid
143325839Spetertag_check_valid_join (join_tag, argc, argv, local, aflag, repository)
143425839Speter     char *join_tag;
143525839Speter     int argc;
143625839Speter     char **argv;
143725839Speter     int local;
143825839Speter     int aflag;
143925839Speter     char *repository;
144025839Speter{
144125839Speter    char *c, *s;
144225839Speter
144325839Speter    c = xstrdup (join_tag);
144425839Speter    s = strchr (c, ':');
144525839Speter    if (s != NULL)
144625839Speter    {
144754427Speter        if (isdigit ((unsigned char) join_tag[0]))
144825839Speter	    error (1, 0,
144925839Speter		   "Numeric join tag %s may not contain a date specifier",
145025839Speter		   join_tag);
145125839Speter
145225839Speter        *s = '\0';
145354427Speter	/* hmmm...  I think it makes sense to allow -j:<date>, but
145454427Speter	 * for now this fixes a bug where CVS just spins and spins (I
145554427Speter	 * think in the RCS code) looking for a zero length tag.
145654427Speter	 */
145754427Speter	if (!*c)
145854427Speter	    error (1, 0,
145954427Speter		   "argument to join may not contain a date specifier without a tag");
146025839Speter    }
146125839Speter
146225839Speter    tag_check_valid (c, argc, argv, local, aflag, repository);
146325839Speter
146425839Speter    free (c);
146525839Speter}
1466