? enh-multiple_edits.diff
? vms/Makefile
Index: lib/ChangeLog
===================================================================
RCS file: /home/nyap/.cvsroot/cvs/lib/ChangeLog,v
retrieving revision 1.1.1.1
retrieving revision 1.1.1.1.4.1
diff -C16 -w -r1.1.1.1 -r1.1.1.1.4.1
*** lib/ChangeLog	2001/03/06 16:21:21	1.1.1.1
--- lib/ChangeLog	2001/03/06 22:47:16	1.1.1.1.4.1
***************
*** 1,16 ****
--- 1,25 ----
+ 2001-03-06  Noel Yap <yap_noel@yahoo.com>
+ 
+ 	* stripslash.c (strip_trailing_slashes)
+ 	Changed to use ISDIRSEP().
+ 
+ 	* system.h (ISDIRSEP)
+ 	Changed on NT to check for '\\' as well.
+ 	DIRSEP added to be the typical directory separator string.
+ 
  2000-07-10  Larry Jones  <larry.jones@sdrc.com>
  
  	* savecwd.c: #include <sys/types.h> before <fcntl.h>.
  
  2000-07-04  Karl Fogel  <kfogel@red-bean.com>
  
  	* getline.h, getline.c (getstr): take new limit arg.
  	(GETLINE_NO_LIMIT): new #define.
  	(getline_safe): new function, takes limit arg and passes it on.
  	(getline): pass GETLINE_NO_LIMIT to getstr().
  
  	See related change of same date in ../src/ChangeLog.
  
  2000-06-19  Larry Jones  <larry.jones@sdrc.com>
  
  	* regex.c, regex.h: Version from emacs 20.7 to plug memory leaks
Index: lib/stripslash.c
===================================================================
RCS file: /home/nyap/.cvsroot/cvs/lib/stripslash.c,v
retrieving revision 1.1.1.1
retrieving revision 1.1.1.1.4.1
diff -C16 -w -r1.1.1.1 -r1.1.1.1.4.1
*** lib/stripslash.c	2001/03/06 16:21:21	1.1.1.1
--- lib/stripslash.c	2001/03/06 22:47:16	1.1.1.1.4.1
***************
*** 2,40 ****
     Copyright (C) 1990 Free Software Foundation, Inc.
  
     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.  */
  
  #ifdef HAVE_CONFIG_H
  #include "config.h"
  #endif
  
  #if STDC_HEADERS || HAVE_STRING_H
  #include <string.h>
  /* An ANSI string.h and pre-ANSI memory.h might conflict. */
  #if !STDC_HEADERS && HAVE_MEMORY_H
  #include <memory.h>
  #endif /* not STDC_HEADERS and HAVE_MEMORY_H */
  #else /* not STDC_HJEADERS and not HAVE_STRING_H */
  #include <strings.h>
  /* memory.h and strings.h conflict on some systems. */
  #endif /* not STDC_HEADERS and not HAVE_STRING_H */
  
  /* Remove trailing slashes from PATH. */
  
  void
  strip_trailing_slashes (path)
       char *path;
  {
    int last;
  
    last = strlen (path) - 1;
!   while (last > 0 && path[last] == '/')
      path[last--] = '\0';
  }
--- 2,42 ----
     Copyright (C) 1990 Free Software Foundation, Inc.
  
     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.  */
  
  #ifdef HAVE_CONFIG_H
  #include "config.h"
  #endif
  
+ #include "system.h"
+ 
  #if STDC_HEADERS || HAVE_STRING_H
  #include <string.h>
  /* An ANSI string.h and pre-ANSI memory.h might conflict. */
  #if !STDC_HEADERS && HAVE_MEMORY_H
  #include <memory.h>
  #endif /* not STDC_HEADERS and HAVE_MEMORY_H */
  #else /* not STDC_HJEADERS and not HAVE_STRING_H */
  #include <strings.h>
  /* memory.h and strings.h conflict on some systems. */
  #endif /* not STDC_HEADERS and not HAVE_STRING_H */
  
  /* Remove trailing slashes from PATH. */
  
  void
  strip_trailing_slashes (path)
       char *path;
  {
    int last;
  
    last = strlen (path) - 1;
!   while (last > 0 && ISDIRSEP (path[last]))
      path[last--] = '\0';
  }
