? contrib/Makefile
? contrib/rcs2log
? contrib/cln_hist
? contrib/cvs2vendor
? contrib/clmerge
? contrib/log_accum
? contrib/commit_prep
? contrib/cvscheck
? contrib/cvs_acls
? contrib/mfpipe
? contrib/rcs-to-cvs
? contrib/log
? contrib/sccs2rcs
? contrib/rcslock
? diff/Makefile
? diff/.pure
? doc/Makefile
? lib/Makefile
? lib/.pure
? man/Makefile
? src/Makefile
? src/cvs
? src/options.h
? src/options.h-SAVED
? src/cvsbug
? src/todo
? src/.pure
? tools/Makefile
? vms/Makefile
? windows-NT/Makefile
? windows-NT/SCC/Makefile
? zlib/Makefile
? zlib/.pure
Index: src/ChangeLog
===================================================================
RCS file: /home/cvs-adm/.cvsroot/cvs/src/ChangeLog,v
retrieving revision 1.1.1.2
diff -b -U16 -r1.1.1.2 ChangeLog
--- src/ChangeLog	2000/01/17 18:04:22	1.1.1.2
+++ src/ChangeLog	2000/04/04 17:52:52
@@ -1,16 +1,71 @@
+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
 	avoidance code.
 	* commit.c (commit): Ditto.
 	* update.c (do_update): Ditto.
 	(Prompted by patch submitted by Pavel Roskin
 	<pavel_roskin@geocities.com>.)
 
 	* hardlink.c: sizeof (char) is 1, by definition.
 	* logmsg.c: Ditto.
