? Makefile
? cvs
? options.h
? cvsbug
Index: ChangeLog
===================================================================
RCS file: /home/cvs-adm/.cvsroot/cvs/src/ChangeLog,v
retrieving revision 1.1.1.2
retrieving revision 1.4
diff -b -U8 -r1.1.1.2 -r1.4
--- ChangeLog	2000/01/17 18:04:22	1.1.1.2
+++ ChangeLog	2000/04/14 14:19:59	1.4
@@ -1,8 +1,73 @@
+2000-04-14  Noel Yap <yap_noel@yahoo.com>
+
+	* commit.c (commit, check_fileproc):
+	Option to check for existence of valid edit added.
+
+2000-04-10  Noel Yap <yap_noel@yahoo.com>
+
+	* commit.c (commit, check_fileproc, commit_fileproc):
+	Existence and activity of server checked.
+
+2000-04-04  Noel Yap <yap_noel@yahoo.com>
+
+	* commit.c (find_fileproc, commit, commit_fileproc, update_delproc):
+	File information sent to server and used to unedit file.
+
+	* cvs.h:
+	repository, hostname, wd, and editor added to logfile_info
+
+	* edit.c (ncheck_fileproc, notify_put, unedit_fileproc, notify_proc,
+	notify_do):
+	Editor key changed to include file location.
+
+	* edit.h (notify_do):
+	editor argument added.
+
+	* rcs.c (RCS_unlock):
+	Notification removed.
+
+	* server.c, server.h (serve_fileinfo, server_getfileinfo,
+	server_delfileinfolist, serve_notify, server_notify):
+	FileInfo added to allow client to tell server about file
+	information (eg file location, editor).  This information
+	is used by commit to know which editor to unedit.
+
+2000-03-13  Noel Yap <yap_noel@yahoo.com>
+
+	* edit.c (unedit_fileproc, unedit, editor_set, notify_do):
+	"cvs unedit -e <editor>" option added to allow specification of
+	which editor to unedit.
+
+2000-01-24  Noel Yap <yap_noel@yahoo.com>
+
+	* edit.c (editor_set):
+	editor_set made static.
+
+	# edit.h (editor_set):
+	editor_set extern declaration removed.
+
+	* update.c (update_fileproc):
+	stop "rm file; cvs up file" from uneditting the file.
+
+2000-01-31  Noel Yap <yap_noel@yahoo.com>
+	* edit.c (editors_output):
+	Function created to consolidate code that outputs editors.
+
+2000-01-26  Noel Yap <yap_noel@yahoo.com>
+
+	* edit.c (check_fileproc):
+	"cvs edit -c" fixed so it *really* checks for existing editors
+
+2000-01-19  Noel Yap <yap_noel@yahoo.com>
+
+	* edit.c (check_fileproc):
+	Memory bugs cleaned up.
+
 2000-01-17  Larry Jones  <larry.jones@sdrc.com>
 
 	* mkmodules.c (init): Create CVSROOT/Emptydir to avoid problems
 	with users not having sufficient permissions to create it later.
 
 2000-01-04  Larry Jones  <larry.jones@sdrc.com>
 
 	* client.c (get_responses_and_close): Simplify time-stamp race
@@ -70,16 +135,34 @@
 
 	* history.c (read_hrecs): st_blksize is unsigned long, not int.
 	This isn't just cosmetic - getting it wrong will cause coredumps
 	and such on 64 bit machines.
 
 	* import.c (import_descend), ignore.c (ignore_files): Placate gcc
 	-Wall by parenthesizing foo || (bar && baz).
 
+1999-12-27  Noel Yap <yap_noel@yahoo.com>
+
+	* client.c, client.h (to_server_buffer_flush,
+	from_server_buffer_read): New functions created to expose static
+	variables.
+
+	* edit.c (edit_fileproc):
+	"cvs edit" fixed to save correct working directory when command is
+	executed from one of its ancestor directories.
+
+	* edit.c (check_fileproc, check_edits, edit):
+	"cvs edit -c" option added to allow checking for other editors
+	before applying the edit.
+	"cvs edit -f" option added to force the edit.
+
+	* edit.c (unedit):
+	Used unedit_usage instead of edit_usage for help info.
+
 1999-12-24  Larry Jones <larry.jones@sdrc.com>
 
 	* release.c (release): Use fputs to echo lines from update instead
 	of printf to avoid problems with lines containing "%".  (Reported
 	by Jean-Luc Simard <Jean-Luc.Simard@matrox.com>.)
 
 	* history.c (read_hrecs): Allocate a single 2-block buffer instead
 	of allocating and freeing a buffer for each block.
Index: client.c
===================================================================
RCS file: /home/cvs-adm/.cvsroot/cvs/src/client.c,v
retrieving revision 1.1.1.2
retrieving revision 1.3
diff -b -U8 -r1.1.1.2 -r1.3
--- client.c	2000/01/05 16:35:45	1.1.1.2
+++ client.c	2000/01/19 21:02:36	1.3
@@ -5770,11 +5770,25 @@
 
 void
 send_init_command ()
 {
     /* This is here because we need the CVSroot_directory variable.  */
     send_to_server ("init ", 0);
     send_to_server (CVSroot_directory, 0);
     send_to_server ("\012", 0);
+}
+
+void
+to_server_buffer_flush (void)
+{
+    buf_flush (to_server, 1);
+}
+
+void
+from_server_buffer_read (line, lenp)
+     char **line;
+     int *lenp;
+{
+    buf_read_line (from_server, line, lenp);
 }
 
 #endif /* CLIENT_SUPPORT */
Index: client.h
===================================================================
RCS file: /home/cvs-adm/.cvsroot/cvs/src/client.h,v
retrieving revision 1.1.1.2
retrieving revision 1.3
diff -b -U8 -r1.1.1.2 -r1.3
--- client.h	2000/01/03 03:21:29	1.1.1.2
+++ client.h	2000/01/19 21:02:36	1.3
@@ -193,9 +193,12 @@
 extern char *toplevel_wd;
 extern void client_import_setup PROTO((char *repository));
 extern int client_process_import_file
     PROTO((char *message, char *vfile, char *vtag,
 	   int targc, char *targv[], char *repository, int all_files_binary,
 	   int modtime));
 extern void client_import_done PROTO((void));
 extern void client_notify PROTO((char *, char *, char *, int, char *));
+
+extern void from_server_buffer_read PROTO((char **, int *));
+extern void to_server_buffer_flush PROTO((void));
 #endif /* CLIENT_SUPPORT */
Index: commit.c
===================================================================
RCS file: /home/cvs-adm/.cvsroot/cvs/src/commit.c,v
retrieving revision 1.1.1.2
retrieving revision 1.4
diff -b -U8 -r1.1.1.2 -r1.4
--- commit.c	2000/01/05 16:35:46	1.1.1.2
+++ commit.c	2000/04/14 14:19:59	1.4
@@ -64,32 +64,34 @@
     char *options;			/* Any sticky -k option */
 };
 struct master_lists
 {
     List *ulist;			/* list for Update_Logfile */
     List *cilist;			/* list with commit_info structs */
 };
 
