PageRenderTime 119ms CodeModel.GetById 27ms app.highlight 77ms RepoModel.GetById 1ms app.codeStats 1ms

/contrib/cvs/src/commit.c

https://bitbucket.org/freebsd/freebsd-head/
C | 2433 lines | 1644 code | 303 blank | 486 comment | 439 complexity | 6e9c8bfcd87887423de6329971706e51 MD5 | raw file

Large files files are truncated, but you can click here to view the full 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 * Commit Files
  14 *
  15 * "commit" commits the present version to the RCS repository, AFTER
  16 * having done a test on conflicts.
  17 *
  18 * The call is: cvs commit [options] files...
  19 *
  20 * $FreeBSD$
  21 */
  22
  23#include <assert.h>
  24#include "cvs.h"
  25#include "getline.h"
  26#include "edit.h"
  27#include "fileattr.h"
  28#include "hardlink.h"
  29
  30static Dtype check_direntproc PROTO ((void *callerdat, const char *dir,
  31                                      const char *repos,
  32                                      const char *update_dir,
  33                                      List *entries));
  34static int check_fileproc PROTO ((void *callerdat, struct file_info *finfo));
  35static int check_filesdoneproc PROTO ((void *callerdat, int err,
  36                                       const char *repos,
  37                                       const char *update_dir,
  38                                       List *entries));
  39static int checkaddfile PROTO((const char *file, const char *repository,
  40                               const char *tag, const char *options,
  41                               RCSNode **rcsnode));
  42static Dtype commit_direntproc PROTO ((void *callerdat, const char *dir,
  43                                       const char *repos,
  44                                       const char *update_dir,
  45                                       List *entries));
  46static int commit_dirleaveproc PROTO ((void *callerdat, const char *dir,
  47                                       int err, const char *update_dir,
  48                                       List *entries));
  49static int commit_fileproc PROTO ((void *callerdat, struct file_info *finfo));
  50static int commit_filesdoneproc PROTO ((void *callerdat, int err,
  51                                        const char *repository,
  52                                        const char *update_dir,
  53                                        List *entries));
  54static int finaladd PROTO((struct file_info *finfo, char *revision, char *tag,
  55			   char *options));
  56static int findmaxrev PROTO((Node * p, void *closure));
  57static int lock_RCS PROTO((const char *user, RCSNode *rcs, const char *rev,
  58			   const char *repository));
  59static int precommit_list_proc PROTO((Node * p, void *closure));
  60static int precommit_proc PROTO((const char *repository, const char *filter));
  61static int remove_file PROTO ((struct file_info *finfo, char *tag,
  62			       char *message));
  63static void fixaddfile PROTO((const char *rcs));
  64static void fixbranch PROTO((RCSNode *, char *branch));
  65static void unlockrcs PROTO((RCSNode *rcs));
  66static void ci_delproc PROTO((Node *p));
  67static void masterlist_delproc PROTO((Node *p));
  68
  69struct commit_info
  70{
  71    Ctype status;			/* as returned from Classify_File() */
  72    char *rev;				/* a numeric rev, if we know it */
  73    char *tag;				/* any sticky tag, or -r option */
  74    char *options;			/* Any sticky -k option */
  75};
  76struct master_lists
  77{
  78    List *ulist;			/* list for Update_Logfile */
  79    List *cilist;			/* list with commit_info structs */
  80};
  81
  82static int force_ci = 0;
  83static int got_message;
  84static int aflag;
  85static char *saved_tag;
  86static char *write_dirtag;
  87static int write_dirnonbranch;
  88static char *logfile;
  89static List *mulist;
  90static List *saved_ulist;
  91static char *saved_message;
  92static time_t last_register_time;
  93
  94static const char *const commit_usage[] =
  95{
  96    "Usage: %s %s [-Rlf] [-m msg | -F logfile] [-r rev] files...\n",
  97    "    -R          Process directories recursively.\n",
  98    "    -l          Local directory only (not recursive).\n",
  99    "    -f          Force the file to be committed; disables recursion.\n",
 100    "    -F logfile  Read the log message from file.\n",
 101    "    -m msg      Log message.\n",
 102    "    -r rev      Commit to this branch or trunk revision.\n",
 103    "(Specify the --help global option for a list of other help options)\n",
 104    NULL
 105};
 106
 107#ifdef CLIENT_SUPPORT
 108/* Identify a file which needs "? foo" or a Questionable request.  */
 109struct question {
 110    /* The two fields for the Directory request.  */
 111    char *dir;
 112    char *repos;
 113
 114    /* The file name.  */
 115    char *file;
 116
 117    struct question *next;
 118};
 119
 120struct find_data {
 121    List *ulist;
 122    int argc;
 123    char **argv;
 124
 125    /* This is used from dirent to filesdone time, for each directory,
 126       to make a list of files we have already seen.  */
 127    List *ignlist;
 128
 129    /* Linked list of files which need "? foo" or a Questionable request.  */
 130    struct question *questionables;
 131
 132    /* Only good within functions called from the filesdoneproc.  Stores
 133       the repository (pointer into storage managed by the recursion
 134       processor.  */
 135    const char *repository;
 136
 137    /* Non-zero if we should force the commit.  This is enabled by
 138       either -f or -r options, unlike force_ci which is just -f.  */
 139    int force;
 140};
 141
 142
 143
 144static Dtype find_dirent_proc PROTO ((void *callerdat, const char *dir,
 145                                      const char *repository,
 146                                      const char *update_dir,
 147                                      List *entries));
 148
 149static Dtype
 150find_dirent_proc (callerdat, dir, repository, update_dir, entries)
 151    void *callerdat;
 152    const char *dir;
 153    const char *repository;
 154    const char *update_dir;
 155    List *entries;
 156{
 157    struct find_data *find_data = (struct find_data *)callerdat;
 158
 159    /* This check seems to slowly be creeping throughout CVS (update
 160       and send_dirent_proc by CVS 1.5, diff in 31 Oct 1995.  My guess
 161       is that it (or some variant thereof) should go in all the
 162       dirent procs.  Unless someone has some better idea...  */
 163    if (!isdir (dir))
 164	return R_SKIP_ALL;
 165
 166    /* initialize the ignore list for this directory */
 167    find_data->ignlist = getlist ();
 168
 169    /* Print the same warm fuzzy as in check_direntproc, since that
 170       code will never be run during client/server operation and we
 171       want the messages to match. */
 172    if (!quiet)
 173	error (0, 0, "Examining %s", update_dir);
 174
 175    return R_PROCESS;
 176}
 177
 178
 179
 180/* Here as a static until we get around to fixing ignore_files to pass
 181   it along as an argument.  */
 182static struct find_data *find_data_static;
 183
 184
 185
 186static void find_ignproc PROTO ((const char *, const char *));
 187
 188static void
 189find_ignproc (file, dir)
 190    const char *file;
 191    const char *dir;
 192{
 193    struct question *p;
 194
 195    p = (struct question *) xmalloc (sizeof (struct question));
 196    p->dir = xstrdup (dir);
 197    p->repos = xstrdup (find_data_static->repository);
 198    p->file = xstrdup (file);
 199    p->next = find_data_static->questionables;
 200    find_data_static->questionables = p;
 201}
 202
 203
 204
 205static int find_filesdoneproc PROTO ((void *callerdat, int err,
 206                                      const char *repository,
 207                                      const char *update_dir,
 208                                      List *entries));
 209
 210static int
 211find_filesdoneproc (callerdat, err, repository, update_dir, entries)
 212    void *callerdat;
 213    int err;
 214    const char *repository;
 215    const char *update_dir;
 216    List *entries;
 217{
 218    struct find_data *find_data = (struct find_data *)callerdat;
 219    find_data->repository = repository;
 220
 221    /* if this directory has an ignore list, process it then free it */
 222    if (find_data->ignlist)
 223    {
 224	find_data_static = find_data;
 225	ignore_files (find_data->ignlist, entries, update_dir, find_ignproc);
 226	dellist (&find_data->ignlist);
 227    }
 228
 229    find_data->repository = NULL;
 230
 231    return err;
 232}
 233
 234
 235
 236static int find_fileproc PROTO ((void *callerdat, struct file_info *finfo));
 237
 238/* Machinery to find out what is modified, added, and removed.  It is
 239   possible this should be broken out into a new client_classify function;
 240   merging it with classify_file is almost sure to be a mess, though,
 241   because classify_file has all kinds of repository processing.  */
 242static int
 243find_fileproc (callerdat, finfo)
 244    void *callerdat;
 245    struct file_info *finfo;
 246{
 247    Vers_TS *vers;
 248    enum classify_type status;
 249    Node *node;
 250    struct find_data *args = (struct find_data *)callerdat;
 251    struct logfile_info *data;
 252    struct file_info xfinfo;
 253
 254    /* if this directory has an ignore list, add this file to it */
 255    if (args->ignlist)
 256    {
 257	Node *p;
 258
 259	p = getnode ();
 260	p->type = FILES;
 261	p->key = xstrdup (finfo->file);
 262	if (addnode (args->ignlist, p) != 0)
 263	    freenode (p);
 264    }
 265
 266    xfinfo = *finfo;
 267    xfinfo.repository = NULL;
 268    xfinfo.rcs = NULL;
 269
 270    vers = Version_TS (&xfinfo, NULL, saved_tag, NULL, 0, 0);
 271    if (vers->vn_user == NULL)
 272    {
 273	if (vers->ts_user == NULL)
 274	    error (0, 0, "nothing known about `%s'", finfo->fullname);
 275	else
 276	    error (0, 0, "use `%s add' to create an entry for %s",
 277		   program_name, finfo->fullname);
 278	freevers_ts (&vers);
 279	return 1;
 280    }
 281    if (vers->vn_user[0] == '-')
 282    {
 283	if (vers->ts_user != NULL)
 284	{
 285	    error (0, 0,
 286		   "`%s' should be removed and is still there (or is back"
 287		   " again)", finfo->fullname);
 288	    freevers_ts (&vers);
 289	    return 1;
 290	}
 291	/* else */
 292	status = T_REMOVED;
 293    }
 294    else if (strcmp (vers->vn_user, "0") == 0)
 295    {
 296	if (vers->ts_user == NULL)
 297	{
 298	    /* This happens when one has `cvs add'ed a file, but it no
 299	       longer exists in the working directory at commit time.
 300	       FIXME: What classify_file does in this case is print
 301	       "new-born %s has disappeared" and removes the entry.
 302	       We probably should do the same.  */
 303	    if (!really_quiet)
 304		error (0, 0, "warning: new-born %s has disappeared",
 305		       finfo->fullname);
 306	    status = T_REMOVE_ENTRY;
 307	}
 308	else
 309	    status = T_ADDED;
 310    }
 311    else if (vers->ts_user == NULL)
 312    {
 313	/* FIXME: What classify_file does in this case is print
 314	   "%s was lost".  We probably should do the same.  */
 315	freevers_ts (&vers);
 316	return 0;
 317    }
 318    else if (vers->ts_rcs != NULL
 319	     && (args->force || strcmp (vers->ts_user, vers->ts_rcs) != 0))
 320	/* If we are forcing commits, pretend that the file is
 321           modified.  */
 322	status = T_MODIFIED;
 323    else
 324    {
 325	/* This covers unmodified files, as well as a variety of other
 326	   cases.  FIXME: we probably should be printing a message and
 327	   returning 1 for many of those cases (but I'm not sure
 328	   exactly which ones).  */
 329	freevers_ts (&vers);
 330	return 0;
 331    }
 332
 333    node = getnode ();
 334    node->key = xstrdup (finfo->fullname);
 335
 336    data = (struct logfile_info *) xmalloc (sizeof (struct logfile_info));
 337    data->type = status;
 338    data->tag = xstrdup (vers->tag);
 339    data->rev_old = data->rev_new = NULL;
 340
 341    node->type = UPDATE;
 342    node->delproc = update_delproc;
 343    node->data = data;
 344    (void)addnode (args->ulist, node);
 345
 346    ++args->argc;
 347
 348    freevers_ts (&vers);
 349    return 0;
 350}
 351
 352
 353
 354static int copy_ulist PROTO ((Node *, void *));
 355
 356static int
 357copy_ulist (node, data)
 358    Node *node;
 359    void *data;
 360{
 361    struct find_data *args = (struct find_data *)data;
 362    args->argv[args->argc++] = node->key;
 363    return 0;
 364}
 365#endif /* CLIENT_SUPPORT */
 366
 367#ifdef SERVER_SUPPORT
 368# define COMMIT_OPTIONS "+nlRm:fF:r:"
 369#else /* !SERVER_SUPPORT */
 370# define COMMIT_OPTIONS "+lRm:fF:r:"
 371#endif /* SERVER_SUPPORT */
 372int
 373commit (argc, argv)
 374    int argc;
 375    char **argv;
 376{
 377    int c;
 378    int err = 0;
 379    int local = 0;
 380
 381    if (argc == -1)
 382	usage (commit_usage);
 383
 384#ifdef CVS_BADROOT
 385    /*
 386     * For log purposes, do not allow "root" to commit files.  If you look
 387     * like root, but are really logged in as a non-root user, it's OK.
 388     */
 389    /* FIXME: Shouldn't this check be much more closely related to the
 390       readonly user stuff (CVSROOT/readers, &c).  That is, why should
 391       root be able to "cvs init", "cvs import", &c, but not "cvs ci"?  */
 392    /* Who we are on the client side doesn't affect logging.  */
 393    if (geteuid () == (uid_t) 0 && !current_parsed_root->isremote)
 394    {
 395	struct passwd *pw;
 396
 397	if ((pw = (struct passwd *) getpwnam (getcaller ())) == NULL)
 398	    error (1, 0,
 399                   "your apparent username (%s) is unknown to this system",
 400                   getcaller ());
 401	if (pw->pw_uid == (uid_t) 0)
 402	    error (1, 0, "'root' is not allowed to commit files");
 403    }
 404#endif /* CVS_BADROOT */
 405
 406    optind = 0;
 407    while ((c = getopt (argc, argv, COMMIT_OPTIONS)) != -1)
 408    {
 409	switch (c)
 410	{
 411#ifdef SERVER_SUPPORT
 412	    case 'n':
 413		/* Silently ignore -n for compatibility with old
 414		 * clients.
 415		 */
 416		if (!server_active) error(0, 0, "the `-n' option is obsolete");
 417		break;
 418#endif /* SERVER_SUPPORT */
 419	    case 'm':
 420#ifdef FORCE_USE_EDITOR
 421		use_editor = 1;
 422#else
 423		use_editor = 0;
 424#endif
 425		if (saved_message)
 426		{
 427		    free (saved_message);
 428		    saved_message = NULL;
 429		}
 430
 431		saved_message = xstrdup(optarg);
 432		break;
 433	    case 'r':
 434		if (saved_tag)
 435		    free (saved_tag);
 436		saved_tag = xstrdup (optarg);
 437		break;
 438	    case 'l':
 439		local = 1;
 440		break;
 441	    case 'R':
 442		local = 0;
 443		break;
 444	    case 'f':
 445		force_ci = 1;
 446		local = 1;		/* also disable recursion */
 447		break;
 448	    case 'F':
 449#ifdef FORCE_USE_EDITOR
 450		use_editor = 1;
 451#else
 452		use_editor = 0;
 453#endif
 454		logfile = optarg;
 455		break;
 456	    case '?':
 457	    default:
 458		usage (commit_usage);
 459		break;
 460	}
 461    }
 462    argc -= optind;
 463    argv += optind;
 464
 465    /* numeric specified revision means we ignore sticky tags... */
 466    if (saved_tag && isdigit ((unsigned char) *saved_tag))
 467    {
 468	char *p = saved_tag + strlen (saved_tag);
 469	aflag = 1;
 470	/* strip trailing dots and leading zeros */
 471	while (*--p == '.') ;
 472	p[1] = '\0';
 473	while (saved_tag[0] == '0' && isdigit ((unsigned char) saved_tag[1]))
 474	    ++saved_tag;
 475    }
 476
 477    /* some checks related to the "-F logfile" option */
 478    if (logfile)
 479    {
 480	size_t size = 0, len;
 481
 482	if (saved_message)
 483	    error (1, 0, "cannot specify both a message and a log file");
 484
 485	get_file (logfile, logfile, "r", &saved_message, &size, &len);
 486    }
 487
 488#ifdef CLIENT_SUPPORT
 489    if (current_parsed_root->isremote)
 490    {
 491	struct find_data find_args;
 492
 493	ign_setup ();
 494
 495	find_args.ulist = getlist ();
 496	find_args.argc = 0;
 497	find_args.questionables = NULL;
 498	find_args.ignlist = NULL;
 499	find_args.repository = NULL;
 500
 501	/* It is possible that only a numeric tag should set this.
 502	   I haven't really thought about it much.
 503	   Anyway, I suspect that setting it unnecessarily only causes
 504	   a little unneeded network traffic.  */
 505	find_args.force = force_ci || saved_tag != NULL;
 506
 507	err = start_recursion (find_fileproc, find_filesdoneproc,
 508			       find_dirent_proc, (DIRLEAVEPROC) NULL,
 509			       (void *)&find_args,
 510			       argc, argv, local, W_LOCAL, 0, CVS_LOCK_NONE,
 511			       (char *) NULL, 0, (char *) NULL);
 512	if (err)
 513	    error (1, 0, "correct above errors first!");
 514
 515	if (find_args.argc == 0)
 516	{
 517	    /* Nothing to commit.  Exit now without contacting the
 518	       server (note that this means that we won't print "?
 519	       foo" for files which merit it, because we don't know
 520	       what is in the CVSROOT/cvsignore file).  */
 521	    dellist (&find_args.ulist);
 522	    return 0;
 523	}
 524
 525	/* Now we keep track of which files we actually are going to
 526	   operate on, and only work with those files in the future.
 527	   This saves time--we don't want to search the file system
 528	   of the working directory twice.  */
 529	if (size_overflow_p (xtimes (find_args.argc, sizeof (char **))))
 530	{
 531	    find_args.argc = 0;
 532	    return 0;
 533	}
 534	find_args.argv = xmalloc (xtimes (find_args.argc, sizeof (char **)));
 535	find_args.argc = 0;
 536	walklist (find_args.ulist, copy_ulist, &find_args);
 537
 538	/* Do this before calling do_editor; don't ask for a log
 539	   message if we can't talk to the server.  But do it after we
 540	   have made the checks that we can locally (to more quickly
 541	   catch syntax errors, the case where no files are modified,
 542	   added or removed, etc.).
 543
 544	   On the other hand, calling start_server before do_editor
 545	   means that we chew up server resources the whole time that
 546	   the user has the editor open (hours or days if the user
 547	   forgets about it), which seems dubious.  */
 548	start_server ();
 549
 550	/*
 551	 * We do this once, not once for each directory as in normal CVS.
 552	 * The protocol is designed this way.  This is a feature.
 553	 */
 554	if (use_editor)
 555	    do_editor (".", &saved_message, (char *)NULL, find_args.ulist);
 556
 557	/* We always send some sort of message, even if empty.  */
 558	option_with_arg ("-m", saved_message ? saved_message : "");
 559
 560	/* OK, now process all the questionable files we have been saving
 561	   up.  */
 562	{
 563	    struct question *p;
 564	    struct question *q;
 565
 566	    p = find_args.questionables;
 567	    while (p != NULL)
 568	    {
 569		if (ign_inhibit_server || !supported_request ("Questionable"))
 570		{
 571		    cvs_output ("? ", 2);
 572		    if (p->dir[0] != '\0')
 573		    {
 574			cvs_output (p->dir, 0);
 575			cvs_output ("/", 1);
 576		    }
 577		    cvs_output (p->file, 0);
 578		    cvs_output ("\n", 1);
 579		}
 580		else
 581		{
 582		    send_to_server ("Directory ", 0);
 583		    send_to_server (p->dir[0] == '\0' ? "." : p->dir, 0);
 584		    send_to_server ("\012", 1);
 585		    send_to_server (p->repos, 0);
 586		    send_to_server ("\012", 1);
 587
 588		    send_to_server ("Questionable ", 0);
 589		    send_to_server (p->file, 0);
 590		    send_to_server ("\012", 1);
 591		}
 592		free (p->dir);
 593		free (p->repos);
 594		free (p->file);
 595		q = p->next;
 596		free (p);
 597		p = q;
 598	    }
 599	}
 600
 601	if (local)
 602	    send_arg("-l");
 603	if (force_ci)
 604	    send_arg("-f");
 605	option_with_arg ("-r", saved_tag);
 606	send_arg ("--");
 607
 608	/* FIXME: This whole find_args.force/SEND_FORCE business is a
 609	   kludge.  It would seem to be a server bug that we have to
 610	   say that files are modified when they are not.  This makes
 611	   "cvs commit -r 2" across a whole bunch of files a very slow
 612	   operation (and it isn't documented in cvsclient.texi).  I
 613	   haven't looked at the server code carefully enough to be
 614	   _sure_ why this is needed, but if it is because the "ci"
 615	   program, which we used to call, wanted the file to exist,
 616	   then it would be relatively simple to fix in the server.  */
 617	send_files (find_args.argc, find_args.argv, local, 0,
 618		    find_args.force ? SEND_FORCE : 0);
 619
 620	/* Sending only the names of the files which were modified, added,
 621	   or removed means that the server will only do an up-to-date
 622	   check on those files.  This is different from local CVS and
 623	   previous versions of client/server CVS, but it probably is a Good
 624	   Thing, or at least Not Such A Bad Thing.  */
 625	send_file_names (find_args.argc, find_args.argv, 0);
 626	free (find_args.argv);
 627	dellist (&find_args.ulist);
 628
 629	send_to_server ("ci\012", 0);
 630	err = get_responses_and_close ();
 631	if (err != 0 && use_editor && saved_message != NULL)
 632	{
 633	    /* If there was an error, don't nuke the user's carefully
 634	       constructed prose.  This is something of a kludge; a better
 635	       solution is probably more along the lines of #150 in TODO
 636	       (doing a second up-to-date check before accepting the
 637	       log message has also been suggested, but that seems kind of
 638	       iffy because the real up-to-date check could still fail,
 639	       another error could occur, &c.  Also, a second check would
 640	       slow things down).  */
 641
 642	    char *fname;
 643	    FILE *fp;
 644
 645	    fp = cvs_temp_file (&fname);
 646	    if (fp == NULL)
 647		error (1, 0, "cannot create temporary file %s",
 648		       fname ? fname : "(null)");
 649	    if (fwrite (saved_message, 1, strlen (saved_message), fp)
 650		!= strlen (saved_message))
 651		error (1, errno, "cannot write temporary file %s", fname);
 652	    if (fclose (fp) < 0)
 653		error (0, errno, "cannot close temporary file %s", fname);
 654	    error (0, 0, "saving log message in %s", fname);
 655	    free (fname);
 656	}
 657	return err;
 658    }
 659#endif
 660
 661    if (saved_tag != NULL)
 662	tag_check_valid (saved_tag, argc, argv, local, aflag, "");
 663
 664    /* XXX - this is not the perfect check for this */
 665    if (argc <= 0)
 666	write_dirtag = saved_tag;
 667
 668    wrap_setup ();
 669
 670    lock_tree_for_write (argc, argv, local, W_LOCAL, aflag);
 671
 672    /*
 673     * Set up the master update list and hard link list
 674     */
 675    mulist = getlist ();
 676
 677#ifdef PRESERVE_PERMISSIONS_SUPPORT
 678    if (preserve_perms)
 679    {
 680	hardlist = getlist ();
 681
 682	/*
 683	 * We need to save the working directory so that
 684	 * check_fileproc can construct a full pathname for each file.
 685	 */
 686	working_dir = xgetwd();
 687    }
 688#endif
 689
 690    /*
 691     * Run the recursion processor to verify the files are all up-to-date
 692     */
 693    err = start_recursion (check_fileproc, check_filesdoneproc,
 694			   check_direntproc, (DIRLEAVEPROC) NULL, NULL, argc,
 695			   argv, local, W_LOCAL, aflag, CVS_LOCK_NONE,
 696			   (char *) NULL, 1, (char *) NULL);
 697    if (err)
 698    {
 699	Lock_Cleanup ();
 700	error (1, 0, "correct above errors first!");
 701    }
 702
 703    /*
 704     * Run the recursion processor to commit the files
 705     */
 706    write_dirnonbranch = 0;
 707    if (noexec == 0)
 708	err = start_recursion (commit_fileproc, commit_filesdoneproc,
 709			       commit_direntproc, commit_dirleaveproc, NULL,
 710			       argc, argv, local, W_LOCAL, aflag, CVS_LOCK_NONE,
 711			       (char *) NULL, 1, (char *) NULL);
 712
 713    /*
 714     * Unlock all the dirs and clean up
 715     */
 716    Lock_Cleanup ();
 717    dellist (&mulist);
 718
 719    if (server_active)
 720	return err;
 721
 722    /* see if we need to sleep before returning to avoid time-stamp races */
 723    if (last_register_time)
 724    {
 725	sleep_past (last_register_time);
 726    }
 727
 728    return err;
 729}
 730
 731
 732
 733/* This routine determines the status of a given file and retrieves
 734   the version information that is associated with that file. */
 735
 736static
 737Ctype
 738classify_file_internal (finfo, vers)
 739    struct file_info *finfo;
 740    Vers_TS **vers;
 741{
 742    int save_noexec, save_quiet, save_really_quiet;
 743    Ctype status;
 744
 745    /* FIXME: Do we need to save quiet as well as really_quiet?  Last
 746       time I glanced at Classify_File I only saw it looking at really_quiet
 747       not quiet.  */
 748    save_noexec = noexec;
 749    save_quiet = quiet;
 750    save_really_quiet = really_quiet;
 751    noexec = quiet = really_quiet = 1;
 752
 753    /* handle specified numeric revision specially */
 754    if (saved_tag && isdigit ((unsigned char) *saved_tag))
 755    {
 756	/* If the tag is for the trunk, make sure we're at the head */
 757	if (numdots (saved_tag) < 2)
 758	{
 759	    status = Classify_File (finfo, (char *) NULL, (char *) NULL,
 760				    (char *) NULL, 1, aflag, vers, 0);
 761	    if (status == T_UPTODATE || status == T_MODIFIED ||
 762		status == T_ADDED)
 763	    {
 764		Ctype xstatus;
 765
 766		freevers_ts (vers);
 767		xstatus = Classify_File (finfo, saved_tag, (char *) NULL,
 768					 (char *) NULL, 1, aflag, vers, 0);
 769		if (xstatus == T_REMOVE_ENTRY)
 770		    status = T_MODIFIED;
 771		else if (status == T_MODIFIED && xstatus == T_CONFLICT)
 772		    status = T_MODIFIED;
 773		else
 774		    status = xstatus;
 775	    }
 776	}
 777	else
 778	{
 779	    char *xtag, *cp;
 780
 781	    /*
 782	     * The revision is off the main trunk; make sure we're
 783	     * up-to-date with the head of the specified branch.
 784	     */
 785	    xtag = xstrdup (saved_tag);
 786	    if ((numdots (xtag) & 1) != 0)
 787	    {
 788		cp = strrchr (xtag, '.');
 789		*cp = '\0';
 790	    }
 791	    status = Classify_File (finfo, xtag, (char *) NULL,
 792				    (char *) NULL, 1, aflag, vers, 0);
 793	    if ((status == T_REMOVE_ENTRY || status == T_CONFLICT)
 794		&& (cp = strrchr (xtag, '.')) != NULL)
 795	    {
 796		/* pluck one more dot off the revision */
 797		*cp = '\0';
 798		freevers_ts (vers);
 799		status = Classify_File (finfo, xtag, (char *) NULL,
 800					(char *) NULL, 1, aflag, vers, 0);
 801		if (status == T_UPTODATE || status == T_REMOVE_ENTRY)
 802		    status = T_MODIFIED;
 803	    }
 804	    /* now, muck with vers to make the tag correct */
 805	    free ((*vers)->tag);
 806	    (*vers)->tag = xstrdup (saved_tag);
 807	    free (xtag);
 808	}
 809    }
 810    else
 811	status = Classify_File (finfo, saved_tag, (char *) NULL, (char *) NULL,
 812				1, 0, vers, 0);
 813    noexec = save_noexec;
 814    quiet = save_quiet;
 815    really_quiet = save_really_quiet;
 816
 817    return status;
 818}
 819
 820
 821
 822/*
 823 * Check to see if a file is ok to commit and make sure all files are
 824 * up-to-date
 825 */
 826/* ARGSUSED */
 827static int
 828check_fileproc (callerdat, finfo)
 829    void *callerdat;
 830    struct file_info *finfo;
 831{
 832    Ctype status;
 833    const char *xdir;
 834    Node *p;
 835    List *ulist, *cilist;
 836    Vers_TS *vers;
 837    struct commit_info *ci;
 838    struct logfile_info *li;
 839
 840    size_t cvsroot_len = strlen (current_parsed_root->directory);
 841
 842    if (!finfo->repository)
 843    {
 844	error (0, 0, "nothing known about `%s'", finfo->fullname);
 845	return 1;
 846    }
 847
 848    if (strncmp (finfo->repository, current_parsed_root->directory,
 849                 cvsroot_len) == 0
 850	&& ISDIRSEP (finfo->repository[cvsroot_len])
 851	&& strncmp (finfo->repository + cvsroot_len + 1,
 852		    CVSROOTADM,
 853		    sizeof (CVSROOTADM) - 1) == 0
 854	&& ISDIRSEP (finfo->repository[cvsroot_len + sizeof (CVSROOTADM)])
 855	&& strcmp (finfo->repository + cvsroot_len + sizeof (CVSROOTADM) + 1,
 856		   CVSNULLREPOS) == 0
 857	)
 858	error (1, 0, "cannot check in to %s", finfo->repository);
 859
 860    status = classify_file_internal (finfo, &vers);
 861
 862    /*
 863     * If the force-commit option is enabled, and the file in question
 864     * appears to be up-to-date, just make it look modified so that
 865     * it will be committed.
 866     */
 867    if (force_ci && status == T_UPTODATE)
 868	status = T_MODIFIED;
 869
 870    switch (status)
 871    {
 872	case T_CHECKOUT:
 873	case T_PATCH:
 874	case T_NEEDS_MERGE:
 875	case T_REMOVE_ENTRY:
 876	    error (0, 0, "Up-to-date check failed for `%s'", finfo->fullname);
 877	    freevers_ts (&vers);
 878	    return 1;
 879	case T_CONFLICT:
 880	case T_MODIFIED:
 881	case T_ADDED:
 882	case T_REMOVED:
 883	    /*
 884	     * some quick sanity checks; if no numeric -r option specified:
 885	     *	- can't have a sticky date
 886	     *	- can't have a sticky tag that is not a branch
 887	     * Also,
 888	     *	- if status is T_REMOVED, file must not exist and its entry
 889	     *	  can't have a numeric sticky tag.
 890	     *	- if status is T_ADDED, rcs file must not exist unless on
 891	     *    a branch or head is dead
 892	     *	- if status is T_ADDED, can't have a non-trunk numeric rev
 893	     *	- if status is T_MODIFIED and a Conflict marker exists, don't
 894	     *    allow the commit if timestamp is identical or if we find
 895	     *    an RCS_MERGE_PAT in the file.
 896	     */
 897	    if (!saved_tag || !isdigit ((unsigned char) *saved_tag))
 898	    {
 899		if (vers->date)
 900		{
 901		    error (0, 0,
 902			   "cannot commit with sticky date for file `%s'",
 903			   finfo->fullname);
 904		    freevers_ts (&vers);
 905		    return 1;
 906		}
 907		if (status == T_MODIFIED && vers->tag &&
 908		    !RCS_isbranch (finfo->rcs, vers->tag))
 909		{
 910		    error (0, 0,
 911			   "sticky tag `%s' for file `%s' is not a branch",
 912			   vers->tag, finfo->fullname);
 913		    freevers_ts (&vers);
 914		    return 1;
 915		}
 916	    }
 917	    if (status == T_CONFLICT && !force_ci)
 918	    {
 919		error (0, 0,
 920		      "file `%s' had a conflict and has not been modified",
 921		       finfo->fullname);
 922		freevers_ts (&vers);
 923		return 1;
 924	    }
 925	    if (status == T_MODIFIED && !force_ci && file_has_markers (finfo))
 926	    {
 927		/* Make this a warning, not an error, because we have
 928		   no way of knowing whether the "conflict indicators"
 929		   are really from a conflict or whether they are part
 930		   of the document itself (cvs.texinfo and sanity.sh in
 931		   CVS itself, for example, tend to want to have strings
 932		   like ">>>>>>>" at the start of a line).  Making people
 933		   kludge this the way they need to kludge keyword
 934		   expansion seems undesirable.  And it is worse than
 935		   keyword expansion, because there is no -ko
 936		   analogue.  */
 937		error (0, 0,
 938		       "\
 939warning: file `%s' seems to still contain conflict indicators",
 940		       finfo->fullname);
 941	    }
 942
 943	    if (status == T_REMOVED)
 944	    {
 945		if (vers->ts_user != NULL)
 946		{
 947		    error (0, 0,
 948			   "`%s' should be removed and is still there (or is"
 949			   " back again)", finfo->fullname);
 950		    freevers_ts (&vers);
 951		    return 1;
 952		}
 953
 954		if (vers->tag && isdigit ((unsigned char) *vers->tag))
 955		{
 956		    /* Remove also tries to forbid this, but we should check
 957		       here.  I'm only _sure_ about somewhat obscure cases
 958		       (hacking the Entries file, using an old version of
 959		       CVS for the remove and a new one for the commit), but
 960		       there might be other cases.  */
 961		    error (0, 0,
 962			   "cannot remove file `%s' which has a numeric sticky"
 963			   " tag of `%s'", finfo->fullname, vers->tag);
 964		    freevers_ts (&vers);
 965		    return 1;
 966		}
 967	    }
 968	    if (status == T_ADDED)
 969	    {
 970	        if (vers->tag == NULL)
 971		{
 972		    if (finfo->rcs != NULL &&
 973			!RCS_isdead (finfo->rcs, finfo->rcs->head))
 974		    {
 975			error (0, 0,
 976		    "cannot add file `%s' when RCS file `%s' already exists",
 977			       finfo->fullname, finfo->rcs->path);
 978			freevers_ts (&vers);
 979			return 1;
 980		    }
 981		}
 982		else if (isdigit ((unsigned char) *vers->tag) &&
 983		    numdots (vers->tag) > 1)
 984		{
 985		    error (0, 0,
 986		"cannot add file `%s' with revision `%s'; must be on trunk",
 987			       finfo->fullname, vers->tag);
 988		    freevers_ts (&vers);
 989		    return 1;
 990		}
 991	    }
 992
 993	    /* done with consistency checks; now, to get on with the commit */
 994	    if (finfo->update_dir[0] == '\0')
 995		xdir = ".";
 996	    else
 997		xdir = finfo->update_dir;
 998	    if ((p = findnode (mulist, xdir)) != NULL)
 999	    {
1000		ulist = ((struct master_lists *) p->data)->ulist;
1001		cilist = ((struct master_lists *) p->data)->cilist;
1002	    }
1003	    else
1004	    {
1005		struct master_lists *ml;
1006
1007		ulist = getlist ();
1008		cilist = getlist ();
1009		p = getnode ();
1010		p->key = xstrdup (xdir);
1011		p->type = UPDATE;
1012		ml = (struct master_lists *)
1013		    xmalloc (sizeof (struct master_lists));
1014		ml->ulist = ulist;
1015		ml->cilist = cilist;
1016		p->data = ml;
1017		p->delproc = masterlist_delproc;
1018		(void) addnode (mulist, p);
1019	    }
1020
1021	    /* first do ulist, then cilist */
1022	    p = getnode ();
1023	    p->key = xstrdup (finfo->file);
1024	    p->type = UPDATE;
1025	    p->delproc = update_delproc;
1026	    li = ((struct logfile_info *)
1027		  xmalloc (sizeof (struct logfile_info)));
1028	    li->type = status;
1029	    li->tag = xstrdup (vers->tag);
1030	    li->rev_old = xstrdup (vers->vn_rcs);
1031	    li->rev_new = NULL;
1032	    p->data = li;
1033	    (void) addnode (ulist, p);
1034
1035	    p = getnode ();
1036	    p->key = xstrdup (finfo->file);
1037	    p->type = UPDATE;
1038	    p->delproc = ci_delproc;
1039	    ci = (struct commit_info *) xmalloc (sizeof (struct commit_info));
1040	    ci->status = status;
1041	    if (vers->tag)
1042		if (isdigit ((unsigned char) *vers->tag))
1043		    ci->rev = xstrdup (vers->tag);
1044		else
1045		    ci->rev = RCS_whatbranch (finfo->rcs, vers->tag);
1046	    else
1047		ci->rev = (char *) NULL;
1048	    ci->tag = xstrdup (vers->tag);
1049	    ci->options = xstrdup(vers->options);
1050	    p->data = ci;
1051	    (void) addnode (cilist, p);
1052
1053#ifdef PRESERVE_PERMISSIONS_SUPPORT
1054	    if (preserve_perms)
1055	    {
1056		/* Add this file to hardlist, indexed on its inode.  When
1057		   we are done, we can find out what files are hardlinked
1058		   to a given file by looking up its inode in hardlist. */
1059		char *fullpath;
1060		Node *linkp;
1061		struct hardlink_info *hlinfo;
1062
1063		/* Get the full pathname of the current file. */
1064		fullpath = xmalloc (strlen(working_dir) +
1065				    strlen(finfo->fullname) + 2);
1066		sprintf (fullpath, "%s/%s", working_dir, finfo->fullname);
1067
1068		/* To permit following links in subdirectories, files
1069                   are keyed on finfo->fullname, not on finfo->name. */
1070		linkp = lookup_file_by_inode (fullpath);
1071
1072		/* If linkp is NULL, the file doesn't exist... maybe
1073		   we're doing a remove operation? */
1074		if (linkp != NULL)
1075		{
1076		    /* Create a new hardlink_info node, which will record
1077		       the current file's status and the links listed in its
1078		       `hardlinks' delta field.  We will append this
1079		       hardlink_info node to the appropriate hardlist entry. */
1080		    hlinfo = (struct hardlink_info *)
1081			xmalloc (sizeof (struct hardlink_info));
1082		    hlinfo->status = status;
1083		    linkp->data = hlinfo;
1084		}
1085	    }
1086#endif
1087
1088	    break;
1089	case T_UNKNOWN:
1090	    error (0, 0, "nothing known about `%s'", finfo->fullname);
1091	    freevers_ts (&vers);
1092	    return 1;
1093	case T_UPTODATE:
1094	    break;
1095	default:
1096	    error (0, 0, "CVS internal error: unknown status %d", status);
1097	    break;
1098    }
1099
1100    freevers_ts (&vers);
1101    return 0;
1102}
1103
1104
1105
1106/*
1107 * By default, return the code that tells do_recursion to examine all
1108 * directories
1109 */
1110/* ARGSUSED */
1111static Dtype
1112check_direntproc (callerdat, dir, repos, update_dir, entries)
1113    void *callerdat;
1114    const char *dir;
1115    const char *repos;
1116    const char *update_dir;
1117    List *entries;
1118{
1119    if (!isdir (dir))
1120	return R_SKIP_ALL;
1121
1122    if (!quiet)
1123	error (0, 0, "Examining %s", update_dir);
1124
1125    return R_PROCESS;
1126}
1127
1128
1129
1130/*
1131 * Walklist proc to run pre-commit checks
1132 */
1133static int
1134precommit_list_proc (p, closure)
1135    Node *p;
1136    void *closure;
1137{
1138    struct logfile_info *li = p->data;
1139    if (li->type == T_ADDED
1140	|| li->type == T_MODIFIED
1141	|| li->type == T_REMOVED)
1142    {
1143	run_arg (p->key);
1144    }
1145    return 0;
1146}
1147
1148
1149
1150/*
1151 * Callback proc for pre-commit checking
1152 */
1153static int
1154precommit_proc (repository, filter)
1155    const char *repository;
1156    const char *filter;
1157{
1158    /* see if the filter is there, only if it's a full path */
1159    if (isabsolute (filter))
1160    {
1161    	char *s, *cp;
1162
1163	s = xstrdup (filter);
1164	for (cp = s; *cp; cp++)
1165	    if (isspace ((unsigned char) *cp))
1166	    {
1167		*cp = '\0';
1168		break;
1169	    }
1170	if (!isfile (s))
1171	{
1172	    error (0, errno, "cannot find pre-commit filter `%s'", s);
1173	    free (s);
1174	    return 1;			/* so it fails! */
1175	}
1176	free (s);
1177    }
1178
1179    run_setup (filter);
1180    run_arg (repository);
1181    (void) walklist (saved_ulist, precommit_list_proc, NULL);
1182    return run_exec (RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL|RUN_REALLY);
1183}
1184
1185
1186
1187/*
1188 * Run the pre-commit checks for the dir
1189 */
1190/* ARGSUSED */
1191static int
1192check_filesdoneproc (callerdat, err, repos, update_dir, entries)
1193    void *callerdat;
1194    int err;
1195    const char *repos;
1196    const char *update_dir;
1197    List *entries;
1198{
1199    int n;
1200    Node *p;
1201
1202    /* find the update list for this dir */
1203    p = findnode (mulist, update_dir);
1204    if (p != NULL)
1205	saved_ulist = ((struct master_lists *) p->data)->ulist;
1206    else
1207	saved_ulist = (List *) NULL;
1208
1209    /* skip the checks if there's nothing to do */
1210    if (saved_ulist == NULL || saved_ulist->list->next == saved_ulist->list)
1211	return err;
1212
1213    /* run any pre-commit checks */
1214    if ((n = Parse_Info (CVSROOTADM_COMMITINFO, repos, precommit_proc, 1)) > 0)
1215    {
1216	error (0, 0, "Pre-commit check failed");
1217	err += n;
1218    }
1219
1220    return err;
1221}
1222
1223
1224
1225/*
1226 * Do the work of committing a file
1227 */
1228static int maxrev;
1229static char *sbranch;
1230
1231/* ARGSUSED */
1232static int
1233commit_fileproc (callerdat, finfo)
1234    void *callerdat;
1235    struct file_info *finfo;
1236{
1237    Node *p;
1238    int err = 0;
1239    List *ulist, *cilist;
1240    struct commit_info *ci;
1241
1242    /* Keep track of whether write_dirtag is a branch tag.
1243       Note that if it is a branch tag in some files and a nonbranch tag
1244       in others, treat it as a nonbranch tag.  It is possible that case
1245       should elicit a warning or an error.  */
1246    if (write_dirtag != NULL
1247	&& finfo->rcs != NULL)
1248    {
1249	char *rev = RCS_getversion (finfo->rcs, write_dirtag, NULL, 1, NULL);
1250	if (rev != NULL
1251	    && !RCS_nodeisbranch (finfo->rcs, write_dirtag))
1252	    write_dirnonbranch = 1;
1253	if (rev != NULL)
1254	    free (rev);
1255    }
1256
1257    if (finfo->update_dir[0] == '\0')
1258	p = findnode (mulist, ".");
1259    else
1260	p = findnode (mulist, finfo->update_dir);
1261
1262    /*
1263     * if p is null, there were file type command line args which were
1264     * all up-to-date so nothing really needs to be done
1265     */
1266    if (p == NULL)
1267	return 0;
1268    ulist = ((struct master_lists *) p->data)->ulist;
1269    cilist = ((struct master_lists *) p->data)->cilist;
1270
1271    /*
1272     * At this point, we should have the commit message unless we were called
1273     * with files as args from the command line.  In that latter case, we
1274     * need to get the commit message ourselves
1275     */
1276    if (!got_message)
1277    {
1278	got_message = 1;
1279	if (!server_active && use_editor)
1280	    do_editor (finfo->update_dir, &saved_message,
1281		       finfo->repository, ulist);
1282	do_verify (&saved_message, finfo->repository);
1283    }
1284
1285    p = findnode (cilist, finfo->file);
1286    if (p == NULL)
1287	return 0;
1288
1289    ci = p->data;
1290    if (ci->status == T_MODIFIED)
1291    {
1292	if (finfo->rcs == NULL)
1293	    error (1, 0, "internal error: no parsed RCS file");
1294	if (lock_RCS (finfo->file, finfo->rcs, ci->rev,
1295		      finfo->repository) != 0)
1296	{
1297	    unlockrcs (finfo->rcs);
1298	    err = 1;
1299	    goto out;
1300	}
1301    }
1302    else if (ci->status == T_ADDED)
1303    {
1304	if (checkaddfile (finfo->file, finfo->repository, ci->tag, ci->options,
1305			  &finfo->rcs) != 0)
1306	{
1307	    if (finfo->rcs != NULL)
1308		fixaddfile (finfo->rcs->path);
1309	    err = 1;
1310	    goto out;
1311	}
1312
1313	/* adding files with a tag, now means adding them on a branch.
1314	   Since the branch test was done in check_fileproc for
1315	   modified files, we need to stub it in again here. */
1316
1317	if (ci->tag
1318
1319	    /* If numeric, it is on the trunk; check_fileproc enforced
1320	       this.  */
1321	    && !isdigit ((unsigned char) ci->tag[0]))
1322	{
1323	    if (finfo->rcs == NULL)
1324		error (1, 0, "internal error: no parsed RCS file");
1325	    if (ci->rev)
1326		free (ci->rev);
1327	    ci->rev = RCS_whatbranch (finfo->rcs, ci->tag);
1328	    err = Checkin ('A', finfo, ci->rev,
1329			   ci->tag, ci->options, saved_message);
1330	    if (err != 0)
1331	    {
1332		unlockrcs (finfo->rcs);
1333		fixbranch (finfo->rcs, sbranch);
1334	    }
1335
1336	    (void) time (&last_register_time);
1337
1338	    ci->status = T_UPTODATE;
1339	}
1340    }
1341
1342    /*
1343     * Add the file for real
1344     */
1345    if (ci->status == T_ADDED)
1346    {
1347	char *xrev = (char *) NULL;
1348
1349	if (ci->rev == NULL)
1350	{
1351	    /* find the max major rev number in this directory */
1352	    maxrev = 0;
1353	    (void) walklist (finfo->entries, findmaxrev, NULL);
1354	    if (finfo->rcs->head) {
1355		/* resurrecting: include dead revision */
1356		int thisrev = atoi (finfo->rcs->head);
1357		if (thisrev > maxrev)
1358		    maxrev = thisrev;
1359	    }
1360	    if (maxrev == 0)
1361		maxrev = 1;
1362	    xrev = xmalloc (20);
1363	    (void) sprintf (xrev, "%d", maxrev);
1364	}
1365
1366	/* XXX - an added file with symbolic -r should add tag as well */
1367	err = finaladd (finfo, ci->rev ? ci->rev : xrev, ci->tag, ci->options);
1368	if (xrev)
1369	    free (xrev);
1370    }
1371    else if (ci->status == T_MODIFIED)
1372    {
1373	err = Checkin ('M', finfo, ci->rev, ci->tag,
1374		       ci->options, saved_message);
1375
1376	(void) time (&last_register_time);
1377
1378	if (err != 0)
1379	{
1380	    unlockrcs (finfo->rcs);
1381	    fixbranch (finfo->rcs, sbranch);
1382	}
1383    }
1384    else if (ci->status == T_REMOVED)
1385    {
1386	err = remove_file (finfo, ci->tag, saved_message);
1387#ifdef SERVER_SUPPORT
1388	if (server_active) {
1389	    server_scratch_entry_only ();
1390	    server_updated (finfo,
1391			    NULL,
1392
1393			    /* Doesn't matter, it won't get checked.  */
1394			    SERVER_UPDATED,
1395
1396			    (mode_t) -1,
1397			    (unsigned char *) NULL,
1398			    (struct buffer *) NULL);
1399	}
1400#endif
1401    }
1402
1403    /* Clearly this is right for T_MODIFIED.  I haven't thought so much
1404       about T_ADDED or T_REMOVED.  */
1405    notify_do ('C', finfo->file, getcaller (), NULL, NULL, finfo->repository);
1406
1407out:
1408    if (err != 0)
1409    {
1410	/* on failure, remove the file from ulist */
1411	p = findnode (ulist, finfo->file);
1412	if (p)
1413	    delnode (p);
1414    }
1415    else
1416    {
1417	/* On success, retrieve the new version number of the file and
1418           copy it into the log information (see logmsg.c
1419           (logfile_write) for more details).  We should only update
1420           the version number for files that have been added or
1421           modified but not removed since classify_file_internal
1422           will return the version number of a file even after it has
1423           been removed from the archive, which is not the behavior we
1424           want for our commitlog messages; we want the old version
1425           number and then "NONE." */
1426
1427	if (ci->status != T_REMOVED)
1428	{
1429	    p = findnode (ulist, finfo->file);
1430	    if (p)
1431	    {
1432		Vers_TS *vers;
1433		struct logfile_info *li;
1434
1435		(void) classify_file_internal (finfo, &vers);
1436		li = p->data;
1437		li->rev_new = xstrdup (vers->vn_rcs);
1438		freevers_ts (&vers);
1439	    }
1440	}
1441    }
1442    if (SIG_inCrSect ())
1443	SIG_endCrSect ();
1444
1445    return err;
1446}
1447
1448
1449
1450/*
1451 * Log the commit and clean up the update list
1452 */
1453/* ARGSUSED */
1454static int
1455commit_filesdoneproc (callerdat, err, repository, update_dir, entries)
1456    void *callerdat;
1457    int err;
1458    const char *repository;
1459    const char *update_dir;
1460    List *entries;
1461{
1462    Node *p;
1463    List *ulist;
1464
1465    assert (repository);
1466
1467    p = findnode (mulist, update_dir);
1468    if (p == NULL)
1469	return err;
1470
1471    ulist = ((struct master_lists *) p->data)->ulist;
1472
1473    got_message = 0;
1474
1475    Update_Logfile (repository, saved_message, (FILE *) 0, ulist);
1476
1477    /* Build the administrative files if necessary.  */
1478    {
1479	const char *p;
1480
1481	if (strncmp (current_parsed_root->directory, repository,
1482		     strlen (current_parsed_root->directory)) != 0)
1483	    error (0, 0,
1484		 "internal error: repository (%s) doesn't begin with root (%s)",
1485		   repository, current_parsed_root->directory);
1486	p = repository + strlen (current_parsed_root->directory);
1487	if (*p == '/')
1488	    ++p;
1489	if (strcmp ("CVSROOT", p) == 0
1490	    /* Check for subdirectories because people may want to create
1491	       subdirectories and list files therein in checkoutlist.  */
1492	    || strncmp ("CVSROOT/", p, strlen ("CVSROOT/")) == 0
1493	    )
1494	{
1495	    /* "Database" might a little bit grandiose and/or vague,
1496	       but "checked-out copies of administrative files, unless
1497	       in the case of modules and you are using ndbm in which
1498	       case modules.{pag,dir,db}" is verbose and excessively
1499	       focused on how the database is implemented.  */
1500
1501	    /* mkmodules requires the absolute name of the CVSROOT directory.
1502	       Remove anything after the `CVSROOT' component -- this is
1503	       necessary when committing in a subdirectory of CVSROOT.  */
1504	    char *admin_dir = xstrdup (repository);
1505	    int cvsrootlen = strlen ("CVSROOT");
1506	    assert (admin_dir[p - repository + cvsrootlen] == '\0'
1507		    || admin_dir[p - repository + cvsrootlen] == '/');
1508	    admin_dir[p - repository + cvsrootlen] = '\0';
1509
1510	    cvs_output (program_name, 0);
1511	    cvs_output (" ", 1);
1512	    cvs_output (cvs_cmd_name, 0);
1513	    cvs_output (": Rebuilding administrative file database\n", 0);
1514	    mkmodules (admin_dir);
1515	    free (admin_dir);
1516	}
1517    }
1518
1519    return err;
1520}
1521
1522
1523
1524/*
1525 * Get the log message for a dir
1526 */
1527/* ARGSUSED */
1528static Dtype
1529commit_direntproc (callerdat, dir, repos, update_dir, entries)
1530    void *callerdat;
1531    const char *dir;
1532    const char *repos;
1533    const char *update_dir;
1534    List *entries;
1535{
1536    Node *p;
1537    List *ulist;
1538    char *real_repos;
1539
1540    if (!isdir (dir))
1541	return R_SKIP_ALL;
1542
1543    /* find the update list for this dir */
1544    p = findnode (mulist, update_dir);
1545    if (p != NULL)
1546	ulist = ((struct master_lists *) p->data)->ulist;
1547    else
1548	ulist = (List *) NULL;
1549
1550    /* skip the files as an optimization */
1551    if (ulist == NULL || ulist->list->next == ulist->list)
1552	return R_SKIP_FILES;
1553
1554    /* get commit message */
1555    real_repos = Name_Repository (dir, update_dir);
1556    got_message = 1;
1557    if (!server_active && use_editor)
1558	do_editor (update_dir, &saved_message, real_repos, ulist);
1559    do_verify (&saved_message, real_repos);
1560    free (real_repos);
1561    return R_PROCESS;
1562}
1563
1564
1565
1566/*
1567 * Process the post-commit proc if necessary
1568 */
1569/* ARGSUSED */
1570static int
1571commit_dirleaveproc (callerdat, dir, err, update_dir, entries)
1572    void *callerdat;
1573    const char *dir;
1574    int err;
1575    const char *update_dir;
1576    List *entries;
1577{
1578    /* update the per-directory tag info */
1579    /* FIXME?  Why?  The "commit examples" node of cvs.texinfo briefly
1580       mentions commit -r being sticky, but apparently in the context of
1581       this being a confusing feature!  */
1582    if (err == 0 && write_dirtag != NULL)
1583    {
1584	char *repos = Name_Repository (NULL, update_dir);
1585	WriteTag (NULL, write_dirtag, NULL, write_dirnonbranch,
1586		  update_dir, repos);
1587	free (repos);
1588    }
1589
1590    return err;
1591}
1592
1593
1594
1595/*
1596 * find the maximum major rev number in an entries file
1597 */
1598static int
1599findmaxrev (p, closure)
1600    Node *p;
1601    void *closure;
1602{
1603    int thisrev;
1604    Entnode *entdata = p->data;
1605
1606    if (entdata->type != ENT_FILE)
1607	return 0;
1608    thisrev = atoi (entdata->version);
1609    if (thisrev > maxrev)
1610	maxrev = thisrev;
1611    return 0;
1612}
1613
1614/*
1615 * Actually remove a file by moving it to the attic
1616 * XXX - if removing a ,v file that is a relative symbolic link to
1617 * another ,v file, we probably should add a ".." component to the
1618 * link to keep it relative after we move it into the attic.
1619
1620   Return value is 0 on success, or >0 on error (in which case we have
1621   printed an error message).  */
1622static int
1623remove_file (finfo, tag, message)
1624    struct file_info *finfo;
1625    char *tag;
1626    char *message;
1627{
1628    int retcode;
1629
1630    int branch;
1631    int lockflag;
1632    char *corev;
1633    char *rev;
1634    char *prev_rev;
1635    char *old_path;
1636
1637    corev = NULL;
1638    rev = NULL;
1639    prev_rev = NULL;
1640
1641    retcode = 0;
1642
1643    if (finfo->rcs == NULL)
1644	error (1, 0, "internal error: no parsed RCS file");
1645
1646    branch = 0;
1647    if (tag && !(branch = RCS_nodeisbranch (finfo->rcs, tag)))
1648    {
1649	/* a symbolic tag is specified; just remove the tag from the file */
1650	if ((retcode = RCS_deltag (finfo->rcs, tag)) != 0)
1651	{
1652	    if (!quiet)
1653		error (0, retcode == -1 ? errno : 0,
1654		       "failed to remove tag `%s' from `%s'", tag,
1655		       finfo->fullname);
1656	    return 1;
1657	}
1658	RCS_rewrite (finfo->rcs, NULL, NULL);
1659	Scratch_Entry (finfo->entries, finfo->file);
1660	return 0;
1661    }
1662
1663    /* we are removing the file from either the head or a branch */
1664    /* commit a new, dead revision. */
1665
1666    /* Print message indicating that file is going to be removed. */
1667    cvs_output ("Removing ", 0);
1668    cvs_output (finfo->fullname, 0);
1669    cvs_output (";\n", 0);
1670
1671    rev = NULL;
1672    lockflag = 1;
1673    if (branch)
1674    {
1675	char *branchname;
1676
1677	rev = RCS_whatbranch (finfo->rcs, tag);
1678	if (rev == NULL)
1679	{
1680	    error (0, 0, "cannot find branch \"%s\".", tag);
1681	    return 1;
1682	}
1683
1684	branchname = RCS_getbranch (finfo->rcs, rev, 1);
1685	if (branchname == NULL)
1686	{
1687	    /* no revision exists on this branch.  use the previous
1688	       revision but do not lock. */
1689	    corev = RCS_gettag (finfo->rcs, tag, 1, (int *) NULL);
1690	    prev_rev = xstrdup (corev);
1691	    lockflag = 0;
1692	} else
1693	{
1694	    corev = xstrdup (rev);
1695	    prev_rev = xstrdup (branchname);
1696	    free (branchname);
1697	}
1698
1699    } else  /* Not a branch */
1700    {
1701        /* Get current head revision of file. */
1702	prev_rev = RCS_head (finfo->rcs);
1703    }
1704
1705    /* if removing without a tag or a branch, then make sure the default
1706       branch is the trunk. */
1707    if (!tag && !branch)
1708    {
1709        if (RCS_setbranch (finfo->rcs, NULL) != 0)
1710	{
1711	    error (0, 0, "cannot change branch to default for %s",
1712		   finfo->fullname);
1713	    return 1;
1714	}
1715	RCS_rewrite (finfo->rcs, NULL, NULL);
1716    }
1717
1718    /* check something out.  Generally this is the head.  If we have a
1719       particular rev, then name it.  */
1720    retcode = RCS_checkout (finfo->rcs, finfo->file, rev ? corev : NULL,
1721			    (char *) NULL, (char *) NULL, RUN_TTY,
1722			    (RCSCHECKOUTPROC) NULL, (void *) NULL);
1723    if (retcode != 0)
1724    {
1725	error (0, 0,
1726	       "failed to check out `%s'", finfo->fullname);
1727	return 1;
1728    }
1729
1730    /* Except when we are creating a branch, lock the revision so that
1731       we can check in the new revision.  */
1732    if (lockflag)
1733    {
1734	if (RCS_lock (finfo->rcs, rev ? corev : NULL, 1) == 0)
1735	    RCS_rewrite (finfo->rcs, NULL, NULL);
1736    }
1737
1738    if (corev != NULL)
1739	free (corev);
1740
1741    retcode = RCS_checkin (finfo->rcs, finfo->file, message, rev, 0,
1742			   RCS_FLAGS_DEAD | RCS_FLAGS_QUIET);
1743    if (retcode	!= 0)
1744    {
1745	if (!quiet)
1746	    error (0, retcode == -1 ? errno : 0,
1747		   "failed to commit dead revision for `%s'", finfo->fullname);
1748	if (prev_rev != NULL)
1749	    free (prev_rev);
1750	return 1;
1751    }
1752    /* At this point, the file has been committed as removed.  We should
1753       probably tell the history file about it  */
1754    corev = rev ? RCS_getbranch (finfo->rcs, rev, 1) : RCS_head (finfo->rcs);
1755    history_write ('R', NULL, corev, finfo->file, finfo->repository);
1756    free (corev);
1757
1758    if (rev != NULL)
1759	free (rev);
1760
1761    old_path = xstrdup (finfo->rcs->path);
1762    if (!branch)
1763	RCS_setattic (finfo->rcs, 1);
1764
1765    /* Print message that file was removed. */
1766    cvs_output (old_path, 0);
1767    cvs_output ("  <--  ", 0);
1768    cvs_output (finfo->file, 0);
1769    cvs_output ("\nnew revision: delete; previous revision: ", 0);
1770    cvs_output (prev_rev, 0);
1771    cvs_output ("\ndone\n", 0);
1772    free(prev_rev);
1773
1774    free (old_path);
1775
1776    Scratch_Entry (finfo->entries, finfo->file);
1777    return 0;
1778}
1779
1780
1781
1782/*
1783 * Do the actual checkin for added files
1784 */
1785static int
1786finaladd (finfo, rev, tag, options)
1787    struct file_info *finfo;
1788    char *rev;
1789    char *tag;
1790    char *options;
1791{
1792    int ret;
1793
1794    ret = Checkin ('A', finfo, rev, tag, options, saved_message);
1795    if (ret == 0)
1796    {
1797	char *tmp = xmalloc (strlen (finfo->file) + sizeof (CVSADM)
1798			     + sizeof (CVSEXT_LOG) + 10);
1799	(void) sprintf (tmp, "%s/%s%s", CVSADM, finfo->file, CVSEXT_LOG);
1800	if (unlink_file (tmp) < 0
1801	    && !existence_error (errno))
1802	    error (0, errno, "cannot remove %s", tmp);
1803	free (tmp);
1804    }
1805    else if (finfo->rcs != NULL)
1806	fixaddfile (finfo->rcs->path);
1807
1808    (void) time (&last_register_time);
1809
1810    return ret;
1811}
1812
1813
1814
1815/*
1816 * Unlock an rcs file
1817 */
1818static void
1819unlockrcs (rcs)
1820    RCSNode *rcs;
1821{
1822    int retcode;
1823
1824    if ((retcode = RCS_unlock (rcs, NULL, 1)) != 0)
1825	error (retcode == -1 ? 1 : 0, retcode == -1 ? errno : 0,
1826	       "could not unlock %s", rcs->path);
1827    else
1828	RCS_rewrite (rcs, NULL, NULL);
1829}
1830
1831
1832
1833/*
1834 * remove a partially added file.  if we can parse it, leave it alone.
1835 *
1836 * FIXME: Every caller that calls this function can access finfo->rcs (the
1837 * parsed RCSNode data), so we should be able to detect that the file needs
1838 * to be removed without reparsing the file as we do below.
1839 */
1840static void
1841fixaddfile (rcs)
1842    const char *rcs;
1843{
1844    RCSNode *rcsfile;
1845    int save_really_qu

Large files files are truncated, but you can click here to view the full file