@@ -62,32 +117,50 @@
 	set, then back the file up and then check out a fresh copy from
 	the repository.  Also, fixed indentation and formatting for a
 	particularly bad stretch of code near (but unrelated to) these
 	changes.
 
 	* sanity.sh: new test `clean', for update -C option.
 	
 1999-12-29  Jim Kingdon  <http://developer.redhat.com/>
 
 	* 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.
 	(fill_hrec): Remove redundant code.
 	(select_hrec): Plug memory leak.
 
 1999-12-22  Larry Jones <larry.jones@sdrc.com>
 
 	* history.c (history): For "modified" or "checkout", sort on
 	file if user specified -l, even if user also specified a date-
 	oriented flag.
Index: src/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 -U16 -r1.1.1.2 -r1.3
--- src/client.c	2000/01/05 16:35:45	1.1.1.2
+++ src/client.c	2000/01/19 21:02:36	1.3
@@ -5762,19 +5762,33 @@
 client_senddate (date)
     const char *date;
 {
     char buf[MAXDATELEN];
 
     date_to_internet (buf, (char *)date);
     option_with_arg ("-D", buf);
 }
 
 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: src/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 -U16 -r1.1.1.2 -r1.3
--- src/client.h	2000/01/03 03:21:29	1.1.1.2
+++ src/client.h	2000/01/19 21:02:36	1.3
@@ -185,17 +185,20 @@
 extern void client_send_expansions PROTO((int local, char *where,
 					  int build_dirs));
 extern void client_nonexpanded_setup PROTO((void));
 
 extern void send_init_command PROTO ((void));
 
 extern char **failed_patches;
 extern int failed_patches_count;
 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: src/commit.c
===================================================================
RCS file: /home/cvs-adm/.cvsroot/cvs/src/commit.c,v
retrieving revision 1.1.1.2
diff -b -U16 -r1.1.1.2 commit.c
--- src/commit.c	2000/01/05 16:35:46	1.1.1.2
+++ src/commit.c	2000/04/04 17:52:52
@@ -279,32 +279,37 @@
            modified.  */
 	status = T_MODIFIED;
     else
     {
 	/* This covers unmodified files, as well as a variety of other
 	   cases.  FIXME: we probably should be printing a message and
 	   returning 1 for many of those cases (but I'm not sure
 	   exactly which ones).  */
 	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);
 
     ++args->argc;
 
     freevers_ts (&vers);
     return 0;
 }
 
 static int copy_ulist PROTO ((Node *, void *));
 
@@ -544,61 +549,117 @@
 		    send_to_server (p->repos, 0);
 		    send_to_server ("\012", 1);
 
 		    send_to_server ("Questionable ", 0);
 		    send_to_server (p->file, 0);
 		    send_to_server ("\012", 1);
 		}
 		free (p->dir);
 		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 (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
 	   say that files are modified when they are not.  This makes
 	   "cvs commit -r 2" across a whole bunch of files a very slow
 	   operation (and it isn't documented in cvsclient.texi).  I
 	   haven't looked at the server code carefully enough to be
 	   _sure_ why this is needed, but if it is because the "ci"
 	   program, which we used to call, wanted the file to exist,
 	   then it would be relatively simple to fix in the server.  */
 	send_files (find_args.argc, find_args.argv, local, 0,
 		    find_args.force ? SEND_FORCE : 0);
 
 	/* Sending only the names of the files which were modified, added,
 	   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,
 	       another error could occur, &c.  Also, a second check would
 	       slow things down).  */
 
 	    char *fname;
 	    FILE *fp;
 
 	    fname = cvs_temp_name ();
 	    fp = CVS_FOPEN (fname, "w+");
@@ -643,32 +704,34 @@
 	working_dir = xgetwd();
     }
 #endif
 
     /*
      * Run the recursion processor to verify the files are all up-to-date
      */
     err = start_recursion (check_fileproc, check_filesdoneproc,
 			   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!");
     }
 
+    server_delfileinfolist ();
+
     /*
      * 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,
 			       (char *) NULL, 1);
 
     /*
      * Unlock all the dirs and clean up
      */
     Lock_Cleanup ();
     dellist (&mulist);
 
@@ -815,35 +878,39 @@
      */
     if (force_ci && status == T_UPTODATE)
 	status = T_MODIFIED;
 
     switch (status)
     {
 	case T_CHECKOUT:
 #ifdef SERVER_SUPPORT
 	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:
+        {
+            struct logfile_info *client_fileinfo = NULL;
+
 	    /*
 	     * 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
 	     *	- if status is T_ADDED, can't have a non-trunk numeric rev
 	     *	- if status is T_MODIFIED and a Conflict marker exists, don't
 	     *    allow the commit if timestamp is identical or if we find
 	     *    an RCS_MERGE_PAT in the file.
 	     */
 	    if (!saved_tag || !isdigit ((unsigned char) *saved_tag))
 	    {
 		if (vers->date)
@@ -991,51 +1058,131 @@
 		    xmalloc (sizeof (struct master_lists));
 		ml->ulist = ulist;
 		ml->cilist = cilist;
 		p->data = (char *) ml;
 		p->delproc = masterlist_delproc;
 		(void) addnode (mulist, p);
 	    }
 
 	    /* 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;
+
+            client_fileinfo = server_getfileinfo (finfo->fullname);
+            if (client_fileinfo == NULL)
+            {
+                li->repository = NULL;
+                li->hostname = NULL;
+                li->wd = NULL;
+                li->editor = NULL;
+            }
+            else
+            {
+                char *editors = NULL;
+
+                li->repository = xstrdup (client_fileinfo->repository);
+                li->hostname = xstrdup (client_fileinfo->hostname);
+                li->wd = xstrdup (client_fileinfo->wd);
+
+                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, ',');
+                        if (p == NULL)
+                        {
+                            break;
+                        }
+                        ++p;
+                        p0 = p;
+                    }
+
+                    if (strcmp (editor, p0) == 0)
+                    {
+                        li->editor = editor;
+                    }
+                    else
+                    {
+                        li->editor = NULL;
+                        free (editor);
+                    }
+
+                    free (editors);
+                }
+            }
+
 	    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)
 	    {
 		/* Add this file to hardlist, indexed on its inode.  When
 		   we are done, we can find out what files are hardlinked
 		   to a given file by looking up its inode in hardlist. */
 		char *fullpath;
 		Node *linkp;
 		struct hardlink_info *hlinfo;
 
 		/* Get the full pathname of the current file. */
@@ -1051,32 +1198,34 @@
 		   we're doing a remove operation? */
 		if (linkp != NULL)
 		{
 		    /* Create a new hardlink_info node, which will record
 		       the current file's status and the links listed in its
 		       `hardlinks' delta field.  We will append this
 		       hardlink_info node to the appropriate hardlist entry. */
 		    hlinfo = (struct hardlink_info *)
 			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);
 	    break;
     }
 
     freevers_ts (&vers);
     return (0);
 }
 
 /*
@@ -1351,33 +1500,71 @@
 	    server_scratch_entry_only ();
 	    server_updated (finfo,
 			    NULL,
 
 			    /* Doesn't matter, it won't get checked.  */
 			    SERVER_UPDATED,
 
 			    (mode_t) -1,
 			    (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 CLIENT_SUPPORT
+        if (client_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);
     }
     else
     {
 	/* On success, retrieve the new version number of the file and
            copy it into the log information (see logmsg.c
            (logfile_write) for more details).  We should only update
            the version number for files that have been added or
            modified but not removed.  Why?  classify_file_internal
@@ -2282,32 +2469,40 @@
 
     if (branch)
 	free (branch);
     return (1);
 }
 
 /*
  * free an UPDATE node's data
  */
 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);
 }
 
 /*
  * Free the commit_info structure in p.
  */
 static void
 ci_delproc (p)
     Node *p;
 {
Index: src/cvs.h
===================================================================
RCS file: /home/cvs-adm/.cvsroot/cvs/src/cvs.h,v
retrieving revision 1.1.1.2
diff -b -U16 -r1.1.1.2 cvs.h
--- src/cvs.h	2000/01/03 20:49:12	1.1.1.2
+++ src/cvs.h	2000/04/04 17:52:52
@@ -778,32 +778,36 @@
 #endif
     T_TITLE				/* title for node type 		 */
 };
 typedef enum classify_type Ctype;
 
 Ctype Classify_File PROTO
     ((struct file_info *finfo, char *tag, char *date, char *options,
       int force_tag_match, int aflag, Vers_TS **versp, int pipeout));
 
 /*
  * 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.  */
 
 typedef enum { WRAP_MERGE, WRAP_COPY } WrapMergeMethod;
 typedef enum {
     /* -t and -f wrapper options.  Treating directories as single files.  */
     WRAP_TOCVS,
     WRAP_FROMCVS,
     /* -k wrapper option.  Default keyword expansion options.  */
     WRAP_RCSOPTION
Index: src/edit.c
===================================================================
RCS file: /home/cvs-adm/.cvsroot/cvs/src/edit.c,v
retrieving revision 1.1.1.2
diff -b -U16 -r1.1.1.2 edit.c
--- src/edit.c	1999/11/19 22:55:47	1.1.1.2
+++ src/edit.c	2000/04/04 17:52:52
@@ -3,34 +3,37 @@
    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 "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));
 
 static int
 onoff_fileproc (callerdat, finfo)
     void *callerdat;
     struct file_info *finfo;
 {
     fileattr_set (finfo->file, "_watched", turning_on ? "" : NULL);
     return 0;
@@ -141,32 +144,33 @@
 }
 
 static int ncheck_fileproc PROTO ((void *callerdat, struct file_info *finfo));
 
 /* 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 (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;
 
     /* 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))
@@ -185,47 +189,51 @@
 	    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');
+	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));
 
 /* Look through the CVSADM_NOTIFY file and process each item there
    accordingly.  */
 static int
 send_notifications (argc, argv, local)
     int argc;
     char **argv;
     int local;
 {
@@ -256,145 +264,434 @@
     }
     else
 #endif
     {
 	/* Local.  */
 
 	lock_tree_for_write (argc, argv, local, 0);
 	err += start_recursion (ncheck_fileproc, (FILESDONEPROC) NULL,
 				(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
        trying to create the output file fails.  But copy_file isn't
        set up to facilitate that.  */
     mkdir_if_needed (CVSADM_BASE);
     basefilename = xmalloc (10 + sizeof CVSADM_BASE + strlen (finfo->file));
     strcpy (basefilename, CVSADM_BASE);
     strcat (basefilename, "/");
     strcat (basefilename, finfo->file);
     copy_file (finfo->file, basefilename);
     free (basefilename);
 
     {
 	Node *node;
 
 	node = findnode_fn (finfo->entries, finfo->file);
 	if (node != NULL)
 	    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;
 		if (strcmp (optarg, "edit") == 0)
 		    setting_tedit = 1;
 		else if (strcmp (optarg, "unedit") == 0)
 		    setting_tunedit = 1;
 		else if (strcmp (optarg, "commit") == 0)
 		    setting_tcommit = 1;
 		else if (strcmp (optarg, "all") == 0)
 		{
@@ -416,111 +713,108 @@
 		usage (edit_usage);
 		break;
 	}
     }
     argc -= optind;
     argv += optind;
 
     if (a_omitted)
     {
 	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.  */
 	free (basefilename);
 	return 0;
     }
 
     if (xcmp (finfo->file, basefilename) != 0)
     {
 	printf ("%s has been modified; revert changes? ", finfo->fullname);
 	if (!yesno ())
 	{
 	    /* "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
 	   something, but I don't want to think about it too hard right
 	   now.  */
 	if (node != NULL)
 	{
 	    entdata = (Entnode *) node->data;
 	    if (baserev == NULL)
 	    {
 		/* This can only happen if the CVS/Baserev file got
@@ -534,64 +828,69 @@
 		   keeping it around would be asking for trouble.  */
 		if (unlink_file (finfo->file) < 0)
 		    error (0, errno, "cannot remove %s", finfo->fullname);
 
 		/* This is cheesy, in a sense; why shouldn't we do the
 		   update for the user?  However, doing that would require
 		   contacting the server, so maybe this is OK.  */
 		error (0, 0, "run update to complete the unedit");
 		return 0;
 	    }
 	    Register (finfo->entries, finfo->file, baserev, entdata->timestamp,
 		      entdata->options, entdata->tag, entdata->date,
 		      entdata->conflict);
 	}
 	free (baserev);
 	base_deregister (finfo);
-    }
 
     xchmod (finfo->file, 0);
+    }
+
     return 0;
 }
 
 static const char *const unedit_usage[] =
 {
-    "Usage: %s %s [-lR] [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;
 {
     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:
 		usage (unedit_usage);
 		break;
 	}
     }
     argc -= optind;
     argv += optind;
 
     /* No need to readlock since we aren't doing anything to the
@@ -611,201 +910,251 @@
     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 = xmalloc (strlen (file) + 80);
     strcpy (base, CVSADM_BASE);
     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;
 
 static int notify_proc PROTO ((char *repository, char *filter));
 
 static int
 notify_proc (repository, filter)
     char *repository;
     char *filter;
 {
     FILE *pipefp;
     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;
 	}
 	*q++ = *p;
     }
     *q = '\0';
 
     /* FIXME: why are we calling expand_proc?  Didn't we already
        expand it in Parse_Info, before passing it to notify_proc?  */
     expanded_prog = expand_path (prog, "notify", 0);
     if (!expanded_prog)
     {
 	free (prog);
 	return 1;
     }
 
     pipefp = run_popen (expanded_prog, "w");
     if (pipefp == NULL)
     {
 	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)
     {
 	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)
+	{
+            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))
 	    {
 		if (nextp == NULL)
 		    endq = q + strlen (q);
 		else
 		    endq = nextp;
 		nextq = NULL;
 	    }
 	    else
@@ -846,122 +1195,126 @@
 		    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;
+	    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, "/");
 	    strcat (usersname, 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);
+			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;
 	    }
 	    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 (repository, update_dir)
     char *repository;
     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
