diff options
Diffstat (limited to 'src/edit.c')
-rw-r--r-- | src/edit.c | 1233 |
1 files changed, 1233 insertions, 0 deletions
diff --git a/src/edit.c b/src/edit.c new file mode 100644 index 0000000..9702872 --- /dev/null +++ b/src/edit.c @@ -0,0 +1,1233 @@ +/* Implementation for "cvs edit", "cvs watch on", and related commands + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2, or (at your option) + any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. */ + +#include "cvs.h" +#include "getline.h" +#include "yesno.h" +#include "watch.h" +#include "edit.h" +#include "fileattr.h" + +static int watch_onoff (int, char **); + +static bool check_edited = false; +static int setting_default; +static int turning_on; + +static bool setting_tedit; +static bool setting_tunedit; +static bool setting_tcommit; + + + +static int +onoff_fileproc (void *callerdat, struct file_info *finfo) +{ + fileattr_get0 (finfo->file, "_watched"); + fileattr_set (finfo->file, "_watched", turning_on ? "" : NULL); + return 0; +} + + + +static int +onoff_filesdoneproc (void *callerdat, int err, const char *repository, + const char *update_dir, List *entries) +{ + if (setting_default) + { + fileattr_get0 (NULL, "_watched"); + fileattr_set (NULL, "_watched", turning_on ? "" : NULL); + } + return err; +} + + + +static int +watch_onoff (int argc, char **argv) +{ + int c; + int local = 0; + int err; + + optind = 0; + while ((c = getopt (argc, argv, "+lR")) != -1) + { + switch (c) + { + case 'l': + local = 1; + break; + case 'R': + local = 0; + break; + case '?': + default: + usage (watch_usage); + break; + } + } + argc -= optind; + argv += optind; + +#ifdef CLIENT_SUPPORT + if (current_parsed_root->isremote) + { + start_server (); + + ign_setup (); + + if (local) + send_arg ("-l"); + send_arg ("--"); + send_files (argc, argv, local, 0, SEND_NO_CONTENTS); + send_file_names (argc, argv, SEND_EXPAND_WILD); + send_to_server (turning_on ? "watch-on\012" : "watch-off\012", 0); + return get_responses_and_close (); + } +#endif /* CLIENT_SUPPORT */ + + setting_default = (argc <= 0); + + lock_tree_promotably (argc, argv, local, W_LOCAL, 0); + + err = start_recursion (onoff_fileproc, onoff_filesdoneproc, NULL, NULL, + NULL, argc, argv, local, W_LOCAL, 0, CVS_LOCK_WRITE, + NULL, 0, NULL); + + Lock_Cleanup (); + return err; +} + +int +watch_on (int argc, char **argv) +{ + turning_on = 1; + return watch_onoff (argc, argv); +} + +int +watch_off (int argc, char **argv) +{ + turning_on = 0; + return watch_onoff (argc, argv); +} + + + +static int +dummy_fileproc (void *callerdat, struct file_info *finfo) +{ + /* This is a pretty hideous hack, but the gist of it is that recurse.c + won't call notify_check unless there is a fileproc, so we can't just + pass NULL for fileproc. */ + return 0; +} + + + +/* Check for and process notifications. Local only. I think that doing + this as a fileproc is the only way to catch all the + cases (e.g. foo/bar.c), even though that means checking over and over + for the same CVSADM_NOTIFY file which we removed the first time we + processed the directory. */ +static int +ncheck_fileproc (void *callerdat, struct file_info *finfo) +{ + int notif_type; + char *filename; + char *val; + char *cp; + char *watches; + + FILE *fp; + char *line = NULL; + size_t line_len = 0; + + /* We send notifications even if noexec. I'm not sure which behavior + is most sensible. */ + + fp = CVS_FOPEN (CVSADM_NOTIFY, "r"); + if (fp == NULL) + { + if (!existence_error (errno)) + error (0, errno, "cannot open %s", CVSADM_NOTIFY); + return 0; + } + + while (getline (&line, &line_len, fp) > 0) + { + notif_type = line[0]; + if (notif_type == '\0') + continue; + filename = line + 1; + cp = strchr (filename, '\t'); + if (cp == NULL) + continue; + *cp++ = '\0'; + val = cp; + cp = strchr (val, '\t'); + if (cp == NULL) + continue; + *cp++ = '+'; + cp = strchr (cp, '\t'); + if (cp == NULL) + continue; + *cp++ = '+'; + cp = strchr (cp, '\t'); + if (cp == NULL) + continue; + *cp++ = '\0'; + watches = cp; + cp = strchr (cp, '\n'); + if (cp == NULL) + continue; + *cp = '\0'; + + notify_do (notif_type, filename, finfo->update_dir, getcaller (), val, + watches, finfo->repository); + } + free (line); + + if (ferror (fp)) + error (0, errno, "cannot read %s", CVSADM_NOTIFY); + if (fclose (fp) < 0) + error (0, errno, "cannot close %s", CVSADM_NOTIFY); + + if ( CVS_UNLINK (CVSADM_NOTIFY) < 0) + error (0, errno, "cannot remove %s", CVSADM_NOTIFY); + + return 0; +} + + + +/* Look through the CVSADM_NOTIFY file and process each item there + accordingly. */ +static int +send_notifications (int argc, char **argv, int local) +{ + int err = 0; + +#ifdef CLIENT_SUPPORT + /* OK, we've done everything which needs to happen on the client side. + Now we can try to contact the server; if we fail, then the + notifications stay in CVSADM_NOTIFY to be sent next time. */ + if (current_parsed_root->isremote) + { + if (strcmp (cvs_cmd_name, "release") != 0) + { + start_server (); + ign_setup (); + } + + err += start_recursion (dummy_fileproc, NULL, NULL, NULL, NULL, argc, + argv, local, W_LOCAL, 0, 0, NULL, 0, NULL); + + send_to_server ("noop\012", 0); + if (strcmp (cvs_cmd_name, "release") == 0) + err += get_server_responses (); + else + err += get_responses_and_close (); + } + else +#endif + { + /* Local. */ + + err += start_recursion (ncheck_fileproc, NULL, NULL, NULL, NULL, argc, + argv, local, W_LOCAL, 0, CVS_LOCK_WRITE, NULL, + 0, NULL); + Lock_Cleanup (); + } + return err; +} + + + +void editors_output (const char *fullname, const char *p) +{ + cvs_output (fullname, 0); + + while (1) + { + cvs_output ("\t", 1); + while (*p != '>' && *p != '\0') + cvs_output (p++, 1); + if (*p == '\0') + { + /* Only happens if attribute is misformed. */ + cvs_output ("\n", 1); + break; + } + ++p; + cvs_output ("\t", 1); + while (1) + { + while (*p != '+' && *p != ',' && *p != '\0') + cvs_output (p++, 1); + if (*p == '\0') + { + cvs_output ("\n", 1); + return; + } + if (*p == ',') + { + ++p; + break; + } + ++p; + cvs_output ("\t", 1); + } + cvs_output ("\n", 1); + } +} + + + +static int find_editors_and_output (struct file_info *finfo) +{ + char *them; + + them = fileattr_get0 (finfo->file, "_editors"); + if (them == NULL) + return 0; + + editors_output (finfo->fullname, them); + + return 0; +} + + + +/* Handle the client-side details of editing a file. + * + * These args could be const but this needs to fit the call_in_directory API. + */ +void +edit_file (void *data, List *ent_list, const char *short_pathname, + const char *filename) +{ + Node *node; + struct file_info finfo; + char *basefilename; + + xchmod (filename, 1); + + mkdir_if_needed (CVSADM_BASE); + basefilename = Xasprintf ("%s/%s", CVSADM_BASE, filename); + copy_file (filename, basefilename); + free (basefilename); + + node = findnode_fn (ent_list, filename); + if (node != NULL) + { + finfo.file = filename; + finfo.fullname = short_pathname; + finfo.update_dir = dir_name (short_pathname); + base_register (&finfo, ((Entnode *) node->data)->version); + free ((char *)finfo.update_dir); + } +} + + + +static int +edit_fileproc (void *callerdat, struct file_info *finfo) +{ + FILE *fp; + time_t now; + char *ascnow; + Vers_TS *vers; + +#if defined (CLIENT_SUPPORT) + assert (!(current_parsed_root->isremote && check_edited)); +#else /* !CLIENT_SUPPORT */ + assert (!check_edited); +#endif /* CLIENT_SUPPORT */ + + if (noexec) + return 0; + + vers = Version_TS (finfo, NULL, NULL, NULL, 1, 0); + + if (!vers->vn_user) + { + error (0, 0, "no such file %s; ignored", finfo->fullname); + return 1; + } + +#ifdef CLIENT_SUPPORT + if (!current_parsed_root->isremote) +#endif /* CLIENT_SUPPORT */ + { + char *editors = fileattr_get0 (finfo->file, "_editors"); + if (editors) + { + if (check_edited) + { + /* In the !CHECK_EDIT case, this message is printed by + * server_notify. + */ + if (!quiet) + editors_output (finfo->fullname, editors); + /* Now warn the user if we skip the file, then return. */ + if (!really_quiet) + error (0, 0, "Skipping file `%s' due to existing editors.", + finfo->fullname); + return 1; + } + free (editors); + } + } + + fp = xfopen (CVSADM_NOTIFY, "a"); + + (void) time (&now); + ascnow = asctime (gmtime (&now)); + ascnow[24] = '\0'; + /* Fix non-standard format. */ + if (ascnow[8] == '0') ascnow[8] = ' '; + fprintf (fp, "E%s\t%s -0000\t%s\t%s\t", finfo->file, + ascnow, hostname, CurDir); + if (setting_tedit) + fprintf (fp, "E"); + if (setting_tunedit) + fprintf (fp, "U"); + if (setting_tcommit) + fprintf (fp, "C"); + fprintf (fp, "\n"); + + if (fclose (fp) < 0) + { + if (finfo->update_dir[0] == '\0') + error (0, errno, "cannot close %s", CVSADM_NOTIFY); + else + error (0, errno, "cannot close %s/%s", finfo->update_dir, + CVSADM_NOTIFY); + } + + /* Now stash the file away in CVSADM so that unedit can revert even if + it can't communicate with the server. We stash away a writable + copy so that if the user removes the working file, then restores it + with "cvs update" (which clears _editors but does not update + CVSADM_BASE), then a future "cvs edit" can still win. */ + /* Could save a system call by only calling mkdir_if_needed if + trying to create the output file fails. But copy_file isn't + set up to facilitate that. */ +#ifdef SERVER_SUPPORT + if (server_active) + server_edit_file (finfo); + else +#endif /* SERVER_SUPPORT */ + edit_file (NULL, finfo->entries, finfo->fullname, finfo->file); + + return 0; +} + +static const char *const edit_usage[] = +{ + "Usage: %s %s [-lRcf] [-a <action>]... [<file>]...\n", + "-l\tLocal directory only, not recursive.\n", + "-R\tProcess directories recursively (default).\n", + "-a\tSpecify action to register for temporary watch, one of:\n", + " \t`edit', `unedit', `commit', `all', `none' (defaults to `all').\n", + "-c\tCheck for <file>s edited by others and abort if found.\n", + "-f\tAllow edit if <file>s are edited by others (default).\n", + "(Specify the --help global option for a list of other help options.)\n", + NULL +}; + +int +edit (int argc, char **argv) +{ + int local = 0; + int c; + int err = 0; + bool a_omitted, a_all, a_none; + + if (argc == -1) + usage (edit_usage); + + a_omitted = true; + a_all = false; + a_none = false; + setting_tedit = false; + setting_tunedit = false; + setting_tcommit = false; + optind = 0; + while ((c = getopt (argc, argv, "+cflRa:")) != -1) + { + switch (c) + { + case 'c': + check_edited = true; + break; + case 'f': + check_edited = false; + break; + case 'l': + local = 1; + break; + case 'R': + local = 0; + break; + case 'a': + a_omitted = false; + if (strcmp (optarg, "edit") == 0) + setting_tedit = true; + else if (strcmp (optarg, "unedit") == 0) + setting_tunedit = true; + else if (strcmp (optarg, "commit") == 0) + setting_tcommit = true; + else if (strcmp (optarg, "all") == 0) + { + a_all = true; + a_none = false; + setting_tedit = true; + setting_tunedit = true; + setting_tcommit = true; + } + else if (strcmp (optarg, "none") == 0) + { + a_none = true; + a_all = false; + setting_tedit = false; + setting_tunedit = false; + setting_tcommit = false; + } + else + usage (edit_usage); + break; + case '?': + default: + usage (edit_usage); + break; + } + } + argc -= optind; + argv += optind; + + if (strpbrk (hostname, "+,>;=\t\n") != NULL) + error (1, 0, + "host name (%s) contains an invalid character (+,>;=\\t\\n)", + hostname); + if (strpbrk (CurDir, "+,>;=\t\n") != NULL) + error (1, 0, +"current directory (%s) contains an invalid character (+,>;=\\t\\n)", + CurDir); + +#ifdef CLIENT_SUPPORT + if (check_edited && current_parsed_root->isremote) + { + /* When CHECK_EDITED, we might as well contact the server and let it do + * the work since we don't want an edit unless we know it is safe. + * + * When !CHECK_EDITED, we set up notifications and then attempt to + * contact the server in order to allow disconnected edits. + */ + start_server(); + + if (!supported_request ("edit")) + error (1, 0, "Server does not support enforced advisory locks."); + + ign_setup(); + + send_to_server ("Hostname ", 0); + send_to_server (hostname, 0); + send_to_server ("\012", 1); + send_to_server ("LocalDir ", 0); + send_to_server (CurDir, 0); + send_to_server ("\012", 1); + + if (local) + send_arg ("-l"); + send_arg ("-c"); + if (!a_omitted) + { + if (a_all) + option_with_arg ("-a", "all"); + else if (a_none) + option_with_arg ("-a", "none"); + else + { + if (setting_tedit) + option_with_arg ("-a", "edit"); + if (setting_tunedit) + option_with_arg ("-a", "unedit"); + if (setting_tcommit) + option_with_arg ("-a", "commit"); + } + } + send_arg ("--"); + send_files (argc, argv, local, 0, SEND_NO_CONTENTS); + send_file_names (argc, argv, SEND_EXPAND_WILD); + send_to_server ("edit\012", 0); + return get_responses_and_close (); + } +#endif /* CLIENT_SUPPORT */ + + /* Now, either SERVER_ACTIVE, local mode, or !CHECK_EDITED. */ + + if (a_omitted) + { + setting_tedit = true; + setting_tunedit = true; + setting_tcommit = true; + } + + TRACE (TRACE_DATA, "edit(): EUC: %d%d%d edit-check: %d", + setting_tedit, setting_tunedit, setting_tcommit, check_edited); + + err = start_recursion (edit_fileproc, NULL, NULL, NULL, NULL, argc, argv, + local, W_LOCAL, 0, 0, NULL, 0, NULL); + + err += send_notifications (argc, argv, local); + + return err; +} + +static int unedit_fileproc (void *callerdat, struct file_info *finfo); + +static int +unedit_fileproc (void *callerdat, struct file_info *finfo) +{ + FILE *fp; + time_t now; + char *ascnow; + char *basefilename = NULL; + + if (noexec) + return 0; + + basefilename = Xasprintf ("%s/%s", CVSADM_BASE, finfo->file); + if (!isfile (basefilename)) + { + /* This file apparently was never cvs edit'd (e.g. we are uneditting + a directory where only some of the files were cvs edit'd. */ + free (basefilename); + return 0; + } + + if (xcmp (finfo->file, basefilename) != 0) + { + printf ("%s has been modified; revert changes? ", finfo->fullname); + fflush (stderr); + fflush (stdout); + if (!yesno ()) + { + /* "no". */ + free (basefilename); + return 0; + } + } + rename_file (basefilename, finfo->file); + free (basefilename); + + fp = xfopen (CVSADM_NOTIFY, "a"); + + (void) time (&now); + ascnow = asctime (gmtime (&now)); + ascnow[24] = '\0'; + /* Fix non-standard format. */ + if (ascnow[8] == '0') ascnow[8] = ' '; + fprintf (fp, "U%s\t%s -0000\t%s\t%s\t\n", finfo->file, + ascnow, hostname, CurDir); + + if (fclose (fp) < 0) + { + if (finfo->update_dir[0] == '\0') + error (0, errno, "cannot close %s", CVSADM_NOTIFY); + else + error (0, errno, "cannot close %s/%s", finfo->update_dir, + CVSADM_NOTIFY); + } + + /* Now update the revision number in CVS/Entries from CVS/Baserev. + The basic idea here is that we are reverting to the revision + that the user edited. If we wanted "cvs update" to update + CVS/Base as we go along (so that an unedit could revert to the + current repository revision), we would need: + + update (or all send_files?) (client) needs to send revision in + new Entry-base request. update (server/local) needs to check + revision against repository and send new Update-base response + (like Update-existing in that the file already exists. While + we are at it, might try to clean up the syntax by having the + mode only in a "Mode" response, not in the Update-base itself). */ + { + char *baserev; + Node *node; + Entnode *entdata; + + baserev = base_get (finfo); + node = findnode_fn (finfo->entries, finfo->file); + /* The case where node is NULL probably should be an error or + something, but I don't want to think about it too hard right + now. */ + if (node != NULL) + { + entdata = node->data; + if (baserev == NULL) + { + /* This can only happen if the CVS/Baserev file got + corrupted. We suspect it might be possible if the + user interrupts CVS, although I haven't verified + that. */ + error (0, 0, "%s not mentioned in %s", finfo->fullname, + CVSADM_BASEREV); + + /* Since we don't know what revision the file derives from, + keeping it around would be asking for trouble. */ + if (unlink_file (finfo->file) < 0) + error (0, errno, "cannot remove %s", finfo->fullname); + + /* This is cheesy, in a sense; why shouldn't we do the + update for the user? However, doing that would require + contacting the server, so maybe this is OK. */ + error (0, 0, "run update to complete the unedit"); + return 0; + } + Register (finfo->entries, finfo->file, baserev, entdata->timestamp, + entdata->options, entdata->tag, entdata->date, + entdata->conflict); + } + free (baserev); + base_deregister (finfo); + } + + xchmod (finfo->file, 0); + return 0; +} + +static const char *const unedit_usage[] = +{ + "Usage: %s %s [-lR] [<file>]...\n", + "-l\tLocal directory only, not recursive.\n", + "-R\tProcess directories recursively (default).\n", + "(Specify the --help global option for a list of other help options.)\n", + NULL +}; + +int +unedit (int argc, char **argv) +{ + int local = 0; + int c; + int err; + + if (argc == -1) + usage (unedit_usage); + + optind = 0; + while ((c = getopt (argc, argv, "+lR")) != -1) + { + switch (c) + { + case 'l': + local = 1; + break; + case 'R': + local = 0; + break; + case '?': + default: + usage (unedit_usage); + break; + } + } + argc -= optind; + argv += optind; + + /* No need to readlock since we aren't doing anything to the + repository. */ + err = start_recursion (unedit_fileproc, NULL, NULL, NULL, NULL, argc, argv, + local, W_LOCAL, 0, 0, NULL, 0, NULL); + + err += send_notifications (argc, argv, local); + + return err; +} + + + +void +mark_up_to_date (const char *file) +{ + char *base; + + /* The file is up to date, so we better get rid of an out of + date file in CVSADM_BASE. */ + base = Xasprintf ("%s/%s", CVSADM_BASE, file); + if (unlink_file (base) < 0 && ! existence_error (errno)) + error (0, errno, "cannot remove %s", file); + free (base); +} + + + +void +editor_set (const char *filename, const char *editor, const char *val) +{ + char *edlist; + char *newlist; + + edlist = fileattr_get0 (filename, "_editors"); + newlist = fileattr_modify (edlist, editor, val, '>', ','); + /* If the attributes is unchanged, don't rewrite the attribute file. */ + if (!((edlist == NULL && newlist == NULL) + || (edlist != NULL + && newlist != NULL + && strcmp (edlist, newlist) == 0))) + fileattr_set (filename, "_editors", newlist); + if (edlist != NULL) + free (edlist); + if (newlist != NULL) + free (newlist); +} + +struct notify_proc_args { + /* What kind of notification, "edit", "tedit", etc. */ + const char *type; + /* User who is running the command which causes notification. */ + const char *who; + /* User to be notified. */ + const char *notifyee; + /* File. */ + const char *file; +}; + + + +static int +notify_proc (const char *repository, const char *filter, void *closure) +{ + char *cmdline; + FILE *pipefp; + const char *srepos = Short_Repository (repository); + struct notify_proc_args *args = closure; + + /* + * Cast any NULL arguments as appropriate pointers as this is an + * stdarg function and we need to be certain the caller gets what + * is expected. + */ + cmdline = format_cmdline ( +#ifdef SUPPORT_OLD_INFO_FMT_STRINGS + false, srepos, +#endif /* SUPPORT_OLD_INFO_FMT_STRINGS */ + filter, + "c", "s", cvs_cmd_name, +#ifdef SERVER_SUPPORT + "R", "s", referrer ? referrer->original : "NONE", +#endif /* SERVER_SUPPORT */ + "p", "s", srepos, + "r", "s", current_parsed_root->directory, + "s", "s", args->notifyee, + (char *) NULL); + if (!cmdline || !strlen (cmdline)) + { + if (cmdline) free (cmdline); + error (0, 0, "pretag proc resolved to the empty string!"); + return 1; + } + + pipefp = run_popen (cmdline, "w"); + if (pipefp == NULL) + { + error (0, errno, "cannot write entry to notify filter: %s", cmdline); + free (cmdline); + return 1; + } + + fprintf (pipefp, "%s %s\n---\n", srepos, args->file); + fprintf (pipefp, "Triggered %s watch on %s\n", args->type, repository); + fprintf (pipefp, "By %s\n", args->who); + + /* Lots more potentially useful information we could add here; see + logfile_write for inspiration. */ + + free (cmdline); + return pclose (pipefp); +} + + + +/* FIXME: this function should have a way to report whether there was + an error so that server.c can know whether to report Notified back + to the client. */ +void +notify_do (int type, const char *filename, const char *update_dir, + const char *who, const char *val, const char *watches, + const char *repository) +{ + static struct addremove_args blank; + struct addremove_args args; + char *watchers; + char *p; + char *endp; + char *nextp; + + /* Print out information on current editors if we were called during an + * edit. + */ + if (type == 'E' && !check_edited && !quiet) + { + char *editors = fileattr_get0 (filename, "_editors"); + if (editors) + { + /* In the CHECK_EDIT case, this message is printed by + * edit_check. It needs to be done here too since files + * which are found to be edited when CHECK_EDIT are not + * added to the notify list. + */ + const char *tmp; + if (update_dir && *update_dir) + tmp = Xasprintf ("%s/%s", update_dir, filename); + else + tmp = filename; + + editors_output (tmp, editors); + + if (update_dir && *update_dir) free ((char *)tmp); + free (editors); + } + } + + /* Initialize fields to 0, NULL, or 0.0. */ + args = blank; + switch (type) + { + case 'E': + if (strpbrk (val, ",>;=\n") != NULL) + { + error (0, 0, "invalid character in editor value"); + return; + } + editor_set (filename, who, val); + break; + case 'U': + case 'C': + editor_set (filename, who, NULL); + break; + default: + return; + } + + watchers = fileattr_get0 (filename, "_watchers"); + p = watchers; + while (p != NULL) + { + char *q; + char *endq; + char *nextq; + char *notif; + + endp = strchr (p, '>'); + if (endp == NULL) + break; + nextp = strchr (p, ','); + + if ((size_t)(endp - p) == strlen (who) && strncmp (who, p, endp - p) == 0) + { + /* Don't notify user of their own changes. Would perhaps + be better to check whether it is the same working + directory, not the same user, but that is hairy. */ + p = nextp == NULL ? nextp : nextp + 1; + continue; + } + + /* Now we point q at a string which looks like + "edit+unedit+commit,"... and walk down it. */ + q = endp + 1; + notif = NULL; + while (q != NULL) + { + endq = strchr (q, '+'); + if (endq == NULL || (nextp != NULL && endq > nextp)) + { + if (nextp == NULL) + endq = q + strlen (q); + else + endq = nextp; + nextq = NULL; + } + else + nextq = endq + 1; + + /* If there is a temporary and a regular watch, send a single + notification, for the regular watch. */ + if (type == 'E' && endq - q == 4 && strncmp ("edit", q, 4) == 0) + { + notif = "edit"; + } + else if (type == 'U' + && endq - q == 6 && strncmp ("unedit", q, 6) == 0) + { + notif = "unedit"; + } + else if (type == 'C' + && endq - q == 6 && strncmp ("commit", q, 6) == 0) + { + notif = "commit"; + } + else if (type == 'E' + && endq - q == 5 && strncmp ("tedit", q, 5) == 0) + { + if (notif == NULL) + notif = "temporary edit"; + } + else if (type == 'U' + && endq - q == 7 && strncmp ("tunedit", q, 7) == 0) + { + if (notif == NULL) + notif = "temporary unedit"; + } + else if (type == 'C' + && endq - q == 7 && strncmp ("tcommit", q, 7) == 0) + { + if (notif == NULL) + notif = "temporary commit"; + } + q = nextq; + } + if (nextp != NULL) + ++nextp; + + if (notif != NULL) + { + struct notify_proc_args args; + size_t len = endp - p; + FILE *fp; + char *usersname; + char *line = NULL; + size_t line_len = 0; + + args.notifyee = NULL; + usersname = Xasprintf ("%s/%s/%s", current_parsed_root->directory, + CVSROOTADM, CVSROOTADM_USERS); + fp = CVS_FOPEN (usersname, "r"); + if (fp == NULL && !existence_error (errno)) + error (0, errno, "cannot read %s", usersname); + if (fp != NULL) + { + while (getline (&line, &line_len, fp) >= 0) + { + if (strncmp (line, p, len) == 0 + && line[len] == ':') + { + char *cp; + args.notifyee = xstrdup (line + len + 1); + + /* There may or may not be more + colon-separated fields added to this in the + future; in any case, we ignore them right + now, and if there are none we make sure to + chop off the final newline, if any. */ + cp = strpbrk (args.notifyee, ":\n"); + + if (cp != NULL) + *cp = '\0'; + break; + } + } + if (ferror (fp)) + error (0, errno, "cannot read %s", usersname); + if (fclose (fp) < 0) + error (0, errno, "cannot close %s", usersname); + } + free (usersname); + if (line != NULL) + free (line); + + if (args.notifyee == NULL) + { + char *tmp; + tmp = xmalloc (endp - p + 1); + strncpy (tmp, p, endp - p); + tmp[endp - p] = '\0'; + args.notifyee = tmp; + } + + args.type = notif; + args.who = who; + args.file = filename; + + (void) Parse_Info (CVSROOTADM_NOTIFY, repository, notify_proc, + PIOPT_ALL, &args); + /* It's okay to cast out the const for the free() below since we + * just allocated this a few lines above. The const was for + * everybody else. + */ + free ((char *)args.notifyee); + } + + p = nextp; + } + if (watchers != NULL) + free (watchers); + + switch (type) + { + case 'E': + if (*watches == 'E') + { + args.add_tedit = 1; + ++watches; + } + if (*watches == 'U') + { + args.add_tunedit = 1; + ++watches; + } + if (*watches == 'C') + { + args.add_tcommit = 1; + } + watch_modify_watchers (filename, &args); + break; + case 'U': + case 'C': + args.remove_temp = 1; + watch_modify_watchers (filename, &args); + break; + } +} + + + +#ifdef CLIENT_SUPPORT +/* Check and send notifications. This is only for the client. */ +void +notify_check (const char *repository, const char *update_dir) +{ + FILE *fp; + char *line = NULL; + size_t line_len = 0; + + if (!server_started) + /* We are in the midst of a command which is not to talk to + the server (e.g. the first phase of a cvs edit). Just chill + out, we'll catch the notifications on the flip side. */ + return; + + /* We send notifications even if noexec. I'm not sure which behavior + is most sensible. */ + + fp = CVS_FOPEN (CVSADM_NOTIFY, "r"); + if (fp == NULL) + { + if (!existence_error (errno)) + error (0, errno, "cannot open %s", CVSADM_NOTIFY); + return; + } + while (getline (&line, &line_len, fp) > 0) + { + int notif_type; + char *filename; + char *val; + char *cp; + + notif_type = line[0]; + if (notif_type == '\0') + continue; + filename = line + 1; + cp = strchr (filename, '\t'); + if (cp == NULL) + continue; + *cp++ = '\0'; + val = cp; + + client_notify (repository, update_dir, filename, notif_type, val); + } + if (line) + free (line); + if (ferror (fp)) + error (0, errno, "cannot read %s", CVSADM_NOTIFY); + if (fclose (fp) < 0) + error (0, errno, "cannot close %s", CVSADM_NOTIFY); + + /* Leave the CVSADM_NOTIFY file there, until the server tells us it + has dealt with it. */ +} +#endif /* CLIENT_SUPPORT */ + + +static const char *const editors_usage[] = +{ + "Usage: %s %s [-lR] [<file>]...\n", + "-l\tProcess this directory only (not recursive).\n", + "-R\tProcess directories recursively (default).\n", + "(Specify the --help global option for a list of other help options.)\n", + NULL +}; + + + +static int +editors_fileproc (void *callerdat, struct file_info *finfo) +{ + return find_editors_and_output (finfo); +} + + + +int +editors (int argc, char **argv) +{ + int local = 0; + int c; + + if (argc == -1) + usage (editors_usage); + + optind = 0; + while ((c = getopt (argc, argv, "+lR")) != -1) + { + switch (c) + { + case 'l': + local = 1; + break; + case 'R': + local = 0; + break; + case '?': + default: + usage (editors_usage); + break; + } + } + argc -= optind; + argv += optind; + +#ifdef CLIENT_SUPPORT + if (current_parsed_root->isremote) + { + start_server (); + ign_setup (); + + if (local) + send_arg ("-l"); + send_arg ("--"); + send_files (argc, argv, local, 0, SEND_NO_CONTENTS); + send_file_names (argc, argv, SEND_EXPAND_WILD); + send_to_server ("editors\012", 0); + return get_responses_and_close (); + } +#endif /* CLIENT_SUPPORT */ + + return start_recursion (editors_fileproc, NULL, NULL, NULL, NULL, + argc, argv, local, W_LOCAL, 0, CVS_LOCK_READ, NULL, + 0, NULL); +} |