Index: lib/system.h
===================================================================
RCS file: /home/nyap/.cvsroot/cvs/lib/system.h,v
retrieving revision 1.1.1.1
retrieving revision 1.1.1.1.4.1
diff -C16 -w -r1.1.1.1 -r1.1.1.1.4.1
*** lib/system.h	2001/03/06 16:21:21	1.1.1.1
--- lib/system.h	2001/03/06 22:47:16	1.1.1.1.4.1
***************
*** 467,527 ****
  /* Wildcard matcher.  Should be case-insensitive if the system is.  */
  #ifndef CVS_FNMATCH
  #define CVS_FNMATCH fnmatch
  #endif
  
  #if defined (__CYGWIN32__) || defined (WIN32)
  
  /* Under Windows NT, filenames are case-insensitive, and both / and \
     are path component separators.  */
  
  #define FOLD_FN_CHAR(c) (WNT_filename_classes[(unsigned char) (c)])
  extern unsigned char WNT_filename_classes[];
  #define FILENAMES_CASE_INSENSITIVE 1
  
  /* Is the character C a path name separator?  Under
     Windows NT, you can use either / or \.  */
! #define ISDIRSEP(c) (FOLD_FN_CHAR(c) == '/')
  
  /* Like strcmp, but with the appropriate tweaks for file names.
     Under Windows NT, filenames are case-insensitive but case-preserving,
     and both \ and / are path element separators.  */
  extern int fncmp (const char *n1, const char *n2);
  
  /* Fold characters in FILENAME to their canonical forms.  
     If FOLD_FN_CHAR is not #defined, the system provides a default
     definition for this.  */
  extern void fnfold (char *FILENAME);
  
  #endif /* defined (__CYGWIN32__) || defined (WIN32) */
  
  /* Some file systems are case-insensitive.  If FOLD_FN_CHAR is
     #defined, it maps the character C onto its "canonical" form.  In a
     case-insensitive system, it would map all alphanumeric characters
     to lower case.  Under Windows NT, / and \ are both path component
     separators, so FOLD_FN_CHAR would map them both to /.  */
  #ifndef FOLD_FN_CHAR
  #define FOLD_FN_CHAR(c) (c)
  #define fnfold(filename) (filename)
  #define fncmp strcmp
  #endif
  
  /* Different file systems have different path component separators.
     For the VMS port we might need to abstract further back than this.  */
  #ifndef ISDIRSEP
  #define ISDIRSEP(c) ((c) == '/')
  #endif
  
  
  /* On some systems, we have to be careful about writing/reading files
     in text or binary mode (so in text mode the system can handle CRLF
     vs. LF, VMS text file conventions, &c).  We decide to just always
     be careful.  That way we don't have to worry about whether text and
     binary differ on this system.  We just have to worry about whether
     the system has O_BINARY and "rb".  The latter is easy; all ANSI C
     libraries have it, SunOS4 has it, and CVS has used it unguarded
     some places for a while now without complaints (e.g. "rb" in
     server.c (server_updated), since CVS 1.8).  The former is just an
     #ifdef.  */
  
  #define FOPEN_BINARY_READ ("rb")
  #define FOPEN_BINARY_WRITE ("wb")
--- 467,535 ----
  /* Wildcard matcher.  Should be case-insensitive if the system is.  */
  #ifndef CVS_FNMATCH
  #define CVS_FNMATCH fnmatch
  #endif
  
  #if defined (__CYGWIN32__) || defined (WIN32)
  
  /* Under Windows NT, filenames are case-insensitive, and both / and \
     are path component separators.  */
  
  #define FOLD_FN_CHAR(c) (WNT_filename_classes[(unsigned char) (c)])
  extern unsigned char WNT_filename_classes[];
  #define FILENAMES_CASE_INSENSITIVE 1
  
  /* Is the character C a path name separator?  Under
     Windows NT, you can use either / or \.  */
