diff options
Diffstat (limited to 'contrib/cvs/src/add.c')
-rw-r--r-- | contrib/cvs/src/add.c | 951 |
1 files changed, 951 insertions, 0 deletions
diff --git a/contrib/cvs/src/add.c b/contrib/cvs/src/add.c new file mode 100644 index 0000000..228ae69 --- /dev/null +++ b/contrib/cvs/src/add.c @@ -0,0 +1,951 @@ +/* + * 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. + * + * The -m option is currently the only supported option. Some may wish to + * supply 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 "savecwd.h" +#include "fileattr.h" + +static int add_directory PROTO ((struct file_info *finfo)); +static int build_entry PROTO((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 (argc, argv) + 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 (last_component(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 (ISDIRSEP (*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). */ + if (options) + free (options); + if (message) + free (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_exit (); + + 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 + && ISDIRSEP (repository[cvsroot_len]) + && strncmp (repository + cvsroot_len + 1, + CVSROOTADM, + sizeof CVSROOTADM - 1) == 0 + && ISDIRSEP (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 = xmalloc (strlen (repository) + strlen (p) + 5); + sprintf (rcsdir, "%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, NULL)) + error_exit (); + free_cwd (&cwd); + + if (tag) + free (tag); + if (date) + free (date); + free (rcsdir); + + if (p == filedir) + Subdir_Register ((List *) NULL, (char *) NULL, argv[j]); + else + { + Subdir_Register ((List *) 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_exit (); + + 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 + && ISDIRSEP (repository[cvsroot_len]) + && strncmp (repository + cvsroot_len + 1, + CVSROOTADM, + sizeof CVSROOTADM - 1) == 0 + && ISDIRSEP (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 = xmalloc (strlen (repository) + + strlen (finfo.file) + + 10); + (void) sprintf (dname, "%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, "illegal 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) + { + if (vers->tag) + error (0, 0, +"file `%s' will be added on branch `%s' from version %s", + finfo.fullname, vers->tag, + vers->vn_rcs); + else + /* I'm not sure that mentioning + vers->vn_rcs makes any sense here; I + can't think of a way to word the + message which is not confusing. */ + error (0, 0, +"Re-adding file `%s' (in place of dead revision %s).", + finfo.fullname, vers->vn_rcs); + } + 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 = xmalloc (strlen (vers->vn_user)); + (void) strcpy (tmp, 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, 1); + 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, NULL)) + error_exit (); + 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 ((char *) 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 (finfo) + 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)) + 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 = xmalloc (strlen (repository) + strlen (dir) + 5); + sprintf (rcsdir, "%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 = xmalloc (strlen (rcsdir) + + 80 + + (tag == NULL ? 0 : strlen (tag) + 80) + + (date == NULL ? 0 : strlen (date) + 80)); + (void) sprintf (message, "Directory %s added to the repository\n", + rcsdir); + if (tag) + { + (void) strcat (message, "--> Using per-directory sticky tag `"); + (void) strcat (message, tag); + (void) strcat (message, "'\n"); + } + if (date) + { + (void) strcat (message, "--> Using per-directory sticky date `"); + (void) strcat (message, date); + (void) strcat (message, "'\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); + attrs = NULL; + } + + /* + * 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 = (struct logfile_info *) 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, (FILE *) NULL, ulist); + dellist (&ulist); + } + + if (!server_active) + Create_Admin (".", finfo->fullname, rcsdir, tag, date, nonbranch, 0, 1); + if (tag) + free (tag); + if (date) + free (date); + + if (restore_cwd (&cwd, NULL)) + error_exit (); + free_cwd (&cwd); + + Subdir_Register (entries, (char *) NULL, dir); + + if (!really_quiet) + cvs_output (message, 0); + + free (rcsdir); + free (message); + if (attrs != NULL) + free (attrs); + + return 0; + +out: + if (restore_cwd (&cwd, NULL)) + error_exit (); + 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 (repository, user, options, message, entries, tag) + 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 = xmalloc (strlen (user) + 80); + (void) sprintf (fname, "%s/%s%s", CVSADM, user, CVSEXT_LOG); + fp = open_file (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 = xmalloc (strlen (user) + 20); + (void) sprintf (line, "Initial %s", user); + Register (entries, user, "0", line, options, tag, (char *) 0, (char *) 0); + free (line); + return 0; +} |