PageRenderTime 108ms CodeModel.GetById 16ms app.highlight 80ms RepoModel.GetById 2ms app.codeStats 0ms

/contrib/cvs/src/tag.c

https://bitbucket.org/freebsd/freebsd-head/
C | 1465 lines | 1075 code | 150 blank | 240 comment | 256 complexity | 44c027368495479d80158c2f7607cf3b MD5 | raw file
   1/*
   2 * Copyright (C) 1986-2005 The Free Software Foundation, Inc.
   3 *
   4 * Portions Copyright (C) 1998-2005 Derek Price, Ximbiot <http://ximbiot.com>,
   5 *                                  and others.
   6 *
   7 * Portions Copyright (C) 1992, Brian Berliner and Jeff Polk
   8 * Portions Copyright (C) 1989-1992, Brian Berliner
   9 * 
  10 * You may distribute under the terms of the GNU General Public License as
  11 * specified in the README file that comes with the CVS source distribution.
  12 * 
  13 * Tag and Rtag
  14 * 
  15 * Add or delete a symbolic name to an RCS file, or a collection of RCS files.
  16 * Tag uses the checked out revision in the current directory, rtag uses
  17 * the modules database, if necessary.
  18 *
  19 * $FreeBSD$
  20 */
  21
  22#include "cvs.h"
  23#include "savecwd.h"
  24
  25static int rtag_proc PROTO((int argc, char **argv, char *xwhere,
  26		      char *mwhere, char *mfile, int shorten,
  27		      int local_specified, char *mname, char *msg));
  28static int check_fileproc PROTO ((void *callerdat, struct file_info *finfo));
  29static int check_filesdoneproc PROTO ((void *callerdat, int err,
  30                                       const char *repos,
  31                                       const char *update_dir,
  32                                       List *entries));
  33static int pretag_proc PROTO((const char *repository, const char *filter));
  34static void masterlist_delproc PROTO((Node *p));
  35static void tag_delproc PROTO((Node *p));
  36static int pretag_list_proc PROTO((Node *p, void *closure));
  37
  38static Dtype tag_dirproc PROTO ((void *callerdat, const char *dir,
  39                                 const char *repos, const char *update_dir,
  40                                 List *entries));
  41static int rtag_fileproc PROTO ((void *callerdat, struct file_info *finfo));
  42static int rtag_delete PROTO((RCSNode *rcsfile));
  43static int tag_fileproc PROTO ((void *callerdat, struct file_info *finfo));
  44
  45static char *numtag;			/* specific revision to tag */
  46static int numtag_validated = 0;
  47static char *date = NULL;
  48static char *symtag;			/* tag to add or delete */
  49static int delete_flag;			/* adding a tag by default */
  50static int branch_mode;			/* make an automagic "branch" tag */
  51static int disturb_branch_tags = 0;	/* allow -F,-d to disturb branch tags */
  52static int force_tag_match = 1;		/* force tag to match by default */
  53static int force_tag_move;		/* don't force tag to move by default */
  54static int check_uptodate;		/* no uptodate-check by default */
  55static int attic_too;			/* remove tag from Attic files */
  56static int is_rtag;
  57
  58struct tag_info
  59{
  60    Ctype status;
  61    char *rev;
  62    char *tag;
  63    char *options;
  64};
  65
  66struct master_lists
  67{
  68    List *tlist;
  69};
  70
  71static List *mtlist;
  72static List *tlist;
  73
  74static const char rtag_opts[] = "+aBbdFflnQqRr:D:";
  75static const char *const rtag_usage[] =
  76{
  77    "Usage: %s %s [-abdFflnR] [-r rev|-D date] tag modules...\n",
  78    "\t-a\tClear tag from removed files that would not otherwise be tagged.\n",
  79    "\t-b\tMake the tag a \"branch\" tag, allowing concurrent development.\n",
  80    "\t-B\tAllows -F and -d to disturb branch tags.  Use with extreme care.\n",
  81    "\t-d\tDelete the given tag.\n",
  82    "\t-F\tMove tag if it already exists.\n",
  83    "\t-f\tForce a head revision match if tag/date not found.\n",
  84    "\t-l\tLocal directory only, not recursive.\n",
  85    "\t-n\tNo execution of 'tag program'.\n",
  86    "\t-R\tProcess directories recursively.\n",
  87    "\t-r rev\tExisting revision/tag.\n",
  88    "\t-D\tExisting date.\n",
  89    "(Specify the --help global option for a list of other help options)\n",
  90    NULL
  91};
  92
  93static const char tag_opts[] = "+BbcdFflQqRr:D:";
  94static const char *const tag_usage[] =
  95{
  96    "Usage: %s %s [-bcdFflR] [-r rev|-D date] tag [files...]\n",
  97    "\t-b\tMake the tag a \"branch\" tag, allowing concurrent development.\n",
  98    "\t-B\tAllows -F and -d to disturb branch tags.  Use with extreme care.\n",
  99    "\t-c\tCheck that working files are unmodified.\n",
 100    "\t-d\tDelete the given tag.\n",
 101    "\t-F\tMove tag if it already exists.\n",
 102    "\t-f\tForce a head revision match if tag/date not found.\n",
 103    "\t-l\tLocal directory only, not recursive.\n",
 104    "\t-R\tProcess directories recursively.\n",
 105    "\t-r rev\tExisting revision/tag.\n",
 106    "\t-D\tExisting date.\n",
 107    "(Specify the --help global option for a list of other help options)\n",
 108    NULL
 109};
 110
 111int
 112cvstag (argc, argv)
 113    int argc;
 114    char **argv;
 115{
 116    int local = 0;			/* recursive by default */
 117    int c;
 118    int err = 0;
 119    int run_module_prog = 1;
 120
 121    is_rtag = (strcmp (cvs_cmd_name, "rtag") == 0);
 122    
 123    if (argc == -1)
 124	usage (is_rtag ? rtag_usage : tag_usage);
 125
 126    optind = 0;
 127    while ((c = getopt (argc, argv, is_rtag ? rtag_opts : tag_opts)) != -1)
 128    {
 129	switch (c)
 130	{
 131	    case 'a':
 132		attic_too = 1;
 133		break;
 134	    case 'b':
 135		branch_mode = 1;
 136		break;
 137	    case 'B':
 138		disturb_branch_tags = 1;
 139		break;
 140	    case 'c':
 141		check_uptodate = 1;
 142		break;
 143	    case 'd':
 144		delete_flag = 1;
 145		break;
 146            case 'F':
 147		force_tag_move = 1;
 148		break;
 149	    case 'f':
 150		force_tag_match = 0;
 151		break;
 152	    case 'l':
 153		local = 1;
 154		break;
 155	    case 'n':
 156		run_module_prog = 0;
 157		break;
 158	    case 'Q':
 159	    case 'q':
 160		/* The CVS 1.5 client sends these options (in addition to
 161		   Global_option requests), so we must ignore them.  */
 162		if (!server_active)
 163		    error (1, 0,
 164			   "-q or -Q must be specified before \"%s\"",
 165			   cvs_cmd_name);
 166		break;
 167	    case 'R':
 168		local = 0;
 169		break;
 170            case 'r':
 171                numtag = optarg;
 172                break;
 173            case 'D':
 174                if (date)
 175                    free (date);
 176                date = Make_Date (optarg);
 177                break;
 178	    case '?':
 179	    default:
 180		usage (is_rtag ? rtag_usage : tag_usage);
 181		break;
 182	}
 183    }
 184    argc -= optind;
 185    argv += optind;
 186
 187    if (argc < (is_rtag ? 2 : 1))
 188	usage (is_rtag ? rtag_usage : tag_usage);
 189    symtag = argv[0];
 190    argc--;
 191    argv++;
 192
 193    if (date && numtag)
 194	error (1, 0, "-r and -D options are mutually exclusive");
 195    if (delete_flag && branch_mode)
 196	error (0, 0, "warning: -b ignored with -d options");
 197    RCS_check_tag (symtag);
 198
 199#ifdef CLIENT_SUPPORT
 200    if (current_parsed_root->isremote)
 201    {
 202	/* We're the client side.  Fire up the remote server.  */
 203	start_server ();
 204	
 205	ign_setup ();
 206
 207	if (attic_too)
 208	    send_arg("-a");
 209	if (branch_mode)
 210	    send_arg("-b");
 211	if (disturb_branch_tags)
 212	    send_arg("-B");
 213	if (check_uptodate)
 214	    send_arg("-c");
 215	if (delete_flag)
 216	    send_arg("-d");
 217	if (force_tag_move)
 218	    send_arg("-F");
 219	if (!force_tag_match)
 220	    send_arg ("-f");
 221	if (local)
 222	    send_arg("-l");
 223	if (!run_module_prog)
 224	    send_arg("-n");
 225
 226	if (numtag)
 227	    option_with_arg ("-r", numtag);
 228	if (date)
 229	    client_senddate (date);
 230
 231	send_arg ("--");
 232
 233	send_arg (symtag);
 234
 235	if (is_rtag)
 236	{
 237	    int i;
 238	    for (i = 0; i < argc; ++i)
 239		send_arg (argv[i]);
 240	    send_to_server ("rtag\012", 0);
 241	}
 242	else
 243	{
 244	    send_files (argc, argv, local, 0,
 245
 246		    /* I think the -c case is like "cvs status", in
 247		       which we really better be correct rather than
 248		       being fast; it is just too confusing otherwise.  */
 249			check_uptodate ? 0 : SEND_NO_CONTENTS);
 250	    send_file_names (argc, argv, SEND_EXPAND_WILD);
 251	    send_to_server ("tag\012", 0);
 252	}
 253
 254        return get_responses_and_close ();
 255    }
 256#endif
 257
 258    if (is_rtag)
 259    {
 260	DBM *db;
 261	int i;
 262	db = open_module ();
 263	for (i = 0; i < argc; i++)
 264	{
 265	    /* XXX last arg should be repository, but doesn't make sense here */
 266	    history_write ('T', (delete_flag ? "D" : (numtag ? numtag : 
 267			   (date ? date : "A"))), symtag, argv[i], "");
 268	    err += do_module (db, argv[i], TAG,
 269			      delete_flag ? "Untagging" : "Tagging",
 270			      rtag_proc, (char *) NULL, 0, local, run_module_prog,
 271			      0, symtag);
 272	}
 273	close_module (db);
 274    }
 275    else
 276    {
 277	err = rtag_proc (argc + 1, argv - 1, NULL, NULL, NULL, 0, local, NULL,
 278			 NULL);
 279    }
 280
 281    return (err);
 282}
 283
 284/*
 285 * callback proc for doing the real work of tagging
 286 */
 287/* ARGSUSED */
 288static int
 289rtag_proc (argc, argv, xwhere, mwhere, mfile, shorten, local_specified,
 290	   mname, msg)
 291    int argc;
 292    char **argv;
 293    char *xwhere;
 294    char *mwhere;
 295    char *mfile;
 296    int shorten;
 297    int local_specified;
 298    char *mname;
 299    char *msg;
 300{
 301    /* Begin section which is identical to patch_proc--should this
 302       be abstracted out somehow?  */
 303    char *myargv[2];
 304    int err = 0;
 305    int which;
 306    char *repository;
 307    char *where;
 308
 309    if (is_rtag)
 310    {
 311	repository = xmalloc (strlen (current_parsed_root->directory) + strlen (argv[0])
 312			      + (mfile == NULL ? 0 : strlen (mfile) + 1) + 2);
 313	(void) sprintf (repository, "%s/%s", current_parsed_root->directory, argv[0]);
 314	where = xmalloc (strlen (argv[0]) + (mfile == NULL ? 0 : strlen (mfile) + 1)
 315			 + 1);
 316	(void) strcpy (where, argv[0]);
 317
 318	/* if mfile isn't null, we need to set up to do only part of the module */
 319	if (mfile != NULL)
 320	{
 321	    char *cp;
 322	    char *path;
 323
 324	    /* if the portion of the module is a path, put the dir part on repos */
 325	    if ((cp = strrchr (mfile, '/')) != NULL)
 326	    {
 327		*cp = '\0';
 328		(void) strcat (repository, "/");
 329		(void) strcat (repository, mfile);
 330		(void) strcat (where, "/");
 331		(void) strcat (where, mfile);
 332		mfile = cp + 1;
 333	    }
 334
 335	    /* take care of the rest */
 336	    path = xmalloc (strlen (repository) + strlen (mfile) + 5);
 337	    (void) sprintf (path, "%s/%s", repository, mfile);
 338	    if (isdir (path))
 339	    {
 340		/* directory means repository gets the dir tacked on */
 341		(void) strcpy (repository, path);
 342		(void) strcat (where, "/");
 343		(void) strcat (where, mfile);
 344	    }
 345	    else
 346	    {
 347		myargv[0] = argv[0];
 348		myargv[1] = mfile;
 349		argc = 2;
 350		argv = myargv;
 351	    }
 352	    free (path);
 353	}
 354
 355	/* cd to the starting repository */
 356	if ( CVS_CHDIR (repository) < 0)
 357	{
 358	    error (0, errno, "cannot chdir to %s", repository);
 359	    free (repository);
 360	    free (where);
 361	    return (1);
 362	}
 363	/* End section which is identical to patch_proc.  */
 364
 365	if (delete_flag || force_tag_move || attic_too || numtag)
 366	    which = W_REPOS | W_ATTIC;
 367	else
 368	    which = W_REPOS;
 369    }
 370    else
 371    {
 372        where = NULL;
 373        which = W_LOCAL;
 374        repository = "";
 375    }
 376
 377    if (numtag != NULL && !numtag_validated)
 378    {
 379	tag_check_valid (numtag, argc - 1, argv + 1, local_specified, 0, repository);
 380	numtag_validated = 1;
 381    }
 382
 383    /* check to make sure they are authorized to tag all the 
 384       specified files in the repository */
 385
 386    mtlist = getlist();
 387    err = start_recursion (check_fileproc, check_filesdoneproc,
 388                           (DIRENTPROC) NULL, (DIRLEAVEPROC) NULL, NULL,
 389			   argc - 1, argv + 1, local_specified, which, 0,
 390			   CVS_LOCK_READ, where, 1, repository);
 391    
 392    if (err)
 393    {
 394       error (1, 0, "correct the above errors first!");
 395    }
 396     
 397    /* It would be nice to provide consistency with respect to
 398       commits; however CVS lacks the infrastructure to do that (see
 399       Concurrency in cvs.texinfo and comment in do_recursion).  */
 400
 401    /* start the recursion processor */
 402    err = start_recursion (is_rtag ? rtag_fileproc : tag_fileproc,
 403			   (FILESDONEPROC) NULL, tag_dirproc,
 404			   (DIRLEAVEPROC) NULL, NULL, argc - 1, argv + 1,
 405			   local_specified, which, 0, CVS_LOCK_WRITE, where, 1,
 406			   repository);
 407    if ( which & W_REPOS ) free ( repository );
 408    dellist (&mtlist);
 409    if (where != NULL)
 410	free (where);
 411    return (err);
 412}
 413
 414/* check file that is to be tagged */
 415/* All we do here is add it to our list */
 416
 417static int
 418check_fileproc (callerdat, finfo)
 419    void *callerdat;
 420    struct file_info *finfo;
 421{
 422    const char *xdir;
 423    Node *p;
 424    Vers_TS *vers;
 425    
 426    if (check_uptodate) 
 427    {
 428	switch (Classify_File (finfo, (char *) NULL, (char *) NULL,
 429				      (char *) NULL, 1, 0, &vers, 0))
 430	{
 431	case T_UPTODATE:
 432	case T_CHECKOUT:
 433	case T_PATCH:
 434	case T_REMOVE_ENTRY:
 435	    break;
 436	case T_UNKNOWN:
 437	case T_CONFLICT:
 438	case T_NEEDS_MERGE:
 439	case T_MODIFIED:
 440	case T_ADDED:
 441	case T_REMOVED:
 442	default:
 443	    error (0, 0, "%s is locally modified", finfo->fullname);
 444	    freevers_ts (&vers);
 445	    return (1);
 446	}
 447    }
 448    else
 449	vers = Version_TS (finfo, NULL, NULL, NULL, 0, 0);
 450    
 451    if (finfo->update_dir[0] == '\0')
 452	xdir = ".";
 453    else
 454	xdir = finfo->update_dir;
 455    if ((p = findnode (mtlist, xdir)) != NULL)
 456    {
 457	tlist = ((struct master_lists *) p->data)->tlist;
 458    }
 459    else
 460    {
 461	struct master_lists *ml;
 462        
 463	tlist = getlist ();
 464	p = getnode ();
 465	p->key = xstrdup (xdir);
 466	p->type = UPDATE;
 467	ml = (struct master_lists *)
 468	    xmalloc (sizeof (struct master_lists));
 469	ml->tlist = tlist;
 470	p->data = ml;
 471	p->delproc = masterlist_delproc;
 472	(void) addnode (mtlist, p);
 473    }
 474    /* do tlist */
 475    p = getnode ();
 476    p->key = xstrdup (finfo->file);
 477    p->type = UPDATE;
 478    p->delproc = tag_delproc;
 479    if (vers->srcfile == NULL)
 480    {
 481        if (!really_quiet)
 482	    error (0, 0, "nothing known about %s", finfo->file);
 483	freevers_ts (&vers);
 484	freenode (p);
 485	return (1);
 486    }
 487
 488    /* Here we duplicate the calculation in tag_fileproc about which
 489       version we are going to tag.  There probably are some subtle races
 490       (e.g. numtag is "foo" which gets moved between here and
 491       tag_fileproc).  */
 492    if (!is_rtag && numtag == NULL && date == NULL)
 493	p->data = xstrdup (vers->vn_user);
 494    else
 495	p->data = RCS_getversion (vers->srcfile, numtag, date,
 496				  force_tag_match, NULL);
 497
 498    if (p->data != NULL)
 499    {
 500        int addit = 1;
 501        char *oversion;
 502        
 503        oversion = RCS_getversion (vers->srcfile, symtag, (char *) NULL, 1,
 504				   (int *) NULL);
 505        if (oversion == NULL) 
 506        {
 507            if (delete_flag)
 508            {
 509		/* Deleting a tag which did not exist is a noop and
 510		   should not be logged.  */
 511                addit = 0;
 512            }
 513        }
 514	else if (delete_flag)
 515	{
 516	    free (p->data);
 517	    p->data = xstrdup (oversion);
 518	}
 519        else if (strcmp(oversion, p->data) == 0)
 520        {
 521            addit = 0;
 522        }
 523        else if (!force_tag_move)
 524        {
 525            addit = 0;
 526        }
 527        if (oversion != NULL)
 528        {
 529            free(oversion);
 530        }
 531        if (!addit)
 532        {
 533            free(p->data);
 534            p->data = NULL;
 535        }
 536    }
 537    freevers_ts (&vers);
 538    (void) addnode (tlist, p);
 539    return (0);
 540}
 541                         
 542static int
 543check_filesdoneproc (callerdat, err, repos, update_dir, entries)
 544    void *callerdat;
 545    int err;
 546    const char *repos;
 547    const char *update_dir;
 548    List *entries;
 549{
 550    int n;
 551    Node *p;
 552
 553    p = findnode(mtlist, update_dir);
 554    if (p != NULL)
 555    {
 556        tlist = ((struct master_lists *) p->data)->tlist;
 557    }
 558    else
 559    {
 560        tlist = (List *) NULL;
 561    }
 562    if ((tlist == NULL) || (tlist->list->next == tlist->list))
 563    {
 564        return (err);
 565    }
 566    if ((n = Parse_Info(CVSROOTADM_TAGINFO, repos, pretag_proc, 1)) > 0)
 567    {
 568        error (0, 0, "Pre-tag check failed");
 569        err += n;
 570    }
 571    return (err);
 572}
 573
 574static int
 575pretag_proc (repository, filter)
 576    const char *repository;
 577    const char *filter;
 578{
 579    if (filter[0] == '/')
 580    {
 581        char *s, *cp;
 582
 583        s = xstrdup(filter);
 584        for (cp=s; *cp; cp++)
 585        {
 586            if (isspace ((unsigned char) *cp))
 587            {
 588                *cp = '\0';
 589                break;
 590            }
 591        }
 592        if (!isfile(s))
 593        {
 594            error (0, errno, "cannot find pre-tag filter '%s'", s);
 595            free(s);
 596            return (1);
 597        }
 598        free(s);
 599    }
 600    run_setup (filter);
 601    run_arg (symtag);
 602    run_arg (delete_flag ? "del" : force_tag_move ? "mov" : "add");
 603    run_arg (repository);
 604    walklist(tlist, pretag_list_proc, NULL);
 605    return (run_exec (RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL));
 606}
 607
 608static void
 609masterlist_delproc(p)
 610    Node *p;
 611{
 612    struct master_lists *ml = p->data;
 613
 614    dellist(&ml->tlist);
 615    free(ml);
 616    return;
 617}
 618
 619static void
 620tag_delproc(p)
 621    Node *p;
 622{
 623    if (p->data != NULL)
 624    {
 625        free(p->data);
 626        p->data = NULL;
 627    }
 628    return;
 629}
 630
 631static int
 632pretag_list_proc(p, closure)
 633    Node *p;
 634    void *closure;
 635{
 636    if (p->data != NULL)
 637    {
 638        run_arg(p->key);
 639        run_arg(p->data);
 640    }
 641    return (0);
 642}
 643
 644
 645/*
 646 * Called to rtag a particular file, as appropriate with the options that were
 647 * set above.
 648 */
 649/* ARGSUSED */
 650static int
 651rtag_fileproc (callerdat, finfo)
 652    void *callerdat;
 653    struct file_info *finfo;
 654{
 655    RCSNode *rcsfile;
 656    char *version, *rev;
 657    int retcode = 0;
 658
 659    /* find the parsed RCS data */
 660    if ((rcsfile = finfo->rcs) == NULL)
 661	return (1);
 662
 663    /*
 664     * For tagging an RCS file which is a symbolic link, you'd best be
 665     * running with RCS 5.6, since it knows how to handle symbolic links
 666     * correctly without breaking your link!
 667     */
 668
 669    if (delete_flag)
 670	return (rtag_delete (rcsfile));
 671
 672    /*
 673     * If we get here, we are adding a tag.  But, if -a was specified, we
 674     * need to check to see if a -r or -D option was specified.  If neither
 675     * was specified and the file is in the Attic, remove the tag.
 676     */
 677    if (attic_too && (!numtag && !date))
 678    {
 679	if ((rcsfile->flags & VALID) && (rcsfile->flags & INATTIC))
 680	    return (rtag_delete (rcsfile));
 681    }
 682
 683    version = RCS_getversion (rcsfile, numtag, date, force_tag_match,
 684			      (int *) NULL);
 685    if (version == NULL)
 686    {
 687	/* If -a specified, clean up any old tags */
 688	if (attic_too)
 689	    (void) rtag_delete (rcsfile);
 690
 691	if (!quiet && !force_tag_match)
 692	{
 693	    error (0, 0, "cannot find tag `%s' in `%s'",
 694		   numtag ? numtag : "head", rcsfile->path);
 695	    return (1);
 696	}
 697	return (0);
 698    }
 699    if (numtag
 700	&& isdigit ((unsigned char) *numtag)
 701	&& strcmp (numtag, version) != 0)
 702    {
 703
 704	/*
 705	 * We didn't find a match for the numeric tag that was specified, but
 706	 * that's OK.  just pass the numeric tag on to rcs, to be tagged as
 707	 * specified.  Could get here if one tried to tag "1.1.1" and there
 708	 * was a 1.1.1 branch with some head revision.  In this case, we want
 709	 * the tag to reference "1.1.1" and not the revision at the head of
 710	 * the branch.  Use a symbolic tag for that.
 711	 */
 712	rev = branch_mode ? RCS_magicrev (rcsfile, version) : numtag;
 713	retcode = RCS_settag(rcsfile, symtag, numtag);
 714	if (retcode == 0)
 715	    RCS_rewrite (rcsfile, NULL, NULL);
 716    }
 717    else
 718    {
 719	char *oversion;
 720       
 721	/*
 722	 * As an enhancement for the case where a tag is being re-applied to
 723	 * a large body of a module, make one extra call to RCS_getversion to
 724	 * see if the tag is already set in the RCS file.  If so, check to
 725	 * see if it needs to be moved.  If not, do nothing.  This will
 726	 * likely save a lot of time when simply moving the tag to the
 727	 * "current" head revisions of a module -- which I have found to be a
 728	 * typical tagging operation.
 729	 */
 730	rev = branch_mode ? RCS_magicrev (rcsfile, version) : version;
 731	oversion = RCS_getversion (rcsfile, symtag, (char *) NULL, 1,
 732				   (int *) NULL);
 733	if (oversion != NULL)
 734	{
 735	    int isbranch = RCS_nodeisbranch (finfo->rcs, symtag);
 736
 737	    /*
 738	     * if versions the same and neither old or new are branches don't
 739	     * have to do anything
 740	     */
 741	    if (strcmp (version, oversion) == 0 && !branch_mode && !isbranch)
 742	    {
 743		free (oversion);
 744		free (version);
 745		return (0);
 746	    }
 747	  
 748	    if (!force_tag_move)
 749	    {
 750		/* we're NOT going to move the tag */
 751		(void) printf ("W %s", finfo->fullname);
 752
 753		(void) printf (" : %s already exists on %s %s", 
 754			       symtag, isbranch ? "branch" : "version",
 755			       oversion);
 756		(void) printf (" : NOT MOVING tag to %s %s\n", 
 757			       branch_mode ? "branch" : "version", rev);
 758		free (oversion);
 759		free (version);
 760		if (branch_mode) free(rev);
 761		return (0);
 762	    }
 763	    else /* force_tag_move is set and... */
 764		if ((isbranch && !disturb_branch_tags) ||
 765		    (!isbranch && disturb_branch_tags))
 766	    {
 767	        error(0,0, "%s: Not moving %s tag `%s' from %s to %s%s.", 
 768			finfo->fullname,
 769			isbranch ? "branch" : "non-branch",
 770			symtag, oversion, rev,
 771			isbranch ? "" : " due to `-B' option"); 
 772		if (branch_mode) free(rev);
 773		free (oversion);
 774		free (version);
 775		return (0);
 776	    }
 777	    free (oversion);
 778	}
 779	retcode = RCS_settag(rcsfile, symtag, rev);
 780	if (retcode == 0)
 781	    RCS_rewrite (rcsfile, NULL, NULL);
 782    }
 783
 784    if (retcode != 0)
 785    {
 786	error (1, retcode == -1 ? errno : 0,
 787	       "failed to set tag `%s' to revision `%s' in `%s'",
 788	       symtag, rev, rcsfile->path);
 789        if (branch_mode)
 790	    free (rev);
 791        free (version);
 792        return (1);
 793    }
 794    if (branch_mode)
 795	free (rev);
 796    free (version);
 797    return (0);
 798}
 799
 800/*
 801 * If -d is specified, "force_tag_match" is set, so that this call to
 802 * RCS_getversion() will return a NULL version string if the symbolic
 803 * tag does not exist in the RCS file.
 804 * 
 805 * If the -r flag was used, numtag is set, and we only delete the
 806 * symtag from files that have numtag.
 807 * 
 808 * This is done here because it's MUCH faster than just blindly calling
 809 * "rcs" to remove the tag... trust me.
 810 */
 811static int
 812rtag_delete (rcsfile)
 813    RCSNode *rcsfile;
 814{
 815    char *version;
 816    int retcode, isbranch;
 817
 818    if (numtag)
 819    {
 820	version = RCS_getversion (rcsfile, numtag, (char *) NULL, 1,
 821				  (int *) NULL);
 822	if (version == NULL)
 823	    return (0);
 824	free (version);
 825    }
 826
 827    version = RCS_getversion (rcsfile, symtag, (char *) NULL, 1,
 828			      (int *) NULL);
 829    if (version == NULL)
 830	return (0);
 831    free (version);
 832
 833
 834    isbranch = RCS_nodeisbranch (rcsfile, symtag);
 835    if ((isbranch && !disturb_branch_tags) || 
 836	(!isbranch && disturb_branch_tags))
 837    {
 838	if (!quiet)
 839	    error(0, 0,
 840		"Not removing %s tag `%s' from `%s'%s.", 
 841		isbranch ? "branch" : "non-branch",
 842		symtag, rcsfile->path,
 843		isbranch ? "" : " due to `-B' option"); 
 844	return (1);
 845    }
 846
 847    if ((retcode = RCS_deltag(rcsfile, symtag)) != 0)
 848    {
 849	if (!quiet)
 850	    error (0, retcode == -1 ? errno : 0,
 851		   "failed to remove tag `%s' from `%s'", symtag,
 852		   rcsfile->path);
 853	return (1);
 854    }
 855    RCS_rewrite (rcsfile, NULL, NULL);
 856    return (0);
 857}
 858
 859
 860/*
 861 * Called to tag a particular file (the currently checked out version is
 862 * tagged with the specified tag - or the specified tag is deleted).
 863 */
 864/* ARGSUSED */
 865static int
 866tag_fileproc (callerdat, finfo)
 867    void *callerdat;
 868    struct file_info *finfo;
 869{
 870    char *version, *oversion;
 871    char *nversion = NULL;
 872    char *rev;
 873    Vers_TS *vers;
 874    int retcode = 0;
 875    int retval = 0;
 876
 877    vers = Version_TS (finfo, NULL, NULL, NULL, 0, 0);
 878
 879    if ((numtag != NULL) || (date != NULL))
 880    {
 881        nversion = RCS_getversion(vers->srcfile,
 882                                  numtag,
 883                                  date,
 884                                  force_tag_match,
 885				  (int *) NULL);
 886        if (nversion == NULL)
 887	    goto free_vars_and_return;
 888    }
 889    if (delete_flag)
 890    {
 891
 892	int isbranch;
 893	/*
 894	 * If -d is specified, "force_tag_match" is set, so that this call to
 895	 * RCS_getversion() will return a NULL version string if the symbolic
 896	 * tag does not exist in the RCS file.
 897	 * 
 898	 * This is done here because it's MUCH faster than just blindly calling
 899	 * "rcs" to remove the tag... trust me.
 900	 */
 901
 902	version = RCS_getversion (vers->srcfile, symtag, (char *) NULL, 1,
 903				  (int *) NULL);
 904	if (version == NULL || vers->srcfile == NULL)
 905	    goto free_vars_and_return;
 906
 907	free (version);
 908
 909	isbranch = RCS_nodeisbranch (finfo->rcs, symtag);
 910	if ((isbranch && !disturb_branch_tags) || 
 911	    (!isbranch && disturb_branch_tags))
 912	{
 913	    if (!quiet)
 914		error(0, 0,
 915		       "Not removing %s tag `%s' from `%s'%s.", 
 916			isbranch ? "branch" : "non-branch",
 917			symtag, vers->srcfile->path,
 918			isbranch ? "" : " due to `-B' option"); 
 919	    retval = 1;
 920	    goto free_vars_and_return;
 921	}
 922
 923	if ((retcode = RCS_deltag(vers->srcfile, symtag)) != 0) 
 924	{
 925	    if (!quiet)
 926		error (0, retcode == -1 ? errno : 0,
 927		       "failed to remove tag %s from %s", symtag,
 928		       vers->srcfile->path);
 929	    retval = 1;
 930	    goto free_vars_and_return;
 931	}
 932	RCS_rewrite (vers->srcfile, NULL, NULL);
 933
 934	/* warm fuzzies */
 935	if (!really_quiet)
 936	{
 937	    cvs_output ("D ", 2);
 938	    cvs_output (finfo->fullname, 0);
 939	    cvs_output ("\n", 1);
 940	}
 941
 942	goto free_vars_and_return;
 943    }
 944
 945    /*
 946     * If we are adding a tag, we need to know which version we have checked
 947     * out and we'll tag that version.
 948     */
 949    if (nversion == NULL)
 950    {
 951        version = vers->vn_user;
 952    }
 953    else
 954    {
 955        version = nversion;
 956    }
 957    if (version == NULL)
 958    {
 959	goto free_vars_and_return;
 960    }
 961    else if (strcmp (version, "0") == 0)
 962    {
 963	if (!quiet)
 964	    error (0, 0, "couldn't tag added but un-commited file `%s'", finfo->file);
 965	goto free_vars_and_return;
 966    }
 967    else if (version[0] == '-')
 968    {
 969	if (!quiet)
 970	    error (0, 0, "skipping removed but un-commited file `%s'", finfo->file);
 971	goto free_vars_and_return;
 972    }
 973    else if (vers->srcfile == NULL)
 974    {
 975	if (!quiet)
 976	    error (0, 0, "cannot find revision control file for `%s'", finfo->file);
 977	goto free_vars_and_return;
 978    }
 979
 980    /*
 981     * As an enhancement for the case where a tag is being re-applied to a
 982     * large number of files, make one extra call to RCS_getversion to see
 983     * if the tag is already set in the RCS file.  If so, check to see if it
 984     * needs to be moved.  If not, do nothing.  This will likely save a lot of
 985     * time when simply moving the tag to the "current" head revisions of a
 986     * module -- which I have found to be a typical tagging operation.
 987     */
 988    rev = branch_mode ? RCS_magicrev (vers->srcfile, version) : version;
 989    oversion = RCS_getversion (vers->srcfile, symtag, (char *) NULL, 1,
 990			       (int *) NULL);
 991    if (oversion != NULL)
 992    {
 993	int isbranch = RCS_nodeisbranch (finfo->rcs, symtag);
 994
 995	/*
 996	 * if versions the same and neither old or new are branches don't have 
 997	 * to do anything
 998	 */
 999	if (strcmp (version, oversion) == 0 && !branch_mode && !isbranch)
1000	{
1001	    free (oversion);
1002	    if (branch_mode)
1003		free (rev);
1004	    goto free_vars_and_return;
1005	}
1006
1007	if (!force_tag_move)
1008	{
1009	    /* we're NOT going to move the tag */
1010	    cvs_output ("W ", 2);
1011	    cvs_output (finfo->fullname, 0);
1012	    cvs_output (" : ", 0);
1013	    cvs_output (symtag, 0);
1014	    cvs_output (" already exists on ", 0);
1015	    cvs_output (isbranch ? "branch" : "version", 0);
1016	    cvs_output (" ", 0);
1017	    cvs_output (oversion, 0);
1018	    cvs_output (" : NOT MOVING tag to ", 0);
1019	    cvs_output (branch_mode ? "branch" : "version", 0);
1020	    cvs_output (" ", 0);
1021	    cvs_output (rev, 0);
1022	    cvs_output ("\n", 1);
1023	    free (oversion);
1024	    if (branch_mode)
1025		free (rev);
1026	    goto free_vars_and_return;
1027	}
1028	else 	/* force_tag_move == 1 and... */
1029		if ((isbranch && !disturb_branch_tags) ||
1030		    (!isbranch && disturb_branch_tags))
1031	{
1032	    error(0,0, "%s: Not moving %s tag `%s' from %s to %s%s.", 
1033		    finfo->fullname,
1034		    isbranch ? "branch" : "non-branch",
1035		    symtag, oversion, rev,
1036		    isbranch ? "" : " due to `-B' option"); 
1037	    free (oversion);
1038	    if (branch_mode)
1039		free (rev);
1040	    goto free_vars_and_return;
1041	}
1042	free (oversion);
1043    }
1044
1045    if ((retcode = RCS_settag(vers->srcfile, symtag, rev)) != 0)
1046    {
1047	error (1, retcode == -1 ? errno : 0,
1048	       "failed to set tag %s to revision %s in %s",
1049	       symtag, rev, vers->srcfile->path);
1050	if (branch_mode)
1051	    free (rev);
1052	retval = 1;
1053	goto free_vars_and_return;
1054    }
1055    if (branch_mode)
1056	free (rev);
1057    RCS_rewrite (vers->srcfile, NULL, NULL);
1058
1059    /* more warm fuzzies */
1060    if (!really_quiet)
1061    {
1062	cvs_output ("T ", 2);
1063	cvs_output (finfo->fullname, 0);
1064	cvs_output ("\n", 1);
1065    }
1066
1067 free_vars_and_return:
1068    if (nversion != NULL)
1069        free (nversion);
1070    freevers_ts (&vers);
1071    return (retval);
1072}
1073
1074/*
1075 * Print a warm fuzzy message
1076 */
1077/* ARGSUSED */
1078static Dtype
1079tag_dirproc (callerdat, dir, repos, update_dir, entries)
1080    void *callerdat;
1081    const char *dir;
1082    const char *repos;
1083    const char *update_dir;
1084    List *entries;
1085{
1086
1087    if (ignore_directory (update_dir))
1088    {
1089	/* print the warm fuzzy message */
1090	if (!quiet)
1091	  error (0, 0, "Ignoring %s", update_dir);
1092        return R_SKIP_ALL;
1093    }
1094
1095    if (!quiet)
1096	error (0, 0, "%s %s", delete_flag ? "Untagging" : "Tagging", update_dir);
1097    return (R_PROCESS);
1098}
1099
1100/* Code relating to the val-tags file.  Note that this file has no way
1101   of knowing when a tag has been deleted.  The problem is that there
1102   is no way of knowing whether a tag still exists somewhere, when we
1103   delete it some places.  Using per-directory val-tags files (in
1104   CVSREP) might be better, but that might slow down the process of
1105   verifying that a tag is correct (maybe not, for the likely cases,
1106   if carefully done), and/or be harder to implement correctly.  */
1107
1108struct val_args {
1109    char *name;
1110    int found;
1111};
1112
1113static int val_fileproc PROTO ((void *callerdat, struct file_info *finfo));
1114
1115static int
1116val_fileproc (callerdat, finfo)
1117    void *callerdat;
1118    struct file_info *finfo;
1119{
1120    RCSNode *rcsdata;
1121    struct val_args *args = (struct val_args *)callerdat;
1122    char *tag;
1123
1124    if ((rcsdata = finfo->rcs) == NULL)
1125	/* Not sure this can happen, after all we passed only
1126	   W_REPOS | W_ATTIC.  */
1127	return 0;
1128
1129    tag = RCS_gettag (rcsdata, args->name, 1, (int *) NULL);
1130    if (tag != NULL)
1131    {
1132	/* FIXME: should find out a way to stop the search at this point.  */
1133	args->found = 1;
1134	free (tag);
1135    }
1136    return 0;
1137}
1138
1139
1140
1141/* This routine determines whether a tag appears in CVSROOT/val-tags.
1142 *
1143 * The val-tags file will be open read-only when IDB is NULL.  Since writes to
1144 * val-tags always append to it, the lack of locking is okay.  The worst case
1145 * race condition might misinterpret a partially written "foobar" matched, for
1146 * instance,  a request for "f", "foo", of "foob".  Such a mismatch would be
1147 * caught harmlessly later.
1148 *
1149 * Before CVS adds a tag to val-tags, it will lock val-tags for write and
1150 * verify that the tag is still not present to avoid adding it twice.
1151 *
1152 * NOTES
1153 *   This function expects its parent to handle any necessary locking of the
1154 *   val-tags file.
1155 *
1156 * INPUTS
1157 *   idb	When this value is NULL, the val-tags file is opened in
1158 *   		in read-only mode.  When present, the val-tags file is opened
1159 *   		in read-write mode and the DBM handle is stored in *IDB.
1160 *   name	The tag to search for.
1161 *
1162 * OUTPUTS
1163 *   *idb	The val-tags file opened for read/write, or NULL if it couldn't
1164 *   		be opened.
1165 *
1166 * ERRORS
1167 *   Exits with an error message if the val-tags file cannot be opened for
1168 *   read (failure to open val-tags read/write is harmless - see below).
1169 *
1170 * RETURNS
1171 *   true	1. If NAME exists in val-tags.
1172 *   		2. If IDB is non-NULL and val-tags cannot be opened for write.
1173 *   		   This allows callers to ignore the harmless inability to
1174 *   		   update the val-tags cache.
1175 *   false	If the file could be opened and the tag is not present.
1176 */
1177static int is_in_val_tags PROTO((DBM **idb, const char *name));
1178static int
1179is_in_val_tags (idb, name)
1180    DBM **idb;
1181    const char *name;
1182{
1183    DBM *db = NULL;
1184    char *valtags_filename;
1185    datum mytag;
1186    int status;
1187
1188    /* Casting out const should be safe here - input datums are not
1189     * written to by the myndbm functions.
1190     */
1191    mytag.dptr = (char *)name;
1192    mytag.dsize = strlen (name);
1193
1194    valtags_filename = xmalloc (strlen (current_parsed_root->directory)
1195				+ sizeof CVSROOTADM
1196				+ sizeof CVSROOTADM_VALTAGS + 3);
1197    sprintf (valtags_filename, "%s/%s/%s", current_parsed_root->directory,
1198					   CVSROOTADM, CVSROOTADM_VALTAGS);
1199
1200    if (idb)
1201    {
1202	db = dbm_open (valtags_filename, O_RDWR, 0666);
1203	if (!db)
1204	{
1205	    mode_t omask;
1206
1207	    if (!existence_error (errno))
1208	    {
1209		error (0, errno, "warning: cannot open %s read/write",
1210		       valtags_filename);
1211		*idb = NULL;
1212		return 1;
1213	    }
1214
1215	    omask = umask (cvsumask);
1216	    db = dbm_open (valtags_filename, O_RDWR | O_CREAT | O_TRUNC, 0666);
1217	    umask (omask);
1218	    if (!db)
1219	    {
1220		error (0, errno, "warning: cannot create %s",
1221		       valtags_filename);
1222		*idb = NULL;
1223		return 1;
1224	    }
1225
1226	    *idb = db;
1227	    return 0;
1228	}
1229
1230	*idb = db;
1231    }
1232    else
1233    {
1234	db = dbm_open (valtags_filename, O_RDONLY, 0444);
1235	if (!db && !existence_error (errno))
1236	    error (1, errno, "cannot read %s", valtags_filename);
1237    }
1238
1239    /* If the file merely fails to exist, we just keep going and create
1240       it later if need be.  */
1241
1242    status = 0;
1243    if (db)
1244    {
1245	datum val;
1246
1247	val = dbm_fetch (db, mytag);
1248	if (val.dptr != NULL)
1249	    /* Found.  The tag is valid.  */
1250	    status = 1;
1251
1252	/* FIXME: should check errors somehow (add dbm_error to myndbm.c?).  */
1253
1254	if (!idb) dbm_close (db);
1255    }
1256
1257    free (valtags_filename);
1258    return status;
1259}
1260
1261
1262
1263/* Add a tag to the CVSROOT/val-tags cache.  Establishes a write lock and
1264 * reverifies that the tag does not exist before adding it.
1265 */
1266static void add_to_val_tags PROTO((const char *name));
1267static void
1268add_to_val_tags (name)
1269    const char *name;
1270{
1271    DBM *db;
1272    datum mytag;
1273    datum value;
1274
1275    if (noexec) return;
1276
1277    val_tags_lock (current_parsed_root->directory);
1278
1279    /* Check for presence again since we have a lock now.  */
1280    if (is_in_val_tags (&db, name))
1281    {
1282	clear_val_tags_lock ();
1283	if (db)
1284	    dbm_close (db);
1285	return;
1286    }
1287
1288    /* Casting out const should be safe here - input datums are not
1289     * written to by the myndbm functions.
1290     */
1291    mytag.dptr = (char *)name;
1292    mytag.dsize = strlen (name);
1293    value.dptr = "y";
1294    value.dsize = 1;
1295
1296    if (dbm_store (db, mytag, value, DBM_REPLACE) < 0)
1297	error (0, errno, "failed to store %s into val-tags", name);
1298    dbm_close (db);
1299
1300    clear_val_tags_lock ();
1301}
1302
1303
1304
1305static Dtype val_direntproc PROTO ((void *, const char *, const char *,
1306                                    const char *, List *));
1307
1308static Dtype
1309val_direntproc (callerdat, dir, repository, update_dir, entries)
1310    void *callerdat;
1311    const char *dir;
1312    const char *repository;
1313    const char *update_dir;
1314    List *entries;
1315{
1316    /* This is not quite right--it doesn't get right the case of "cvs
1317       update -d -r foobar" where foobar is a tag which exists only in
1318       files in a directory which does not exist yet, but which is
1319       about to be created.  */
1320    if (isdir (dir))
1321	return R_PROCESS;
1322    return R_SKIP_ALL;
1323}
1324
1325/* Check to see whether NAME is a valid tag.  If so, return.  If not
1326   print an error message and exit.  ARGC, ARGV, LOCAL, and AFLAG specify
1327   which files we will be operating on.
1328
1329   REPOSITORY is the repository if we need to cd into it, or NULL if
1330   we are already there, or "" if we should do a W_LOCAL recursion.
1331   Sorry for three cases, but the "" case is needed in case the
1332   working directories come from diverse parts of the repository, the
1333   NULL case avoids an unneccesary chdir, and the non-NULL, non-""
1334   case is needed for checkout, where we don't want to chdir if the
1335   tag is found in CVSROOTADM_VALTAGS, but there is not (yet) any
1336   local directory.  */
1337void
1338tag_check_valid (name, argc, argv, local, aflag, repository)
1339    char *name;
1340    int argc;
1341    char **argv;
1342    int local;
1343    int aflag;
1344    char *repository;
1345{
1346    struct val_args the_val_args;
1347    struct saved_cwd cwd;
1348    int which;
1349
1350    /* Numeric tags require only a syntactic check.  */
1351    if (isdigit ((unsigned char) name[0]))
1352    {
1353	char *p;
1354	for (p = name; *p != '\0'; ++p)
1355	{
1356	    if (!(isdigit ((unsigned char) *p) || *p == '.'))
1357		error (1, 0, "\
1358Numeric tag %s contains characters other than digits and '.'", name);
1359	}
1360	return;
1361    }
1362
1363    /* Special tags are always valid.  */
1364    if (strcmp (name, TAG_BASE) == 0
1365	|| strcmp (name, TAG_HEAD) == 0)
1366	return;
1367
1368    if (readonlyfs)
1369	return;
1370
1371    /* Verify that the tag is valid syntactically.  Some later code once made
1372     * assumptions about this.
1373     */
1374    RCS_check_tag (name);
1375
1376    if (is_in_val_tags (NULL, name)) return;
1377
1378    /* We didn't find the tag in val-tags, so look through all the RCS files
1379       to see whether it exists there.  Yes, this is expensive, but there
1380       is no other way to cope with a tag which might have been created
1381       by an old version of CVS, from before val-tags was invented.
1382
1383       Since we need this code anyway, we also use it to create
1384       entries in val-tags in general (that is, the val-tags entry
1385       will get created the first time the tag is used, not when the
1386       tag is created).  */
1387
1388    the_val_args.name = name;
1389    the_val_args.found = 0;
1390
1391    which = W_REPOS | W_ATTIC;
1392
1393    if (repository != NULL)
1394    {
1395	if (repository[0] == '\0')
1396	    which |= W_LOCAL;
1397	else
1398	{
1399	    if (save_cwd (&cwd))
1400		error_exit ();
1401	    if (CVS_CHDIR (repository) < 0)
1402		error (1, errno, "cannot change to %s directory", repository);
1403	}
1404    }
1405
1406    start_recursion (val_fileproc, (FILESDONEPROC) NULL,
1407		     val_direntproc, (DIRLEAVEPROC) NULL,
1408		     (void *)&the_val_args,
1409		     argc, argv, local, which, aflag,
1410		     CVS_LOCK_READ, NULL, 1, repository);
1411    if (repository != NULL && repository[0] != '\0')
1412    {
1413	if (restore_cwd (&cwd, NULL))
1414	    error_exit ();
1415	free_cwd (&cwd);
1416    }
1417
1418    if (!the_val_args.found)
1419	error (1, 0, "no such tag %s", name);
1420    else
1421	/* The tags is valid but not mentioned in val-tags.  Add it.  */
1422	add_to_val_tags (name);
1423}
1424
1425
1426
1427/*
1428 * Check whether a join tag is valid.  This is just like
1429 * tag_check_valid, but we must stop before the colon if there is one.
1430 */
1431
1432void
1433tag_check_valid_join (join_tag, argc, argv, local, aflag, repository)
1434     char *join_tag;
1435     int argc;
1436     char **argv;
1437     int local;
1438     int aflag;
1439     char *repository;
1440{
1441    char *c, *s;
1442
1443    c = xstrdup (join_tag);
1444    s = strchr (c, ':');
1445    if (s != NULL)
1446    {
1447        if (isdigit ((unsigned char) join_tag[0]))
1448	    error (1, 0,
1449		   "Numeric join tag %s may not contain a date specifier",
1450		   join_tag);
1451
1452        *s = '\0';
1453	/* hmmm...  I think it makes sense to allow -j:<date>, but
1454	 * for now this fixes a bug where CVS just spins and spins (I
1455	 * think in the RCS code) looking for a zero length tag.
1456	 */
1457	if (!*c)
1458	    error (1, 0,
1459		   "argument to join may not contain a date specifier without a tag");
1460    }
1461
1462    tag_check_valid (c, argc, argv, local, aflag, repository);
1463
1464    free (c);
1465}