! #define ISDIRSEP(c) (FOLD_FN_CHAR(c) == '/'  ||  FOLD_FN_CHAR(c) == '\\')
  
  /* Like strcmp, but with the appropriate tweaks for file names.
     Under Windows NT, filenames are case-insensitive but case-preserving,
     and both \ and / are path element separators.  */
  extern int fncmp (const char *n1, const char *n2);
  
  /* Fold characters in FILENAME to their canonical forms.
     If FOLD_FN_CHAR is not #defined, the system provides a default
     definition for this.  */
  extern void fnfold (char *FILENAME);
  
  #endif /* defined (__CYGWIN32__) || defined (WIN32) */
  
  /* Some file systems are case-insensitive.  If FOLD_FN_CHAR is
     #defined, it maps the character C onto its "canonical" form.  In a
     case-insensitive system, it would map all alphanumeric characters
     to lower case.  Under Windows NT, / and \ are both path component
     separators, so FOLD_FN_CHAR would map them both to /.  */
  #ifndef FOLD_FN_CHAR
  #define FOLD_FN_CHAR(c) (c)
  #define fnfold(filename) (filename)
  #define fncmp strcmp
  #endif
  
  /* Different file systems have different path component separators.
     For the VMS port we might need to abstract further back than this.  */
  #ifndef ISDIRSEP
  #define ISDIRSEP(c) ((c) == '/')
+ #endif
+ 
+ #ifndef DIRSEP
+ #if defined (WIN32)
+ #define DIRSEP "\\"
+ #else
+ #define DIRSEP "/"
+ #endif /* defined (WIN32) */
  #endif
  
  
  /* On some systems, we have to be careful about writing/reading files
     in text or binary mode (so in text mode the system can handle CRLF
     vs. LF, VMS text file conventions, &c).  We decide to just always
     be careful.  That way we don't have to worry about whether text and
     binary differ on this system.  We just have to worry about whether
     the system has O_BINARY and "rb".  The latter is easy; all ANSI C
     libraries have it, SunOS4 has it, and CVS has used it unguarded
     some places for a while now without complaints (e.g. "rb" in
     server.c (server_updated), since CVS 1.8).  The former is just an
     #ifdef.  */
  
  #define FOPEN_BINARY_READ ("rb")
  #define FOPEN_BINARY_WRITE ("wb")
Index: src/ChangeLog
===================================================================
RCS file: /home/nyap/.cvsroot/cvs/src/ChangeLog,v
retrieving revision 1.1.1.1
retrieving revision 1.1.1.1.4.2
diff -C16 -w -r1.1.1.1 -r1.1.1.1.4.2
*** src/ChangeLog	2001/03/06 16:21:21	1.1.1.1
--- src/ChangeLog	2001/03/06 22:47:16	1.1.1.1.4.2
***************
*** 1,16 ****
--- 1,45 ----
+ 2001-03-06  Noel Yap <yap_noel@yahoo.com>
+ 
+ 	* version.c: Version 1.11 (multiple edits)
+ 
+ 	* 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.c (notify_put)
+ 	Relative paths removed from working directories.
+ 
+ 	* 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-09-19  Larry Jones  <larry.jones@sdrc.com>
  
  	* version.c: Version 1.11.
  
  2000-09-07  Larry Jones  <larry.jones@sdrc.com>
  
  	* Makefile.in: Use @bindir@, @libdir@, @infodir@, and @mandir@
  	from autoconf.
  
  2000-08-23  Larry Jones  <larry.jones@sdrc.com>
  
  	* mkmodules.c (init): Create an empty val-tags file if it doesn't
  	already exist to avoid problems with users not having sufficient
  	permissions to create it later.
  
  2000-09-06  Jim Kingdon  <jkingdon@dhcp-net200-89.su.valinux.com>
Index: src/add.c
===================================================================
RCS file: /home/nyap/.cvsroot/cvs/src/add.c,v
retrieving revision 1.1.1.1
retrieving revision 1.1.1.1.4.1
diff -C16 -w -r1.1.1.1 -r1.1.1.1.4.1
*** src/add.c	2001/03/06 16:21:21	1.1.1.1
--- src/add.c	2001/03/08 14:26:05	1.1.1.1.4.1
***************
*** 777,808 ****
--- 777,812 ----
  	fileattr_setall (NULL, attrs);
  	fileattr_write ();
  	fileattr_free ();
  	if (attrs != NULL)
  	    free (attrs);
  
  	/*
  	 * Set up an update list with a single title node for Update_Logfile
  	 */
  	ulist = getlist ();
  	p = getnode ();
  	p->type = UPDATE;
  	p->delproc = update_delproc;
  	p->key = xstrdup ("- New directory");
  	li = (struct logfile_info *) xmalloc (sizeof (struct logfile_info));
  	li->type = T_TITLE;
