diff options
Diffstat (limited to 'src/add.c')
-rw-r--r-- | src/add.c | 923 |
1 files changed, 923 insertions, 0 deletions
diff --git a/src/add.c b/src/add.c new file mode 100644 index 0000000..02404b5 --- /dev/null +++ b/src/add.c @@ -0,0 +1,923 @@ +/* + * 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. + * + * Add + * + * Adds a file or directory to the RCS source repository. For a file, + * the entry is marked as "needing to be added" in the user's own CVS + * directory, and really added to the repository when it is committed. + * For a directory, it is added at the appropriate place in the source + * repository and a CVS directory is generated within the directory. + * + * `cvs add' supports `-k <mode>' and `-m <description>' options. + * Some may wish to supply other standard "rcs" options here, but I've + * found that this causes more trouble than anything else. + * + * The user files or directories must already exist. For a directory, it must + * not already have a CVS file in it. + * + * An "add" on a file that has been "remove"d but not committed will cause the + * file to be resurrected. + */ + +#include <assert.h> +#include "cvs.h" +#include "save-cwd.h" +#include "fileattr.h" + +static int add_directory (struct file_info *finfo); +static int build_entry (const char *repository, const char *user, + const char *options, const char *message, + List * entries, const char *tag); + +static const char *const add_usage[] = +{ + "Usage: %s %s [-k rcs-kflag] [-m message] files...\n", + "\t-k rcs-kflag\tUse \"rcs-kflag\" to add the file with the specified\n", + "\t\t\tkflag.\n", + "\t-m message\tUse \"message\" for the creation log.\n", + "(Specify the --help global option for a list of other help options)\n", + NULL +}; + +int +add (int argc, char **argv) +{ + char *message = NULL; + int i; + char *repository; + int c; + int err = 0; + int added_files = 0; + char *options = NULL; + List *entries; + Vers_TS *vers; + struct saved_cwd cwd; + /* Nonzero if we found a slash, and are thus adding files in a + subdirectory. */ + int found_slash = 0; + size_t cvsroot_len; + + if (argc == 1 || argc == -1) + usage (add_usage); + + wrap_setup (); + + /* parse args */ + optind = 0; + while ((c = getopt (argc, argv, "+k:m:")) != -1) + { + switch (c) + { + case 'k': + if (options) free (options); + options = RCS_check_kflag (optarg); + break; + + case 'm': + if (message) free (message); + message = xstrdup (optarg); + break; + case '?': + default: + usage (add_usage); + break; + } + } + argc -= optind; + argv += optind; + + if (argc <= 0) + usage (add_usage); + + cvsroot_len = strlen (current_parsed_root->directory); + + /* First some sanity checks. I know that the CVS case is (sort of) + also handled by add_directory, but we need to check here so the + client won't get all confused in send_file_names. */ + for (i = 0; i < argc; i++) + { + int skip_file = 0; + + /* If it were up to me I'd probably make this a fatal error. + But some people are really fond of their "cvs add *", and + don't seem to object to the warnings. + Whatever. */ + strip_trailing_slashes (argv[i]); + if (strcmp (argv[i], ".") == 0 + || strcmp (argv[i], "..") == 0 + || fncmp (argv[i], CVSADM) == 0) + { + if (!quiet) + error (0, 0, "cannot add special file `%s'; skipping", argv[i]); + skip_file = 1; + } + else + { + char *p; + p = argv[i]; + while (*p != '\0') + { + if (ISSLASH (*p)) + { + found_slash = 1; + break; + } + ++p; + } + } + + if (skip_file) + { + int j; + + /* FIXME: We don't do anything about free'ing argv[i]. But + the problem is that it is only sometimes allocated (see + cvsrc.c). */ + + for (j = i; j < argc - 1; ++j) + argv[j] = argv[j + 1]; + --argc; + /* Check the new argv[i] again. */ + --i; + ++err; + } + } + +#ifdef CLIENT_SUPPORT + if (current_parsed_root->isremote) + { + int j; + + if (argc == 0) + /* We snipped out all the arguments in the above sanity + check. We can just forget the whole thing (and we + better, because if we fired up the server and passed it + nothing, it would spit back a usage message). */ + return err; + + start_server (); + ign_setup (); + if (options) + { + send_arg (options); + free (options); + } + option_with_arg ("-m", message); + send_arg ("--"); + + /* If !found_slash, refrain from sending "Directory", for + CVS 1.9 compatibility. If we only tried to deal with servers + which are at least CVS 1.9.26 or so, we wouldn't have to + special-case this. */ + if (found_slash) + { + repository = Name_Repository (NULL, NULL); + send_a_repository ("", repository, ""); + free (repository); + } + + for (j = 0; j < argc; ++j) + { + /* FIXME: Does this erroneously call Create_Admin in error + conditions which are only detected once the server gets its + hands on things? */ + if (isdir (argv[j])) + { + char *tag; + char *date; + int nonbranch; + char *rcsdir; + char *p; + char *update_dir; + /* This is some mungeable storage into which we can point + with p and/or update_dir. */ + char *filedir; + + if (save_cwd (&cwd)) + error (1, errno, "Failed to save current directory."); + + filedir = xstrdup (argv[j]); + /* Deliberately discard the const below since we know we just + * allocated filedir and can do what we like with it. + */ + p = (char *)last_component (filedir); + if (p == filedir) + { + update_dir = ""; + } + else + { + p[-1] = '\0'; + update_dir = filedir; + if (CVS_CHDIR (update_dir) < 0) + error (1, errno, + "could not chdir to `%s'", update_dir); + } + + /* find the repository associated with our current dir */ + repository = Name_Repository (NULL, update_dir); + + /* don't add stuff to Emptydir */ + if (strncmp (repository, current_parsed_root->directory, cvsroot_len) == 0 + && ISSLASH (repository[cvsroot_len]) + && strncmp (repository + cvsroot_len + 1, + CVSROOTADM, + sizeof CVSROOTADM - 1) == 0 + && ISSLASH (repository[cvsroot_len + sizeof CVSROOTADM]) + && strcmp (repository + cvsroot_len + sizeof CVSROOTADM + 1, + CVSNULLREPOS) == 0) + error (1, 0, "cannot add to `%s'", repository); + + /* before we do anything else, see if we have any + per-directory tags */ + ParseTag (&tag, &date, &nonbranch); + + rcsdir = Xasprintf ("%s/%s", repository, p); + + Create_Admin (p, argv[j], rcsdir, tag, date, + nonbranch, 0, 1); + + if (found_slash) + send_a_repository ("", repository, update_dir); + + if (restore_cwd (&cwd)) + error (1, errno, + "Failed to restore current directory, `%s'.", + cwd.name); + free_cwd (&cwd); + + if (tag) + free (tag); + if (date) + free (date); + free (rcsdir); + + if (p == filedir) + Subdir_Register (NULL, NULL, argv[j]); + else + { + Subdir_Register (NULL, update_dir, p); + } + free (repository); + free (filedir); + } + } + send_files (argc, argv, 0, 0, SEND_BUILD_DIRS | SEND_NO_CONTENTS); + send_file_names (argc, argv, SEND_EXPAND_WILD); + send_to_server ("add\012", 0); + if (message) + free (message); + return err + get_responses_and_close (); + } +#endif + + /* walk the arg list adding files/dirs */ + for (i = 0; i < argc; i++) + { + int begin_err = err; +#ifdef SERVER_SUPPORT + int begin_added_files = added_files; +#endif + struct file_info finfo; + char *filename, *p; + + memset (&finfo, 0, sizeof finfo); + + if (save_cwd (&cwd)) + error (1, errno, "Failed to save current directory."); + + finfo.fullname = xstrdup (argv[i]); + filename = xstrdup (argv[i]); + /* We know we can discard the const below since we just allocated + * filename and can do as we like with it. + */ + p = (char *)last_component (filename); + if (p == filename) + { + finfo.update_dir = ""; + finfo.file = p; + } + else + { + p[-1] = '\0'; + finfo.update_dir = filename; + finfo.file = p; + if (CVS_CHDIR (finfo.update_dir) < 0) + error (1, errno, "could not chdir to `%s'", finfo.update_dir); + } + + /* Add wrappers for this directory. They exist only until + the next call to wrap_add_file. */ + wrap_add_file (CVSDOTWRAPPER, 1); + + finfo.rcs = NULL; + + /* Find the repository associated with our current dir. */ + repository = Name_Repository (NULL, finfo.update_dir); + + /* don't add stuff to Emptydir */ + if (strncmp (repository, current_parsed_root->directory, + cvsroot_len) == 0 + && ISSLASH (repository[cvsroot_len]) + && strncmp (repository + cvsroot_len + 1, + CVSROOTADM, + sizeof CVSROOTADM - 1) == 0 + && ISSLASH (repository[cvsroot_len + sizeof CVSROOTADM]) + && strcmp (repository + cvsroot_len + sizeof CVSROOTADM + 1, + CVSNULLREPOS) == 0) + error (1, 0, "cannot add to `%s'", repository); + + entries = Entries_Open (0, NULL); + + finfo.repository = repository; + finfo.entries = entries; + + /* We pass force_tag_match as 1. If the directory has a + sticky branch tag, and there is already an RCS file which + does not have that tag, then the head revision is + meaningless to us. */ + vers = Version_TS (&finfo, options, NULL, NULL, 1, 0); + if (vers->vn_user == NULL) + { + /* No entry available, ts_rcs is invalid */ + if (vers->vn_rcs == NULL) + { + /* There is no RCS file either */ + if (vers->ts_user == NULL) + { + /* There is no user file either */ + error (0, 0, "nothing known about `%s'", finfo.fullname); + err++; + } + else if (!isdir (finfo.file) + || wrap_name_has (finfo.file, WRAP_TOCVS)) + { + /* + * See if a directory exists in the repository with + * the same name. If so, blow this request off. + */ + char *dname = Xasprintf ("%s/%s", repository, finfo.file); + if (isdir (dname)) + { + error (0, 0, + "cannot add file `%s' since the directory", + finfo.fullname); + error (0, 0, "`%s' already exists in the repository", + dname); + error (1, 0, "invalid filename overlap"); + } + free (dname); + + if (vers->options == NULL || *vers->options == '\0') + { + /* No options specified on command line (or in + rcs file if it existed, e.g. the file exists + on another branch). Check for a value from + the wrapper stuff. */ + if (wrap_name_has (finfo.file, WRAP_RCSOPTION)) + { + if (vers->options) + free (vers->options); + vers->options = wrap_rcsoption (finfo.file, 1); + } + } + + if (vers->nonbranch) + { + error (0, 0, + "cannot add file on non-branch tag `%s'", + vers->tag); + ++err; + } + else + { + /* There is a user file, so build the entry for it */ + if (build_entry (repository, finfo.file, vers->options, + message, entries, vers->tag) != 0) + err++; + else + { + added_files++; + if (!quiet) + { + if (vers->tag) + error (0, 0, "scheduling %s `%s' for" + " addition on branch `%s'", + (wrap_name_has (finfo.file, + WRAP_TOCVS) + ? "wrapper" + : "file"), + finfo.fullname, vers->tag); + else + error (0, 0, + "scheduling %s `%s' for addition", + (wrap_name_has (finfo.file, + WRAP_TOCVS) + ? "wrapper" + : "file"), + finfo.fullname); + } + } + } + } + } + else if (RCS_isdead (vers->srcfile, vers->vn_rcs)) + { + if (isdir (finfo.file) + && !wrap_name_has (finfo.file, WRAP_TOCVS)) + { + error (0, 0, + "the directory `%s' cannot be added because a file" + " of the", finfo.fullname); + error (1, 0, "same name already exists in the repository."); + } + else + { + if (vers->nonbranch) + { + error (0, 0, + "cannot add file on non-branch tag `%s'", + vers->tag); + ++err; + } + else + { + char *timestamp = NULL; + if (vers->ts_user == NULL) + { + /* If this file does not exist locally, assume that + * the last version on the branch is being + * resurrected. + * + * Compute previous revision. We assume that it + * exists and that it is not a revision on the + * trunk of the form X.1 (1.1, 2.1, 3.1, ...). We + * also assume that it is not dead, which seems + * fair since we know vers->vn_rcs is dead + * and we shouldn't see two dead revisions in a + * row. + */ + char *prev = previous_rev (vers->srcfile, + vers->vn_rcs); + int status; + if (prev == NULL) + { + /* There is no previous revision. Either: + * + * * Revision 1.1 was dead, as when a file was + * inititially added on a branch, + * + * or + * + * * All previous revisions have been deleted. + * For instance, via `admin -o'. + */ + if (!really_quiet) + error (0, 0, +"File `%s' has no previous revision to resurrect.", + finfo.fullname); + free (prev); + goto skip_this_file; + } + if (!quiet) + error (0, 0, +"Resurrecting file `%s' from revision %s.", + finfo.fullname, prev); + status = RCS_checkout (vers->srcfile, finfo.file, + prev, vers->tag, + vers->options, RUN_TTY, + NULL, NULL); + xchmod (finfo.file, 1); + if (status != 0) + { + error (0, 0, "Failed to resurrect revision %s", + prev); + err++; + } + else + { + /* I don't actually set vers->ts_user here + * because it would confuse server_update(). + */ + timestamp = time_stamp (finfo.file); + if (!really_quiet) + write_letter (&finfo, 'U'); + } + free (prev); + } + if (!quiet) + { + char *bbuf; + if (vers->tag) + { + bbuf = Xasprintf (" on branch `%s'", + vers->tag); + } + else + bbuf = ""; + error (0, 0, +"Re-adding file `%s'%s after dead revision %s.", + finfo.fullname, bbuf, vers->vn_rcs); + if (vers->tag) + free (bbuf); + } + Register (entries, finfo.file, "0", + timestamp ? timestamp : vers->ts_user, + vers->options, vers->tag, vers->date, NULL); + if (timestamp) free (timestamp); +#ifdef SERVER_SUPPORT + if (server_active && vers->ts_user == NULL) + { + /* If we resurrected the file from the archive, we + * need to tell the client about it. + */ + server_updated (&finfo, vers, + SERVER_UPDATED, + (mode_t) -1, NULL, NULL); + /* This is kinda hacky or, at least, it renders the + * name "begin_added_files" obsolete, but we want + * the added_files to be counted without triggering + * the check that causes server_checked_in() to be + * called below since we have already called + * server_updated() to complete the resurrection. + */ + ++begin_added_files; + } +#endif + ++added_files; + } + } + } + else + { + /* + * There is an RCS file already, so somebody else must've + * added it + */ + error (0, 0, "`%s' added independently by second party", + finfo.fullname); + err++; + } + } + else if (vers->vn_user[0] == '0' && vers->vn_user[1] == '\0') + { + + /* + * An entry for a new-born file, ts_rcs is dummy, but that is + * inappropriate here + */ + if (!quiet) + error (0, 0, "`%s' has already been entered", finfo.fullname); + err++; + } + else if (vers->vn_user[0] == '-') + { + /* An entry for a removed file, ts_rcs is invalid */ + if (vers->ts_user == NULL) + { + /* There is no user file (as it should be) */ + if (vers->vn_rcs == NULL) + { + + /* + * There is no RCS file, so somebody else must've removed + * it from under us + */ + error (0, 0, + "cannot resurrect `%s'; RCS file removed by" + " second party", finfo.fullname); + err++; + } + else + { + int status; + /* + * There is an RCS file, so remove the "-" from the + * version number and restore the file + */ + char *tmp = xstrdup (vers->vn_user + 1); + (void) strcpy (vers->vn_user, tmp); + free (tmp); + status = RCS_checkout (vers->srcfile, finfo.file, + vers->vn_user, vers->tag, + vers->options, RUN_TTY, + NULL, NULL); + xchmod (finfo.file, cvswrite); + if (status != 0) + { + error (0, 0, "Failed to resurrect revision %s.", + vers->vn_user); + err++; + tmp = NULL; + } + else + { + /* I don't actually set vers->ts_user here because it + * would confuse server_update(). + */ + tmp = time_stamp (finfo.file); + write_letter (&finfo, 'U'); + if (!quiet) + error (0, 0, "`%s', version %s, resurrected", + finfo.fullname, vers->vn_user); + } + Register (entries, finfo.file, vers->vn_user, + tmp, vers->options, + vers->tag, vers->date, NULL); + if (tmp) free (tmp); +#ifdef SERVER_SUPPORT + if (server_active) + { + /* If we resurrected the file from the archive, we + * need to tell the client about it. + */ + server_updated (&finfo, vers, + SERVER_UPDATED, + (mode_t) -1, NULL, NULL); + } + /* We don't increment added_files here because this isn't + * a change that needs to be committed. + */ +#endif + } + } + else + { + /* The user file shouldn't be there */ + error (0, 0, "\ +`%s' should be removed and is still there (or is back again)", finfo.fullname); + err++; + } + } + else + { + /* A normal entry, ts_rcs is valid, so it must already be there */ + if (!quiet) + error (0, 0, "`%s' already exists, with version number %s", + finfo.fullname, + vers->vn_user); + err++; + } + freevers_ts (&vers); + + /* passed all the checks. Go ahead and add it if its a directory */ + if (begin_err == err + && isdir (finfo.file) + && !wrap_name_has (finfo.file, WRAP_TOCVS)) + { + err += add_directory (&finfo); + } + else + { +#ifdef SERVER_SUPPORT + if (server_active && begin_added_files != added_files) + server_checked_in (finfo.file, finfo.update_dir, repository); +#endif + } + +skip_this_file: + free (repository); + Entries_Close (entries); + + if (restore_cwd (&cwd)) + error (1, errno, "Failed to restore current directory, `%s'.", + cwd.name); + free_cwd (&cwd); + + /* It's okay to discard the const to free this - we allocated this + * above. The const is for everybody else. + */ + free ((char *) finfo.fullname); + free (filename); + } + if (added_files && !really_quiet) + error (0, 0, "use `%s commit' to add %s permanently", + program_name, + (added_files == 1) ? "this file" : "these files"); + + if (message) + free (message); + if (options) + free (options); + + return err; +} + + + +/* + * The specified user file is really a directory. So, let's make sure that + * it is created in the RCS source repository, and that the user's directory + * is updated to include a CVS directory. + * + * Returns 1 on failure, 0 on success. + */ +static int +add_directory (struct file_info *finfo) +{ + const char *repository = finfo->repository; + List *entries = finfo->entries; + const char *dir = finfo->file; + + char *rcsdir = NULL; + struct saved_cwd cwd; + char *message = NULL; + char *tag, *date; + int nonbranch; + char *attrs; + + if (strchr (dir, '/') != NULL) + { + /* "Can't happen". */ + error (0, 0, + "directory %s not added; must be a direct sub-directory", dir); + return 1; + } + if (fncmp (dir, CVSADM) == 0) + { + error (0, 0, "cannot add a `%s' directory", CVSADM); + return 1; + } + + /* before we do anything else, see if we have any per-directory tags */ + ParseTag (&tag, &date, &nonbranch); + + /* Remember the default attributes from this directory, so we can apply + them to the new directory. */ + fileattr_startdir (repository); + attrs = fileattr_getall (NULL); + fileattr_free (); + + /* now, remember where we were, so we can get back */ + if (save_cwd (&cwd)) + { + error (0, errno, "Failed to save current directory."); + return 1; + } + if (CVS_CHDIR (dir) < 0) + { + error (0, errno, "cannot chdir to %s", finfo->fullname); + return 1; + } + if (!server_active && isfile (CVSADM)) + { + error (0, 0, "%s/%s already exists", finfo->fullname, CVSADM); + goto out; + } + + rcsdir = Xasprintf ("%s/%s", repository, dir); + if (isfile (rcsdir) && !isdir (rcsdir)) + { + error (0, 0, "%s is not a directory; %s not added", rcsdir, + finfo->fullname); + goto out; + } + + /* setup the log message */ + message = Xasprintf ("Directory %s added to the repository\n%s%s%s%s%s%s", + rcsdir, + tag ? "--> Using per-directory sticky tag `" : "", + tag ? tag : "", tag ? "'\n" : "", + date ? "--> Using per-directory sticky date `" : "", + date ? date : "", date ? "'\n" : ""); + + if (!isdir (rcsdir)) + { + mode_t omask; + Node *p; + List *ulist; + struct logfile_info *li; + + /* There used to be some code here which would prompt for + whether to add the directory. The details of that code had + bitrotted, but more to the point it can't work + client/server, doesn't ask in the right way for GUIs, etc. + A better way of making it harder to accidentally add + directories would be to have to add and commit directories + like for files. The code was #if 0'd at least since CVS 1.5. */ + + if (!noexec) + { + omask = umask (cvsumask); + if (CVS_MKDIR (rcsdir, 0777) < 0) + { + error (0, errno, "cannot mkdir %s", rcsdir); + (void) umask (omask); + goto out; + } + (void) umask (omask); + } + + /* Now set the default file attributes to the ones we inherited + from the parent directory. */ + fileattr_startdir (rcsdir); + fileattr_setall (NULL, attrs); + fileattr_write (); + fileattr_free (); + if (attrs != NULL) + free (attrs); + + /* + * Set up an update list with a single title node for Update_Logfile + */ + ulist = getlist (); + p = getnode (); + p->type = UPDATE; + p->delproc = update_delproc; + p->key = xstrdup ("- New directory"); + li = xmalloc (sizeof (struct logfile_info)); + li->type = T_TITLE; + li->tag = xstrdup (tag); + li->rev_old = li->rev_new = NULL; + p->data = li; + (void) addnode (ulist, p); + Update_Logfile (rcsdir, message, NULL, ulist); + dellist (&ulist); + } + + if (server_active) + WriteTemplate (finfo->fullname, 1, rcsdir); + else + Create_Admin (".", finfo->fullname, rcsdir, tag, date, nonbranch, 0, 1); + + if (tag) + free (tag); + if (date) + free (date); + + if (restore_cwd (&cwd)) + error (1, errno, "Failed to restore current directory, `%s'.", + cwd.name); + free_cwd (&cwd); + + Subdir_Register (entries, NULL, dir); + + if (!really_quiet) + cvs_output (message, 0); + + free (rcsdir); + free (message); + + return 0; + +out: + if (restore_cwd (&cwd)) + error (1, errno, "Failed to restore current directory, `%s'.", + cwd.name); + free_cwd (&cwd); + if (message) free (message); + if (rcsdir != NULL) + free (rcsdir); + return 0; +} + + + +/* + * Builds an entry for a new file and sets up "CVS/file",[pt] by + * interrogating the user. Returns non-zero on error. + */ +static int +build_entry (const char *repository, const char *user, const char *options, + const char *message, List *entries, const char *tag) +{ + char *fname; + char *line; + FILE *fp; + + if (noexec) + return 0; + + /* + * The requested log is read directly from the user and stored in the + * file user,t. If the "message" argument is set, use it as the + * initial creation log (which typically describes the file). + */ + fname = Xasprintf ("%s/%s%s", CVSADM, user, CVSEXT_LOG); + fp = xfopen (fname, "w+"); + if (message && fputs (message, fp) == EOF) + error (1, errno, "cannot write to %s", fname); + if (fclose (fp) == EOF) + error (1, errno, "cannot close %s", fname); + free (fname); + + /* + * Create the entry now, since this allows the user to interrupt us above + * without needing to clean anything up (well, we could clean up the + * ,t file, but who cares). + */ + line = Xasprintf ("Initial %s", user); + Register (entries, user, "0", line, options, tag, NULL, NULL); + free (line); + return 0; +} |