+static int check_valid_edit = 0;
 static int force_ci = 0;
 static int got_message;
 static int run_module_prog = 1;
 static int aflag;
 static char *saved_tag;
 static char *write_dirtag;
 static int write_dirnonbranch;
 static char *logfile;
 static List *mulist;
 static List *saved_ulist;
 static char *saved_message;
 static time_t last_register_time;
 
 static const char *const commit_usage[] =
 {
     "Usage: %s %s [-nRlf] [-m msg | -F logfile] [-r rev] files...\n",
+    "\t-c\tCheck for valid edits before committing.\n",
     "\t-n\tDo not run the module program (if any).\n",
     "\t-R\tProcess directories recursively.\n",
     "\t-l\tLocal directory only (not recursive).\n",
     "\t-f\tForce the file to be committed; disables recursion.\n",
     "\t-F file\tRead the log message from file.\n",
     "\t-m msg\tLog message.\n",
     "\t-r rev\tCommit to this branch or trunk revision.\n",
     "(Specify the --help global option for a list of other help options)\n",
@@ -287,16 +289,21 @@
 	return 0;
     }
 
     node = getnode ();
     node->key = xstrdup (finfo->fullname);
 
     data = (struct logfile_info *) xmalloc (sizeof (struct logfile_info));
     data->type = status;
+    data->repository = xstrdup (finfo->repository);
+    data->hostname = xstrdup (hostname);
+    data->wd = (char *) xmalloc (strlen (CurDir) + sizeof ('/') + strlen (finfo->update_dir) + 1);
+    sprintf (data->wd, "%s%c%s", CurDir, finfo->update_dir != NULL  &&  *finfo->update_dir != '\0'   ?   '/'   :   '\0', finfo->update_dir);
+    data->editor = NULL;
     data->tag = xstrdup (vers->tag);
     data->rev_old = data->rev_new = NULL;
 
     node->type = UPDATE;
     node->delproc = update_delproc;
     node->data = (char *) data;
     (void)addnode (args->ulist, node);
 
@@ -351,20 +358,23 @@
 	if ((pw = (struct passwd *) getpwnam (getcaller ())) == NULL)
 	    error (1, 0, "you are unknown to this system");
 	if (pw->pw_uid == (uid_t) 0)
 	    error (1, 0, "cannot commit files as 'root'");
     }
 #endif /* CVS_BADROOT */
 
     optind = 0;
-    while ((c = getopt (argc, argv, "+nlRm:fF:r:")) != -1)
+    while ((c = getopt (argc, argv, "+cnlRm:fF:r:")) != -1)
     {
 	switch (c)
 	{
+            case 'c':
+                check_valid_edit = 1;
+                break;
 	    case 'n':
 		run_module_prog = 0;
 		break;
 	    case 'm':
 #ifdef FORCE_USE_EDITOR
 		use_editor = 1;
 #else
 		use_editor = 0;
@@ -385,16 +395,17 @@
 	    case 'l':
 		local = 1;
 		break;
 	    case 'R':
 		local = 0;
 		break;
 	    case 'f':
 		force_ci = 1;
+                check_valid_edit = 0;
 		local = 1;		/* also disable recursion */
 		break;
 	    case 'F':
 #ifdef FORCE_USE_EDITOR
 		use_editor = 1;
 #else
 		use_editor = 0;
 #endif
@@ -552,18 +563,44 @@
 		free (p->repos);
 		free (p->file);
 		q = p->next;
 		free (p);
 		p = q;
 	    }
 	}
 
+        /* send local file info to server */
+        if (supported_request ("FileInfo"))
+        {
+            char **v = NULL;
+
+            for(v = find_args.argv; v < find_args.argv + find_args.argc; ++v)
+            {
+                Node *p = NULL;
+
+                p = findnode (find_args.ulist, *v);
+                if (p != NULL)
+                {
+                    send_to_server ("FileInfo ", 0);
+                    send_to_server (*v, 0);
+                    send_to_server ("\012", 1);
+                    send_to_server (((struct logfile_info *) p->data)->hostname, 0);
+                    send_to_server ("\t", 1);
+                    send_to_server (((struct logfile_info *) p->data)->wd, 0);
+                    send_to_server ("\t", 1);
+                    send_to_server ("\012", 1);
+                }
+            }
+        }
+
 	if (local)
 	    send_arg("-l");
+        if (check_valid_edit)
+            send_arg("-c");
 	if (force_ci)
 	    send_arg("-f");
 	if (!run_module_prog)
 	    send_arg("-n");
 	option_with_arg ("-r", saved_tag);
 
 	/* FIXME: This whole find_args.force/SEND_FORCE business is a
 	   kludge.  It would seem to be a server bug that we have to
@@ -581,16 +618,48 @@
 	   or removed means that the server will only do an up-to-date
 	   check on those files.  This is different from local CVS and
 	   previous versions of client/server CVS, but it probably is a Good
 	   Thing, or at least Not Such A Bad Thing.  */
 	send_file_names (find_args.argc, find_args.argv, 0);
 
 	send_to_server ("ci\012", 0);
 	err = get_responses_and_close ();