@@ -1013,76 +1366,33 @@
 static const char *const editors_usage[] =
 {
     "Usage: %s %s [-lR] [files...]\n",
     "\t-l\tProcess this directory only (not recursive).\n",
     "\t-R\tProcess directories recursively.\n",
     "(Specify the --help global option for a list of other help options)\n",
     NULL
 };
 
 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;
     int c;
 
     if (argc == -1)
 	usage (editors_usage);
 
     optind = 0;
     while ((c = getopt (argc, argv, "+lR")) != -1)
     {
Index: src/edit.h
===================================================================
RCS file: /home/cvs-adm/.cvsroot/cvs/src/edit.h,v
retrieving revision 1.1.1.1
diff -b -U16 -r1.1.1.1 edit.h
--- src/edit.h	1999/12/27 13:46:33	1.1.1.1
+++ src/edit.h	2000/04/04 17:52:52
@@ -13,26 +13,22 @@
 extern int watch_on PROTO ((int argc, char **argv));
 extern int watch_off PROTO ((int argc, char **argv));
 
 #ifdef CLIENT_SUPPORT
 /* Check to see if any notifications are sitting around in need of being
    sent.  These are the notifications stored in CVSADM_NOTIFY (edit,unedit);
    commit calls notify_do directly.  */
 extern void notify_check PROTO ((char *repository, char *update_dir));
 #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: src/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 -U16 -r1.1.1.2 -r1.3
Index: src/rcs.c
===================================================================
RCS file: /home/cvs-adm/.cvsroot/cvs/src/rcs.c,v
retrieving revision 1.1.1.2
diff -b -U16 -r1.1.1.2 rcs.c
--- src/rcs.c	2000/01/05 16:35:46	1.1.1.2
+++ src/rcs.c	2000/04/04 17:52:52
@@ -6109,34 +6109,34 @@
     (void) addnode_at_front (locks, p);
 
     if (!lock_quiet)
     {
 	cvs_output (xrev, 0);
 	cvs_output (" locked\n", 0);
     }
 
     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;
     List *locks;
     char *user;
     char *xrev = NULL;
 
     user = getcaller();
     if (rcs->flags & PARTIAL)
 	RCS_reparsercsfile (rcs, (FILE **) NULL, (struct rcsbuffer *) NULL);
 
@@ -6188,46 +6188,32 @@
 	    error (0, 0, "%s: branch %s absent", rcs->path, rev);
 	    return 1;
 	}
     }
     else
 	/* REV is an exact revision number. */
 	xrev = xstrdup (rev);
 
     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);
     return 0;
 }
 
 /* Add USER to the access list of RCS.  Do nothing if already present.
    FIXME-twp: check syntax of USER to make sure it's a valid id. */
 
 void
 RCS_addaccess (rcs, user)
