diff options
author | eadler <eadler@FreeBSD.org> | 2013-06-15 20:29:07 +0000 |
---|---|---|
committer | eadler <eadler@FreeBSD.org> | 2013-06-15 20:29:07 +0000 |
commit | bf7c0f2705c32e44d3c3b62d60453a30dbbffe3f (patch) | |
tree | dca088b474d4fedf5e6d4ef16e823d7756d587bc /contrib/cvs/src/commit.c | |
parent | b95c459e182fd072e6dac884c7eed86a220534e7 (diff) | |
download | FreeBSD-src-bf7c0f2705c32e44d3c3b62d60453a30dbbffe3f.zip FreeBSD-src-bf7c0f2705c32e44d3c3b62d60453a30dbbffe3f.tar.gz |
Remove CVS from the base system.
Discussed with: many
Reviewed by: peter, zi
Approved by: core
Diffstat (limited to 'contrib/cvs/src/commit.c')
-rw-r--r-- | contrib/cvs/src/commit.c | 2433 |
1 files changed, 0 insertions, 2433 deletions
diff --git a/contrib/cvs/src/commit.c b/contrib/cvs/src/commit.c deleted file mode 100644 index b3ba47b..0000000 --- a/contrib/cvs/src/commit.c +++ /dev/null @@ -1,2433 +0,0 @@ -/* - * Copyright (C) 1986-2005 The Free Software Foundation, Inc. - * - * Portions Copyright (C) 1998-2005 Derek Price, Ximbiot <http://ximbiot.com>, - * and others. - * - * Portions Copyright (C) 1992, Brian Berliner and Jeff Polk - * Portions 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 source distribution. - * - * 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... - * - * $FreeBSD$ - */ - -#include <assert.h> -#include "cvs.h" -#include "getline.h" -#include "edit.h" -#include "fileattr.h" -#include "hardlink.h" - -static Dtype check_direntproc PROTO ((void *callerdat, const char *dir, - const char *repos, - const char *update_dir, - List *entries)); -static int check_fileproc PROTO ((void *callerdat, struct file_info *finfo)); -static int check_filesdoneproc PROTO ((void *callerdat, int err, - const char *repos, - const char *update_dir, - List *entries)); -static int checkaddfile PROTO((const char *file, const char *repository, - const char *tag, const char *options, - RCSNode **rcsnode)); -static Dtype commit_direntproc PROTO ((void *callerdat, const char *dir, - const char *repos, - const char *update_dir, - List *entries)); -static int commit_dirleaveproc PROTO ((void *callerdat, const char *dir, - int err, const char *update_dir, - List *entries)); -static int commit_fileproc PROTO ((void *callerdat, struct file_info *finfo)); -static int commit_filesdoneproc PROTO ((void *callerdat, int err, - const char *repository, - const char *update_dir, - List *entries)); -static int finaladd PROTO((struct file_info *finfo, char *revision, char *tag, - char *options)); -static int findmaxrev PROTO((Node * p, void *closure)); -static int lock_RCS PROTO((const char *user, RCSNode *rcs, const char *rev, - const char *repository)); -static int precommit_list_proc PROTO((Node * p, void *closure)); -static int precommit_proc PROTO((const char *repository, const char *filter)); -static int remove_file PROTO ((struct file_info *finfo, char *tag, - char *message)); -static void fixaddfile PROTO((const char *rcs)); -static void fixbranch PROTO((RCSNode *, char *branch)); -static void unlockrcs PROTO((RCSNode *rcs)); -static void ci_delproc PROTO((Node *p)); -static void masterlist_delproc PROTO((Node *p)); - -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 aflag; -static char *saved_tag; -static char *write_dirtag; -static int write_dirnonbranch; -static char *logfile; -static List *mulist; -static List *saved_ulist; -static char *saved_message; -static time_t last_register_time; - -static const char *const commit_usage[] = -{ - "Usage: %s %s [-Rlf] [-m msg | -F logfile] [-r rev] files...\n", - " -R Process directories recursively.\n", - " -l Local directory only (not recursive).\n", - " -f Force the file to be committed; disables recursion.\n", - " -F logfile Read the log message from file.\n", - " -m msg Log message.\n", - " -r rev Commit to this branch or trunk revision.\n", - "(Specify the --help global option for a list of other help options)\n", - NULL -}; - -#ifdef CLIENT_SUPPORT -/* Identify a file which needs "? foo" or a Questionable request. */ -struct question { - /* The two fields for the Directory request. */ - char *dir; - char *repos; - - /* The file name. */ - char *file; - - struct question *next; -}; - -struct find_data { - List *ulist; - int argc; - char **argv; - - /* This is used from dirent to filesdone time, for each directory, - to make a list of files we have already seen. */ - List *ignlist; - - /* Linked list of files which need "? foo" or a Questionable request. */ - struct question *questionables; - - /* Only good within functions called from the filesdoneproc. Stores - the repository (pointer into storage managed by the recursion - processor. */ - const char *repository; - - /* Non-zero if we should force the commit. This is enabled by - either -f or -r options, unlike force_ci which is just -f. */ - int force; -}; - - - -static Dtype find_dirent_proc PROTO ((void *callerdat, const char *dir, - const char *repository, - const char *update_dir, - List *entries)); - -static Dtype -find_dirent_proc (callerdat, dir, repository, update_dir, entries) - void *callerdat; - const char *dir; - const char *repository; - const char *update_dir; - List *entries; -{ - struct find_data *find_data = (struct find_data *)callerdat; - - /* This check seems to slowly be creeping throughout CVS (update - and send_dirent_proc by CVS 1.5, diff in 31 Oct 1995. My guess - is that it (or some variant thereof) should go in all the - dirent procs. Unless someone has some better idea... */ - if (!isdir (dir)) - return R_SKIP_ALL; - - /* initialize the ignore list for this directory */ - find_data->ignlist = getlist (); - - /* Print the same warm fuzzy as in check_direntproc, since that - code will never be run during client/server operation and we - want the messages to match. */ - if (!quiet) - error (0, 0, "Examining %s", update_dir); - - return R_PROCESS; -} - - - -/* Here as a static until we get around to fixing ignore_files to pass - it along as an argument. */ -static struct find_data *find_data_static; - - - -static void find_ignproc PROTO ((const char *, const char *)); - -static void -find_ignproc (file, dir) - const char *file; - const char *dir; -{ - struct question *p; - - p = (struct question *) xmalloc (sizeof (struct question)); - p->dir = xstrdup (dir); - p->repos = xstrdup (find_data_static->repository); - p->file = xstrdup (file); - p->next = find_data_static->questionables; - find_data_static->questionables = p; -} - - - -static int find_filesdoneproc PROTO ((void *callerdat, int err, - const char *repository, - const char *update_dir, - List *entries)); - -static int -find_filesdoneproc (callerdat, err, repository, update_dir, entries) - void *callerdat; - int err; - const char *repository; - const char *update_dir; - List *entries; -{ - struct find_data *find_data = (struct find_data *)callerdat; - find_data->repository = repository; - - /* if this directory has an ignore list, process it then free it */ - if (find_data->ignlist) - { - find_data_static = find_data; - ignore_files (find_data->ignlist, entries, update_dir, find_ignproc); - dellist (&find_data->ignlist); - } - - find_data->repository = NULL; - - return err; -} - - - -static int find_fileproc PROTO ((void *callerdat, struct file_info *finfo)); - -/* Machinery to find out what is modified, added, and removed. It is - possible this should be broken out into a new client_classify function; - merging it with classify_file is almost sure to be a mess, though, - because classify_file has all kinds of repository processing. */ -static int -find_fileproc (callerdat, finfo) - void *callerdat; - struct file_info *finfo; -{ - Vers_TS *vers; - enum classify_type status; - Node *node; - struct find_data *args = (struct find_data *)callerdat; - struct logfile_info *data; - struct file_info xfinfo; - - /* if this directory has an ignore list, add this file to it */ - if (args->ignlist) - { - Node *p; - - p = getnode (); - p->type = FILES; - p->key = xstrdup (finfo->file); - if (addnode (args->ignlist, p) != 0) - freenode (p); - } - - xfinfo = *finfo; - xfinfo.repository = NULL; - xfinfo.rcs = NULL; - - vers = Version_TS (&xfinfo, NULL, saved_tag, NULL, 0, 0); - if (vers->vn_user == NULL) - { - if (vers->ts_user == NULL) - error (0, 0, "nothing known about `%s'", finfo->fullname); - else - error (0, 0, "use `%s add' to create an entry for %s", - program_name, finfo->fullname); - freevers_ts (&vers); - return 1; - } - if (vers->vn_user[0] == '-') - { - if (vers->ts_user != NULL) - { - error (0, 0, - "`%s' should be removed and is still there (or is back" - " again)", finfo->fullname); - freevers_ts (&vers); - return 1; - } - /* else */ - status = T_REMOVED; - } - else if (strcmp (vers->vn_user, "0") == 0) - { - if (vers->ts_user == NULL) - { - /* This happens when one has `cvs add'ed a file, but it no - longer exists in the working directory at commit time. - FIXME: What classify_file does in this case is print - "new-born %s has disappeared" and removes the entry. - We probably should do the same. */ - if (!really_quiet) - error (0, 0, "warning: new-born %s has disappeared", - finfo->fullname); - status = T_REMOVE_ENTRY; - } - else - status = T_ADDED; - } - else if (vers->ts_user == NULL) - { - /* FIXME: What classify_file does in this case is print - "%s was lost". We probably should do the same. */ - freevers_ts (&vers); - return 0; - } - else if (vers->ts_rcs != NULL - && (args->force || strcmp (vers->ts_user, vers->ts_rcs) != 0)) - /* If we are forcing commits, pretend that the file is - modified. */ - status = T_MODIFIED; - else - { - /* This covers unmodified files, as well as a variety of other - cases. FIXME: we probably should be printing a message and - returning 1 for many of those cases (but I'm not sure - exactly which ones). */ - freevers_ts (&vers); - return 0; - } - - node = getnode (); - node->key = xstrdup (finfo->fullname); - - data = (struct logfile_info *) xmalloc (sizeof (struct logfile_info)); - data->type = status; - data->tag = xstrdup (vers->tag); - data->rev_old = data->rev_new = NULL; - - node->type = UPDATE; - node->delproc = update_delproc; - node->data = data; - (void)addnode (args->ulist, node); - - ++args->argc; - - freevers_ts (&vers); - return 0; -} - - - -static int copy_ulist PROTO ((Node *, void *)); - -static int -copy_ulist (node, data) - Node *node; - void *data; -{ - struct find_data *args = (struct find_data *)data; - args->argv[args->argc++] = node->key; - return 0; -} -#endif /* CLIENT_SUPPORT */ - -#ifdef SERVER_SUPPORT -# define COMMIT_OPTIONS "+nlRm:fF:r:" -#else /* !SERVER_SUPPORT */ -# define COMMIT_OPTIONS "+lRm:fF:r:" -#endif /* SERVER_SUPPORT */ -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. - */ - /* FIXME: Shouldn't this check be much more closely related to the - readonly user stuff (CVSROOT/readers, &c). That is, why should - root be able to "cvs init", "cvs import", &c, but not "cvs ci"? */ - /* Who we are on the client side doesn't affect logging. */ - if (geteuid () == (uid_t) 0 && !current_parsed_root->isremote) - { - struct passwd *pw; - - if ((pw = (struct passwd *) getpwnam (getcaller ())) == NULL) - error (1, 0, - "your apparent username (%s) is unknown to this system", - getcaller ()); - if (pw->pw_uid == (uid_t) 0) - error (1, 0, "'root' is not allowed to commit files"); - } -#endif /* CVS_BADROOT */ - - optind = 0; - while ((c = getopt (argc, argv, COMMIT_OPTIONS)) != -1) - { - switch (c) - { -#ifdef SERVER_SUPPORT - case 'n': - /* Silently ignore -n for compatibility with old - * clients. - */ - if (!server_active) error(0, 0, "the `-n' option is obsolete"); - break; -#endif /* SERVER_SUPPORT */ - case 'm': -#ifdef FORCE_USE_EDITOR - use_editor = 1; -#else - use_editor = 0; -#endif - if (saved_message) - { - free (saved_message); - saved_message = NULL; - } - - saved_message = xstrdup(optarg); - break; - case 'r': - if (saved_tag) - free (saved_tag); - saved_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 = 1; -#else - use_editor = 0; -#endif - logfile = optarg; - break; - case '?': - default: - usage (commit_usage); - break; - } - } - argc -= optind; - argv += optind; - - /* numeric specified revision means we ignore sticky tags... */ - if (saved_tag && isdigit ((unsigned char) *saved_tag)) - { - char *p = saved_tag + strlen (saved_tag); - aflag = 1; - /* strip trailing dots and leading zeros */ - while (*--p == '.') ; - p[1] = '\0'; - while (saved_tag[0] == '0' && isdigit ((unsigned char) saved_tag[1])) - ++saved_tag; - } - - /* some checks related to the "-F logfile" option */ - if (logfile) - { - size_t size = 0, len; - - if (saved_message) - error (1, 0, "cannot specify both a message and a log file"); - - get_file (logfile, logfile, "r", &saved_message, &size, &len); - } - -#ifdef CLIENT_SUPPORT - if (current_parsed_root->isremote) - { - struct find_data find_args; - - ign_setup (); - - find_args.ulist = getlist (); - find_args.argc = 0; - find_args.questionables = NULL; - find_args.ignlist = NULL; - find_args.repository = NULL; - - /* It is possible that only a numeric tag should set this. - I haven't really thought about it much. - Anyway, I suspect that setting it unnecessarily only causes - a little unneeded network traffic. */ - find_args.force = force_ci || saved_tag != NULL; - - err = start_recursion (find_fileproc, find_filesdoneproc, - find_dirent_proc, (DIRLEAVEPROC) NULL, - (void *)&find_args, - argc, argv, local, W_LOCAL, 0, CVS_LOCK_NONE, - (char *) NULL, 0, (char *) NULL); - if (err) - error (1, 0, "correct above errors first!"); - - if (find_args.argc == 0) - { - /* Nothing to commit. Exit now without contacting the - server (note that this means that we won't print "? - foo" for files which merit it, because we don't know - what is in the CVSROOT/cvsignore file). */ - dellist (&find_args.ulist); - return 0; - } - - /* Now we keep track of which files we actually are going to - operate on, and only work with those files in the future. - This saves time--we don't want to search the file system - of the working directory twice. */ - if (size_overflow_p (xtimes (find_args.argc, sizeof (char **)))) - { - find_args.argc = 0; - return 0; - } - find_args.argv = xmalloc (xtimes (find_args.argc, sizeof (char **))); - find_args.argc = 0; - walklist (find_args.ulist, copy_ulist, &find_args); - - /* Do this before calling do_editor; don't ask for a log - message if we can't talk to the server. But do it after we - have made the checks that we can locally (to more quickly - catch syntax errors, the case where no files are modified, - added or removed, etc.). - - On the other hand, calling start_server before do_editor - means that we chew up server resources the whole time that - the user has the editor open (hours or days if the user - forgets about it), which seems dubious. */ - start_server (); - - /* - * We do this once, not once for each directory as in normal CVS. - * The protocol is designed this way. This is a feature. - */ - if (use_editor) - do_editor (".", &saved_message, (char *)NULL, find_args.ulist); - - /* We always send some sort of message, even if empty. */ - option_with_arg ("-m", saved_message ? saved_message : ""); - - /* OK, now process all the questionable files we have been saving - up. */ - { - struct question *p; - struct question *q; - - p = find_args.questionables; - while (p != NULL) - { - if (ign_inhibit_server || !supported_request ("Questionable")) - { - cvs_output ("? ", 2); - if (p->dir[0] != '\0') - { - cvs_output (p->dir, 0); - cvs_output ("/", 1); - } - cvs_output (p->file, 0); - cvs_output ("\n", 1); - } - else - { - send_to_server ("Directory ", 0); - send_to_server (p->dir[0] == '\0' ? "." : p->dir, 0); - send_to_server ("\012", 1); - send_to_server (p->repos, 0); - send_to_server ("\012", 1); - - send_to_server ("Questionable ", 0); - send_to_server (p->file, 0); - send_to_server ("\012", 1); - } - free (p->dir); - free (p->repos); - free (p->file); - q = p->next; - free (p); - p = q; - } - } - - if (local) - send_arg("-l"); - if (force_ci) - send_arg("-f"); - option_with_arg ("-r", saved_tag); - send_arg ("--"); - - /* FIXME: This whole find_args.force/SEND_FORCE business is a - kludge. It would seem to be a server bug that we have to - say that files are modified when they are not. This makes - "cvs commit -r 2" across a whole bunch of files a very slow - operation (and it isn't documented in cvsclient.texi). I - haven't looked at the server code carefully enough to be - _sure_ why this is needed, but if it is because the "ci" - program, which we used to call, wanted the file to exist, - then it would be relatively simple to fix in the server. */ - send_files (find_args.argc, find_args.argv, local, 0, - find_args.force ? SEND_FORCE : 0); - - /* Sending only the names of the files which were modified, added, - or removed means that the server will only do an up-to-date - check on those files. This is different from local CVS and - previous versions of client/server CVS, but it probably is a Good - Thing, or at least Not Such A Bad Thing. */ - send_file_names (find_args.argc, find_args.argv, 0); - free (find_args.argv); - dellist (&find_args.ulist); - - send_to_server ("ci\012", 0); - err = get_responses_and_close (); - if (err != 0 && use_editor && saved_message != NULL) - { - /* If there was an error, don't nuke the user's carefully - constructed prose. This is something of a kludge; a better - solution is probably more along the lines of #150 in TODO - (doing a second up-to-date check before accepting the - log message has also been suggested, but that seems kind of - iffy because the real up-to-date check could still fail, - another error could occur, &c. Also, a second check would - slow things down). */ - - char *fname; - FILE *fp; - - fp = cvs_temp_file (&fname); - if (fp == NULL) - error (1, 0, "cannot create temporary file %s", - fname ? fname : "(null)"); - if (fwrite (saved_message, 1, strlen (saved_message), fp) - != strlen (saved_message)) - error (1, errno, "cannot write temporary file %s", fname); - if (fclose (fp) < 0) - error (0, errno, "cannot close temporary file %s", fname); - error (0, 0, "saving log message in %s", fname); - free (fname); - } - return err; - } -#endif - - if (saved_tag != NULL) - tag_check_valid (saved_tag, argc, argv, local, aflag, ""); - - /* XXX - this is not the perfect check for this */ - if (argc <= 0) - write_dirtag = saved_tag; - - wrap_setup (); - - lock_tree_for_write (argc, argv, local, W_LOCAL, aflag); - - /* - * Set up the master update list and hard link list - */ - mulist = getlist (); - -#ifdef PRESERVE_PERMISSIONS_SUPPORT - if (preserve_perms) - { - hardlist = getlist (); - - /* - * We need to save the working directory so that - * check_fileproc can construct a full pathname for each file. - */ - working_dir = xgetwd(); - } -#endif - - /* - * Run the recursion processor to verify the files are all up-to-date - */ - err = start_recursion (check_fileproc, check_filesdoneproc, - check_direntproc, (DIRLEAVEPROC) NULL, NULL, argc, - argv, local, W_LOCAL, aflag, CVS_LOCK_NONE, - (char *) NULL, 1, (char *) NULL); - if (err) - { - Lock_Cleanup (); - error (1, 0, "correct above errors first!"); - } - - /* - * Run the recursion processor to commit the files - */ - write_dirnonbranch = 0; - if (noexec == 0) - err = start_recursion (commit_fileproc, commit_filesdoneproc, - commit_direntproc, commit_dirleaveproc, NULL, - argc, argv, local, W_LOCAL, aflag, CVS_LOCK_NONE, - (char *) NULL, 1, (char *) NULL); - - /* - * Unlock all the dirs and clean up - */ - Lock_Cleanup (); - dellist (&mulist); - - if (server_active) - return err; - - /* see if we need to sleep before returning to avoid time-stamp races */ - if (last_register_time) - { - sleep_past (last_register_time); - } - - return err; -} - - - -/* This routine determines the status of a given file and retrieves - the version information that is associated with that file. */ - -static -Ctype -classify_file_internal (finfo, vers) - struct file_info *finfo; - Vers_TS **vers; -{ - int save_noexec, save_quiet, save_really_quiet; - Ctype status; - - /* FIXME: Do we need to save quiet as well as really_quiet? Last - time I glanced at Classify_File I only saw it looking at really_quiet - not quiet. */ - save_noexec = noexec; - save_quiet = quiet; - save_really_quiet = really_quiet; - noexec = quiet = really_quiet = 1; - - /* handle specified numeric revision specially */ - if (saved_tag && isdigit ((unsigned char) *saved_tag)) - { - /* If the tag is for the trunk, make sure we're at the head */ - if (numdots (saved_tag) < 2) - { - status = Classify_File (finfo, (char *) NULL, (char *) NULL, - (char *) NULL, 1, aflag, vers, 0); - if (status == T_UPTODATE || status == T_MODIFIED || - status == T_ADDED) - { - Ctype xstatus; - - freevers_ts (vers); - xstatus = Classify_File (finfo, saved_tag, (char *) NULL, - (char *) NULL, 1, aflag, vers, 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 (saved_tag); - if ((numdots (xtag) & 1) != 0) - { - cp = strrchr (xtag, '.'); - *cp = '\0'; - } - status = Classify_File (finfo, xtag, (char *) NULL, - (char *) NULL, 1, aflag, vers, 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 (finfo, xtag, (char *) NULL, - (char *) NULL, 1, aflag, vers, 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 (saved_tag); - free (xtag); - } - } - else - status = Classify_File (finfo, saved_tag, (char *) NULL, (char *) NULL, - 1, 0, vers, 0); - noexec = save_noexec; - quiet = save_quiet; - really_quiet = save_really_quiet; - - return status; -} - - - -/* - * Check to see if a file is ok to commit and make sure all files are - * up-to-date - */ -/* ARGSUSED */ -static int -check_fileproc (callerdat, finfo) - void *callerdat; - struct file_info *finfo; -{ - Ctype status; - const char *xdir; - Node *p; - List *ulist, *cilist; - Vers_TS *vers; - struct commit_info *ci; - struct logfile_info *li; - - size_t cvsroot_len = strlen (current_parsed_root->directory); - - if (!finfo->repository) - { - error (0, 0, "nothing known about `%s'", finfo->fullname); - return 1; - } - - if (strncmp (finfo->repository, current_parsed_root->directory, - cvsroot_len) == 0 - && ISDIRSEP (finfo->repository[cvsroot_len]) - && strncmp (finfo->repository + cvsroot_len + 1, - CVSROOTADM, - sizeof (CVSROOTADM) - 1) == 0 - && ISDIRSEP (finfo->repository[cvsroot_len + sizeof (CVSROOTADM)]) - && strcmp (finfo->repository + cvsroot_len + sizeof (CVSROOTADM) + 1, - CVSNULLREPOS) == 0 - ) - error (1, 0, "cannot check in to %s", finfo->repository); - - status = classify_file_internal (finfo, &vers); - - /* - * 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: - case T_PATCH: - case T_NEEDS_MERGE: - case T_REMOVE_ENTRY: - error (0, 0, "Up-to-date check failed for `%s'", finfo->fullname); - freevers_ts (&vers); - return 1; - case T_CONFLICT: - 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, file must not exist and its entry - * can't have a numeric sticky tag. - * - if status is T_ADDED, rcs file must not exist unless on - * a branch or head is dead - * - 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 (!saved_tag || !isdigit ((unsigned char) *saved_tag)) - { - if (vers->date) - { - error (0, 0, - "cannot commit with sticky date for file `%s'", - finfo->fullname); - freevers_ts (&vers); - return 1; - } - if (status == T_MODIFIED && vers->tag && - !RCS_isbranch (finfo->rcs, vers->tag)) - { - error (0, 0, - "sticky tag `%s' for file `%s' is not a branch", - vers->tag, finfo->fullname); - freevers_ts (&vers); - return 1; - } - } - if (status == T_CONFLICT && !force_ci) - { - error (0, 0, - "file `%s' had a conflict and has not been modified", - finfo->fullname); - freevers_ts (&vers); - return 1; - } - if (status == T_MODIFIED && !force_ci && file_has_markers (finfo)) - { - /* Make this a warning, not an error, because we have - no way of knowing whether the "conflict indicators" - are really from a conflict or whether they are part - of the document itself (cvs.texinfo and sanity.sh in - CVS itself, for example, tend to want to have strings - like ">>>>>>>" at the start of a line). Making people - kludge this the way they need to kludge keyword - expansion seems undesirable. And it is worse than - keyword expansion, because there is no -ko - analogue. */ - error (0, 0, - "\ -warning: file `%s' seems to still contain conflict indicators", - finfo->fullname); - } - - if (status == T_REMOVED) - { - if (vers->ts_user != NULL) - { - error (0, 0, - "`%s' should be removed and is still there (or is" - " back again)", finfo->fullname); - freevers_ts (&vers); - return 1; - } - - if (vers->tag && isdigit ((unsigned char) *vers->tag)) - { - /* Remove also tries to forbid this, but we should check - here. I'm only _sure_ about somewhat obscure cases - (hacking the Entries file, using an old version of - CVS for the remove and a new one for the commit), but - there might be other cases. */ - error (0, 0, - "cannot remove file `%s' which has a numeric sticky" - " tag of `%s'", finfo->fullname, vers->tag); - freevers_ts (&vers); - return 1; - } - } - if (status == T_ADDED) - { - if (vers->tag == NULL) - { - if (finfo->rcs != NULL && - !RCS_isdead (finfo->rcs, finfo->rcs->head)) - { - error (0, 0, - "cannot add file `%s' when RCS file `%s' already exists", - finfo->fullname, finfo->rcs->path); - freevers_ts (&vers); - return 1; - } - } - else if (isdigit ((unsigned char) *vers->tag) && - numdots (vers->tag) > 1) - { - error (0, 0, - "cannot add file `%s' with revision `%s'; must be on trunk", - finfo->fullname, vers->tag); - freevers_ts (&vers); - return 1; - } - } - - /* done with consistency checks; now, to get on with the commit */ - if (finfo->update_dir[0] == '\0') - xdir = "."; - else - xdir = finfo->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 = ml; - p->delproc = masterlist_delproc; - (void) addnode (mulist, p); - } - - /* first do ulist, then cilist */ - p = getnode (); - p->key = xstrdup (finfo->file); - p->type = UPDATE; - p->delproc = update_delproc; - li = ((struct logfile_info *) - xmalloc (sizeof (struct logfile_info))); - li->type = status; - li->tag = xstrdup (vers->tag); - li->rev_old = xstrdup (vers->vn_rcs); - li->rev_new = NULL; - p->data = li; - (void) addnode (ulist, p); - - p = getnode (); - p->key = xstrdup (finfo->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 ((unsigned char) *vers->tag)) - ci->rev = xstrdup (vers->tag); - else - ci->rev = RCS_whatbranch (finfo->rcs, vers->tag); - else - ci->rev = (char *) NULL; - ci->tag = xstrdup (vers->tag); - ci->options = xstrdup(vers->options); - p->data = ci; - (void) addnode (cilist, p); - -#ifdef PRESERVE_PERMISSIONS_SUPPORT - if (preserve_perms) - { - /* Add this file to hardlist, indexed on its inode. When - we are done, we can find out what files are hardlinked - to a given file by looking up its inode in hardlist. */ - char *fullpath; - Node *linkp; - struct hardlink_info *hlinfo; - - /* Get the full pathname of the current file. */ - fullpath = xmalloc (strlen(working_dir) + - strlen(finfo->fullname) + 2); - sprintf (fullpath, "%s/%s", working_dir, finfo->fullname); - - /* To permit following links in subdirectories, files - are keyed on finfo->fullname, not on finfo->name. */ - linkp = lookup_file_by_inode (fullpath); - - /* If linkp is NULL, the file doesn't exist... maybe - we're doing a remove operation? */ - if (linkp != NULL) - { - /* Create a new hardlink_info node, which will record - the current file's status and the links listed in its - `hardlinks' delta field. We will append this - hardlink_info node to the appropriate hardlist entry. */ - hlinfo = (struct hardlink_info *) - xmalloc (sizeof (struct hardlink_info)); - hlinfo->status = status; - linkp->data = hlinfo; - } - } -#endif - - break; - case T_UNKNOWN: - error (0, 0, "nothing known about `%s'", finfo->fullname); - 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; -} - - - -/* - * By default, return the code that tells do_recursion to examine all - * directories - */ -/* ARGSUSED */ -static Dtype -check_direntproc (callerdat, dir, repos, update_dir, entries) - void *callerdat; - const char *dir; - const char *repos; - const char *update_dir; - List *entries; -{ - if (!isdir (dir)) - return R_SKIP_ALL; - - 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; -{ - struct logfile_info *li = p->data; - if (li->type == T_ADDED - || li->type == T_MODIFIED - || li->type == T_REMOVED) - { - run_arg (p->key); - } - return 0; -} - - - -/* - * Callback proc for pre-commit checking - */ -static int -precommit_proc (repository, filter) - const char *repository; - const 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 ((unsigned char) *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 (filter); - run_arg (repository); - (void) walklist (saved_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 (callerdat, err, repos, update_dir, entries) - void *callerdat; - int err; - const char *repos; - const char *update_dir; - List *entries; -{ - int n; - Node *p; - - /* find the update list for this dir */ - p = findnode (mulist, update_dir); - if (p != NULL) - saved_ulist = ((struct master_lists *) p->data)->ulist; - else - saved_ulist = (List *) NULL; - - /* skip the checks if there's nothing to do */ - if (saved_ulist == NULL || saved_ulist->list->next == saved_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; - -/* ARGSUSED */ -static int -commit_fileproc (callerdat, finfo) - void *callerdat; - struct file_info *finfo; -{ - Node *p; - int err = 0; - List *ulist, *cilist; - struct commit_info *ci; - - /* Keep track of whether write_dirtag is a branch tag. - Note that if it is a branch tag in some files and a nonbranch tag - in others, treat it as a nonbranch tag. It is possible that case - should elicit a warning or an error. */ - if (write_dirtag != NULL - && finfo->rcs != NULL) - { - char *rev = RCS_getversion (finfo->rcs, write_dirtag, NULL, 1, NULL); - if (rev != NULL - && !RCS_nodeisbranch (finfo->rcs, write_dirtag)) - write_dirnonbranch = 1; - if (rev != NULL) - free (rev); - } - - if (finfo->update_dir[0] == '\0') - p = findnode (mulist, "."); - else - p = findnode (mulist, finfo->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 (!got_message) - { - got_message = 1; - if (!server_active && use_editor) - do_editor (finfo->update_dir, &saved_message, - finfo->repository, ulist); - do_verify (&saved_message, finfo->repository); - } - - p = findnode (cilist, finfo->file); - if (p == NULL) - return 0; - - ci = p->data; - if (ci->status == T_MODIFIED) - { - if (finfo->rcs == NULL) - error (1, 0, "internal error: no parsed RCS file"); - if (lock_RCS (finfo->file, finfo->rcs, ci->rev, - finfo->repository) != 0) - { - unlockrcs (finfo->rcs); - err = 1; - goto out; - } - } - else if (ci->status == T_ADDED) - { - if (checkaddfile (finfo->file, finfo->repository, ci->tag, ci->options, - &finfo->rcs) != 0) - { - if (finfo->rcs != NULL) - fixaddfile (finfo->rcs->path); - err = 1; - goto out; - } - - /* 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 - - /* If numeric, it is on the trunk; check_fileproc enforced - this. */ - && !isdigit ((unsigned char) ci->tag[0])) - { - if (finfo->rcs == NULL) - error (1, 0, "internal error: no parsed RCS file"); - if (ci->rev) - free (ci->rev); - ci->rev = RCS_whatbranch (finfo->rcs, ci->tag); - err = Checkin ('A', finfo, ci->rev, - ci->tag, ci->options, saved_message); - if (err != 0) - { - unlockrcs (finfo->rcs); - fixbranch (finfo->rcs, sbranch); - } - - (void) time (&last_register_time); - - ci->status = T_UPTODATE; - } - } - - /* - * 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 (finfo->entries, findmaxrev, NULL); - if (finfo->rcs->head) { - /* resurrecting: include dead revision */ - int thisrev = atoi (finfo->rcs->head); - if (thisrev > maxrev) - maxrev = thisrev; - } - 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 (finfo, ci->rev ? ci->rev : xrev, ci->tag, ci->options); - if (xrev) - free (xrev); - } - else if (ci->status == T_MODIFIED) - { - err = Checkin ('M', finfo, ci->rev, ci->tag, - ci->options, saved_message); - - (void) time (&last_register_time); - - if (err != 0) - { - unlockrcs (finfo->rcs); - fixbranch (finfo->rcs, sbranch); - } - } - else if (ci->status == T_REMOVED) - { - err = remove_file (finfo, ci->tag, saved_message); -#ifdef SERVER_SUPPORT - if (server_active) { - server_scratch_entry_only (); - server_updated (finfo, - NULL, - - /* Doesn't matter, it won't get checked. */ - SERVER_UPDATED, - - (mode_t) -1, - (unsigned char *) NULL, - (struct buffer *) NULL); - } -#endif - } - - /* Clearly this is right for T_MODIFIED. I haven't thought so much - about T_ADDED or T_REMOVED. */ - notify_do ('C', finfo->file, getcaller (), NULL, NULL, finfo->repository); - -out: - if (err != 0) - { - /* on failure, remove the file from ulist */ - p = findnode (ulist, finfo->file); - if (p) - delnode (p); - } - else - { - /* On success, retrieve the new version number of the file and - copy it into the log information (see logmsg.c - (logfile_write) for more details). We should only update - the version number for files that have been added or - modified but not removed since classify_file_internal - will return the version number of a file even after it has - been removed from the archive, which is not the behavior we - want for our commitlog messages; we want the old version - number and then "NONE." */ - - if (ci->status != T_REMOVED) - { - p = findnode (ulist, finfo->file); - if (p) - { - Vers_TS *vers; - struct logfile_info *li; - - (void) classify_file_internal (finfo, &vers); - li = p->data; - li->rev_new = xstrdup (vers->vn_rcs); - freevers_ts (&vers); - } - } - } - if (SIG_inCrSect ()) - SIG_endCrSect (); - - return err; -} - - - -/* - * Log the commit and clean up the update list - */ -/* ARGSUSED */ -static int -commit_filesdoneproc (callerdat, err, repository, update_dir, entries) - void *callerdat; - int err; - const char *repository; - const char *update_dir; - List *entries; -{ - Node *p; - List *ulist; - - assert (repository); - - p = findnode (mulist, update_dir); - if (p == NULL) - return err; - - ulist = ((struct master_lists *) p->data)->ulist; - - got_message = 0; - - Update_Logfile (repository, saved_message, (FILE *) 0, ulist); - - /* Build the administrative files if necessary. */ - { - const char *p; - - if (strncmp (current_parsed_root->directory, repository, - strlen (current_parsed_root->directory)) != 0) - error (0, 0, - "internal error: repository (%s) doesn't begin with root (%s)", - repository, current_parsed_root->directory); - p = repository + strlen (current_parsed_root->directory); - if (*p == '/') - ++p; - if (strcmp ("CVSROOT", p) == 0 - /* Check for subdirectories because people may want to create - subdirectories and list files therein in checkoutlist. */ - || strncmp ("CVSROOT/", p, strlen ("CVSROOT/")) == 0 - ) - { - /* "Database" might a little bit grandiose and/or vague, - but "checked-out copies of administrative files, unless - in the case of modules and you are using ndbm in which - case modules.{pag,dir,db}" is verbose and excessively - focused on how the database is implemented. */ - - /* mkmodules requires the absolute name of the CVSROOT directory. - Remove anything after the `CVSROOT' component -- this is - necessary when committing in a subdirectory of CVSROOT. */ - char *admin_dir = xstrdup (repository); - int cvsrootlen = strlen ("CVSROOT"); - assert (admin_dir[p - repository + cvsrootlen] == '\0' - || admin_dir[p - repository + cvsrootlen] == '/'); - admin_dir[p - repository + cvsrootlen] = '\0'; - - cvs_output (program_name, 0); - cvs_output (" ", 1); - cvs_output (cvs_cmd_name, 0); - cvs_output (": Rebuilding administrative file database\n", 0); - mkmodules (admin_dir); - free (admin_dir); - } - } - - return err; -} - - - -/* - * Get the log message for a dir - */ -/* ARGSUSED */ -static Dtype -commit_direntproc (callerdat, dir, repos, update_dir, entries) - void *callerdat; - const char *dir; - const char *repos; - const char *update_dir; - List *entries; -{ - Node *p; - List *ulist; - char *real_repos; - - if (!isdir (dir)) - return R_SKIP_ALL; - - /* 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; - - /* get commit message */ - real_repos = Name_Repository (dir, update_dir); - got_message = 1; - if (!server_active && use_editor) - do_editor (update_dir, &saved_message, real_repos, ulist); - do_verify (&saved_message, real_repos); - free (real_repos); - return R_PROCESS; -} - - - -/* - * Process the post-commit proc if necessary - */ -/* ARGSUSED */ -static int -commit_dirleaveproc (callerdat, dir, err, update_dir, entries) - void *callerdat; - const char *dir; - int err; - const char *update_dir; - List *entries; -{ - /* update the per-directory tag info */ - /* FIXME? Why? The "commit examples" node of cvs.texinfo briefly - mentions commit -r being sticky, but apparently in the context of - this being a confusing feature! */ - if (err == 0 && write_dirtag != NULL) - { - char *repos = Name_Repository (NULL, update_dir); - WriteTag (NULL, write_dirtag, NULL, write_dirnonbranch, - update_dir, repos); - free (repos); - } - - return err; -} - - - -/* - * find the maximum major rev number in an entries file - */ -static int -findmaxrev (p, closure) - Node *p; - void *closure; -{ - int thisrev; - Entnode *entdata = p->data; - - if (entdata->type != ENT_FILE) - return 0; - thisrev = atoi (entdata->version); - 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. - - Return value is 0 on success, or >0 on error (in which case we have - printed an error message). */ -static int -remove_file (finfo, tag, message) - struct file_info *finfo; - char *tag; - char *message; -{ - int retcode; - - int branch; - int lockflag; - char *corev; - char *rev; - char *prev_rev; - char *old_path; - - corev = NULL; - rev = NULL; - prev_rev = NULL; - - retcode = 0; - - if (finfo->rcs == NULL) - error (1, 0, "internal error: no parsed RCS file"); - - branch = 0; - if (tag && !(branch = RCS_nodeisbranch (finfo->rcs, tag))) - { - /* a symbolic tag is specified; just remove the tag from the file */ - if ((retcode = RCS_deltag (finfo->rcs, tag)) != 0) - { - if (!quiet) - error (0, retcode == -1 ? errno : 0, - "failed to remove tag `%s' from `%s'", tag, - finfo->fullname); - return 1; - } - RCS_rewrite (finfo->rcs, NULL, NULL); - Scratch_Entry (finfo->entries, finfo->file); - return 0; - } - - /* 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. */ - cvs_output ("Removing ", 0); - cvs_output (finfo->fullname, 0); - cvs_output (";\n", 0); - - rev = NULL; - lockflag = 1; - if (branch) - { - char *branchname; - - rev = RCS_whatbranch (finfo->rcs, tag); - if (rev == NULL) - { - error (0, 0, "cannot find branch \"%s\".", tag); - return 1; - } - - branchname = RCS_getbranch (finfo->rcs, rev, 1); - if (branchname == NULL) - { - /* no revision exists on this branch. use the previous - revision but do not lock. */ - corev = RCS_gettag (finfo->rcs, tag, 1, (int *) NULL); - prev_rev = xstrdup (corev); - lockflag = 0; - } else - { - corev = xstrdup (rev); - prev_rev = xstrdup (branchname); - free (branchname); - } - - } else /* Not a branch */ - { - /* Get current head revision of file. */ - prev_rev = RCS_head (finfo->rcs); - } - - /* if removing without a tag or a branch, then make sure the default - branch is the trunk. */ - if (!tag && !branch) - { - if (RCS_setbranch (finfo->rcs, NULL) != 0) - { - error (0, 0, "cannot change branch to default for %s", - finfo->fullname); - return 1; - } - RCS_rewrite (finfo->rcs, NULL, NULL); - } - - /* check something out. Generally this is the head. If we have a - particular rev, then name it. */ - retcode = RCS_checkout (finfo->rcs, finfo->file, rev ? corev : NULL, - (char *) NULL, (char *) NULL, RUN_TTY, - (RCSCHECKOUTPROC) NULL, (void *) NULL); - if (retcode != 0) - { - error (0, 0, - "failed to check out `%s'", finfo->fullname); - return 1; - } - - /* Except when we are creating a branch, lock the revision so that - we can check in the new revision. */ - if (lockflag) - { - if (RCS_lock (finfo->rcs, rev ? corev : NULL, 1) == 0) - RCS_rewrite (finfo->rcs, NULL, NULL); - } - - if (corev != NULL) - free (corev); - - retcode = RCS_checkin (finfo->rcs, finfo->file, message, rev, 0, - RCS_FLAGS_DEAD | RCS_FLAGS_QUIET); - if (retcode != 0) - { - if (!quiet) - error (0, retcode == -1 ? errno : 0, - "failed to commit dead revision for `%s'", finfo->fullname); - if (prev_rev != NULL) - free (prev_rev); - return 1; - } - /* At this point, the file has been committed as removed. We should - probably tell the history file about it */ - corev = rev ? RCS_getbranch (finfo->rcs, rev, 1) : RCS_head (finfo->rcs); - history_write ('R', NULL, corev, finfo->file, finfo->repository); - free (corev); - - if (rev != NULL) - free (rev); - - old_path = xstrdup (finfo->rcs->path); - if (!branch) - RCS_setattic (finfo->rcs, 1); - - /* Print message that file was removed. */ - cvs_output (old_path, 0); - cvs_output (" <-- ", 0); - cvs_output (finfo->file, 0); - cvs_output ("\nnew revision: delete; previous revision: ", 0); - cvs_output (prev_rev, 0); - cvs_output ("\ndone\n", 0); - free(prev_rev); - - free (old_path); - - Scratch_Entry (finfo->entries, finfo->file); - return 0; -} - - - -/* - * Do the actual checkin for added files - */ -static int -finaladd (finfo, rev, tag, options) - struct file_info *finfo; - char *rev; - char *tag; - char *options; -{ - int ret; - - ret = Checkin ('A', finfo, rev, tag, options, saved_message); - if (ret == 0) - { - char *tmp = xmalloc (strlen (finfo->file) + sizeof (CVSADM) - + sizeof (CVSEXT_LOG) + 10); - (void) sprintf (tmp, "%s/%s%s", CVSADM, finfo->file, CVSEXT_LOG); - if (unlink_file (tmp) < 0 - && !existence_error (errno)) - error (0, errno, "cannot remove %s", tmp); - free (tmp); - } - else if (finfo->rcs != NULL) - fixaddfile (finfo->rcs->path); - - (void) time (&last_register_time); - - return ret; -} - - - -/* - * Unlock an rcs file - */ -static void -unlockrcs (rcs) - RCSNode *rcs; -{ - int retcode; - - if ((retcode = RCS_unlock (rcs, NULL, 1)) != 0) - error (retcode == -1 ? 1 : 0, retcode == -1 ? errno : 0, - "could not unlock %s", rcs->path); - else - RCS_rewrite (rcs, NULL, NULL); -} - - - -/* - * remove a partially added file. if we can parse it, leave it alone. - * - * FIXME: Every caller that calls this function can access finfo->rcs (the - * parsed RCSNode data), so we should be able to detect that the file needs - * to be removed without reparsing the file as we do below. - */ -static void -fixaddfile (rcs) - const char *rcs; -{ - RCSNode *rcsfile; - int save_really_quiet; - - save_really_quiet = really_quiet; - really_quiet = 1; - if ((rcsfile = RCS_parsercsfile (rcs)) == NULL) - { - if (unlink_file (rcs) < 0) - error (0, errno, "cannot remove %s", rcs); - } - else - freercsnode (&rcsfile); - really_quiet = save_really_quiet; -} - - - -/* - * put the branch back on an rcs file - */ -static void -fixbranch (rcs, branch) - RCSNode *rcs; - char *branch; -{ - int retcode; - - if (branch != NULL) - { - 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->path); - RCS_rewrite (rcs, NULL, NULL); - } -} - - - -/* - * 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. - * - * INPUTS - * file The name of the file in the workspace. - * repository The repository directory to expect to find FILE,v in. - * tag The name or rev num of the branch being added to, if any. - * options Any RCS keyword expansion options specified by the user. - * rcsnode A pointer to the pre-parsed RCSNode for this file, if the file - * exists in the repository. If this is NULL, assume the file - * does not yet exist. - * - * RETURNS - * 0 on success. - * 1 on errors, after printing any appropriate error messages. - * - * ERRORS - * This function will return an error when any of the following functions do: - * add_rcs_file - * RCS_setattic - * lock_RCS - * RCS_checkin - * RCS_parse (called to verify the newly created archive file) - * RCS_settag - */ - -static int -checkaddfile (file, repository, tag, options, rcsnode) - const char *file; - const char *repository; - const char *tag; - const char *options; - RCSNode **rcsnode; -{ - RCSNode *rcs; - char *fname; - int newfile = 0; /* Set to 1 if we created a new RCS archive. */ - int retval = 1; - int adding_on_branch; - - assert (rcsnode != NULL); - - /* Callers expect to be able to use either "" or NULL to mean the - default keyword expansion. */ - if (options != NULL && options[0] == '\0') - options = NULL; - if (options != NULL) - assert (options[0] == '-' && options[1] == 'k'); - - /* If numeric, it is on the trunk; check_fileproc enforced - this. */ - adding_on_branch = tag != NULL && !isdigit ((unsigned char) tag[0]); - - if (*rcsnode == NULL) - { - char *rcsname; - char *desc = NULL; - size_t descalloc = 0; - size_t desclen = 0; - const char *opt; - - if ( adding_on_branch ) - { - mode_t omask; - rcsname = xmalloc (strlen (repository) - + sizeof (CVSATTIC) - + strlen (file) - + sizeof (RCSEXT) - + 3); - (void) sprintf (rcsname, "%s/%s", repository, CVSATTIC); - omask = umask ( cvsumask ); - if (CVS_MKDIR (rcsname, 0777 ) != 0 && errno != EEXIST) - error (1, errno, "cannot make directory `%s'", rcsname); - (void) umask ( omask ); - (void) sprintf (rcsname, - "%s/%s/%s%s", - repository, - CVSATTIC, - file, - RCSEXT); - } - else - { - rcsname = xmalloc (strlen (repository) - + strlen (file) - + sizeof (RCSEXT) - + 2); - (void) sprintf (rcsname, - "%s/%s%s", - repository, - file, - RCSEXT); - } - - /* this is the first time we have ever seen this file; create - an RCS file. */ - fname = xmalloc (strlen (file) + sizeof (CVSADM) - + sizeof (CVSEXT_LOG) + 10); - (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)) - /* FIXME: Should be including update_dir in the appropriate - place here. */ - get_file (fname, fname, "r", &desc, &descalloc, &desclen); - free (fname); - - /* From reading the RCS 5.7 source, "rcs -i" adds a newline to the - end of the log message if the message is nonempty. - Do it. RCS also deletes certain whitespace, in cleanlogmsg, - which we don't try to do here. */ - if (desclen > 0) - { - expand_string (&desc, &descalloc, desclen + 1); - desc[desclen++] = '\012'; - } - - /* Set RCS keyword expansion options. */ - if (options != NULL) - opt = options + 2; - else - opt = NULL; - - /* This message is an artifact of the time when this - was implemented via "rcs -i". It should be revised at - some point (does the "initial revision" in the message from - RCS_checkin indicate that this is a new file? Or does the - "RCS file" message serve some function?). */ - cvs_output ("RCS file: ", 0); - cvs_output (rcsname, 0); - cvs_output ("\ndone\n", 0); - - if (add_rcs_file (NULL, rcsname, file, NULL, opt, - NULL, NULL, 0, NULL, - desc, desclen, NULL) != 0) - { - if (rcsname != NULL) - free (rcsname); - goto out; - } - rcs = RCS_parsercsfile (rcsname); - newfile = 1; - if (rcsname != NULL) - free (rcsname); - if (desc != NULL) - free (desc); - *rcsnode = rcs; - } - else - { - /* file has existed in the past. Prepare to resurrect. */ - char *rev; - char *oldexpand; - - rcs = *rcsnode; - - oldexpand = RCS_getexpand (rcs); - if ((oldexpand != NULL - && options != NULL - && strcmp (options + 2, oldexpand) != 0) - || (oldexpand == NULL && options != NULL)) - { - /* We tell the user about this, because it means that the - old revisions will no longer retrieve the way that they - used to. */ - error (0, 0, "changing keyword expansion mode to %s", options); - RCS_setexpand (rcs, options + 2); - } - - if (!adding_on_branch) - { - /* We are adding on the trunk, so move the file out of the - Attic. */ - if (!(rcs->flags & INATTIC)) - { - error (0, 0, "warning: expected %s to be in Attic", - rcs->path); - } - - /* Begin a critical section around the code that spans the - first commit on the trunk of a file that's already been - committed on a branch. */ - SIG_beginCrSect (); - - if (RCS_setattic (rcs, 0)) - { - goto out; - } - } - - rev = RCS_getversion (rcs, tag, NULL, 1, (int *) NULL); - /* and lock it */ - if (lock_RCS (file, rcs, rev, repository)) - { - error (0, 0, "cannot lock revision %s in `%s'.", - rev ? rev : tag ? tag : "HEAD", rcs->path); - if (rev != NULL) - free (rev); - goto out; - } - - if (rev != NULL) - free (rev); - } - - /* when adding a file for the first time, and using a tag, we need - to create a dead revision on the trunk. */ - if (adding_on_branch) - { - if (newfile) - { - char *tmp; - FILE *fp; - int retcode; - - /* move the new file out of the way. */ - fname = xmalloc (strlen (file) + sizeof (CVSADM) - + sizeof (CVSPREFIX) + 10); - (void) sprintf (fname, "%s/%s%s", CVSADM, CVSPREFIX, file); - rename_file (file, fname); - - /* Create empty FILE. Can't use copy_file with a DEVNULL - argument -- copy_file now ignores device files. */ - fp = fopen (file, "w"); - if (fp == NULL) - error (1, errno, "cannot open %s for writing", file); - if (fclose (fp) < 0) - error (0, errno, "cannot close %s", file); - - tmp = xmalloc (strlen (file) + strlen (tag) + 80); - /* commit a dead revision. */ - (void) sprintf (tmp, "file %s was initially added on branch %s.", - file, tag); - retcode = RCS_checkin (rcs, NULL, tmp, NULL, 0, - RCS_FLAGS_DEAD | RCS_FLAGS_QUIET); - free (tmp); - if (retcode != 0) - { - error (retcode == -1 ? 1 : 0, retcode == -1 ? errno : 0, - "could not create initial dead revision %s", rcs->path); - free (fname); - goto out; - } - - /* put the new file back where it was */ - rename_file (fname, file); - free (fname); - - /* double-check that the file was written correctly */ - freercsnode (&rcs); - rcs = RCS_parse (file, repository); - if (rcs == NULL) - { - error (0, 0, "could not read %s in %s", file, repository); - goto out; - } - *rcsnode = rcs; - - /* and lock it once again. */ - if (lock_RCS (file, rcs, NULL, repository)) - { - error (0, 0, "cannot lock initial revision in `%s'.", - rcs->path); - goto out; - } - } - - /* when adding with a tag, we need to stub a branch, if it - doesn't already exist. */ - if (!RCS_nodeisbranch (rcs, tag)) - { - /* branch does not exist. Stub it. */ - char *head; - char *magicrev; - int retcode; - time_t headtime = -1; - char *revnum, *tmp; - FILE *fp; - time_t t = -1; - struct tm *ct; - - fixbranch (rcs, sbranch); - - head = RCS_getversion (rcs, NULL, NULL, 0, (int *) NULL); - if (!head) - error (1, 0, "No head revision in archive file `%s'.", - rcs->path); - magicrev = RCS_magicrev (rcs, head); - - /* If this is not a new branch, then we will want a dead - version created before this one. */ - if (!newfile) - headtime = RCS_getrevtime (rcs, head, 0, 0); - - retcode = RCS_settag (rcs, tag, magicrev); - RCS_rewrite (rcs, NULL, NULL); - - free (head); - free (magicrev); - - if (retcode != 0) - { - error (retcode == -1 ? 1 : 0, retcode == -1 ? errno : 0, - "could not stub branch %s for %s", tag, rcs->path); - goto out; - } - /* We need to add a dead version here to avoid -rtag -Dtime - checkout problems between when the head version was - created and now. */ - if (!newfile && headtime != -1) - { - /* move the new file out of the way. */ - fname = xmalloc (strlen (file) + sizeof (CVSADM) - + sizeof (CVSPREFIX) + 10); - (void) sprintf (fname, "%s/%s%s", CVSADM, CVSPREFIX, file); - rename_file (file, fname); - - /* Create empty FILE. Can't use copy_file with a DEVNULL - argument -- copy_file now ignores device files. */ - fp = fopen (file, "w"); - if (fp == NULL) - error (1, errno, "cannot open %s for writing", file); - if (fclose (fp) < 0) - error (0, errno, "cannot close %s", file); - - /* As we will be hacking the delta date, put the time - this was added into the log message. */ - t = time(NULL); - ct = gmtime(&t); - tmp = xmalloc (strlen (file) + strlen (tag) + 80); - - (void) sprintf (tmp, - "file %s was added on branch %s on %d-%02d-%02d %02d:%02d:%02d +0000", - file, tag, - ct->tm_year + (ct->tm_year < 100 ? 0 : 1900), - ct->tm_mon + 1, ct->tm_mday, - ct->tm_hour, ct->tm_min, ct->tm_sec); - - /* commit a dead revision. */ - revnum = RCS_whatbranch (rcs, tag); - retcode = RCS_checkin (rcs, NULL, tmp, revnum, headtime, - RCS_FLAGS_DEAD | - RCS_FLAGS_QUIET | - RCS_FLAGS_USETIME); - free (revnum); - free (tmp); - - if (retcode != 0) - { - error (retcode == -1 ? 1 : 0, retcode == -1 ? errno : 0, - "could not created dead stub %s for %s", tag, - rcs->path); - goto out; - } - - /* put the new file back where it was */ - rename_file (fname, file); - free (fname); - - /* double-check that the file was written correctly */ - freercsnode (&rcs); - rcs = RCS_parse (file, repository); - if (rcs == NULL) - { - error (0, 0, "could not read %s", rcs->path); - goto out; - } - *rcsnode = rcs; - } - } - else - { - /* lock the branch. (stubbed branches need not be locked.) */ - if (lock_RCS (file, rcs, NULL, repository)) - { - error (0, 0, "cannot lock head revision in `%s'.", rcs->path); - goto out; - } - } - - if (*rcsnode != rcs) - { - freercsnode(rcsnode); - *rcsnode = rcs; - } - } - - fileattr_newfile (file); - - /* At this point, we used to set the file mode of the RCS file - based on the mode of the file in the working directory. If we - are creating the RCS file for the first time, add_rcs_file does - this already. If we are re-adding the file, then perhaps it is - consistent to preserve the old file mode, just as we preserve - the old keyword expansion mode. - - If we decide that we should change the modes, then we can't do - it here anyhow. At this point, the RCS file may be owned by - somebody else, so a chmod will fail. We need to instead do the - chmod after rewriting it. - - FIXME: In general, I think the file mode (and the keyword - expansion mode) should be associated with a particular revision - of the file, so that it is possible to have different revisions - of a file have different modes. */ - - retval = 0; - - out: - if (retval != 0 && SIG_inCrSect ()) - SIG_endCrSect (); - return retval; -} - - - -/* - * 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) - const char *user; - RCSNode *rcs; - const char *rev; - const char *repository; -{ - 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 ((unsigned char) *rev) && numdots (rev) < 2)) - { - branch = xstrdup (rcs->branch); - if (branch != NULL) - { - if (RCS_setbranch (rcs, NULL) != 0) - { - error (0, 0, "cannot change branch to default for %s", - rcs->path); - if (branch) - free (branch); - return 1; - } - } - err = RCS_lock (rcs, NULL, 1); - } - else - { - RCS_lock (rcs, rev, 1); - } - - /* We used to call RCS_rewrite here, and that might seem - appropriate in order to write out the locked revision - information. However, such a call would actually serve no - purpose. CVS locks will prevent any interference from other - CVS processes. The comment above rcs_internal_lockfile - explains that it is already unsafe to use RCS and CVS - simultaneously. It follows that writing out the locked - revision information here would add no additional security. - - If we ever do care about it, the proper fix is to create the - RCS lock file before calling this function, and maintain it - until the checkin is complete. - - The call to RCS_lock is still required at present, since in - some cases RCS_checkin will determine which revision to check - in by looking for a lock. FIXME: This is rather roundabout, - and a more straightforward approach would probably be easier to - understand. */ - - if (err == 0) - { - if (sbranch != NULL) - free (sbranch); - sbranch = branch; - return 0; - } - - /* try to restore the branch if we can on error */ - if (branch != NULL) - fixbranch (rcs, branch); - - if (branch) - free (branch); - return 1; -} - - - -/* - * free an UPDATE node's data - */ -void -update_delproc (p) - Node *p; -{ - struct logfile_info *li = p->data; - - if (li->tag) - free (li->tag); - if (li->rev_old) - free (li->rev_old); - if (li->rev_new) - free (li->rev_new); - free (li); -} - -/* - * Free the commit_info structure in p. - */ -static void -ci_delproc (p) - Node *p; -{ - struct commit_info *ci = 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 = p->data; - - dellist (&ml->ulist); - dellist (&ml->cilist); - free (ml); -} |