+        if (err == 0  &&  !supported_request ("FileInfo"))
+        {
+            /* FIXME: Noel Yap (yap_noel@yahoo.com) 2000 Feb 28
+               This block is to support old servers.  It should be
+               removed once it's deemed that all servers support the
+               "FileInfo" request.  IMHO, it should be safe to remove
+               by 2002 Feb 28 (ie in two years).
+            */
+            char **v = NULL;
+
+            for(v = find_args.argv; v < find_args.argv + find_args.argc; ++v)
+            {
+                Node *p = NULL;
+
+                p = findnode (find_args.ulist, *v);
+                if (p != NULL)
+                {
+                    char *repository = NULL;
+                    char *user = NULL;
+
+                    user = getcaller ();
+                    repository = ((struct logfile_info *) p->data)->repository;
+
+                    fileattr_startdir (repository);
+
+                    notify_do ('C', *v, user, user, NULL, "", repository);
+
+                    fileattr_free ();
+                }
+            }
+        }
+
 	if (err != 0 && use_editor && saved_message != NULL)
 	{
 	    /* If there was an error, don't nuke the user's carefully
 	       constructed prose.  This is something of a kludge; a better
 	       solution is probably more along the lines of #150 in TODO
 	       (doing a second up-to-date check before accepting the
 	       log message has also been suggested, but that seems kind of
 	       iffy because the real up-to-date check could still fail,
@@ -651,16 +720,23 @@
 			   check_direntproc, (DIRLEAVEPROC) NULL, NULL, argc,
 			   argv, local, W_LOCAL, aflag, 0, (char *) NULL, 1);
     if (err)
     {
 	Lock_Cleanup ();
 	error (1, 0, "correct above errors first!");
     }
 
+#ifdef SERVER_SUPPORT
+    if (server_active)
+    {
+        server_delfileinfolist ();
+    }
+#endif
+
     /*
      * Run the recursion processor to commit the files
      */
     write_dirnonbranch = 0;
     if (noexec == 0)
 	err = start_recursion (commit_fileproc, commit_filesdoneproc,
 			       commit_direntproc, commit_dirleaveproc, NULL,
 			       argc, argv, local, W_LOCAL, aflag, 0,
@@ -823,19 +899,21 @@
 	case T_PATCH:
 #endif
 	case T_NEEDS_MERGE:
 	case T_CONFLICT:
 	case T_REMOVE_ENTRY:
 	    error (0, 0, "Up-to-date check failed for `%s'", finfo->fullname);
 	    freevers_ts (&vers);
 	    return (1);
+
 	case T_MODIFIED:
 	case T_ADDED:
 	case T_REMOVED:
+        {
 	    /*
 	     * some quick sanity checks; if no numeric -r option specified:
 	     *	- can't have a sticky date
 	     *	- can't have a sticky tag that is not a branch
 	     * Also,
 	     *	- if status is T_REMOVED, can't have a numeric tag
 	     *	- if status is T_ADDED, rcs file must not exist unless on
 	     *    a branch
@@ -999,35 +1077,148 @@
 	    /* first do ulist, then cilist */
 	    p = getnode ();
 	    p->key = xstrdup (finfo->file);
 	    p->type = UPDATE;
 	    p->delproc = update_delproc;
 	    li = ((struct logfile_info *)
 		  xmalloc (sizeof (struct logfile_info)));
 	    li->type = status;
+
+#if SERVER_SUPPORT
+            if (server_active)
+            {
+                struct logfile_info *client_fileinfo = NULL;
+
+                client_fileinfo = server_getfileinfo (finfo->fullname);
+                if (client_fileinfo == NULL)
+                {
+                    li->repository = NULL;
+                    li->hostname = NULL;
+                    li->wd = NULL;
+                }
+                else
+                {
+                    li->repository = xstrdup (client_fileinfo->repository);
+                    li->hostname = xstrdup (client_fileinfo->hostname);
+                    li->wd = xstrdup (client_fileinfo->wd);
+                }
+            }
+            else
+#endif
+            {
+                li->repository = xstrdup (finfo->repository);
+                li->hostname = xstrdup (hostname);
+                li->wd = (char *) xmalloc (strlen (CurDir) + sizeof ('/') + strlen (finfo->update_dir) + 1);
+
+                strcpy (li->wd, CurDir);
+                if (finfo->update_dir != NULL  &&  *finfo->update_dir != '\0')
+                {
+                    strcat (li->wd, "/");
+                    strcat (li->wd, finfo->update_dir);
+                }
+            }
+
+            if (li->hostname == NULL  ||  li->wd == NULL)
+            {
+                li->editor = NULL;
+            }
+            else
+            {
+                char *editors = NULL;
+
+                editors = fileattr_get0 (finfo->file, "_editors");
+                if (editors == NULL)
+                {
+                    li->editor = NULL;
+                }
+                else
+                {
+                    char *editor = NULL;
+                    char *user = NULL;
+                    char *p = NULL;
+                    char *p0 = NULL;
+
+                    user = getcaller ();
+
+                    editor = (char *) xmalloc (strlen (user) + sizeof ('@') + strlen (li->hostname) + sizeof (':') + strlen (li->wd) + 1);
+                    sprintf (editor, "%s@%s:%s", user, li->hostname, li->wd);
+
+                    p = editors;
+                    p0 = p;
+                    while (*p != '\0')
+                    {
+                        p = strchr (p, '>');
+                        if (p == NULL)
+                        {
+                            break;
+                        }
+                        *p = '\0';
+                        if (strcmp (editor, p0) == 0)
+                        {
+                            break;
+                        }
+                        p = strchr (p + 1, ',');
+                        if (p == NULL)
+                        {
+                            break;
+                        }
+                        ++p;
+                        p0 = p;
+                    }
+
+                    if (strcmp (editor, p0) == 0)
+                    {
+                        li->editor = editor;
+                    }
+                    else
+                    {
+                        li->editor = NULL;
+                        free (editor);
+                    }
+
+                    free (editors);
+                }
+            }
+
+            if (check_valid_edit  &&  li->editor == NULL)
+            {
+                error (0, 0, "Valid edit does not exist for %s", finfo->fullname);
+                freevers_ts (&vers);
+                return 1;
+            }
+
 	    li->tag = xstrdup (vers->tag);
 	    li->rev_old = xstrdup (vers->vn_rcs);
 	    li->rev_new = NULL;
+
 	    p->data = (char *) li;
 	    (void) addnode (ulist, p);
 
 	    p = getnode ();
 	    p->key = xstrdup (finfo->file);
 	    p->type = UPDATE;
 	    p->delproc = ci_delproc;
 	    ci = (struct commit_info *) xmalloc (sizeof (struct commit_info));
 	    ci->status = status;
 	    if (vers->tag)
+            {
 		if (isdigit ((unsigned char) *vers->tag))
+                {
 		    ci->rev = xstrdup (vers->tag);
+                }
 		else
+                {
 		    ci->rev = RCS_whatbranch (finfo->rcs, vers->tag);
+                }
+            }
 	    else
+            {
 		ci->rev = (char *) NULL;
+            }
 	    ci->tag = xstrdup (vers->tag);
 	    ci->options = xstrdup(vers->options);
 	    p->data = (char *) ci;
 	    (void) addnode (cilist, p);
 
 #ifdef PRESERVE_PERMISSIONS_SUPPORT
 	    if (preserve_perms)
 	    {
@@ -1059,16 +1250,18 @@
 			xmalloc (sizeof (struct hardlink_info));
 		    hlinfo->status = status;
 		    linkp->data = (char *) hlinfo;
 		}
 	    }
 #endif
 
 	    break;
+        }
+
 	case T_UNKNOWN:
 	    error (0, 0, "nothing known about `%s'", finfo->fullname);
 	    freevers_ts (&vers);
 	    return (1);
 	case T_UPTODATE:
 	    break;
 	default:
 	    error (0, 0, "CVS internal error: unknown status %d", status);
@@ -1359,17 +1552,55 @@
 			    (unsigned char *) NULL,
 			    (struct buffer *) NULL);
 	}
 #endif
     }
 
     /* Clearly this is right for T_MODIFIED.  I haven't thought so much
        about T_ADDED or T_REMOVED.  */
-    notify_do ('C', finfo->file, getcaller (), NULL, NULL, finfo->repository);
+    {
+        char *editor = NULL;
+
+#ifdef SERVER_SUPPORT
+        if (server_active)
+        {
+            Node *p = NULL;
+
+            p = findnode (ulist, finfo->file);
+            if (p != NULL)
+            {
+                struct logfile_info *li = NULL;
+
+                li = (struct logfile_info *) p->data;
+                if (li != NULL)
+                {
+                    editor = li->editor;
+                }
+            }
+
+            notify_do ('C', finfo->file, getcaller (), editor ? editor : getcaller (), NULL, "", finfo->repository);
+        }
+        else
+#endif
+        {
+            editor = (char *) xmalloc (strlen (getcaller ()) + sizeof ('@') + strlen (hostname) + sizeof (':') + strlen (CurDir) + sizeof ('/') + strlen (finfo->update_dir) + 1);
+            sprintf (editor, "%s@%s:%s", getcaller (), hostname, CurDir);
+
+            if (*finfo->update_dir != '\0')
+            {
+                strcat (editor, "/");
+                strcat (editor, finfo->update_dir);
+            }
+
+            notify_do ('C', finfo->file, getcaller (), editor, NULL, "", finfo->repository);
+
+            free (editor);
+        }
+    }
 
 out:
     if (err != 0)
     {
 	/* on failure, remove the file from ulist */
 	p = findnode (ulist, finfo->file);
 	if (p)
 	    delnode (p);
@@ -2290,16 +2521,24 @@
  */
 void
 update_delproc (p)
     Node *p;
 {
     struct logfile_info *li;
 
     li = (struct logfile_info *) p->data;
+    if (li->repository)
+        free (li->repository);
+    if (li->hostname)
+        free (li->hostname);
+    if (li->wd)
+        free (li->wd);
+    if (li->editor)
+        free (li->editor);
     if (li->tag)
 	free (li->tag);
     if (li->rev_old)
 	free (li->rev_old);
     if (li->rev_new)
 	free (li->rev_new);
     free (li);
 }
Index: cvs.h
===================================================================
RCS file: /home/cvs-adm/.cvsroot/cvs/src/cvs.h,v
retrieving revision 1.1.1.2
retrieving revision 1.2
diff -b -U8 -r1.1.1.2 -r1.2
--- cvs.h	2000/01/03 20:49:12	1.1.1.2
+++ cvs.h	2000/04/04 18:06:41	1.2
@@ -786,16 +786,20 @@
 
 /*
  * structure used for list nodes passed to Update_Logfile() and
  * do_editor().
  */
 struct logfile_info
 {
   enum classify_type type;
+  char *repository;
+  char *hostname;
+  char *wd;     /* working directory */
+  char *editor;
   char *tag;
   char *rev_old;		/* rev number before a commit/modify,
 				   NULL for add or import */
   char *rev_new;		/* rev number after a commit/modify,
 				   add, or import, NULL for remove */
 };
 
 /* Wrappers.  */
Index: edit.c
===================================================================
RCS file: /home/cvs-adm/.cvsroot/cvs/src/edit.c,v
retrieving revision 1.1.1.2
retrieving revision 1.11
diff -b -U8 -r1.1.1.2 -r1.11
--- edit.c	1999/11/19 22:55:47	1.1.1.2
+++ edit.c	2000/04/04 18:06:41	1.11
@@ -11,18 +11,21 @@
    GNU General Public License for more details.  */
 
 #include "cvs.h"
 #include "getline.h"
 #include "watch.h"
 #include "edit.h"
 #include "fileattr.h"
 
+static int check_edited = 0;
 static int watch_onoff PROTO ((int, char **));
 
+static char *unedit_editor = NULL;
+
 static int setting_default;
 static int turning_on;
 
 static int setting_tedit;
 static int setting_tunedit;
 static int setting_tcommit;
 
 static int onoff_fileproc PROTO ((void *callerdat, struct file_info *finfo));
@@ -149,16 +152,17 @@
    processed the directory.  */
 
 static int
 ncheck_fileproc (callerdat, finfo)
     void *callerdat;
     struct file_info *finfo;
 {
     int notif_type;
+    char *editor = NULL;
     char *filename;
     char *val;
     char *cp;
     char *watches;
 
     FILE *fp;
     char *line = NULL;
     size_t line_len = 0;
@@ -193,31 +197,35 @@
 	if (cp == NULL)
 	    continue;
 	*cp++ = '+';
 	cp = strchr (cp, '\t');
 	if (cp == NULL)
 	    continue;
 	*cp++ = '\0';
 	watches = cp;
-	cp = strchr (cp, '\n');
+	cp = strchr (cp, '\t');
+	if (cp == NULL)
+	    continue;
+	*cp++ = '\0';
+        editor = cp;
+        cp = strchr (cp, '\t');
 	if (cp == NULL)
 	    continue;
 	*cp = '\0';
 
-	notify_do (notif_type, filename, getcaller (), val, watches,
+	notify_do (notif_type, filename, getcaller (), editor, 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;
 }
 
 static int send_notifications PROTO ((int, char **, int));
 
@@ -264,67 +272,348 @@
 				(DIRENTPROC) NULL, (DIRLEAVEPROC) NULL, NULL,
 				argc, argv, local, W_LOCAL, 0, 0, (char *)NULL,
 				0);
 	Lock_Cleanup ();
     }
     return err;
 }
 