+         li->repository = NULL;
+         li->hostname = NULL;
+         li->wd = NULL;
+         li->editor = NULL;
  	li->tag = xstrdup (tag);
  	li->rev_old = li->rev_new = NULL;
  	p->data = (char *) li;
  	(void) addnode (ulist, p);
  	Update_Logfile (rcsdir, message, (FILE *) NULL, ulist);
  	dellist (&ulist);
      }
  
  #ifdef SERVER_SUPPORT
      if (!server_active)
  #endif
          Create_Admin (".", finfo->fullname, rcsdir, tag, date, nonbranch, 0, 1);
      if (tag)
  	free (tag);
      if (date)
  	free (date);
Index: src/commit.c
===================================================================
RCS file: /home/nyap/.cvsroot/cvs/src/commit.c,v
retrieving revision 1.1.1.1
retrieving revision 1.1.1.1.4.2
diff -C16 -w -r1.1.1.1 -r1.1.1.1.4.2
*** src/commit.c	2001/03/06 16:21:21	1.1.1.1
--- src/commit.c	2001/03/08 14:16:57	1.1.1.1.4.2
***************
*** 281,312 ****
--- 281,317 ----
  	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).  */
  	freevers_ts (&vers);
  	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 *));
  
***************
*** 533,595 ****
--- 538,656 ----
  		    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);
  	free (find_args.argv);
  	dellist (&find_args.ulist);
  
  	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+");
***************
*** 634,665 ****
--- 695,733 ----
  	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!");
      }
  
+ #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,
  			       (char *) NULL, 1);
  
      /*
       * Unlock all the dirs and clean up
       */
      Lock_Cleanup ();
      dellist (&mulist);
  
***************
*** 809,840 ****
--- 877,909 ----
  
      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:
+         {
  	    /*
  	     * 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)
***************
*** 982,1013 ****
--- 1051,1179 ----
  		    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;
+ 
+ #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, ',');
+                         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
***************
*** 1042,1073 ****
--- 1208,1241 ----
  		   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);
  }
  
  /*
***************
*** 1344,1376 ****
  	    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);
  
  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
--- 1512,1582 ----
  	    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.  */
!     {
!         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);
      }
      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
***************
*** 2289,2320 ****
--- 2495,2534 ----
  
      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/nyap/.cvsroot/cvs/src/cvs.h,v
retrieving revision 1.1.1.1
retrieving revision 1.1.1.1.4.1
diff -C16 -w -r1.1.1.1 -r1.1.1.1.4.1
*** src/cvs.h	2001/03/06 16:21:21	1.1.1.1
--- src/cvs.h	2001/03/06 22:16:18	1.1.1.1.4.1
***************
*** 780,811 ****
--- 780,815 ----
  #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/nyap/.cvsroot/cvs/src/edit.c,v
retrieving revision 1.1.1.1
retrieving revision 1.1.1.1.4.2
diff -C16 -w -r1.1.1.1 -r1.1.1.1.4.2
*** src/edit.c	2001/03/06 16:21:21	1.1.1.1
--- src/edit.c	2001/03/06 22:47:17	1.1.1.1.4.2
***************
*** 3,34 ****
--- 3,36 ----
     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"
  
+ #include <assert.h>
+ 
  static int watch_onoff PROTO ((int, char **));
  
  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;
  {
***************
*** 141,172 ****
--- 143,175 ----
  }
  
  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,222 ****
  	    continue;
  	*cp++ = '\0';
  	val = cp;
  	cp = strchr (val, '\t');
  	if (cp == NULL)
  	    continue;
  	*cp++ = '+';
  	cp = strchr (cp, '\t');
  	if (cp == NULL)
  	    continue;
  	*cp++ = '+';
  	cp = strchr (cp, '\t');
  	if (cp == NULL)
  	    continue;
  	*cp++ = '\0';
  	watches = cp;
! 	cp = strchr (cp, '\n');
  	if (cp == NULL)
  	    continue;
  	*cp = '\0';
  
