cvs-cvs
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[Cvs-cvs] Changes to ccvs/src/edit.c [signed-commits2]


From: Derek Robert Price
Subject: [Cvs-cvs] Changes to ccvs/src/edit.c [signed-commits2]
Date: Wed, 09 Nov 2005 23:47:50 -0500

Index: ccvs/src/edit.c
diff -u /dev/null ccvs/src/edit.c:1.88.4.1
--- /dev/null   Thu Nov 10 04:47:50 2005
+++ ccvs/src/edit.c     Thu Nov 10 04:47:44 2005
@@ -0,0 +1,1257 @@
+/* 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"
+#include "base.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 *editbasefn, *basefn, *rev;
+
+
+    node = findnode_fn (ent_list, filename);
+
+    /* I'm not sure why this isn't an error.  */
+    if (!node) return;
+    rev = ((Entnode *) node->data)->version;
+
+    xchmod (filename, 1);
+    mkdir_if_needed (CVSADM_BASE);
+    basefn = make_base_file_name (filename, rev);
+
+    if (!isfile (basefn))
+    {
+       /* If this client is interoperating with clients older than 1.12.14,
+        * then the CVS/Base/.#FILENAME.REV may not have been created on
+        * checkout/update/commit.
+        */
+       if (!quiet)
+       {
+           error (0, 0, "Base revision (%s) for `%s' is missing.",
+                  rev, short_pathname);
+           error (0, 0, "Using `%s' to create unedit fallback.",
+                  short_pathname);
+       }
+
+       free (basefn);
+       basefn = xstrdup (filename);
+    }
+
+    editbasefn = Xasprintf ("%s/%s", CVSADM_BASE, filename);
+    copy_file (basefn, editbasefn);
+    free (basefn);
+    free (editbasefn);
+
+    finfo.file = filename;
+    finfo.fullname = short_pathname;
+    finfo.update_dir = dir_name (short_pathname);
+    base_register (finfo.update_dir, finfo.file, rev);
+    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->update_dir, finfo->file);
+       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->update_dir, finfo->file);
+    }
+
+    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'", base);
+    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);
+}




reply via email to

[Prev in Thread] Current Thread [Next in Thread]