-static int edit_fileproc PROTO ((void *callerdat, struct file_info *finfo));
+
+static int editors_output PROTO ((struct file_info *finfo));
 
 static int
-edit_fileproc (callerdat, finfo)
-    void *callerdat;
+editors_output (finfo)
     struct file_info *finfo;
 {
-    FILE *fp;
-    time_t now;
-    char *ascnow;
-    char *basefilename;
+    char *them;
+    char *p;
 
-    if (noexec)
+    them = fileattr_get0 (finfo->file, "_editors");
+    if (them == NULL)
 	return 0;
 
+    cvs_output (finfo->fullname, 0);
+
+    p = them;
+    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);
+		goto out;
+	    }
+	    if (*p == ',')
+	    {
+		++p;
+		break;
+	    }
+	    ++p;
+	    cvs_output ("\t", 1);
+	}
+	cvs_output ("\n", 1);
+    }
+
+out:
+    free (them);
+
+    return 0;
+}
+
+
+static int check_fileproc PROTO ((void *callerdat, struct file_info *finfo));
+
+/* check file that is to be edited if it's already being edited */
+
+static int
+check_fileproc (callerdat, finfo)
+    void *callerdat;
+    struct file_info *finfo;
+{
+    char *editors = NULL;
+    int editors_found = 0;
+    int status;
+
+#ifdef CLIENT_SUPPORT
+    if (client_active)
+    {
+        int first_time;
+        int len = 0;
+        int possibly_more_editors = 0;
+
+        send_file_names (1, &finfo->fullname, SEND_EXPAND_WILD);
+        send_to_server ("editors\012", 0);
+
+        first_time = 1;
+        do
+        {
+            possibly_more_editors = 0;
+
+            to_server_buffer_flush ();
+            from_server_buffer_read (&editors, &len);
+
+            if (editors != NULL)
+            {
+                if (strcmp (editors, "ok") != 0)
+                {
+                    possibly_more_editors = 1;
+
+                    switch (editors[0])
+                    {
+                        case 'M':
+                        {
+                            editors_found = 1;
+
+                            if (!really_quiet)
+                            {
+                                cvs_output (editors + 2, 0);
+                                cvs_output ("\n", 0);
+                            }
+
+                            break;
+                        }
+
+                        default:
+                        {
+                            struct response *rs = NULL;
+                            char *cmd = NULL;
+
+                            cmd = editors;
+
+                            for (rs = responses; rs->name != NULL; ++rs)
+                                if (strncmp (cmd, rs->name, strlen (rs->name)) == 0)
+                                {
+                                    int cmdlen = strlen (rs->name);
+                                    if (cmd[cmdlen] == ' ')
+                                        ++cmdlen;
+                                    else if (cmd[cmdlen] != '\0')
+                                        /*
+                                         * The first len characters match, but it's a different
+                                         * response.  e.g. the response is "oklahoma" but we
+                                         * matched "ok".
+                                         */
+                                        continue;
+                                    (*rs->func) (cmd + cmdlen, len - cmdlen);
+                                    break;
+                                }
+                            if (rs->name == NULL)
+                                /* It's OK to print just to the first '\0'.  */
+                                /* We might want to handle control characters and the like
+                                   in some other way other than just sending them to stdout.
+                                   One common reason for this error is if people use :ext:
+                                   with a version of rsh which is doing CRLF translation or
+                                   something, and so the client gets "ok^M" instead of "ok".
+                                   Right now that will tend to print part of this error
+                                   message over the other part of it.  It seems like we could
+                                   do better (either in general, by quoting or omitting all
+                                   control characters, and/or specifically, by detecting the CRLF
+                                   case and printing a specific error message).  */
+                                error (0, 0,
+                                       "warning: unrecognized response `%s' from cvs server",
+                                       cmd);
+
+                            break;
+                        }
+                    }
+                }
+
+                free(editors);
+            }
+        } while (possibly_more_editors);
+    }
+    else
+#endif /* CLIENT_SUPPORT */
+    {
     /* This is a somewhat screwy way to check for this, because it
        doesn't help errors other than the nonexistence of the file
        (e.g. permissions problems).  It might be better to rearrange
        the code so that CVSADM_NOTIFY gets written only after the
        various actions succeed (but what if only some of them
        succeed).  */
     if (!isfile (finfo->file))
     {
 	error (0, 0, "no such file %s; ignored", finfo->fullname);
 	return 0;
     }
 
+        editors = fileattr_get0 (finfo->file, "_editors");
+        if (!really_quiet && editors != NULL)
+        {
+            editors_output (finfo);
+        }
+
+        if (editors != NULL)
+        {
+            editors_found = 1;
+
+            free (editors);
+        }
+    }
+
+    if (check_edited && editors_found)
+    {
+        status = 1;
+    }
+    else
+    {
+        status = 0;
+    }
+
+    return status;
+}
+
+static int check_edits PROTO ((int, char **, int));
+
+/* Look through the CVS/fileattr file and check for editors */
+static int
+check_edits (argc, argv, local)
+    int argc;
+    char **argv;
+    int local;
+{
+    int err = 0;
+
+#ifdef CLIENT_SUPPORT
+    if (client_active)
+    {
+        if (strcmp (command_name, "release") != 0)
+        {
+            start_server ();
+            ign_setup ();
+        }
+
+        if (local)
+            send_arg ("-l");
+        send_files (argc, argv, local, 0, SEND_NO_CONTENTS);
+    }
+#endif
+
+	err += start_recursion (check_fileproc, (FILESDONEPROC) NULL,
+                            (DIRENTPROC) NULL, (DIRLEAVEPROC) NULL, NULL,
+                            argc, argv, local, W_LOCAL, 0, 0, (char *)NULL,
+                            0);
+
+#ifdef CLIENT_SUPPORT
+    if (client_active)
+    {
+        send_to_server ("noop\012", 0);
+        if (strcmp (command_name, "release") == 0)
+            err += get_server_responses ();
+        else
+            err += get_responses_and_close ();
+    }
+#endif
+    return err;
+}
+
+static void notify_put PROTO ((struct file_info *finfo, char *editor, char action, int tedit, int tunedit, int tcommit));
+
+static void
+notify_put (finfo, editor, action, tedit, tunedit, tcommit)
+    struct file_info *finfo;
+    char *editor;
+    char action;
+    int tedit;
+    int tunedit;
+    int tcommit;
+{
+    FILE *fp;
+    time_t now;
+    char *ascnow;
+    char *wd = NULL;
+
     fp = open_file (CVSADM_NOTIFY, "a");
 
     (void) time (&now);
     ascnow = asctime (gmtime (&now));
     ascnow[24] = '\0';
-    fprintf (fp, "E%s\t%s GMT\t%s\t%s\t", finfo->file,
-	     ascnow, hostname, CurDir);
-    if (setting_tedit)
+
+    wd = (char *) malloc (strlen (CurDir) + strlen ("/") + strlen (finfo->update_dir) + 1);
+    strcpy(wd, CurDir);
+
+    if (finfo->update_dir != NULL  &&  *finfo->update_dir != '\0')
+    {
+        strcat(wd, "/");
+        strcat(wd, finfo->update_dir);
+    }
+
+    fprintf (fp, "%c%s\t%s GMT\t%s\t%s\t", action, finfo->file, ascnow, hostname, wd);
+
+    if (tedit)
 	fprintf (fp, "E");
-    if (setting_tunedit)
+    if (tunedit)
 	fprintf (fp, "U");
-    if (setting_tcommit)
+    if (tcommit)
 	fprintf (fp, "C");
+
+    if (editor == NULL  ||  strcmp (editor, "") == 0)
+    {
+        fprintf (fp, "\t%s@%s:%s\t", getcaller (), hostname, wd);
+    }
+    else
+    {
+        fprintf (fp, "\t%s\t", editor);
+    }
+
     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);
     }
 