! 	notify_do (notif_type, filename, getcaller (), val, watches,
  		   finfo->repository);
      }
      free (line);
  
      if (ferror (fp))
  	error (0, errno, "cannot read %s", CVSADM_NOTIFY);
      if (fclose (fp) < 0)
  	error (0, errno, "cannot close %s", CVSADM_NOTIFY);
  
      if ( CVS_UNLINK (CVSADM_NOTIFY) < 0)
  	error (0, errno, "cannot remove %s", CVSADM_NOTIFY);
  
      return 0;
  }
  
  static int send_notifications PROTO ((int, char **, int));
--- 188,230 ----
  	    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, '\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 (), 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));
***************
*** 256,338 ****
      }
      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
! edit_fileproc (callerdat, finfo)
!     void *callerdat;
      struct file_info *finfo;
  {
      FILE *fp;
      time_t now;
      char *ascnow;
!     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;
!     }
  
      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)
  	fprintf (fp, "E");
!     if (setting_tunedit)
  	fprintf (fp, "U");
!     if (setting_tcommit)
  	fprintf (fp, "C");
      fprintf (fp, "\n");
  
      if (fclose (fp) < 0)
      {
  	if (finfo->update_dir[0] == '\0')
  	    error (0, errno, "cannot close %s", CVSADM_NOTIFY);
  	else
  	    error (0, errno, "cannot close %s/%s", finfo->update_dir,
  		   CVSADM_NOTIFY);
      }
  
      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);
--- 264,455 ----
      }
      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 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';
! 
!     wd = (char *) malloc (strlen (CurDir) + strlen (DIRSEP) + strlen (finfo->update_dir) + 1);
!     strcpy(wd, CurDir);
!     if (finfo->update_dir != NULL && *finfo->update_dir != '\0')
!     {
!         strcat (wd, DIRSEP);
!         strcat (wd, finfo->update_dir);
!     }
! 
!     /* remove "./" */
!     {
!         char *p = NULL;
! 
!         assert (strlen (wd) > 1
!                 && (wd[0] != '.'  ||  !ISDIRSEP(wd[1])));
! 
!         for (p = strstr (wd, ".");
!              p < wd + strlen (wd)  &&  p != NULL;
!              p = strstr (p + 1, "."))
!         {
!             if (ISDIRSEP (*(p - 1))
!                 && ISDIRSEP (*(p + 1)))
!             {
!                 char *p0 = NULL;
!                 char *p1 = NULL;
! 
!                 for (p0 = p, p1 = p + 2;
!                      p1 < wd + strlen (wd)  &&  *p1 != '\0';
!                      ++p0, ++p1)
!                 {
!                     *p0 = *p1;
!                 }
! 
!                 *p0 = '\0';
!             }
!         }
!     }
! 
!     /* remove "../" */
!     {
!         char *p = NULL;
! 
!         assert (strlen (wd) > 2
!                 && (wd[0] != '.'  ||  wd[1] != '.'  ||  !ISDIRSEP(wd[2])));
! 
!         for (p = strstr (wd, "..");
!              p < wd + strlen (wd)  &&  p != NULL;
!              p = strstr (p + 2, ".."))
!         {
!             if (ISDIRSEP (*(p - 1))
!                 && ISDIRSEP (*(p + 2)))
!             {
!                 char *p0 = NULL;
!                 char *p1 = NULL;
! 
!                 p0 = p - 1;
!                 do
!                 {
!                     --p0;
!                 }
!                 while (p0 >wd  &&  !ISDIRSEP (*p0));
! 
!                 p1 = p + 2;
!                 do
!                 {
!                     ++p1;
!                 }
!                 while (p1 < wd + strlen (wd)  &&  !ISDIRSEP (*p1));
! 
!                 for (; p1 < wd + strlen (wd)  &&  *p1 != '\0'; ++p0, ++p1)
!                 {
!                     *p0 = *p1;
!                 }
! 
!                 *p0 = '\0';
!             }
!         }
!     }
! 
!     strip_trailing_slashes (wd);
! 
!     fprintf (fp, "%c%s\t%s GMT\t%s\t%s\t", action, finfo->file, ascnow, hostname, wd);
!     if (tedit)
  	fprintf (fp, "E");
!     if (tunedit)
  	fprintf (fp, "U");