Index: src/server.c
===================================================================
RCS file: /home/cvs-adm/.cvsroot/cvs/src/server.c,v
retrieving revision 1.1.1.2
diff -b -U16 -r1.1.1.2 server.c
--- src/server.c	1999/11/22 22:17:00	1.1.1.2
+++ src/server.c	2000/04/04 17:52:52
@@ -1394,33 +1394,33 @@
 	    }
 
 	    toread -= nread;
 
 	    if (filebuf != NULL)
 	    {
 		memcpy (p, data, nread);
 		p += nread;
 	    }
 	}
 	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
 	receive_partial_file (size, fd);
 
     if (pending_error_text)
     {
 	char *p = realloc (pending_error_text,
 			   strlen (pending_error_text) + strlen (arg) + 30);
 	if (p)
 	{
@@ -1870,46 +1870,170 @@
 	    }
 	}
 	free (p->entry);
 	q = p->next;
 	free (p);
 	p = q;
     }
     entries = NULL;
     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;
 
 static void serve_notify PROTO ((char *));
 
 static void
 serve_notify (arg)
     char *arg;
 {
     struct notify_note *new;
     char *data;
@@ -1983,36 +2107,66 @@
 	cp = strchr (cp, '\t');
 	if (cp == NULL)
 	    goto error;
 	*cp++ = '+';
 	cp = strchr (cp, '\t');
 	if (cp == NULL)
 	    goto error;
 	*cp++ = '+';
 	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
 	    last_node->next = new;
 	last_node = new;
     }
     return;
   error:
     pending_error_text = malloc (80);
     if (pending_error_text)
 	strcpy (pending_error_text,
@@ -2031,32 +2185,33 @@
     char *repos;
 
     while (notify_list != NULL)
     {
 	if ( CVS_CHDIR (notify_list->dir) < 0)
 	{
 	    error (0, errno, "cannot change to %s", notify_list->dir);
 	    return -1;
 	}
 	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
 	        buf_output0 (buf_to_net, dir);
 	    buf_append_char (buf_to_net, '/');
 	    buf_append_char (buf_to_net, '\n');
 	}
 	buf_output0 (buf_to_net, repos);
 	buf_append_char (buf_to_net, '/');
 	buf_output0 (buf_to_net, notify_list->filename);
 	buf_append_char (buf_to_net, '\n');
@@ -4004,33 +4159,33 @@
 		status = buf_read_file (f, size, &list, &last);
 		if (status == -2)
 		    (*protocol->memory_error) (protocol);
 		else if (status != 0)
 		    error (1, ferror (f) ? errno : 0, "reading %s",
 			   finfo->fullname);
 		if (fclose (f) == EOF)
 		    error (1, errno, "reading %s", finfo->fullname);
 	    }
 	}
 
 	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);
 	    buf_free (filebuf);
 	}
 	/* Note we only send a newline here if the file ended with one.  */
 
 	/*
 	 * Avoid using up too much disk space for temporary files.
 	 * A file which does not exist indicates that the file is up-to-date,
 	 * which is now the case.  If this is SERVER_MERGED, the file is
@@ -4554,32 +4709,33 @@
   REQ_LINE("Static-directory", serve_static_directory, 0),
   REQ_LINE("Sticky", serve_sticky, 0),
   REQ_LINE("Checkin-prog", serve_checkin_prog, 0),
   REQ_LINE("Update-prog", serve_update_prog, 0),
   REQ_LINE("Entry", serve_entry, RQ_ESSENTIAL),
   REQ_LINE("Kopt", serve_kopt, 0),
   REQ_LINE("Checkin-time", serve_checkin_time, 0),
   REQ_LINE("Modified", serve_modified, RQ_ESSENTIAL),
   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",
            serve_wrapper_sendme_rcs_options,
            0),
   REQ_LINE("Set", serve_set, RQ_ROOTLESS),
 #ifdef ENCRYPTION
 #  ifdef HAVE_KERBEROS
   REQ_LINE("Kerberos-encrypt", serve_kerberos_encrypt, 0),
 #  endif
 #  ifdef HAVE_GSSAPI
Index: src/server.h
===================================================================
RCS file: /home/cvs-adm/.cvsroot/cvs/src/server.h,v
retrieving revision 1.1.1.1
diff -b -U16 -r1.1.1.1 server.h
--- src/server.h	1999/12/27 13:46:33	1.1.1.1
+++ src/server.h	2000/04/04 17:52:52
@@ -106,32 +106,35 @@
 
 /* Set or clear a per-directory sticky tag or date.  */
 extern void server_set_sticky PROTO((char *update_dir, char *repository,
 				     char *tag, char *date, int nonbranch));
 /* Send Template response.  */
 extern void server_template PROTO ((char *, char *));
 
 extern void server_update_entries
     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 */
 
 #ifdef AUTH_SERVER_SUPPORT
 extern char *CVS_Username;
 extern int system_auth;
 #endif /* AUTH_SERVER_SUPPORT */
 
 #endif /* SERVER_SUPPORT */
 