+    if (wd != NULL)
+        free (wd);
+}
+
+static int edit_fileproc PROTO ((void *callerdat, struct file_info *finfo));
+
+static int
+edit_fileproc (callerdat, finfo)
+    void *callerdat;
+    struct file_info *finfo;
+{
+    char *basefilename;
+
+    if (noexec)
+	return 0;
+
+    /* This is a somewhat screwy way to check for this, because it
+       doesn't help errors other than the nonexistence of the file
+       (e.g. permissions problems).  It might be better to rearrange
+       the code so that CVSADM_NOTIFY gets written only after the
+       various actions succeed (but what if only some of them
+       succeed).  */
+    if (!isfile (finfo->file))
+    {
+	error (0, 0, "no such file %s; ignored", finfo->fullname);
+	return 0;
+    }
+
+    notify_put (finfo, NULL, 'E', setting_tedit, setting_tunedit, setting_tcommit);
+
     xchmod (finfo->file, 1);
 
     /* 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
@@ -346,47 +635,55 @@
 	    base_register (finfo, ((Entnode *) node->data)->version);
     }
 
     return 0;
 }
 
 static const char *const edit_usage[] =
 {
-    "Usage: %s %s [-lR] [files...]\n",
+    "Usage: %s %s [-cflR] [files...]\n",
+    "-c: Check that working files are unedited\n",
+    "-f: Force edit if working files are edited (default)\n",
     "-l: Local directory only, not recursive\n",
-    "-R: Process directories recursively\n",
+    "-R: Process directories recursively (default)\n",
     "-a: Specify what actions for temporary watch, one of\n",
     "    edit,unedit,commit,all,none\n",
     "(Specify the --help global option for a list of other help options)\n",
     NULL
 };
 
 int
 edit (argc, argv)
     int argc;
     char **argv;
 {
     int local = 0;
     int c;
-    int err;
+    int err = 0;
     int a_omitted;
 
     if (argc == -1)
 	usage (edit_usage);
 
     a_omitted = 1;
     setting_tedit = 0;
     setting_tunedit = 0;
     setting_tcommit = 0;
     optind = 0;
-    while ((c = getopt (argc, argv, "+lRa:")) != -1)
+    while ((c = getopt (argc, argv, "+cflRa:")) != -1)
     {
 	switch (c)
 	{
+            case 'c':
+                check_edited = 1;
+                break;
+            case 'f':
+                check_edited = 0;
+                break;
 	    case 'l':
 		local = 1;
 		break;
 	    case 'R':
 		local = 0;
 		break;
 	    case 'a':
 		a_omitted = 0;
@@ -424,41 +721,51 @@
     {
 	setting_tedit = 1;
 	setting_tunedit = 1;
 	setting_tcommit = 1;
     }
 
     /* No need to readlock since we aren't doing anything to the
        repository.  */
+    err = check_edits (argc, argv, local);
+    if (err)
+    {
+        error (1, 0, "files being edited!");
+    }
+    else
+    {
     err = start_recursion (edit_fileproc, (FILESDONEPROC) NULL,
 			   (DIRENTPROC) NULL, (DIRLEAVEPROC) NULL, NULL,
 			   argc, argv, local, W_LOCAL, 0, 0, (char *)NULL,
 			   0);
-
     err += send_notifications (argc, argv, local);
+    }
 
     return err;
 }
 
 static int unedit_fileproc PROTO ((void *callerdat, struct file_info *finfo));
 
 static int
 unedit_fileproc (callerdat, finfo)
     void *callerdat;
     struct file_info *finfo;
 {
     FILE *fp;
     time_t now;
     char *ascnow;
-    char *basefilename;
 
     if (noexec)
 	return 0;
 
+    if (unedit_editor == NULL)
+    {
+        char *basefilename = NULL;
+
     basefilename = xmalloc (10 + sizeof CVSADM_BASE + strlen (finfo->file));
     strcpy (basefilename, CVSADM_BASE);
     strcat (basefilename, "/");
     strcat (basefilename, 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.  */
@@ -473,46 +780,33 @@
 	{
 	    /* "no".  */
 	    free (basefilename);
 	    return 0;
 	}
     }
     rename_file (basefilename, finfo->file);
     free (basefilename);
-
-    fp = open_file (CVSADM_NOTIFY, "a");
-
-    (void) time (&now);
-    ascnow = asctime (gmtime (&now));
-    ascnow[24] = '\0';
-    fprintf (fp, "U%s\t%s GMT\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);
     }
 
+    notify_put (finfo, unedit_editor, 'U', 0, 0, 0);
+
     /* 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).  */
+    if (unedit_editor == NULL)
     {
 	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
@@ -542,27 +836,29 @@
 		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] [files...]\n",
-    "-l: Local directory only, not recursive\n",
-    "-R: Process directories recursively\n",
+    "Usage: %s %s [-lR] [-e editor] [files...]\n",
+    "\t-l\tLocal directory only, not recursive\n",
+    "\t-R\tProcess directories recursively\n",
+    "\t-e editor\tSpecify editor\n",
     "(Specify the --help global option for a list of other help options)\n",
     NULL
 };
 
 int
 unedit (argc, argv)
     int argc;
     char **argv;
@@ -570,20 +866,23 @@
     int local = 0;
     int c;
     int err;
 
     if (argc == -1)
 	usage (unedit_usage);
 
     optind = 0;
-    while ((c = getopt (argc, argv, "+lR")) != -1)
+    while ((c = getopt (argc, argv, "+lRe:")) != -1)
     {
 	switch (c)
 	{
+            case 'e':
+                unedit_editor = optarg;
+                break;
 	    case 'l':
 		local = 1;
 		break;
 	    case 'R':
 		local = 0;
 		break;
 	    case '?':
 	    default:
@@ -619,46 +918,55 @@
     strcat (base, "/");
     strcat (base, file);
     if (unlink_file (base) < 0 && ! existence_error (errno))
 	error (0, errno, "cannot remove %s", file);
     free (base);
 }
 
 
-void
+static void
 editor_set (filename, editor, val)
     char *filename;
     char *editor;
     char *val;
 {
     char *edlist;
     char *newlist;
 
+    if (unedit_editor != NULL)
+    {
+        editor = unedit_editor;
+    }
+
     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.  */
     char *type;
     /* User who is running the command which causes notification.  */
-    char *who;
+    char *notifier;
     /* User to be notified.  */
-    char *notifyee;
+    char *watcher;
+    /* Editor affected */
+    char *editor;
     /* File.  */
     char *file;
 };
 
 /* Pass as a static until we get around to fixing Parse_Info to pass along
    a void * where we can stash it.  */
 static struct notify_proc_args *notify_args;
 
@@ -673,27 +981,27 @@
     char *prog;
     char *expanded_prog;
     char *p;
     char *q;
     char *srepos;
     struct notify_proc_args *args = notify_args;
 
     srepos = Short_Repository (repository);
-    prog = xmalloc (strlen (filter) + strlen (args->notifyee) + 1);
+    prog = xmalloc (strlen (filter) + strlen (args->watcher) + 1);
     /* Copy FILTER to PROG, replacing the first occurrence of %s with
-       the notifyee.  We only allocated enough memory for one %s, and I doubt
+       the watcher.  We only allocated enough memory for one %s, and I doubt
        there is a need for more.  */
     for (p = filter, q = prog; *p != '\0'; ++p)
     {
 	if (p[0] == '%')
 	{
 	    if (p[1] == 's')
 	    {
-		strcpy (q, args->notifyee);
+		strcpy (q, args->watcher);
 		q += strlen (q);
 		strcpy (q, p + 2);
 		q += strlen (q);
 		break;
 	    }
 	    else
 		continue;
 	}
@@ -716,60 +1024,67 @@
 	error (0, errno, "cannot write entry to notify filter: %s", prog);
 	free (prog);
 	free (expanded_prog);
 	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);
+    fprintf (pipefp, "By %s\n", args->notifier);
+
+    if (args->editor != NULL  &&  strcmp (args->editor, "") != 0)
+    {
+        fprintf (pipefp, "For %s\n", args->editor);
+    }
 
     /* Lots more potentially useful information we could add here; see
        logfile_write for inspiration.  */
 
     free (prog);
     free (expanded_prog);
     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 (type, filename, who, val, watches, repository)
+notify_do (type, filename, who, editor, val, watches, repository)
     int type;
     char *filename;
     char *who;
+    char *editor;
     char *val;
     char *watches;
     char *repository;
 {
     static struct addremove_args blank;
     struct addremove_args args;
+    int edit_count = 0;
     char *watchers;
     char *p;
     char *endp;
     char *nextp;
 
     /* 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);
+	    editor_set (filename, editor, val);
 	    break;
 	case 'U':
 	case 'C':
-	    editor_set (filename, who, NULL);
+	    editor_set (filename, editor, NULL);
 	    break;
 	default:
 	    return;
     }
 
     watchers = fileattr_get0 (filename, "_watchers");
     p = watchers;
     while (p != NULL)
@@ -780,24 +1095,58 @@
 	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)
+	{
+            char *e = NULL;
+            char *editors = NULL;
+
+            editors = fileattr_get0 (filename, "_editors");
+            e = editors;
+            while (e != NULL)
 	{
-	    /* 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.  */
+                char *e_at = NULL;
+                char *e_end = NULL;
+                char *e_gt = NULL;
+
+                e_at = strchr (e, '@');
+                e_gt = strchr (e, '>');
+
+                e_end = (e_at != NULL)   ?   e_at   :   e_gt;
+
+                if ((size_t) (e_end - e) == strlen (who)  &&
+                    strncmp (who, e, e_end - e) == 0)
+                {
+                    ++edit_count;
+                }
+
+                e = strchr (e, ',');
+                if (e != NULL)
+                {
+                    ++e;
+                }
+            }
+
+            if (edit_count <= 1)
+            {
 	    p = nextp == NULL ? nextp : nextp + 1;
 	    continue;
 	}
 
+            if (editors != NULL)
+            {
+                free (editors);
+            }
+	}
+
 	/* 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))
@@ -854,17 +1203,17 @@
 	{
 	    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;
+	    args.watcher = NULL;
 	    usersname = xmalloc (strlen (CVSroot_directory)
 				 + sizeof CVSROOTADM
 				 + sizeof CVSROOTADM_USERS
 				 + 20);
 	    strcpy (usersname, CVSroot_directory);
 	    strcat (usersname, "/");
 	    strcat (usersname, CVSROOTADM);
 	    strcat (usersname, "/");
@@ -875,60 +1224,63 @@
 	    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);
+			args.watcher = 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");
+			cp = strpbrk (args.watcher, ":\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)
+	    if (args.watcher == NULL)
 	    {
-		args.notifyee = xmalloc (endp - p + 1);
-		strncpy (args.notifyee, p, endp - p);
-		args.notifyee[endp - p] = '\0';
+		args.watcher = xmalloc (endp - p + 1);
+		strncpy (args.watcher, p, endp - p);
+		args.watcher[endp - p] = '\0';
 	    }
 
 	    notify_args = &args;
 	    args.type = notif;
-	    args.who = who;
+	    args.notifier = who;
+        args.editor = editor;
 	    args.file = filename;
 
 	    (void) Parse_Info (CVSROOTADM_NOTIFY, repository, notify_proc, 1);
-	    free (args.notifyee);
+	    free (args.watcher);
 	}
 
 	p = nextp;
     }
     if (watchers != NULL)
 	free (watchers);
 
+    if (edit_count <= 1)
+    {
     switch (type)
     {
 	case 'E':
 	    if (*watches == 'E')
 	    {
 		args.add_tedit = 1;
 		++watches;
 	    }
@@ -944,16 +1296,17 @@
 	    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 (repository, update_dir)
     char *repository;
     char *update_dir;
@@ -1021,60 +1374,17 @@
 
 static int editors_fileproc PROTO ((void *callerdat, struct file_info *finfo));
 
 static int
 editors_fileproc (callerdat, finfo)
     void *callerdat;
     struct file_info *finfo;
 {
-    char *them;
-    char *p;
-
-    them = fileattr_get0 (finfo->file, "_editors");
-    if (them == NULL)
-	return 0;
-
-    cvs_output (finfo->fullname, 0);
-
-    p = them;
-    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);
-		goto out;
-	    }
-	    if (*p == ',')
-	    {
-		++p;
-		break;
-	    }
-	    ++p;
-	    cvs_output ("\t", 1);
-	}
-	cvs_output ("\n", 1);
-    }
-  out:;
-    return 0;
+    return editors_output (finfo);
 }
 
 int
 editors (argc, argv)
     int argc;
     char **argv;
 {
     int local = 0;
Index: edit.h
===================================================================
RCS file: /home/cvs-adm/.cvsroot/cvs/src/edit.h,v
retrieving revision 1.1.1.1
retrieving revision 1.3
diff -b -U8 -r1.1.1.1 -r1.3
--- edit.h	1999/12/27 13:46:33	1.1.1.1
+++ edit.h	2000/04/04 18:06:41	1.3
@@ -21,18 +21,14 @@
 #endif /* CLIENT_SUPPORT */
 
 /* Issue a notification for file FILENAME.  TYPE is 'E' for edit, 'U'
    for unedit, and 'C' for commit.  WHO is the user currently running.
    For TYPE 'E', VAL is the time+host+directory data which goes in
    _editors, and WATCHES is zero or more of E,U,C, in that order, to specify
    what kinds of temporary watches to set.  */
 extern void notify_do PROTO ((int type, char *filename, char *who,
+                              char *editor,
 			      char *val, char *watches, char *repository));
-
-/* Set attributes to reflect the fact that EDITOR is editing FILENAME.
-   VAL is time+host+directory, or NULL if we are to say that EDITOR is
-   *not* editing FILENAME.  */
-extern void editor_set PROTO ((char *filename, char *editor, char *val));
 
 /* Take note of the fact that FILE is up to date (this munges CVS/Base;
    processing of CVS/Entries is done separately).  */
 extern void mark_up_to_date PROTO ((char *file));
Index: lock.c
===================================================================
RCS file: /home/cvs-adm/.cvsroot/cvs/src/lock.c,v
retrieving revision 1.1.1.2
retrieving revision 1.3
diff -b -U8 -r1.1.1.2 -r1.3
Index: rcs.c
===================================================================
RCS file: /home/cvs-adm/.cvsroot/cvs/src/rcs.c,v
retrieving revision 1.1.1.2
retrieving revision 1.2
diff -b -U8 -r1.1.1.2 -r1.2
--- rcs.c	2000/01/05 16:35:46	1.1.1.2
+++ rcs.c	2000/04/04 18:06:42	1.2
@@ -6117,18 +6117,18 @@
     return 0;
 }
 
 /* Unlock revision REV.  UNLOCK_QUIET is 1 to suppress output.  FIXME:
    Like RCS_lock, this can become a no-op if we do the checkin
    ourselves.
 
    If REV is not null and is locked by someone else, break their
-   lock and notify them.  It is an open issue whether RCS_unlock
-   queries the user about whether or not to break the lock. */
+   lock.  It is an open issue whether RCS_unlock queries the user
+   about whether or not to break the lock. */
 
 int
 RCS_unlock (rcs, rev, unlock_quiet)
      RCSNode *rcs;
      const char *rev;
      int unlock_quiet;
 {
     Node *lock;
@@ -6196,30 +6196,16 @@
     lock = findnode (RCS_getlocks (rcs), xrev);
     if (lock == NULL)
     {
 	/* This revision isn't locked. */
 	free (xrev);
 	return 0;
     }
 
-    if (! STREQ (lock->data, user))
-    {
-        /* If the revision is locked by someone else, notify
-	   them.  Note that this shouldn't ever happen if RCS_unlock
-	   is called with a NULL revision, since that means "whatever
-	   revision is currently locked by the caller." */
-	char *repos, *workfile;
-	repos = xstrdup (rcs->path);
-	workfile = strrchr (repos, '/');
-	*workfile++ = '\0';
-	notify_do ('C', workfile, user, NULL, NULL, repos);
-	free (repos);
-    }
-
     delnode (lock);
     if (!unlock_quiet)
     {
 	cvs_output (xrev, 0);
 	cvs_output (" unlocked\n", 0);
     }
 
     free (xrev);
Index: server.c
===================================================================
RCS file: /home/cvs-adm/.cvsroot/cvs/src/server.c,v
retrieving revision 1.1.1.2
retrieving revision 1.2
diff -b -U8 -r1.1.1.2 -r1.2
--- server.c	1999/11/22 22:17:00	1.1.1.2
+++ server.c	2000/04/04 18:06:42	1.2
@@ -1402,17 +1402,17 @@
 	    }
 	}
 	if (filebuf == NULL)
 	{
 	    pending_error = ENOMEM;
 	    goto out;
 	}
 