!     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);
***************
*** 359,391 ****
      "(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 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)
      {
  	switch (c)
  	{
  	    case 'l':
  		local = 1;
  		break;
  	    case 'R':
  		local = 0;
  		break;
  	    case 'a':
--- 476,507 ----
***************
*** 474,522 ****
  	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);
!     }
  
      /* Now update the revision number in CVS/Entries from CVS/Baserev.
         The basic idea here is that we are reverting to the revision
         that the user edited.  If we wanted "cvs update" to update
         CVS/Base as we go along (so that an unedit could revert to the
         current repository revision), we would need:
  
         update (or all send_files?) (client) needs to send revision in
         new Entry-base request.  update (server/local) needs to check
         revision against repository and send new Update-base response
         (like Update-existing in that the file already exists.  While
         we are at it, might try to clean up the syntax by having the
         mode only in a "Mode" response, not in the Update-base itself).  */
      {
  	char *baserev;
  	Node *node;
--- 590,623 ----
  	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);
  
!     notify_put (finfo, NULL, '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).  */
      {
  	char *baserev;
  	Node *node;
***************
*** 543,577 ****
  		   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",
      "(Specify the --help global option for a list of other help options)\n",
      NULL
  };
  
  int
  unedit (argc, argv)
      int argc;
      char **argv;
--- 644,679 ----
  		   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",
      "(Specify the --help global option for a list of other help options)\n",
      NULL
  };
  
  int
  unedit (argc, argv)
      int argc;
      char **argv;
***************
*** 649,716 ****
  	  || (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;
      /* User to be notified.  */
!     char *notifyee;
      /* 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);
      /* Copy FILTER to PROG, replacing the first occurrence of %s with
!        the notifyee.  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);
  		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)
--- 751,818 ----
  	  || (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;
      /* User to be notified.  */
!     char *watcher;
      /* 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->watcher) + 1);
      /* Copy FILTER to PROG, replacing the first occurrence of %s with
!        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->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)
***************
*** 731,820 ****
      fprintf (pipefp, "%s %s\n---\n", srepos, args->file);
      fprintf (pipefp, "Triggered %s watch on %s\n", args->type, repository);
      fprintf (pipefp, "By %s\n", args->who);
  
      /* Lots more potentially useful information we could add here; see
         logfile_write for inspiration.  */
  
      free (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)
      int type;
      char *filename;
      char *who;
      char *val;
      char *watches;
      char *repository;
  {
      static struct addremove_args blank;
      struct addremove_args args;
      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);
  	    break;
  	case 'U':
  	case 'C':
! 	    editor_set (filename, who, NULL);
  	    break;
  	default:
  	    return;
      }
  
      watchers = fileattr_get0 (filename, "_watchers");
      p = watchers;
      while (p != NULL)
      {
  	char *q;
  	char *endq;
  	char *nextq;
  	char *notif;
  
  	endp = strchr (p, '>');
  	if (endp == NULL)
  	    break;
  	nextp = strchr (p, ',');
  
  	if ((size_t)(endp - p) == strlen (who) && strncmp (who, p, endp - p) == 0)
  	{
! 	    /* Don't notify user of their own changes.  Would perhaps
! 	       be better to check whether it is the same working
! 	       directory, not the same user, but that is hairy.  */
  	    p = nextp == NULL ? nextp : nextp + 1;
  	    continue;
  	}
  
  	/* Now we point q at a string which looks like
  	   "edit+unedit+commit,"... and walk down it.  */
  	q = endp + 1;
  	notif = NULL;
  	while (q != NULL)
  	{
  	    endq = strchr (q, '+');
  	    if (endq == NULL || (nextp != NULL && endq > nextp))
  	    {
  		if (nextp == NULL)
  		    endq = q + strlen (q);
  		else
  		    endq = nextp;
  		nextq = NULL;
  	    }
  	    else
--- 833,958 ----
      fprintf (pipefp, "%s %s\n---\n", srepos, args->file);
      fprintf (pipefp, "Triggered %s watch on %s\n", args->type, repository);
      fprintf (pipefp, "By %s\n", args->who);
  
      /* Lots more potentially useful information we could add here; see
         logfile_write for inspiration.  */
  
      free (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, 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, editor, val);
  	    break;
  	case 'U':
  	case 'C':
! 	    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)
  	{
!                 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
***************
*** 855,975 ****
  		    notif = "temporary commit";
  	    }
  	    q = nextq;
  	}
  	if (nextp != NULL)
  	    ++nextp;
  
  	if (notif != NULL)
  	{
  	    struct notify_proc_args args;
  	    size_t len = endp - p;
  	    FILE *fp;
  	    char *usersname;
  	    char *line = NULL;
  	    size_t line_len = 0;
  
! 	    args.notifyee = NULL;
  	    usersname = 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);
  
                          /* There may or may not be more
                             colon-separated fields added to this in the
                             future; in any case, we ignore them right
                             now, and if there are none we make sure to
                             chop off the final newline, if any. */