Index: src/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 -U16 -r1.1.1.2 -r1.2
--- src/update.c	2000/01/05 16:35:46	1.1.1.2
+++ src/update.c	2000/01/24 19:29:24	1.2
@@ -1361,48 +1361,32 @@
 	    if (cvswrite
 		&& !file_is_dead
 		&& !fileattr_get (finfo->file, "_watched"))
 	    {
 		if (revbuf == NULL)
 		    xchmod (finfo->file, 1);
 		else
 		{
 		    /* 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);
 
 	    xvers_ts = Version_TS (finfo, options, tag, date, 
 				   force_tag_match, set_time);
 	    if (strcmp (xvers_ts->options, "-V4") == 0)
 		xvers_ts->options[0] = '\0';
 
 	    if (revbuf != NULL)
 	    {
Index: src/version.c
===================================================================
RCS file: /home/cvs-adm/.cvsroot/cvs/src/version.c,v
retrieving revision 1.1.1.2
diff -b -U16 -r1.1.1.2 version.c
--- src/version.c	2000/01/18 01:46:49	1.1.1.2
+++ src/version.c	2000/04/04 17:52:52
@@ -1,29 +1,29 @@
 /*
  * Copyright (c) 1994 david d `zoo' zuhn
  * Copyright (c) 1994 Free Software Foundation, Inc.
  * Copyright (c) 1992, Brian Berliner and Jeff Polk
  * Copyright (c) 1989-1992, Brian Berliner
  * 
  * You may distribute under the terms of the GNU General Public License as
  * specified in the README file that comes with 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.6";
 
 #ifdef CLIENT_SUPPORT
 #ifdef SERVER_SUPPORT
 char *config_string = " (client/server)\n";
 #else
 char *config_string = " (client)\n";
 #endif
 #else
 #ifdef SERVER_SUPPORT
 char *config_string = " (server)\n";
 #else
 char *config_string = "\n";
 #endif
 #endif