-	if (gunzip_and_write (fd, file, filebuf, size))
+	if (gunzip_and_write (fd, file, (unsigned char *) filebuf, size))
 	{
 	    if (alloc_pending (80))
 		sprintf (pending_error_text,
 			 "E aborting due to compression error");
 	}
 	free (filebuf);
     }
     else
@@ -1878,30 +1878,154 @@
     if (f != NULL && fclose (f) == EOF && !error_pending ())
     {
 	pending_error = errno;
 	if (alloc_pending (80 + strlen (CVSADM_ENT)))
 	    sprintf (pending_error_text, "E cannot close %s", CVSADM_ENT);
     }
 }
 
+List *fileinfo_list = NULL;
+
+static void serve_fileinfo PROTO ((char *));
+
+static void
+serve_fileinfo (arg)
+     char *arg;
+{
+    char *data = NULL;
+    int status;
+
+    status = buf_read_line (buf_from_net, &data, (int *) NULL);
+    if (status != 0)
+    {
+	if (status == -2)
+        {
+	    pending_error = ENOMEM;
+        }
+	else
+	{
+	    pending_error_text = malloc (80 + strlen (arg));
+	    if (pending_error_text == NULL)
+            {
+		pending_error = ENOMEM;
+            }
+	    else
+	    {
+		if (status == -1)
+                {
+		    sprintf (
+                        pending_error_text,
+                        "E end of file reading notification for %s",
+                        arg);
+                }
+		else
+		{
+		    sprintf (
+                        pending_error_text,
+                        "E error reading notification for %s",
+                        arg);
+		    pending_error = status;
+		}
+	    }
+	}
+    }
+    else
+    {
+        char *cp = NULL;
+        char *cp0 = NULL;
+        struct logfile_info *li;
+        Node *p = NULL;
+
+        if (fileinfo_list == NULL)
+        {
+            fileinfo_list = getlist ();
+        }
+
+        p = getnode ();
+        p->key = xstrdup (arg);
+        p->type = UPDATE;
+        p->delproc = update_delproc;
+
+        li = (struct logfile_info *) xmalloc (sizeof (struct logfile_info));
+        li->type = T_UNKNOWN;
+
+        cp = data;
+
+        cp0 = cp;
+        cp = strchr (cp0, '\t');
+        if (cp == NULL)
+        {
+            goto error;
+        }
+        *cp++ = '\0';
+
+        li->hostname = xstrdup (cp0);
+
+        cp0 = cp;
+        cp = strchr (cp0, '\t');
+        if (cp != NULL)
+        {
+            *cp = '\0';
+        }
+
+        li->wd = xstrdup (cp0);
+        li->editor = NULL;
+
+        li->tag = NULL;
+        li->rev_old = NULL;
+        li->rev_new = NULL;
+
+        p->data = (char *) li;
+        (void) addnode (fileinfo_list, p);
+    }
+
+error:
+    return;
+}
+
+struct logfile_info *
+server_getfileinfo (filename)
+    char *filename;
+{
+    Node *p = NULL;
+
+    p = findnode (fileinfo_list, filename);
+    if (p != NULL)
+    {
+        return (struct logfile_info *) p->data;
+    }
+    else
+    {
+        return NULL;
+    }
+}
+
+void
+server_delfileinfolist (void)
+{
+    if (fileinfo_list != NULL)
+        dellist (&fileinfo_list);
+}
+
 struct notify_note {
     /* Directory in which this notification happens.  malloc'd*/
     char *dir;
 
     /* malloc'd.  */
     char *filename;
 
     /* The following three all in one malloc'd block, pointed to by TYPE.
        Each '\0' terminated.  */
     /* "E" or "U".  */
     char *type;
     /* time+host+dir */
     char *val;
     char *watches;
+    char *editor;
 
     struct notify_note *next;
 };
 
 static struct notify_note *notify_list;
 /* Used while building list, to point to the last node that already exists.  */
 static struct notify_note *last_node;
 