! 			cp = strpbrk (args.notifyee, ":\n");
  
  			if (cp != NULL)
  			    *cp = '\0';
  			break;
  		    }
  		}
  		if (ferror (fp))
  		    error (0, errno, "cannot read %s", usersname);
  		if (fclose (fp) < 0)
  		    error (0, errno, "cannot close %s", usersname);
  	    }
  	    free (usersname);
  	    if (line != NULL)
  		free (line);
  
! 	    if (args.notifyee == NULL)
  	    {
! 		args.notifyee = xmalloc (endp - p + 1);
! 		strncpy (args.notifyee, p, endp - p);
! 		args.notifyee[endp - p] = '\0';
  	    }
  
  	    notify_args = &args;
  	    args.type = notif;
  	    args.who = who;
  	    args.file = filename;
  
  	    (void) Parse_Info (CVSROOTADM_NOTIFY, repository, notify_proc, 1);
! 	    free (args.notifyee);
  	}
  
  	p = nextp;
      }
      if (watchers != NULL)
  	free (watchers);
  
      switch (type)
      {
  	case 'E':
  	    if (*watches == 'E')
  	    {
  		args.add_tedit = 1;
  		++watches;
  	    }
  	    if (*watches == 'U')
  	    {
  		args.add_tunedit = 1;
  		++watches;
  	    }
  	    if (*watches == 'C')
  	    {
  		args.add_tcommit = 1;
  	    }
  	    watch_modify_watchers (filename, &args);
  	    break;
  	case 'U':
  	case 'C':
  	    args.remove_temp = 1;
  	    watch_modify_watchers (filename, &args);
  	    break;
      }
  }
  
  #ifdef CLIENT_SUPPORT
  /* Check and send notifications.  This is only for the client.  */
  void
  notify_check (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
--- 993,1116 ----
  		    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.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.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.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.watcher == NULL)
  	    {
! 		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.file = filename;
  
  	    (void) Parse_Info (CVSROOTADM_NOTIFY, repository, notify_proc, 1);
! 	    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
Index: src/edit.h
===================================================================
RCS file: /home/nyap/.cvsroot/cvs/src/edit.h,v
retrieving revision 1.1.1.1
retrieving revision 1.1.1.1.4.1
diff -C16 -w -r1.1.1.1 -r1.1.1.1.4.1
*** src/edit.h	2001/03/06 16:21:21	1.1.1.1
--- src/edit.h	2001/03/06 22:16:18	1.1.1.1.4.1
***************
*** 13,38 ****
--- 13,39 ----
  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/import.c
===================================================================
RCS file: /home/nyap/.cvsroot/cvs/src/import.c,v
retrieving revision 1.1.1.1
retrieving revision 1.1.1.1.4.1
diff -C16 -w -r1.1.1.1 -r1.1.1.1.4.1
*** src/import.c	2001/03/06 16:21:21	1.1.1.1
--- src/import.c	2001/03/08 14:26:05	1.1.1.1.4.1
***************
*** 361,392 ****
--- 361,396 ----
      {
  	if (!really_quiet)
  	    cvs_output ("\nNo conflicts created by this import\n\n", 0);
  	(void) fprintf (logfp, "\nNo conflicts created by this import\n\n");
      }
  
      /*
       * Write out the logfile and clean up.
       */
      ulist = getlist ();
      p = getnode ();
      p->type = UPDATE;
      p->delproc = update_delproc;
      p->key = xstrdup ("- Imported sources");
      li = (struct logfile_info *) xmalloc (sizeof (struct logfile_info));
      li->type = T_TITLE;
+     li->repository = NULL;
+     li->hostname = NULL;
+     li->wd = NULL;
+     li->editor = NULL;
      li->tag = xstrdup (vbranch);
      li->rev_old = li->rev_new = NULL;
      p->data = (char *) li;
      (void) addnode (ulist, p);
      Update_Logfile (repository, message, logfp, ulist);
      dellist (&ulist);
      if (fclose (logfp) < 0)
  	error (0, errno, "error closing %s", tmpfile);
  
      /* Make sure the temporary file goes away, even on systems that don't let
         you delete a file that's in use.  */
      if (CVS_UNLINK (tmpfile) < 0 && !existence_error (errno))
  	error (0, errno, "cannot remove %s", tmpfile);
      free (tmpfile);
  
      if (message)
Index: src/rcs.c
===================================================================
RCS file: /home/nyap/.cvsroot/cvs/src/rcs.c,v
retrieving revision 1.1.1.1
retrieving revision 1.1.1.1.4.1
diff -C16 -w -r1.1.1.1 -r1.1.1.1.4.1
*** src/rcs.c	2001/03/06 16:21:21	1.1.1.1
--- src/rcs.c	2001/03/06 22:16:18	1.1.1.1.4.1
***************
*** 5779,5812 ****
      (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. */
  
  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);
  
--- 5779,5812 ----
      (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.  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);
  
***************
*** 5858,5903 ****
  	    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)
