summaryrefslogtreecommitdiffstats
path: root/gnu/usr.bin/cvs/src/commit.c
diff options
context:
space:
mode:
authorderaadt <deraadt@openbsd.org>1995-12-19 09:21:26 +0000
committerderaadt <deraadt@openbsd.org>1995-12-19 09:21:26 +0000
commit1e72d8d26fae84dfb4bcd4cecabd10b989ec3f29 (patch)
tree8e75671b795f0e506ffa01d9af4546c5238f2d89 /gnu/usr.bin/cvs/src/commit.c
parentAdded support for using a special makefile: Makefile.bsd-wrapper when (diff)
downloadwireguard-openbsd-1e72d8d26fae84dfb4bcd4cecabd10b989ec3f29.tar.xz
wireguard-openbsd-1e72d8d26fae84dfb4bcd4cecabd10b989ec3f29.zip
raw import of cvs-1.6
Diffstat (limited to 'gnu/usr.bin/cvs/src/commit.c')
-rw-r--r--gnu/usr.bin/cvs/src/commit.c1840
1 files changed, 1840 insertions, 0 deletions
diff --git a/gnu/usr.bin/cvs/src/commit.c b/gnu/usr.bin/cvs/src/commit.c
new file mode 100644
index 00000000000..a50e84298cc
--- /dev/null
+++ b/gnu/usr.bin/cvs/src/commit.c
@@ -0,0 +1,1840 @@
+/*
+ * Copyright (c) 1992, Brian Berliner and Jeff Polk
+ * Copyright (c) 1989-1992, Brian Berliner
+ *
+ * You may distribute under the terms of the GNU General Public License as
+ * specified in the README file that comes with the CVS 1.4 kit.
+ *
+ * Commit Files
+ *
+ * "commit" commits the present version to the RCS repository, AFTER
+ * having done a test on conflicts.
+ *
+ * The call is: cvs commit [options] files...
+ *
+ */
+
+#include "cvs.h"
+
+#ifndef lint
+static const char rcsid[] = "$CVSid: @(#)commit.c 1.101 94/10/07 $";
+USE(rcsid);
+#endif
+
+static Dtype check_direntproc PROTO((char *dir, char *repos, char *update_dir));
+static int check_fileproc PROTO((char *file, char *update_dir, char *repository,
+ List * entries, List * srcfiles));
+static int check_filesdoneproc PROTO((int err, char *repos, char *update_dir));
+static int checkaddfile PROTO((char *file, char *repository, char *tag,
+ List *srcfiles));
+static Dtype commit_direntproc PROTO((char *dir, char *repos, char *update_dir));
+static int commit_dirleaveproc PROTO((char *dir, int err, char *update_dir));
+static int commit_fileproc PROTO((char *file, char *update_dir, char *repository,
+ List * entries, List * srcfiles));
+static int commit_filesdoneproc PROTO((int err, char *repository, char *update_dir));
+static int finaladd PROTO((char *file, char *revision, char *tag,
+ char *options, char *update_dir,
+ char *repository, List *entries));
+static int findmaxrev PROTO((Node * p, void *closure));
+static int fsortcmp PROTO((const Node * p, const Node * q));
+static int lock_RCS PROTO((char *user, char *rcs, char *rev, char *repository));
+static int lock_filesdoneproc PROTO((int err, char *repository, char *update_dir));
+static int lockrcsfile PROTO((char *file, char *repository, char *rev));
+static int precommit_list_proc PROTO((Node * p, void *closure));
+static int precommit_proc PROTO((char *repository, char *filter));
+static int remove_file PROTO((char *file, char *repository, char *tag,
+ char *message, List *entries, List *srcfiles));
+static void fix_rcs_modes PROTO((char *rcs, char *user));
+static void fixaddfile PROTO((char *file, char *repository));
+static void fixbranch PROTO((char *file, char *repository, char *branch));
+static void unlockrcs PROTO((char *file, char *repository));
+static void ci_delproc PROTO((Node *p));
+static void masterlist_delproc PROTO((Node *p));
+static void locate_rcs PROTO((char *file, char *repository, char *rcs));
+
+struct commit_info
+{
+ Ctype status; /* as returned from Classify_File() */
+ char *rev; /* a numeric rev, if we know it */
+ char *tag; /* any sticky tag, or -r option */
+ char *options; /* Any sticky -k option */
+};
+struct master_lists
+{
+ List *ulist; /* list for Update_Logfile */
+ List *cilist; /* list with commit_info structs */
+};
+
+static int force_ci = 0;
+static int got_message;
+static int run_module_prog = 1;
+static int aflag;
+static char *tag;
+static char *write_dirtag;
+static char *logfile;
+static List *mulist;
+static List *locklist;
+static char *message;
+
+static const char *const commit_usage[] =
+{
+ "Usage: %s %s [-nRlf] [-m msg | -F logfile] [-r rev] files...\n",
+ "\t-n\tDo not run the module program (if any).\n",
+ "\t-R\tProcess directories recursively.\n",
+ "\t-l\tLocal directory only (not recursive).\n",
+ "\t-f\tForce the file to be committed; disables recursion.\n",
+ "\t-F file\tRead the log message from file.\n",
+ "\t-m msg\tLog message.\n",
+ "\t-r rev\tCommit to this branch or trunk revision.\n",
+ NULL
+};
+
+int
+commit (argc, argv)
+ int argc;
+ char **argv;
+{
+ int c;
+ int err = 0;
+ int local = 0;
+
+ if (argc == -1)
+ usage (commit_usage);
+
+#ifdef CVS_BADROOT
+ /*
+ * For log purposes, do not allow "root" to commit files. If you look
+ * like root, but are really logged in as a non-root user, it's OK.
+ */
+ if (geteuid () == (uid_t) 0)
+ {
+ struct passwd *pw;
+
+ if ((pw = (struct passwd *) getpwnam (getcaller ())) == NULL)
+ error (1, 0, "you are unknown to this system");
+ if (pw->pw_uid == (uid_t) 0)
+ error (1, 0, "cannot commit files as 'root'");
+ }
+#endif /* CVS_BADROOT */
+
+ optind = 1;
+ while ((c = getopt (argc, argv, "nlRm:fF:r:")) != -1)
+ {
+ switch (c)
+ {
+ case 'n':
+ run_module_prog = 0;
+ break;
+ case 'm':
+#ifdef FORCE_USE_EDITOR
+ use_editor = TRUE;
+#else
+ use_editor = FALSE;
+#endif
+ if (message)
+ {
+ free (message);
+ message = NULL;
+ }
+
+ message = xstrdup(optarg);
+ break;
+ case 'r':
+ if (tag)
+ free (tag);
+ tag = xstrdup (optarg);
+ break;
+ case 'l':
+ local = 1;
+ break;
+ case 'R':
+ local = 0;
+ break;
+ case 'f':
+ force_ci = 1;
+ local = 1; /* also disable recursion */
+ break;
+ case 'F':
+#ifdef FORCE_USE_EDITOR
+ use_editor = TRUE;
+#else
+ use_editor = FALSE;
+#endif
+ logfile = optarg;
+ break;
+ case '?':
+ default:
+ usage (commit_usage);
+ break;
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ /* numeric specified revision means we ignore sticky tags... */
+ if (tag && isdigit (*tag))
+ {
+ aflag = 1;
+ /* strip trailing dots */
+ while (tag[strlen (tag) - 1] == '.')
+ tag[strlen (tag) - 1] = '\0';
+ }
+
+ /* some checks related to the "-F logfile" option */
+ if (logfile)
+ {
+ int n, logfd;
+ struct stat statbuf;
+
+ if (message)
+ error (1, 0, "cannot specify both a message and a log file");
+
+ if ((logfd = open (logfile, O_RDONLY | OPEN_BINARY)) < 0)
+ error (1, errno, "cannot open log file %s", logfile);
+
+ if (fstat(logfd, &statbuf) < 0)
+ error (1, errno, "cannot find size of log file %s", logfile);
+
+ message = xmalloc (statbuf.st_size + 1);
+
+ if ((n = read (logfd, message, statbuf.st_size + 1)) < 0)
+ error (1, errno, "cannot read log message from %s", logfile);
+
+ (void) close (logfd);
+ message[n] = '\0';
+ }
+
+#ifdef CLIENT_SUPPORT
+ if (client_active)
+ {
+ /*
+ * Do this now; don't ask for a log message if we can't talk to the
+ * server. But if there is a syntax error in the options, give
+ * an error message without connecting.
+ */
+ start_server ();
+
+ ign_setup ();
+
+ /*
+ * We do this once, not once for each directory as in normal CVS.
+ * The protocol is designed this way. This is a feature.
+ *
+ * We could provide the lists of changed, modified, etc. files,
+ * however. Our failure to do so is just laziness, not design.
+ */
+ if (use_editor)
+ do_editor (".", &message, (char *)NULL, (List *)NULL);
+
+ /* We always send some sort of message, even if empty. */
+ option_with_arg ("-m", message);
+
+ if (local)
+ send_arg("-l");
+ if (force_ci)
+ send_arg("-f");
+ if (!run_module_prog)
+ send_arg("-n");
+ option_with_arg ("-r", tag);
+
+ send_files (argc, argv, local, 0);
+
+ if (fprintf (to_server, "ci\n") < 0)
+ error (1, errno, "writing to server");
+ return get_responses_and_close ();
+ }
+#endif
+
+ /* XXX - this is not the perfect check for this */
+ if (argc <= 0)
+ write_dirtag = tag;
+
+ wrap_setup ();
+
+ /*
+ * Run the recursion processor to find all the dirs to lock and lock all
+ * the dirs
+ */
+ locklist = getlist ();
+ err = start_recursion ((int (*) ()) NULL, lock_filesdoneproc,
+ (Dtype (*) ()) NULL, (int (*) ()) NULL, argc,
+ argv, local, W_LOCAL, aflag, 0, (char *) NULL, 0,
+ 0);
+ sortlist (locklist, fsortcmp);
+ if (Writer_Lock (locklist) != 0)
+ error (1, 0, "lock failed - giving up");
+
+ /*
+ * Set up the master update list
+ */
+ mulist = getlist ();
+
+ /*
+ * Run the recursion processor to verify the files are all up-to-date
+ */
+ err = start_recursion (check_fileproc, check_filesdoneproc,
+ check_direntproc, (int (*) ()) NULL, argc,
+ argv, local, W_LOCAL, aflag, 0, (char *) NULL, 1,
+ 0);
+ if (err)
+ {
+ Lock_Cleanup ();
+ error (1, 0, "correct above errors first!");
+ }
+
+ /*
+ * Run the recursion processor to commit the files
+ */
+ if (noexec == 0)
+ err = start_recursion (commit_fileproc, commit_filesdoneproc,
+ commit_direntproc, commit_dirleaveproc,
+ argc, argv, local, W_LOCAL, aflag, 0,
+ (char *) NULL, 1, 0);
+
+ /*
+ * Unlock all the dirs and clean up
+ */
+ Lock_Cleanup ();
+ dellist (&mulist);
+ dellist (&locklist);
+ return (err);
+}
+
+/*
+ * compare two lock list nodes (for sort)
+ */
+static int
+fsortcmp (p, q)
+ const Node *p;
+ const Node *q;
+{
+ return (strcmp (p->key, q->key));
+}
+
+/*
+ * Create a list of repositories to lock
+ */
+/* ARGSUSED */
+static int
+lock_filesdoneproc (err, repository, update_dir)
+ int err;
+ char *repository;
+ char *update_dir;
+{
+ Node *p;
+
+ p = getnode ();
+ p->type = LOCK;
+ p->key = xstrdup (repository);
+ /* FIXME-KRP: this error condition should not simply be passed by. */
+ if (p->key == NULL || addnode (locklist, p) != 0)
+ freenode (p);
+ return (err);
+}
+
+/*
+ * Check to see if a file is ok to commit and make sure all files are
+ * up-to-date
+ */
+/* ARGSUSED */
+static int
+check_fileproc (file, update_dir, repository, entries, srcfiles)
+ char *file;
+ char *update_dir;
+ char *repository;
+ List *entries;
+ List *srcfiles;
+{
+ Ctype status;
+ char *xdir;
+ Node *p;
+ List *ulist, *cilist;
+ Vers_TS *vers;
+ struct commit_info *ci;
+ int save_noexec, save_quiet, save_really_quiet;
+
+ save_noexec = noexec;
+ save_quiet = quiet;
+ save_really_quiet = really_quiet;
+ noexec = quiet = really_quiet = 1;
+
+ /* handle specified numeric revision specially */
+ if (tag && isdigit (*tag))
+ {
+ /* If the tag is for the trunk, make sure we're at the head */
+ if (numdots (tag) < 2)
+ {
+ status = Classify_File (file, (char *) NULL, (char *) NULL,
+ (char *) NULL, 1, aflag, repository,
+ entries, srcfiles, &vers, update_dir, 0);
+ if (status == T_UPTODATE || status == T_MODIFIED ||
+ status == T_ADDED)
+ {
+ Ctype xstatus;
+
+ freevers_ts (&vers);
+ xstatus = Classify_File (file, tag, (char *) NULL,
+ (char *) NULL, 1, aflag, repository,
+ entries, srcfiles, &vers, update_dir,
+ 0);
+ if (xstatus == T_REMOVE_ENTRY)
+ status = T_MODIFIED;
+ else if (status == T_MODIFIED && xstatus == T_CONFLICT)
+ status = T_MODIFIED;
+ else
+ status = xstatus;
+ }
+ }
+ else
+ {
+ char *xtag, *cp;
+
+ /*
+ * The revision is off the main trunk; make sure we're
+ * up-to-date with the head of the specified branch.
+ */
+ xtag = xstrdup (tag);
+ if ((numdots (xtag) & 1) != 0)
+ {
+ cp = strrchr (xtag, '.');
+ *cp = '\0';
+ }
+ status = Classify_File (file, xtag, (char *) NULL,
+ (char *) NULL, 1, aflag, repository,
+ entries, srcfiles, &vers, update_dir, 0);
+ if ((status == T_REMOVE_ENTRY || status == T_CONFLICT)
+ && (cp = strrchr (xtag, '.')) != NULL)
+ {
+ /* pluck one more dot off the revision */
+ *cp = '\0';
+ freevers_ts (&vers);
+ status = Classify_File (file, xtag, (char *) NULL,
+ (char *) NULL, 1, aflag, repository,
+ entries, srcfiles, &vers, update_dir,
+ 0);
+ if (status == T_UPTODATE || status == T_REMOVE_ENTRY)
+ status = T_MODIFIED;
+ }
+ /* now, muck with vers to make the tag correct */
+ free (vers->tag);
+ vers->tag = xstrdup (tag);
+ free (xtag);
+ }
+ }
+ else
+ status = Classify_File (file, tag, (char *) NULL, (char *) NULL,
+ 1, 0, repository, entries, srcfiles, &vers,
+ update_dir, 0);
+ noexec = save_noexec;
+ quiet = save_quiet;
+ really_quiet = save_really_quiet;
+
+ /*
+ * If the force-commit option is enabled, and the file in question
+ * appears to be up-to-date, just make it look modified so that
+ * it will be committed.
+ */
+ if (force_ci && status == T_UPTODATE)
+ status = T_MODIFIED;
+
+ switch (status)
+ {
+ case T_CHECKOUT:
+#ifdef SERVER_SUPPORT
+ case T_PATCH:
+#endif
+ case T_NEEDS_MERGE:
+ case T_CONFLICT:
+ case T_REMOVE_ENTRY:
+ if (update_dir[0] == '\0')
+ error (0, 0, "Up-to-date check failed for `%s'", file);
+ else
+ error (0, 0, "Up-to-date check failed for `%s/%s'",
+ update_dir, file);
+ freevers_ts (&vers);
+ return (1);
+ case T_MODIFIED:
+ case T_ADDED:
+ case T_REMOVED:
+ /*
+ * some quick sanity checks; if no numeric -r option specified:
+ * - can't have a sticky date
+ * - can't have a sticky tag that is not a branch
+ * Also,
+ * - if status is T_REMOVED, can't have a numeric tag
+ * - if status is T_ADDED, rcs file must not exist
+ * - if status is T_ADDED, can't have a non-trunk numeric rev
+ * - if status is T_MODIFIED and a Conflict marker exists, don't
+ * allow the commit if timestamp is identical or if we find
+ * an RCS_MERGE_PAT in the file.
+ */
+ if (!tag || !isdigit (*tag))
+ {
+ if (vers->date)
+ {
+ if (update_dir[0] == '\0')
+ error (0, 0,
+ "cannot commit with sticky date for file `%s'",
+ file);
+ else
+ error
+ (0, 0,
+ "cannot commit with sticky date for file `%s/%s'",
+ update_dir, file);
+ freevers_ts (&vers);
+ return (1);
+ }
+ if (status == T_MODIFIED && vers->tag &&
+ !RCS_isbranch (file, vers->tag, srcfiles))
+ {
+ if (update_dir[0] == '\0')
+ error (0, 0,
+ "sticky tag `%s' for file `%s' is not a branch",
+ vers->tag, file);
+ else
+ error
+ (0, 0,
+ "sticky tag `%s' for file `%s/%s' is not a branch",
+ vers->tag, update_dir, file);
+ freevers_ts (&vers);
+ return (1);
+ }
+ }
+ if (status == T_MODIFIED && !force_ci && vers->ts_conflict)
+ {
+ char *filestamp;
+ int retcode;
+
+ /*
+ * We found a "conflict" marker.
+ *
+ * If the timestamp on the file is the same as the
+ * timestamp stored in the Entries file, we block the commit.
+ */
+#ifdef SERVER_SUPPORT
+ if (server_active)
+ retcode = vers->ts_conflict[0] != '=';
+ else {
+ filestamp = time_stamp (file);
+ retcode = strcmp (vers->ts_conflict, filestamp);
+ free (filestamp);
+ }
+#else
+ filestamp = time_stamp (file);
+ retcode = strcmp (vers->ts_conflict, filestamp);
+ free (filestamp);
+#endif
+ if (retcode == 0)
+ {
+ if (update_dir[0] == '\0')
+ error (0, 0,
+ "file `%s' had a conflict and has not been modified",
+ file);
+ else
+ error (0, 0,
+ "file `%s/%s' had a conflict and has not been modified",
+ update_dir, file);
+ freevers_ts (&vers);
+ return (1);
+ }
+
+ /*
+ * If the timestamps differ, look for Conflict indicators
+ * in the file to see if we should block the commit anyway
+ */
+ run_setup ("%s -s", GREP);
+ run_arg (RCS_MERGE_PAT);
+ run_arg (file);
+ retcode = run_exec (RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL);
+
+ if (retcode == -1)
+ {
+ if (update_dir[0] == '\0')
+ error (1, errno,
+ "fork failed while examining conflict in `%s'",
+ file);
+ else
+ error (1, errno,
+ "fork failed while examining conflict in `%s/%s'",
+ update_dir, file);
+ }
+ else if (retcode == 0)
+ {
+ if (update_dir[0] == '\0')
+ error (0, 0,
+ "file `%s' still contains conflict indicators",
+ file);
+ else
+ error (0, 0,
+ "file `%s/%s' still contains conflict indicators",
+ update_dir, file);
+ freevers_ts (&vers);
+ return (1);
+ }
+ }
+
+ if (status == T_REMOVED && vers->tag && isdigit (*vers->tag))
+ {
+ if (update_dir[0] == '\0')
+ error (0, 0,
+ "cannot remove file `%s' which has a numeric sticky tag of `%s'",
+ file, vers->tag);
+ else
+ error (0, 0,
+ "cannot remove file `%s/%s' which has a numeric sticky tag of `%s'",
+ update_dir, file, vers->tag);
+ freevers_ts (&vers);
+ return (1);
+ }
+ if (status == T_ADDED)
+ {
+ char rcs[PATH_MAX];
+
+#ifdef DEATH_SUPPORT
+ /* Don't look in the attic; if it exists there we will
+ move it back out in checkaddfile. */
+ sprintf(rcs, "%s/%s%s", repository, file, RCSEXT);
+#else
+ locate_rcs (file, repository, rcs);
+#endif
+ if (isreadable (rcs))
+ {
+ if (update_dir[0] == '\0')
+ error (0, 0,
+ "cannot add file `%s' when RCS file `%s' already exists",
+ file, rcs);
+ else
+ error (0, 0,
+ "cannot add file `%s/%s' when RCS file `%s' already exists",
+ update_dir, file, rcs);
+ freevers_ts (&vers);
+ return (1);
+ }
+ if (vers->tag && isdigit (*vers->tag) &&
+ numdots (vers->tag) > 1)
+ {
+ if (update_dir[0] == '\0')
+ error (0, 0,
+ "cannot add file `%s' with revision `%s'; must be on trunk",
+ file, vers->tag);
+ else
+ error (0, 0,
+ "cannot add file `%s/%s' with revision `%s'; must be on trunk",
+ update_dir, file, vers->tag);
+ freevers_ts (&vers);
+ return (1);
+ }
+ }
+
+ /* done with consistency checks; now, to get on with the commit */
+ if (update_dir[0] == '\0')
+ xdir = ".";
+ else
+ xdir = update_dir;
+ if ((p = findnode (mulist, xdir)) != NULL)
+ {
+ ulist = ((struct master_lists *) p->data)->ulist;
+ cilist = ((struct master_lists *) p->data)->cilist;
+ }
+ else
+ {
+ struct master_lists *ml;
+
+ ulist = getlist ();
+ cilist = getlist ();
+ p = getnode ();
+ p->key = xstrdup (xdir);
+ p->type = UPDATE;
+ ml = (struct master_lists *)
+ xmalloc (sizeof (struct master_lists));
+ ml->ulist = ulist;
+ ml->cilist = cilist;
+ p->data = (char *) ml;
+ p->delproc = masterlist_delproc;
+ (void) addnode (mulist, p);
+ }
+
+ /* first do ulist, then cilist */
+ p = getnode ();
+ p->key = xstrdup (file);
+ p->type = UPDATE;
+ p->delproc = update_delproc;
+ p->data = (char *) status;
+ (void) addnode (ulist, p);
+
+ p = getnode ();
+ p->key = xstrdup (file);
+ p->type = UPDATE;
+ p->delproc = ci_delproc;
+ ci = (struct commit_info *) xmalloc (sizeof (struct commit_info));
+ ci->status = status;
+ if (vers->tag)
+ if (isdigit (*vers->tag))
+ ci->rev = xstrdup (vers->tag);
+ else
+ ci->rev = RCS_whatbranch (file, vers->tag, srcfiles);
+ else
+ ci->rev = (char *) NULL;
+ ci->tag = xstrdup (vers->tag);
+ ci->options = xstrdup(vers->options);
+ p->data = (char *) ci;
+ (void) addnode (cilist, p);
+ break;
+ case T_UNKNOWN:
+ if (update_dir[0] == '\0')
+ error (0, 0, "nothing known about `%s'", file);
+ else
+ error (0, 0, "nothing known about `%s/%s'", update_dir, file);
+ freevers_ts (&vers);
+ return (1);
+ case T_UPTODATE:
+ break;
+ default:
+ error (0, 0, "CVS internal error: unknown status %d", status);
+ break;
+ }
+
+ freevers_ts (&vers);
+ return (0);
+}
+
+/*
+ * Print warm fuzzies while examining the dirs
+ */
+/* ARGSUSED */
+static Dtype
+check_direntproc (dir, repos, update_dir)
+ char *dir;
+ char *repos;
+ char *update_dir;
+{
+ if (!quiet)
+ error (0, 0, "Examining %s", update_dir);
+
+ return (R_PROCESS);
+}
+
+/*
+ * Walklist proc to run pre-commit checks
+ */
+static int
+precommit_list_proc (p, closure)
+ Node *p;
+ void *closure;
+{
+ if (p->data == (char *) T_ADDED || p->data == (char *) T_MODIFIED ||
+ p->data == (char *) T_REMOVED)
+ {
+ run_arg (p->key);
+ }
+ return (0);
+}
+
+/*
+ * Callback proc for pre-commit checking
+ */
+static List *ulist;
+static int
+precommit_proc (repository, filter)
+ char *repository;
+ char *filter;
+{
+ /* see if the filter is there, only if it's a full path */
+ if (isabsolute (filter))
+ {
+ char *s, *cp;
+
+ s = xstrdup (filter);
+ for (cp = s; *cp; cp++)
+ if (isspace (*cp))
+ {
+ *cp = '\0';
+ break;
+ }
+ if (!isfile (s))
+ {
+ error (0, errno, "cannot find pre-commit filter `%s'", s);
+ free (s);
+ return (1); /* so it fails! */
+ }
+ free (s);
+ }
+
+ run_setup ("%s %s", filter, repository);
+ (void) walklist (ulist, precommit_list_proc, NULL);
+ return (run_exec (RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL|RUN_REALLY));
+}
+
+/*
+ * Run the pre-commit checks for the dir
+ */
+/* ARGSUSED */
+static int
+check_filesdoneproc (err, repos, update_dir)
+ int err;
+ char *repos;
+ char *update_dir;
+{
+ int n;
+ Node *p;
+
+ /* find the update list for this dir */
+ p = findnode (mulist, update_dir);
+ if (p != NULL)
+ ulist = ((struct master_lists *) p->data)->ulist;
+ else
+ ulist = (List *) NULL;
+
+ /* skip the checks if there's nothing to do */
+ if (ulist == NULL || ulist->list->next == ulist->list)
+ return (err);
+
+ /* run any pre-commit checks */
+ if ((n = Parse_Info (CVSROOTADM_COMMITINFO, repos, precommit_proc, 1)) > 0)
+ {
+ error (0, 0, "Pre-commit check failed");
+ err += n;
+ }
+
+ return (err);
+}
+
+/*
+ * Do the work of committing a file
+ */
+static int maxrev;
+static char sbranch[PATH_MAX];
+
+/* ARGSUSED */
+static int
+commit_fileproc (file, update_dir, repository, entries, srcfiles)
+ char *file;
+ char *update_dir;
+ char *repository;
+ List *entries;
+ List *srcfiles;
+{
+ Node *p;
+ int err = 0;
+ List *ulist, *cilist;
+ struct commit_info *ci;
+ char rcs[PATH_MAX];
+
+ if (update_dir[0] == '\0')
+ p = findnode (mulist, ".");
+ else
+ p = findnode (mulist, update_dir);
+
+ /*
+ * if p is null, there were file type command line args which were
+ * all up-to-date so nothing really needs to be done
+ */
+ if (p == NULL)
+ return (0);
+ ulist = ((struct master_lists *) p->data)->ulist;
+ cilist = ((struct master_lists *) p->data)->cilist;
+
+ /*
+ * At this point, we should have the commit message unless we were called
+ * with files as args from the command line. In that latter case, we
+ * need to get the commit message ourselves
+ */
+ if (use_editor && !got_message)
+ {
+ got_message = 1;
+ do_editor (update_dir, &message, repository, ulist);
+ }
+
+ p = findnode (cilist, file);
+ if (p == NULL)
+ return (0);
+
+ ci = (struct commit_info *) p->data;
+ if (ci->status == T_MODIFIED)
+ {
+ if (lockrcsfile (file, repository, ci->rev) != 0)
+ {
+ unlockrcs (file, repository);
+ err = 1;
+ goto out;
+ }
+ }
+ else if (ci->status == T_ADDED)
+ {
+ if (checkaddfile (file, repository, ci->tag, srcfiles) != 0)
+ {
+ fixaddfile (file, repository);
+ err = 1;
+ goto out;
+ }
+
+#ifdef DEATH_SUPPORT
+ /* adding files with a tag, now means adding them on a branch.
+ Since the branch test was done in check_fileproc for
+ modified files, we need to stub it in again here. */
+
+ if (ci->tag) {
+ locate_rcs (file, repository, rcs);
+ ci->rev = RCS_whatbranch (file, ci->tag, srcfiles);
+ err = Checkin ('A', file, update_dir, repository, rcs, ci->rev,
+ ci->tag, ci->options, message, entries);
+ if (err != 0)
+ {
+ unlockrcs (file, repository);
+ fixbranch (file, repository, sbranch);
+ }
+
+ ci->status = T_UPTODATE;
+ }
+#endif /* DEATH_SUPPORT */
+ }
+
+ /*
+ * Add the file for real
+ */
+ if (ci->status == T_ADDED)
+ {
+ char *xrev = (char *) NULL;
+
+ if (ci->rev == NULL)
+ {
+ /* find the max major rev number in this directory */
+ maxrev = 0;
+ (void) walklist (entries, findmaxrev, NULL);
+ if (maxrev == 0)
+ maxrev = 1;
+ xrev = xmalloc (20);
+ (void) sprintf (xrev, "%d", maxrev);
+ }
+
+ /* XXX - an added file with symbolic -r should add tag as well */
+ err = finaladd (file, ci->rev ? ci->rev : xrev, ci->tag, ci->options,
+ update_dir, repository, entries);
+ if (xrev)
+ free (xrev);
+ }
+ else if (ci->status == T_MODIFIED)
+ {
+ locate_rcs (file, repository, rcs);
+ err = Checkin ('M', file, update_dir, repository,
+ rcs, ci->rev, ci->tag,
+ ci->options, message, entries);
+ if (err != 0)
+ {
+ unlockrcs (file, repository);
+ fixbranch (file, repository, sbranch);
+ }
+ }
+ else if (ci->status == T_REMOVED)
+ {
+ err = remove_file (file, repository, ci->tag, message,
+ entries, srcfiles);
+#ifdef SERVER_SUPPORT
+ if (server_active) {
+ server_scratch_entry_only ();
+ server_updated (file, update_dir, repository,
+ /* Doesn't matter, it won't get checked. */
+ SERVER_UPDATED, (struct stat *) NULL,
+ (unsigned char *) NULL);
+ }
+#endif
+ }
+
+out:
+ if (err != 0)
+ {
+ /* on failure, remove the file from ulist */
+ p = findnode (ulist, file);
+ if (p)
+ delnode (p);
+ }
+
+ return (err);
+}
+
+/*
+ * Log the commit and clean up the update list
+ */
+/* ARGSUSED */
+static int
+commit_filesdoneproc (err, repository, update_dir)
+ int err;
+ char *repository;
+ char *update_dir;
+{
+ char *xtag = (char *) NULL;
+ Node *p;
+ List *ulist;
+
+ p = findnode (mulist, update_dir);
+ if (p == NULL)
+ return (err);
+
+ ulist = ((struct master_lists *) p->data)->ulist;
+
+ got_message = 0;
+
+ /* see if we need to specify a per-directory or -r option tag */
+ if (tag == NULL)
+ ParseTag (&xtag, (char **) NULL);
+
+ Update_Logfile (repository, message, tag ? tag : xtag, (FILE *) 0, ulist);
+ if (xtag)
+ free (xtag);
+
+ if (err == 0 && run_module_prog)
+ {
+ char *cp;
+ FILE *fp;
+ char line[MAXLINELEN];
+ char *repository;
+
+ /* It is not an error if Checkin.prog does not exist. */
+ if ((fp = fopen (CVSADM_CIPROG, "r")) != NULL)
+ {
+ if (fgets (line, sizeof (line), fp) != NULL)
+ {
+ if ((cp = strrchr (line, '\n')) != NULL)
+ *cp = '\0';
+ repository = Name_Repository ((char *) NULL, update_dir);
+ run_setup ("%s %s", line, repository);
+ (void) printf ("%s %s: Executing '", program_name,
+ command_name);
+ run_print (stdout);
+ (void) printf ("'\n");
+ (void) run_exec (RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL);
+ free (repository);
+ }
+ (void) fclose (fp);
+ }
+ }
+
+ return (err);
+}
+
+/*
+ * Get the log message for a dir and print a warm fuzzy
+ */
+/* ARGSUSED */
+static Dtype
+commit_direntproc (dir, repos, update_dir)
+ char *dir;
+ char *repos;
+ char *update_dir;
+{
+ Node *p;
+ List *ulist;
+ char *real_repos;
+
+ /* find the update list for this dir */
+ p = findnode (mulist, update_dir);
+ if (p != NULL)
+ ulist = ((struct master_lists *) p->data)->ulist;
+ else
+ ulist = (List *) NULL;
+
+ /* skip the files as an optimization */
+ if (ulist == NULL || ulist->list->next == ulist->list)
+ return (R_SKIP_FILES);
+
+ /* print the warm fuzzy */
+ if (!quiet)
+ error (0, 0, "Committing %s", update_dir);
+
+ /* get commit message */
+ if (use_editor)
+ {
+ got_message = 1;
+ real_repos = Name_Repository (dir, update_dir);
+ do_editor (update_dir, &message, real_repos, ulist);
+ free (real_repos);
+ }
+ return (R_PROCESS);
+}
+
+/*
+ * Process the post-commit proc if necessary
+ */
+/* ARGSUSED */
+static int
+commit_dirleaveproc (dir, err, update_dir)
+ char *dir;
+ int err;
+ char *update_dir;
+{
+ /* update the per-directory tag info */
+ if (err == 0 && write_dirtag != NULL)
+ {
+ WriteTag ((char *) NULL, write_dirtag, (char *) NULL);
+#ifdef SERVER_SUPPORT
+ if (server_active)
+ server_set_sticky (update_dir, Name_Repository (dir, update_dir),
+ write_dirtag, (char *) NULL);
+#endif
+ }
+
+ return (err);
+}
+
+/*
+ * find the maximum major rev number in an entries file
+ */
+static int
+findmaxrev (p, closure)
+ Node *p;
+ void *closure;
+{
+ char *cp;
+ int thisrev;
+ Entnode *entdata;
+
+ entdata = (Entnode *) p->data;
+ cp = strchr (entdata->version, '.');
+ if (cp != NULL)
+ *cp = '\0';
+ thisrev = atoi (entdata->version);
+ if (cp != NULL)
+ *cp = '.';
+ if (thisrev > maxrev)
+ maxrev = thisrev;
+ return (0);
+}
+
+/*
+ * Actually remove a file by moving it to the attic
+ * XXX - if removing a ,v file that is a relative symbolic link to
+ * another ,v file, we probably should add a ".." component to the
+ * link to keep it relative after we move it into the attic.
+ */
+static int
+remove_file (file, repository, tag, message, entries, srcfiles)
+ char *file;
+ char *repository;
+ char *tag;
+ char *message;
+ List *entries;
+ List *srcfiles;
+{
+ mode_t omask;
+ int retcode;
+ char rcs[PATH_MAX];
+ char *tmp;
+
+#ifdef DEATH_SUPPORT
+ int branch;
+ char *lockflag;
+ char *corev;
+ char *rev;
+ char *prev_rev;
+ Node *p;
+ RCSNode *rcsfile;
+
+ corev = NULL;
+ rev = NULL;
+ prev_rev = NULL;
+ lockflag = 0;
+#endif /* DEATH_SUPPORT */
+
+ retcode = 0;
+
+ locate_rcs (file, repository, rcs);
+
+#ifdef DEATH_SUPPORT
+ branch = 0;
+ if (tag && !(branch = RCS_isbranch (file, tag, srcfiles)))
+#else
+ if (tag)
+#endif
+ {
+ /* a symbolic tag is specified; just remove the tag from the file */
+ if ((retcode = RCS_deltag (rcs, tag, 1)) != 0)
+ {
+ if (!quiet)
+ error (0, retcode == -1 ? errno : 0,
+ "failed to remove tag `%s' from `%s'", tag, rcs);
+ return (1);
+ }
+ Scratch_Entry (entries, file);
+ return (0);
+ }
+
+#ifdef DEATH_SUPPORT
+ /* we are removing the file from either the head or a branch */
+ /* commit a new, dead revision. */
+
+ /* Print message indicating that file is going to be removed. */
+ (void) printf ("Removing %s;\n", file);
+
+ rev = NULL;
+ lockflag = "-l";
+ if (branch)
+ {
+ char *branchname;
+
+ rev = RCS_whatbranch (file, tag, srcfiles);
+ if (rev == NULL)
+ {
+ error (0, 0, "cannot find branch \"%s\".", tag);
+ return (1);
+ }
+
+ p = findnode (srcfiles, file);
+ if (p == NULL)
+ {
+ error (0, 0, "boy, I'm confused.");
+ return (1);
+ }
+ rcsfile = (RCSNode *) p->data;
+ branchname = RCS_getbranch (rcsfile, rev, 1);
+ if (branchname == NULL)
+ {
+ /* no revision exists on this branch. use the previous
+ revision but do not lock. */
+ corev = RCS_gettag (rcsfile, tag, 1);
+ prev_rev = xstrdup(rev);
+ lockflag = "";
+ } else
+ {
+ corev = xstrdup (rev);
+ prev_rev = xstrdup(branchname);
+ free (branchname);
+ }
+
+ } else /* Not a branch */
+ {
+
+ /* Get current head revision of file. */
+ p = findnode (srcfiles, file);
+ if (p == NULL)
+ {
+ error (0, 0, "could not find parsed rcsfile %s", file);
+ return (1);
+ }
+ rcsfile = (RCSNode *) p->data;
+ prev_rev = RCS_head (rcsfile);
+ }
+
+ /* if removing without a tag or a branch, then make sure the default
+ branch is the trunk. */
+ if (!tag && !branch)
+ {
+ if (RCS_setbranch (rcs, NULL) != 0)
+ {
+ error (0, 0, "cannot change branch to default for %s",
+ rcs);
+ return (1);
+ }
+ }
+
+#ifdef SERVER_SUPPORT
+ if (server_active) {
+ /* If this is the server, there will be a file sitting in the
+ temp directory which is the kludgy way in which server.c
+ tells time_stamp that the file is no longer around. Remove
+ it so we can create temp files with that name (ignore errors). */
+ unlink_file (file);
+ }
+#endif
+
+ /* check something out. Generally this is the head. If we have a
+ particular rev, then name it. except when creating a branch,
+ lock the rev we're checking out. */
+ run_setup ("%s%s %s %s%s %s", Rcsbin, RCS_CO,
+ lockflag,
+ rev ? "-r" : "",
+ rev ? corev : "", rcs);
+ if ((retcode = run_exec (RUN_TTY, RUN_TTY, DEVNULL, RUN_NORMAL))
+ != 0) {
+ if (!quiet)
+ error (0, retcode == -1 ? errno : 0,
+ "failed to check out `%s'", rcs);
+ return (1);
+ }
+
+ if (corev != NULL)
+ free (corev);
+
+#ifdef DEATH_STATE
+ run_setup ("%s%s -f -sdead %s%s", Rcsbin, RCS_CI, rev ? "-r" : "",
+#else
+ run_setup ("%s%s -K %s%s", Rcsbin, RCS_CI, rev ? "-r" : "",
+#endif
+ rev ? rev : "");
+ run_args ("-m%s", make_message_rcslegal (message));
+ run_arg (rcs);
+ if ((retcode = run_exec (RUN_TTY, RUN_TTY, DEVNULL, RUN_NORMAL))
+ != 0) {
+ if (!quiet)
+ error (0, retcode == -1 ? errno : 0,
+ "failed to commit dead revision for `%s'", rcs);
+ return (1);
+ }
+
+ if (rev != NULL)
+ free (rev);
+
+ if (!branch)
+#else /* No DEATH_SUPPORT */
+ else
+#endif /* No DEATH_SUPPORT */
+ {
+ /* this was the head; really move it into the Attic */
+ tmp = xmalloc(strlen(repository) +
+ sizeof('/') +
+ sizeof(CVSATTIC) +
+ sizeof('/') +
+ strlen(file) +
+ sizeof(RCSEXT) + 1);
+ (void) sprintf (tmp, "%s/%s", repository, CVSATTIC);
+ omask = umask (2);
+ (void) CVS_MKDIR (tmp, 0777);
+ (void) umask (omask);
+ (void) sprintf (tmp, "%s/%s/%s%s", repository, CVSATTIC, file, RCSEXT);
+
+#ifdef DEATH_SUPPORT
+ if (strcmp (rcs, tmp) != 0
+ && rename (rcs, tmp) == -1
+ && (isreadable (rcs) || !isreadable (tmp)))
+ {
+ free(tmp);
+ return (1);
+ }
+ free(tmp);
+ }
+
+ /* Print message that file was removed. */
+ (void) printf ("%s <-- %s\n", rcs, file);
+ (void) printf ("new revision: delete; ");
+ (void) printf ("previous revision: %s\n", prev_rev);
+ (void) printf ("done\n");
+ free(prev_rev);
+
+ Scratch_Entry (entries, file);
+ return (0);
+#else /* No DEATH_SUPPORT */
+
+ if ((strcmp (rcs, tmp) == 0 || rename (rcs, tmp) != -1) ||
+ (!isreadable (rcs) && isreadable (tmp)))
+ {
+ Scratch_Entry (entries, file);
+ /* FIXME: should free tmp. */
+ return (0);
+ }
+ /* FIXME: should free tmp. */
+ }
+ return (1);
+#endif /* No DEATH_SUPPORT */
+}
+
+/*
+ * Do the actual checkin for added files
+ */
+static int
+finaladd (file, rev, tag, options, update_dir, repository, entries)
+ char *file;
+ char *rev;
+ char *tag;
+ char *options;
+ char *update_dir;
+ char *repository;
+ List *entries;
+{
+ int ret;
+ char tmp[PATH_MAX];
+ char rcs[PATH_MAX];
+
+ locate_rcs (file, repository, rcs);
+ ret = Checkin ('A', file, update_dir, repository, rcs, rev, tag, options,
+ message, entries);
+ if (ret == 0)
+ {
+ (void) sprintf (tmp, "%s/%s%s", CVSADM, file, CVSEXT_OPT);
+ (void) unlink_file (tmp);
+ (void) sprintf (tmp, "%s/%s%s", CVSADM, file, CVSEXT_LOG);
+ (void) unlink_file (tmp);
+ }
+ else
+ fixaddfile (file, repository);
+ return (ret);
+}
+
+/*
+ * Unlock an rcs file
+ */
+static void
+unlockrcs (file, repository)
+ char *file;
+ char *repository;
+{
+ char rcs[PATH_MAX];
+ int retcode = 0;
+
+ locate_rcs (file, repository, rcs);
+
+ if ((retcode = RCS_unlock (rcs, NULL, 0)) != 0)
+ error (retcode == -1 ? 1 : 0, retcode == -1 ? errno : 0,
+ "could not unlock %s", rcs);
+}
+
+/*
+ * remove a partially added file. if we can parse it, leave it alone.
+ */
+static void
+fixaddfile (file, repository)
+ char *file;
+ char *repository;
+{
+ RCSNode *rcsfile;
+ char rcs[PATH_MAX];
+ int save_really_quiet;
+
+ locate_rcs (file, repository, rcs);
+ save_really_quiet = really_quiet;
+ really_quiet = 1;
+ if ((rcsfile = RCS_parsercsfile (rcs)) == NULL)
+ (void) unlink_file (rcs);
+ else
+ freercsnode (&rcsfile);
+ really_quiet = save_really_quiet;
+}
+
+/*
+ * put the branch back on an rcs file
+ */
+static void
+fixbranch (file, repository, branch)
+ char *file;
+ char *repository;
+ char *branch;
+{
+ char rcs[PATH_MAX];
+ int retcode = 0;
+
+ if (branch != NULL && branch[0] != '\0')
+ {
+ locate_rcs (file, repository, rcs);
+ if ((retcode = RCS_setbranch (rcs, branch)) != 0)
+ error (retcode == -1 ? 1 : 0, retcode == -1 ? errno : 0,
+ "cannot restore branch to %s for %s", branch, rcs);
+ }
+}
+
+/*
+ * do the initial part of a file add for the named file. if adding
+ * with a tag, put the file in the Attic and point the symbolic tag
+ * at the committed revision.
+ */
+
+static int
+checkaddfile (file, repository, tag, srcfiles)
+ char *file;
+ char *repository;
+ char *tag;
+ List *srcfiles;
+{
+ FILE *fp;
+ char *cp;
+ char rcs[PATH_MAX];
+ char fname[PATH_MAX];
+ mode_t omask;
+ int retcode = 0;
+#ifdef DEATH_SUPPORT
+ int newfile = 0;
+#endif
+
+ if (tag)
+ {
+ (void) sprintf(rcs, "%s/%s", repository, CVSATTIC);
+ omask = umask (2);
+ if (CVS_MKDIR (rcs, 0777) != 0 && errno != EEXIST)
+ error (1, errno, "cannot make directory `%s'", rcs);;
+ (void) umask (omask);
+ (void) sprintf (rcs, "%s/%s/%s%s", repository, CVSATTIC, file, RCSEXT);
+ }
+ else
+ locate_rcs (file, repository, rcs);
+
+#ifdef DEATH_SUPPORT
+ if (isreadable(rcs))
+ {
+ /* file has existed in the past. Prepare to resurrect. */
+ char oldfile[PATH_MAX];
+ char *rev;
+ Node *p;
+ RCSNode *rcsfile;
+
+ if (tag == NULL)
+ {
+ /* we are adding on the trunk, so move the file out of the
+ Attic. */
+ strcpy (oldfile, rcs);
+ sprintf (rcs, "%s/%s%s", repository, file, RCSEXT);
+
+ if (strcmp (oldfile, rcs) == 0
+ || rename (oldfile, rcs) != 0
+ || isreadable (oldfile)
+ || !isreadable (rcs))
+ {
+ error (0, 0, "failed to move `%s' out of the attic.",
+ file);
+ return (1);
+ }
+ }
+
+ p = findnode (srcfiles, file);
+ if (p == NULL)
+ {
+ error (0, 0, "could not find parsed rcsfile %s", file);
+ return (1);
+ }
+
+ rcsfile = (RCSNode *) p->data;
+ rev = RCS_getversion (rcsfile, tag, NULL, 1);
+ /* and lock it */
+ if (lock_RCS (file, rcs, rev, repository)) {
+ error (0, 0, "cannot lock `%s'.", rcs);
+ free (rev);
+ return (1);
+ }
+
+ free (rev);
+ } else {
+ /* this is the first time we have ever seen this file; create
+ an rcs file. */
+ run_setup ("%s%s -i", Rcsbin, RCS);
+
+ (void) sprintf (fname, "%s/%s%s", CVSADM, file, CVSEXT_LOG);
+ /* If the file does not exist, no big deal. In particular, the
+ server does not (yet at least) create CVSEXT_LOG files. */
+ if (isfile (fname))
+ run_args ("-t%s/%s%s", CVSADM, file, CVSEXT_LOG);
+
+ (void) sprintf (fname, "%s/%s%s", CVSADM, file, CVSEXT_OPT);
+ fp = fopen (fname, "r");
+ /* If the file does not exist, no big deal. In particular, the
+ server does not (yet at least) create CVSEXT_OPT files. */
+ if (fp == NULL)
+ {
+ if (errno != ENOENT)
+ error (1, errno, "cannot open %s", fname);
+ }
+ else
+ {
+ while (fgets (fname, sizeof (fname), fp) != NULL)
+ {
+ if ((cp = strrchr (fname, '\n')) != NULL)
+ *cp = '\0';
+ if (*fname)
+ run_arg (fname);
+ }
+ (void) fclose (fp);
+ }
+ run_arg (rcs);
+ if ((retcode = run_exec (RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL)) != 0)
+ {
+ error (retcode == -1 ? 1 : 0, retcode == -1 ? errno : 0,
+ "could not create %s", rcs);
+ return (1);
+ }
+ newfile = 1;
+ }
+
+ /* when adding a file for the first time, and using a tag, we need
+ to create a dead revision on the trunk. */
+ if (tag && newfile)
+ {
+ char tmp[PATH_MAX];
+
+ /* move the new file out of the way. */
+ (void) sprintf (fname, "%s/%s%s", CVSADM, CVSPREFIX, file);
+ rename_file (file, fname);
+ copy_file (DEVNULL, file);
+
+ /* commit a dead revision. */
+ (void) sprintf (tmp, "-mfile %s was initially added on branch %s.", file, tag);
+#ifdef DEATH_STATE
+ run_setup ("%s%s -q -f -sdead", Rcsbin, RCS_CI);
+#else
+ run_setup ("%s%s -q -K", Rcsbin, RCS_CI);
+#endif
+ run_arg (tmp);
+ run_arg (rcs);
+ if ((retcode = run_exec (RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL)) != 0)
+ {
+ error (retcode == -1 ? 1 : 0, retcode == -1 ? errno : 0,
+ "could not create initial dead revision %s", rcs);
+ return (1);
+ }
+
+ /* put the new file back where it was */
+ rename_file (fname, file);
+
+ /* and lock it once again. */
+ if (lock_RCS (file, rcs, NULL, repository)) {
+ error (0, 0, "cannot lock `%s'.", rcs);
+ return (1);
+ }
+ }
+
+ if (tag != NULL)
+ {
+ /* when adding with a tag, we need to stub a branch, if it
+ doesn't already exist. */
+ Node *p;
+ RCSNode *rcsfile;
+
+ rcsfile = RCS_parse (file, repository);
+ if (rcsfile == NULL)
+ {
+ error (0, 0, "could not read %s", rcs);
+ return (1);
+ }
+
+ if (!RCS_nodeisbranch (tag, rcsfile)) {
+ /* branch does not exist. Stub it. */
+ char *head;
+ char *magicrev;
+
+ head = RCS_getversion (rcsfile, NULL, NULL, 0);
+ magicrev = RCS_magicrev (rcsfile, head);
+ if ((retcode = RCS_settag(rcs, tag, magicrev)) != 0)
+ {
+ error (retcode == -1 ? 1 : 0, retcode == -1 ? errno : 0,
+ "could not stub branch %s for %s", tag, rcs);
+ return (1);
+ }
+
+ freercsnode (&rcsfile);
+
+ /* reparse the file, then add it to our list. */
+ rcsfile = RCS_parse (file, repository);
+ if (rcsfile == NULL)
+ {
+ error (0, 0, "could not reparse %s", rcs);
+ return (1);
+ }
+
+ free (head);
+ free (magicrev);
+ }
+ else
+ {
+ /* lock the branch. (stubbed branches need not be locked.) */
+ if (lock_RCS (file, rcs, NULL, repository)) {
+ error (0, 0, "cannot lock `%s'.", rcs);
+ return (1);
+ }
+ }
+
+ /* add (replace) this rcs file to our list */
+ p = findnode (srcfiles, file);
+
+ if (p != NULL)
+ freercsnode((RCSNode **) &p->data);
+
+ delnode(p);
+
+ RCS_addnode (file, rcsfile, srcfiles);
+ }
+#else /* No DEATH_SUPPORT */
+ run_setup ("%s%s -i", Rcsbin, RCS);
+ run_args ("-t%s/%s%s", CVSADM, file, CVSEXT_LOG);
+ (void) sprintf (fname, "%s/%s%s", CVSADM, file, CVSEXT_OPT);
+ fp = open_file (fname, "r");
+ while (fgets (fname, sizeof (fname), fp) != NULL)
+ {
+ if ((cp = strrchr (fname, '\n')) != NULL)
+ *cp = '\0';
+ if (*fname)
+ run_arg (fname);
+ }
+ (void) fclose (fp);
+ run_arg (rcs);
+ if ((retcode = run_exec (RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL)) != 0)
+ {
+ error (retcode == -1 ? 1 : 0, retcode == -1 ? errno : 0,
+ "could not create %s", rcs);
+ return (1);
+ }
+#endif /* No DEATH_SUPPORT */
+
+ fix_rcs_modes (rcs, file);
+ return (0);
+}
+
+/*
+ * Lock the rcs file ``file''
+ */
+static int
+lockrcsfile (file, repository, rev)
+ char *file;
+ char *repository;
+ char *rev;
+{
+ char rcs[PATH_MAX];
+
+ locate_rcs (file, repository, rcs);
+ if (lock_RCS (file, rcs, rev, repository) != 0)
+ return (1);
+ else
+ return (0);
+}
+
+/*
+ * Attempt to place a lock on the RCS file; returns 0 if it could and 1 if it
+ * couldn't. If the RCS file currently has a branch as the head, we must
+ * move the head back to the trunk before locking the file, and be sure to
+ * put the branch back as the head if there are any errors.
+ */
+static int
+lock_RCS (user, rcs, rev, repository)
+ char *user;
+ char *rcs;
+ char *rev;
+ char *repository;
+{
+ RCSNode *rcsfile;
+ char *branch = NULL;
+ int err = 0;
+
+ /*
+ * For a specified, numeric revision of the form "1" or "1.1", (or when
+ * no revision is specified ""), definitely move the branch to the trunk
+ * before locking the RCS file.
+ *
+ * The assumption is that if there is more than one revision on the trunk,
+ * the head points to the trunk, not a branch... and as such, it's not
+ * necessary to move the head in this case.
+ */
+ if (rev == NULL || (rev && isdigit (*rev) && numdots (rev) < 2))
+ {
+ if ((rcsfile = RCS_parsercsfile (rcs)) == NULL)
+ {
+ /* invalid rcs file? */
+ err = 1;
+ }
+ else
+ {
+ /* rcsfile is valid */
+ branch = xstrdup (rcsfile->branch);
+ freercsnode (&rcsfile);
+ if (branch != NULL)
+ {
+ if (RCS_setbranch (rcs, NULL) != 0)
+ {
+ error (0, 0, "cannot change branch to default for %s",
+ rcs);
+ if (branch)
+ free (branch);
+ return (1);
+ }
+ }
+ err = RCS_lock(rcs, NULL, 0);
+ }
+ }
+ else
+ {
+ (void) RCS_lock(rcs, rev, 1);
+ }
+
+ if (err == 0)
+ {
+ if (branch)
+ {
+ (void) strcpy (sbranch, branch);
+ free (branch);
+ }
+ else
+ sbranch[0] = '\0';
+ return (0);
+ }
+
+ /* try to restore the branch if we can on error */
+ if (branch != NULL)
+ fixbranch (user, repository, branch);
+
+ if (branch)
+ free (branch);
+ return (1);
+}
+
+/*
+ * Called when "add"ing files to the RCS respository, as it is necessary to
+ * preserve the file modes in the same fashion that RCS does. This would be
+ * automatic except that we are placing the RCS ,v file very far away from
+ * the user file, and I can't seem to convince RCS of the location of the
+ * user file. So we munge it here, after the ,v file has been successfully
+ * initialized with "rcs -i".
+ */
+static void
+fix_rcs_modes (rcs, user)
+ char *rcs;
+ char *user;
+{
+ struct stat sb;
+
+ if (stat (user, &sb) != -1)
+ (void) chmod (rcs, (int) sb.st_mode & ~0222);
+}
+
+/*
+ * free an UPDATE node's data (really nothing to do)
+ */
+void
+update_delproc (p)
+ Node *p;
+{
+ p->data = (char *) NULL;
+}
+
+/*
+ * Free the commit_info structure in p.
+ */
+static void
+ci_delproc (p)
+ Node *p;
+{
+ struct commit_info *ci;
+
+ ci = (struct commit_info *) p->data;
+ if (ci->rev)
+ free (ci->rev);
+ if (ci->tag)
+ free (ci->tag);
+ if (ci->options)
+ free (ci->options);
+ free (ci);
+}
+
+/*
+ * Free the commit_info structure in p.
+ */
+static void
+masterlist_delproc (p)
+ Node *p;
+{
+ struct master_lists *ml;
+
+ ml = (struct master_lists *) p->data;
+ dellist (&ml->ulist);
+ dellist (&ml->cilist);
+ free (ml);
+}
+
+/*
+ * Find an RCS file in the repository.
+ */
+static void
+locate_rcs (file, repository, rcs)
+ char *file;
+ char *repository;
+ char *rcs;
+{
+ (void) sprintf (rcs, "%s/%s%s", repository, file, RCSEXT);
+ if (!isreadable (rcs))
+ {
+ (void) sprintf (rcs, "%s/%s/%s%s", repository, CVSATTIC, file, RCSEXT);
+ if (!isreadable (rcs))
+ (void) sprintf (rcs, "%s/%s%s", repository, file, RCSEXT);
+ }
+}