@@ -1991,20 +2115,50 @@
 	cp = strchr (cp, '\t');
 	if (cp == NULL)
 	    goto error;
 	*cp++ = '\0';
 	new->watches = cp;
 	/* If there is another tab, ignore everything after it,
 	   for future expansion.  */
 	cp = strchr (cp, '\t');
+	if (cp == NULL)
+        {
+            /* FIXME: Noel Yap (yap_noel@yahoo.com) 2000 Feb 28
+               This block is to support old clients.  It should be
+               removed once it's deemed that all clients send the
+               editor field.  IMHO, it should be safe to remove
+               by 2002 Feb 28 (ie in two years).
+            */
+            new->editor = getcaller();  /* to support old clients */
+        }
+        else
+	{
+	    *cp++ = '\0';
+            new->editor = cp;
+
+            /* If there is another tab, ignore everything after it,
+               for future expansion.  */
+            cp = strchr (cp, '\t');
+            if (cp != NULL)
+            {
+                *cp = '\0';
+            }
+
+            /* lob off extra val fields */
+            {
+                char *cp = NULL;
+
+                cp = strchr (new->val, '+');
 	if (cp != NULL)
 	{
 	    *cp = '\0';
 	}
+            }
+	}
 
 	new->next = NULL;
 
 	if (last_node == NULL)
 	{
 	    notify_list = new;
 	}
 	else