--- 5858,5889 ----
Index: src/server.c
===================================================================
RCS file: /home/nyap/.cvsroot/cvs/src/server.c,v
retrieving revision 1.1.1.1
retrieving revision 1.1.1.1.4.2
diff -C16 -w -r1.1.1.1 -r1.1.1.1.4.2
*** src/server.c	2001/03/06 16:21:21	1.1.1.1
--- src/server.c	2001/03/08 14:26:05	1.1.1.1.4.2
***************
*** 1900,1945 ****
--- 1900,2070 ----
  	}
  	free (p->entry);
  	q = p->next;
  	free (p);
  	p = q;
      }
      entries = NULL;
      if (f != NULL && fclose (f) == EOF && !error_pending ())
      {
  	int save_errno = errno;
  	if (alloc_pending (80 + strlen (CVSADM_ENT)))
  	    sprintf (pending_error_text, "E cannot close %s", CVSADM_ENT);
  	pending_error = save_errno;
      }
  }
  
+ 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;
+         li->repository = NULL;
+ 
+         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 = NULL;
      char *data = NULL;
***************
*** 2015,2050 ****
--- 2140,2205 ----
  	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) 2001 Mar 15
+                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 2003 Mar 15 (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 = 0;
      if (alloc_pending (80))
  	strcpy (pending_error_text,
***************
*** 2070,2101 ****
--- 2225,2257 ----
      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');
***************
*** 4650,4681 ****
--- 4806,4838 ----
    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, RQ_ROOTLESS),
    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/nyap/.cvsroot/cvs/src/server.h,v
retrieving revision 1.1.1.1
retrieving revision 1.1.1.1.4.1
diff -C16 -w -r1.1.1.1 -r1.1.1.1.4.1
*** src/server.h	2001/03/06 16:21:21	1.1.1.1
--- src/server.h	2001/03/06 22:16:19	1.1.1.1.4.1
***************
*** 106,137 ****
--- 106,140 ----
  
  /* 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/version.c
===================================================================
RCS file: /home/nyap/.cvsroot/cvs/src/version.c,v
retrieving revision 1.1.1.1
retrieving revision 1.1.1.1.4.1
diff -C16 -w -r1.1.1.1 -r1.1.1.1.4.1
*** src/version.c	2001/03/06 16:21:21	1.1.1.1
--- src/version.c	2001/03/06 22:16:19	1.1.1.1.4.1
***************
*** 1,31 ****
  /*
   * 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 = "Concurrent Versions System (CVS) 1.11";
  
  #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
  
  static const char *const version_usage[] =
--- 1,31 ----
  /*
   * 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 = "Concurrent Versions System (CVS) 1.11 (multiple edits)";
  
  #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
  
  static const char *const version_usage[] =
***************
*** 59,75 ****
      if (client_active)
      {
  	(void) fputs ("Server: ", stdout);
  	start_server ();
  	if (supported_request ("version"))
  	    send_to_server ("version\012", 0);
  	else
  	{
  	    send_to_server ("noop\012", 0);
  	    fputs ("(unknown)\n", stdout);
  	}
  	err = get_responses_and_close ();
      }
  #endif
      return err;
  }
- 	
--- 59,74 ----