@@ -2039,16 +2193,17 @@
 	}
 	repos = Name_Repository (NULL, NULL);
 
 	lock_dir_for_write (repos);
 
 	fileattr_startdir (repos);
 
 	notify_do (*notify_list->type, notify_list->filename, getcaller(),
+                   notify_list->editor,
 		   notify_list->val, notify_list->watches, repos);
 
 	buf_output0 (buf_to_net, "Notified ");
 	{
 	    char *dir = notify_list->dir + strlen (server_temp_dir) + 1;
 	    if (dir[0] == '\0')
 	        buf_append_char (buf_to_net, '.');
 	    else
@@ -4012,17 +4167,17 @@
 	    }
 	}
 
 	sprintf (size_text, "%lu\n", size);
 	buf_output0 (protocol, size_text);
 
 	if (file != NULL)
 	{
-	    buf_output (protocol, file, file_used);
+	    buf_output (protocol, (const char *) file, file_used);
 	    free (file);
 	    file = NULL;
 	}
 	else if (filebuf == NULL)
 	    buf_append_data (protocol, list, last);
 	else
 	{
 	    buf_append_buffer (protocol, filebuf);
@@ -4562,16 +4717,17 @@
   REQ_LINE("Is-modified", serve_is_modified, 0),
 
   /* The client must send this request to interoperate with CVS 1.5
      through 1.9 servers.  The server must support it (although it can
      be and is a noop) to interoperate with CVS 1.5 to 1.9 clients.  */
   REQ_LINE("UseUnchanged", serve_enable_unchanged, RQ_ENABLEME | RQ_ROOTLESS),
 
   REQ_LINE("Unchanged", serve_unchanged, RQ_ESSENTIAL),
+  REQ_LINE("FileInfo", serve_fileinfo, 0),
   REQ_LINE("Notify", serve_notify, 0),
   REQ_LINE("Questionable", serve_questionable, 0),
   REQ_LINE("Case", serve_case, 0),
   REQ_LINE("Argument", serve_argument, RQ_ESSENTIAL),
   REQ_LINE("Argumentx", serve_argumentx, RQ_ESSENTIAL),
   REQ_LINE("Global_option", serve_global_option, 0),
   REQ_LINE("Gzip-stream", serve_gzip_stream, 0),
   REQ_LINE("wrapper-sendme-rcsOptions",
Index: server.h
===================================================================
RCS file: /home/cvs-adm/.cvsroot/cvs/src/server.h,v
retrieving revision 1.1.1.1
retrieving revision 1.2
diff -b -U8 -r1.1.1.1 -r1.2
--- server.h	1999/12/27 13:46:33	1.1.1.1
+++ server.h	2000/04/04 18:06:42	1.2
@@ -114,16 +114,19 @@
     PROTO((char *file, char *update_dir, char *repository,
 	   enum server_updated_arg4 updated));
 
 /* Pointer to a malloc'd string which is the directory which
    the server should prepend to the pathnames which it sends
    to the client.  */
 extern char *server_dir;
 
+extern struct logfile_info *server_getfileinfo PROTO((char *));
+extern void server_delfileinfolist PROTO((void));
+
 enum progs {PROG_CHECKIN, PROG_UPDATE};
 extern void server_prog PROTO((char *, char *, enum progs));
 extern void server_cleanup PROTO((int sig));
 
 #ifdef SERVER_FLOWCONTROL
 /* Pause if it's convenient to avoid memory blowout */
 extern void server_pause_check PROTO((void));
 #endif /* SERVER_FLOWCONTROL */
Index: update.c
===================================================================
RCS file: /home/cvs-adm/.cvsroot/cvs/src/update.c,v
retrieving revision 1.1.1.2
retrieving revision 1.2
diff -b -U8 -r1.1.1.2 -r1.2
--- update.c	2000/01/05 16:35:46	1.1.1.2
+++ update.c	2000/01/24 19:29:24	1.2
@@ -1369,32 +1369,16 @@
 		    /* We know that we are the server here, so
                        although xchmod checks umask, we don't bother.  */
 		    mode |= (((mode & S_IRUSR) ? S_IWUSR : 0)
 			     | ((mode & S_IRGRP) ? S_IWGRP : 0)
 			     | ((mode & S_IROTH) ? S_IWOTH : 0));
 		}
 	    }
 
-	    {
-		/* A newly checked out file is never under the spell
-		   of "cvs edit".  If we think we were editing it
-		   from a previous life, clean up.  Would be better to
-		   check for same the working directory instead of
-		   same user, but that is hairy.  */
-
-		struct addremove_args args;
-
-		editor_set (finfo->file, getcaller (), NULL);
-
-		memset (&args, 0, sizeof args);
-		args.remove_temp = 1;
-		watch_modify_watchers (finfo->file, &args);
-	    }
-
 	    /* set the time from the RCS file iff it was unknown before */
 	    set_time =
 		(!noexec
 		 && (vers_ts->vn_user == NULL ||
 		     strncmp (vers_ts->ts_rcs, "Initial", 7) == 0)
 		 && !file_is_dead);
 
 	    wrap_fromcvs_process_file (finfo->file);
Index: version.c
===================================================================
RCS file: /home/cvs-adm/.cvsroot/cvs/src/version.c,v
retrieving revision 1.1.1.2
retrieving revision 1.11
diff -b -U8 -r1.1.1.2 -r1.11
--- version.c	2000/01/18 01:46:49	1.1.1.2
+++ version.c	2000/04/14 14:19:59	1.11
@@ -7,17 +7,17 @@
  * You may distribute under the terms of the GNU General Public License as
  * specified in the README file that comes with this  CVS source distribution.
  * 
  * version.c - the CVS version number
  */
 
 #include "cvs.h"
 
-char *version_string = "\nConcurrent Versions System (CVS) 1.10.8";
+char *version_string = "\nConcurrent Versions System (CVS) 1.10.8.8";
 
 #ifdef CLIENT_SUPPORT
 #ifdef SERVER_SUPPORT
 char *config_string = " (client/server)\n";
 #else
 char *config_string = " (client)\n";
 #endif
 #else
