aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPeter Wemm <peter@FreeBSD.org>1998-03-10 13:40:57 +0000
committerPeter Wemm <peter@FreeBSD.org>1998-03-10 13:40:57 +0000
commit4b06c8929116f104d337c272761ff7d11f8e48e6 (patch)
treef60b0014663435c30f2efea2b10ca4f8ecc0208c
parent7b9d41140503f7f79f2cd1d54160042f56f92551 (diff)
downloadsrc-4b06c8929116f104d337c272761ff7d11f8e48e6.tar.gz
src-4b06c8929116f104d337c272761ff7d11f8e48e6.zip
Import cvs-1.9.26 onto vendor branch
Notes
Notes: svn path=/vendor/cvs/dist/; revision=34461
-rw-r--r--contrib/cvs/ChangeLog26
-rw-r--r--contrib/cvs/INSTALL2
-rw-r--r--contrib/cvs/TODO3
-rw-r--r--contrib/cvs/acconfig.h3
-rw-r--r--contrib/cvs/config.h.in3
-rwxr-xr-xcontrib/cvs/configure11
-rw-r--r--contrib/cvs/configure.in8
-rw-r--r--contrib/cvs/contrib/ChangeLog4
-rw-r--r--contrib/cvs/contrib/Makefile.in2
-rw-r--r--contrib/cvs/doc/ChangeLog42
-rw-r--r--contrib/cvs/doc/cvs.texinfo307
-rw-r--r--contrib/cvs/doc/cvsclient.texi2
-rw-r--r--contrib/cvs/lib/ChangeLog22
-rw-r--r--contrib/cvs/lib/Makefile.in1
-rw-r--r--contrib/cvs/lib/getline.h3
-rw-r--r--contrib/cvs/lib/regex.c35
-rw-r--r--contrib/cvs/lib/system.h7
-rw-r--r--contrib/cvs/src/ChangeLog485
-rw-r--r--contrib/cvs/src/Makefile.in8
-rw-r--r--contrib/cvs/src/add.c2
-rw-r--r--contrib/cvs/src/admin.c24
-rw-r--r--contrib/cvs/src/buffer.c48
-rw-r--r--contrib/cvs/src/buffer.h6
-rw-r--r--contrib/cvs/src/checkin.c85
-rw-r--r--contrib/cvs/src/checkout.c125
-rw-r--r--contrib/cvs/src/classify.c4
-rw-r--r--contrib/cvs/src/client.c68
-rw-r--r--contrib/cvs/src/client.h2
-rw-r--r--contrib/cvs/src/commit.c124
-rw-r--r--contrib/cvs/src/create_adm.c13
-rw-r--r--contrib/cvs/src/cvs.h8
-rw-r--r--contrib/cvs/src/diff.c19
-rw-r--r--contrib/cvs/src/entries.c34
-rw-r--r--contrib/cvs/src/filesubr.c205
-rw-r--r--contrib/cvs/src/find_names.c16
-rw-r--r--contrib/cvs/src/hardlink.c298
-rw-r--r--contrib/cvs/src/hardlink.h33
-rw-r--r--contrib/cvs/src/hash.c11
-rw-r--r--contrib/cvs/src/hash.h1
-rw-r--r--contrib/cvs/src/import.c211
-rw-r--r--contrib/cvs/src/lock.c12
-rw-r--r--contrib/cvs/src/main.c2
-rw-r--r--contrib/cvs/src/mkmodules.c6
-rw-r--r--contrib/cvs/src/myndbm.c31
-rw-r--r--contrib/cvs/src/no_diff.c7
-rw-r--r--contrib/cvs/src/parseinfo.c20
-rw-r--r--contrib/cvs/src/patch.c68
-rw-r--r--contrib/cvs/src/rcs.c2449
-rw-r--r--contrib/cvs/src/rcs.h8
-rw-r--r--contrib/cvs/src/rcscmds.c63
-rw-r--r--contrib/cvs/src/recurse.c2
-rwxr-xr-xcontrib/cvs/src/sanity.sh3701
-rw-r--r--contrib/cvs/src/server.c88
-rw-r--r--contrib/cvs/src/server.h31
-rw-r--r--contrib/cvs/src/subr.c37
-rw-r--r--contrib/cvs/src/update.c650
-rw-r--r--contrib/cvs/src/vers_ts.c4
-rw-r--r--contrib/cvs/src/version.c2
-rw-r--r--contrib/cvs/tools/ChangeLog4
-rw-r--r--contrib/cvs/tools/Makefile.in2
60 files changed, 6816 insertions, 2682 deletions
diff --git a/contrib/cvs/ChangeLog b/contrib/cvs/ChangeLog
index 6ff5bd11be30..82ea5ac486ee 100644
--- a/contrib/cvs/ChangeLog
+++ b/contrib/cvs/ChangeLog
@@ -1,3 +1,29 @@
+1998-03-04 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * acconfig.h, configure.in: Add PRESERVE_PERMISSIONS_SUPPORT and
+ always define it.
+ * configure, config.h.in: Regenerated.
+
+Tue Feb 17 18:32:36 1998 Ian Lance Taylor <ian@cygnus.com>
+
+ * configure.in: Add memmove back to AC_REPLACE_FUNCS list.
+ * configure: Rebuild.
+
+1998-02-16 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * TODO (190): Remove "failed to check out" from commit.c from
+ lists of error messages suppressed by -q; it no longer is.
+
+4 Feb 1998 Jim Kingdon
+
+ * cvsnt.mak: The usual "because Visual C++ feels like it"
+ changes. These ones seem to have to do with reordering
+ files and release versus debug configurations, mainly.
+
+Fri Jan 30 10:37:40 1998 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * INSTALL: Update which version of CVS was tested with EMX.
+
15 Jan 1998 W. L. Estes <wlestes@hamlet.uncg.edu>
and Jim Kingdon <kingdon@harvey.cyclic.com>
diff --git a/contrib/cvs/INSTALL b/contrib/cvs/INSTALL
index c13948c4ace3..576f9a6f62cc 100644
--- a/contrib/cvs/INSTALL
+++ b/contrib/cvs/INSTALL
@@ -143,7 +143,7 @@ i386 family:
Windows 95 (1.9 client and local)
QNX (1.9.1 + patches for strippath() and va_list)
OS/2 Version 3 using IBM C/C++ Tools 2.01 (1.8.86 + patches, client)
- OS/2 Version 3 using EMX 0.9c (1.9.10 + patches, client)
+ OS/2 Version 3 using EMX 0.9c (1.9.22, client)
OS/2 Version 3 using Watcom version ? (? - has this been tested?)
m68k:
Sun 3 running SunOS 4.1.1_U1 w/ bundled K&R /usr/5bin/cc (1.8.86+)
diff --git a/contrib/cvs/TODO b/contrib/cvs/TODO
index cddd580845ca..cc40ab2a68bd 100644
--- a/contrib/cvs/TODO
+++ b/contrib/cvs/TODO
@@ -642,9 +642,6 @@ of -q; I don't know whether anyone has done a similar investigation of
Rationale might be that we already printed another message
elsewhere but why would it be necessary to avoid
the extra message in such an uncommon case?)
- commit.c: failed to check out `%s' (likewise; this one seems to be a
- vestige from before RCS_checkout was internal. Take a look at how
- RCS_checkout handles errors)
commit.c: failed to commit dead revision for `%s' (likewise)
remove.c: file `%s' still in working directory (see below about rm
-f analogy)
diff --git a/contrib/cvs/acconfig.h b/contrib/cvs/acconfig.h
index 21c5fe7449aa..8ccbf8f90217 100644
--- a/contrib/cvs/acconfig.h
+++ b/contrib/cvs/acconfig.h
@@ -20,6 +20,9 @@
/* Define if you have the connect function. */
#undef HAVE_CONNECT
+/* Define if this system supports chown(), link(), and friends. */
+#undef PRESERVE_PERMISSIONS_SUPPORT
+
/* Define if you have memchr (always for CVS). */
#undef HAVE_MEMCHR
diff --git a/contrib/cvs/config.h.in b/contrib/cvs/config.h.in
index a49219093463..af42df6b3f32 100644
--- a/contrib/cvs/config.h.in
+++ b/contrib/cvs/config.h.in
@@ -90,6 +90,9 @@
/* Define if you have the connect function. */
#undef HAVE_CONNECT
+/* Define if this system supports chown(), link(), and friends. */
+#undef PRESERVE_PERMISSIONS_SUPPORT
+
/* Define if you have memchr (always for CVS). */
#undef HAVE_MEMCHR
diff --git a/contrib/cvs/configure b/contrib/cvs/configure
index f79a25dfe563..01c0ace6d150 100755
--- a/contrib/cvs/configure
+++ b/contrib/cvs/configure
@@ -1782,7 +1782,7 @@ EOF
fi
-for ac_func in mkdir rename strstr dup2 strerror valloc waitpid vasprintf strtoul
+for ac_func in mkdir rename strstr dup2 strerror valloc waitpid memmove vasprintf strtoul
do
echo $ac_n "checking for $ac_func""... $ac_c" 1>&6
if eval "test \"`echo '$''{'ac_cv_func_$ac_func'+set}'`\" = set"; then
@@ -3254,12 +3254,17 @@ EOF
fi
fi # enable_server
+cat >> confdefs.h <<\EOF
+#define PRESERVE_PERMISSIONS_SUPPORT 1
+EOF
+
+
echo $ac_n "checking for cygwin32""... $ac_c" 1>&6
if eval "test \"`echo '$''{'ccvs_cv_sys_cygwin32'+set}'`\" = set"; then
echo $ac_n "(cached) $ac_c" 1>&6
else
cat > conftest.$ac_ext <<EOF
-#line 3263 "configure"
+#line 3268 "configure"
#include "confdefs.h"
int main() { return 0; }
@@ -3267,7 +3272,7 @@ int t() {
return __CYGWIN32__;
; return 0; }
EOF
-if { (eval echo configure:3271: \"$ac_compile\") 1>&5; (eval $ac_compile) 2>&5; }; then
+if { (eval echo configure:3276: \"$ac_compile\") 1>&5; (eval $ac_compile) 2>&5; }; then
rm -rf conftest*
ccvs_cv_sys_cygwin32=yes
else
diff --git a/contrib/cvs/configure.in b/contrib/cvs/configure.in
index 292958ddcd56..b848ab6f1dde 100644
--- a/contrib/cvs/configure.in
+++ b/contrib/cvs/configure.in
@@ -63,7 +63,7 @@ AC_TYPE_MODE_T
AC_TYPE_SIZE_T
AC_TYPE_PID_T
AC_STRUCT_ST_BLKSIZE
-AC_REPLACE_FUNCS(mkdir rename strstr dup2 strerror valloc waitpid vasprintf strtoul)
+AC_REPLACE_FUNCS(mkdir rename strstr dup2 strerror valloc waitpid memmove vasprintf strtoul)
AC_CHECK_FUNCS(fchmod fsync ftime mktemp putenv vprintf ftruncate timezone getpagesize initgroups fchdir sigaction sigprocmask sigvec sigsetmask sigblock tempnam tzset readlink wait3)
dnl
@@ -340,6 +340,12 @@ if test "$ac_cv_func_crypt" = yes; then
fi
fi # enable_server
+dnl For the moment we will assume that all systems which have
+dnl the unixyness to run configure are unixy enough to do the
+dnl PreservePermissions stuff. I have this sinking feeling that
+dnl things won't be that simple, before long.
+AC_DEFINE(PRESERVE_PERMISSIONS_SUPPORT)
+
dnl On cygwin32, we configure like a Unix system, but we use the
dnl Windows support code in lib/fncase.c to handle the case
dnl insensitive file system. We also need some support libraries. We
diff --git a/contrib/cvs/contrib/ChangeLog b/contrib/cvs/contrib/ChangeLog
index 3db22a5604b4..e0d87e14a103 100644
--- a/contrib/cvs/contrib/ChangeLog
+++ b/contrib/cvs/contrib/ChangeLog
@@ -1,3 +1,7 @@
+Sat Feb 21 21:59:45 1998 Ian Lance Taylor <ian@cygnus.com>
+
+ * Makefile.in (clean): Change "/bin/rm" to "rm".
+
Thu Aug 7 22:42:23 1997 Jim Kingdon <kingdon@harvey.cyclic.com>
* pvcs_to_rcs: Remove RCS keywords. Remove $Log and move the data
diff --git a/contrib/cvs/contrib/Makefile.in b/contrib/cvs/contrib/Makefile.in
index 2a6e9a267401..9997bd84c5c7 100644
--- a/contrib/cvs/contrib/Makefile.in
+++ b/contrib/cvs/contrib/Makefile.in
@@ -106,7 +106,7 @@ ls:
.PHONY: ls
clean:
- /bin/rm -f *.o core
+ rm -f *.o core
.PHONY: clean
distclean: clean
diff --git a/contrib/cvs/doc/ChangeLog b/contrib/cvs/doc/ChangeLog
index 3fd44332d4ff..9a01d1b6a79e 100644
--- a/contrib/cvs/doc/ChangeLog
+++ b/contrib/cvs/doc/ChangeLog
@@ -1,3 +1,45 @@
+1998-03-04 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (Special Files): Add notes about client/server CVS
+ and hard links across directories.
+
+1998-03-01 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (Keeping a checked out copy): The magic loginfo
+ incantation isn't too likely to work except on unix.
+
+1998-02-23 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (user-defined logging): Double "@" literal.
+
+1998-02-18 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (user-defined logging): Add taginfo example.
+
+1998-02-04 Tim Pierce <twp@skepsis.com>
+
+ * cvs.texinfo (config): PreservePermissions variable.
+ (Special Files): New.
+ (Editing files): Add note about PreservePermissions.
+
+Tue Feb 10 18:07:35 1998 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (Connection): New node.
+
+ * cvsclient.texi (Protocol): Fix typo (lots -> lost).
+
+Sun Feb 8 21:39:22 1998 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (Invoking CVS): For admin -b, point to the section
+ where we talk about reverting to vendor branch.
+
+ * cvs.texinfo (Invoking CVS, rdiff options): Document rdiff -V
+ option as obsolete, since it was made a fatal error some time ago.
+
+ * cvs.texinfo (Invoking CVS): Add global options, keywords, and
+ keyword substitution modes. Wording fix in reference to --help
+ and Index.
+
Wed Jan 28 23:09:39 1998 Jim Kingdon <kingdon@harvey.cyclic.com>
* cvs.texinfo (Excluding directories): Add index entry for "!".
diff --git a/contrib/cvs/doc/cvs.texinfo b/contrib/cvs/doc/cvs.texinfo
index e50a793d720b..55585386264a 100644
--- a/contrib/cvs/doc/cvs.texinfo
+++ b/contrib/cvs/doc/cvs.texinfo
@@ -152,6 +152,7 @@ CVS and the Real World.
* Keyword substitution:: CVS can include the revision inside the file
* Tracking sources:: Tracking third-party sources
* Builds:: Issues related to CVS and builds
+* Special Files:: Devices, links and other non-regular files
References.
-----------
@@ -724,7 +725,7 @@ they should be in separate locations.
@c /home/joe/sources. But this node is too long
@c as it is; need a little reorganization...
-@cindex :local:
+@cindex :local:, setting up
@sc{Cvs} can access a repository by a variety of
means. It might be on the local computer, or it might
be on a computer across the room or across the world.
@@ -2058,8 +2059,8 @@ for example @file{/usr/local/bin/cvs-1.6}.
There is no need to edit @code{inetd.conf} or start a
@sc{cvs} server daemon.
-@cindex :server:
-@cindex :ext:
+@cindex :server:, setting up
+@cindex :ext:, setting up
There are two access methods that you use in CVSROOT
for rsh. @code{:server:} specifies an internal rsh
client, which is supported only by some CVS ports.
@@ -2284,7 +2285,7 @@ passwd} command.
@cindex Login (subcommand)
@cindex password client, using
@cindex authenticated client, using
-@cindex :pserver:
+@cindex :pserver:, setting up
Before connecting to the server, the client must @dfn{log
in} with the command @code{cvs login}. Logging in
verifies a password with the server, and also records
@@ -2417,7 +2418,7 @@ security, get Kerberos.
@cindex GSSAPI
@cindex security, GSSAPI
-@cindex :gserver:
+@cindex :gserver:, setting up
GSSAPI is a generic interface to network security
systems such as Kerberos 5.
If you have a working GSSAPI library, you can have
@@ -2470,7 +2471,7 @@ cvs -d :gserver:chainsaw.yard.com:/usr/local/cvsroot checkout foo
@cindex kerberos
@cindex security, kerberos
-@cindex :kserver:
+@cindex :kserver:, setting up
The easiest way to use kerberos is to use the kerberos
@code{rsh}, as described in @ref{Connecting via rsh}.
The main disadvantage of using rsh is that all the data
@@ -4731,6 +4732,21 @@ pairs of @var{filename} @var{revision}. A non-zero
exit of the filter program will cause the tag to be
aborted.
+Here is an example of using taginfo to log tag and rtag
+commands. In the taginfo file put:
+
+@example
+ALL /usr/local/cvsroot/CVSROOT/loggit
+@end example
+
+Where @file{/usr/local/cvsroot/CVSROOT/loggit} contains the
+following script:
+
+@example
+#!/bin/sh
+echo "$@@" >>/home/kingdon/cvsroot/CVSROOT/taglog
+@end example
+
@node annotate
@section Annotate command
@cindex annotate (subcommand)
@@ -5841,6 +5857,14 @@ receive notifications, she should specify @code{-a none}.
The @var{files} and options are processed as for the @code{cvs
watch} commands.
+@strong{Caution:} If the @var{PreservePermissions}
+option is enabled in the repository (@pxref{config}),
+CVS will not change the permissions on any of the
+@var{files}. The reason for this change is to ensure
+that using @samp{cvs edit} does not interfere with the
+ability to store file permissions in the CVS
+repository.
+
@end deffn
Normally when you are done with a set of changes, you
@@ -6740,6 +6764,67 @@ is Odin (see
@c can work with CVS.
@c ---------------------------------------------------------------------
+@node Special Files
+@chapter Special Files
+
+In normal circumstances, CVS works only with regular
+files. Every file in a project is assumed to be
+persistent; it must be possible to open, read and close
+them; and so on. CVS also ignores file permissions and
+ownerships, leaving such issues to be resolved by the
+developer at installation time. In other words, it is
+not possible to "check in" a device into a repository;
+if the device file cannot be opened, CVS will refuse to
+handle it. Files also lose their ownerships and
+permissions during repository transactions.
+
+If the configuration variable @var{PreservePermissions}
+(@pxref{config}) is set in the repository, CVS will
+preserve file permissions and ownership across
+repository transactions, and will permit checkin and
+checkout of special files and symbolic links.
+
+Using this option affects the behavior of CVS in
+several ways. First, some of the new operations
+supported by CVS are not accessible to all users. In
+particular, file ownership and special file
+characteristics may only be changed by the superuser.
+When the @var{PreservePermissions} configuration
+variable is set, therefore, users will have to be
+`root' in order to perform CVS operations.
+
+A more subtle difference is that CVS considers a file
+to have changed only if its contents have changed
+(specifically, if the modification time of the working
+file does not match that of the repository's file).
+Therefore, if only the permissions or ownership have
+changed, or if a device's major or minor numbers have
+changed, CVS will not notice. In order to commit such
+a change to the repository, you must force the commit
+with @samp{cvs commit -f}. This also means that if a
+file's permissions have changed and the repository file
+is newer than the working copy, performing @samp{cvs
+update} will silently change the permissions on the
+working copy.
+
+It is worth noting that only regular files may
+be merged, for reasons that hopefully are obvious. If
+@samp{cvs update} or @samp{cvs checkout -j} attempts to
+merge a symbolic link with a regular file, or two
+device files for different kinds of devices, CVS will
+report a conflict and refuse to perform the merge. At
+the same time, @samp{cvs diff} will not report any
+differences between these files, since no meaningful
+textual comparisons can be made on files which contain
+no text.
+
+The PreservePermissions features do not work with
+client/server @sc{cvs}. Another limitation is that
+hard links must be to other files within the same
+directory; hard links across directories are not
+supported.
+
+@c ---------------------------------------------------------------------
@node CVS commands
@appendix Guide to CVS commands
@@ -9117,7 +9202,9 @@ you should probably not use @samp{-u}.
@item -V @var{vn}
Expand keywords according to the rules current in
@sc{rcs} version @var{vn} (the expansion format changed with
-@sc{rcs} version 5).
+@sc{rcs} version 5). Note that this option is no
+longer accepted. CVS will always expand keywords the
+way that @sc{rcs} version 5 does.
@end table
@c . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
@@ -9741,9 +9828,121 @@ description of the @samp{-I} option, and
This appendix describes how to invoke @sc{cvs}, with
references to where each command or feature is
-described in detail. Other relevant references are the
-@samp{--help}/@samp{-H} option to @sc{cvs}
-(@pxref{Global options}) and @ref{Index}.
+described in detail. For other references run the
+@code{cvs --help} command, or see @ref{Index}.
+
+A @sc{cvs} command looks like:
+
+@example
+cvs [ @var{global_options} ] @var{command} [ @var{command_options} ] [ @var{command_args} ]
+@end example
+
+Global options:
+
+@table @code
+@item --allow-root=@var{rootdir}
+Specify legal @sc{cvsroot} directory (server only) (not
+in @sc{cvs} 1.9 and older). See @ref{Password
+authentication server}.
+
+@item -a
+Authenticate all communication (client only) (not in @sc{cvs}
+1.9 and older). See @ref{Global options}.
+
+@item -b
+Specify RCS location (@sc{cvs} 1.9 and older). See
+@ref{Global options}.
+
+@item -d @var{root}
+Specify the @sc{cvsroot}. See @ref{Repository}.
+
+@item -e @var{editor}
+Edit messages with @var{editor}. See @ref{Committing
+your changes}.
+
+@item -f
+Do not read the @file{~/.cvsrc} file. See @ref{Global
+options}.
+
+@item -H
+@itemx --help
+Print a help message. See @ref{Global options}.
+
+@item -l
+Do not log in CVSROOT/history file. See @ref{Global
+options}.
+
+@item -n
+Do not change any files. See @ref{Global options}.
+
+@item -Q
+Cause the command to be really quiet. See @ref{Global options}.
+
+@item -q
+Cause the command to be somewhat quiet. See @ref{Global options}.
+
+@item -r
+Make new working files files read-only. See @ref{Global options}.
+
+@item -s @var{variable}=@var{value}
+Set a user variable. See @ref{Variables}.
+
+@item -T @var{tempdir}
+Put temporary files in @var{tempdir}. See @ref{Global
+options}.
+
+@item -t
+Trace @sc{cvs} execution. See @ref{Global options}.
+
+@item -v
+@item --version
+Display version and copyright information for @sc{cvs}.
+
+@item -w
+Make new working files read-write. See @ref{Global
+options}.
+
+@item -x
+Encrypt all communication (client only). See
+@ref{Global options}.
+
+@item -z @var{gzip-level}
+Set the compression level (client only).
+@c FIXME: what are the valid values for gzip-level.
+@c And shouldn't this be documented in at least a
+@c little bit of detail somewhere?
+
+@end table
+
+Keyword expansion modes (@pxref{Substitution modes}):
+
+@example
+-kkv $@asis{}Id: file1,v 1.1 1993/12/09 03:21:13 joe Exp $
+-kkvl $@asis{}Id: file1,v 1.1 1993/12/09 03:21:13 joe Exp harry $
+-kk $@asis{}Id$
+-kv file1,v 1.1 1993/12/09 03:21:13 joe Exp
+-ko @i{no expansion}
+-kb @i{no expansion, file is binary}
+@end example
+
+Keywords (@pxref{Keyword list}):
+
+@example
+$@asis{}Author: joe $
+$@asis{}Date: 1993/12/09 03:21:13 $
+$@asis{}Header: /home/files/file1,v 1.1 1993/12/09 03:21:13 joe Exp harry $
+$@asis{}Id: file1,v 1.1 1993/12/09 03:21:13 joe Exp harry $
+$@asis{}Locker: harry $
+$@asis{}Name: snapshot_1_14 $
+$@asis{}RCSfile: file1,v $
+$@asis{}Revision: 1.1 $
+$@asis{}Source: /home/files/file1,v $
+$@asis{}State: Exp $
+$@asis{}Log: file1,v $
+Revision 1.1 1993/12/09 03:30:17 joe
+Initial revision
+
+@end example
@c The idea behind this table is that we want each item
@c to be a sentence or two at most. Preferably a
@@ -9752,6 +9951,8 @@ described in detail. Other relevant references are the
@c In some cases refs to "foo options" are just to get
@c this thing written quickly, not because the "foo
@c options" node is really the best place to point.
+Commands, command options, and command arguments:
+
@table @code
@item add [@var{options}] [@var{files}@dots{}]
Add a new file/directory. See @ref{Adding files}.
@@ -9773,9 +9974,7 @@ Administration of history files in the repository. See
@table @code
@item -b[@var{rev}]
-Set default branch.
-@c FIXME: Should xref to a section which describes how
-@c to use this with the vendor branch.
+Set default branch. See @ref{Reverting local changes}.
@item -c@var{string}
Set comment leader.
@@ -10209,7 +10408,7 @@ Top two diffs - last change made to the file. See
Unidiff output format. See @ref{rdiff options}.
@item -V @var{vers}
-Use RCS Version @var{vers} for keyword expansion. See
+Use RCS Version @var{vers} for keyword expansion (obsolete). See
@ref{rdiff options}.
@end table
@@ -11493,7 +11692,7 @@ must be run in the background.
@c out locks (I think) but I don't see many advantages
@c of that and we might as well document something which
@c works for multiple files.
-Here is an example (this should all be on one line):
+Here is an example for unix (this should all be on one line):
@example
^cyclic-pages (date; cat; (sleep 2; cd /u/www/local-docs;
@@ -11811,6 +12010,14 @@ for users in the system's user database if not found in
pserver users must exist in @file{CVSROOT/passwd}.
The default is @samp{yes}. For more on pserver, see
@ref{Password authenticated}.
+
+@cindex PreservePermissions, in CVSROOT/config
+@item PreservePermissions=@var{value}
+Enable support for saving special device files,
+symbolic links, file permissions and ownerships in the
+repository. The default value is @samp{no}.
+@xref{Special Files} for the full implications of using
+this keyword.
@end table
@c ---------------------------------------------------------------------
@@ -12060,6 +12267,7 @@ mentioned there.
@menu
* Error messages:: Partial list of CVS errors
+* Connection:: Trouble making a connection to a CVS server
* Other problems:: Problems not readily listed by error message
@end menu
@@ -12394,12 +12602,75 @@ exit 0
@c potentially confusing for the new user.
@end table
+@node Connection
+@appendixsec Trouble making a connection to a CVS server
+
+This section concerns what to do if you are having
+trouble making a connection to a @sc{cvs} server. If
+you are running the @sc{cvs} command line client
+running on Windows, first upgrade the client to
+@sc{cvs} 1.9.12 or later. The error reporting in
+earlier versions provided much less information about
+what the problem was. If the client is non-Windows,
+@sc{cvs} 1.9 should be fine.
+
+If the error messages are not sufficient to track down
+the problem, the next steps depend largely on which
+access method you are using.
+
+@table @code
+@cindex :ext:, troubleshooting
+@item :ext:
+Try running the rsh program from the command line. For
+example: "rsh servername cvs -v" should print @sc{cvs}
+version information. If this doesn't work, you need to
+fix it before you can worry about @sc{cvs} problems.
+
+@cindex :server:, troubleshooting
+@item :server:
+You don't need a command line rsh program to use this
+access method, but if you have an rsh program around,
+it may be useful as a debugging tool. Follow the
+directions given for :ext:.
+
+@cindex :pserver:, troubleshooting
+@item :pserver:
+One good debugging tool is to "telnet servername
+2401". After connecting, send any text (for example
+"foo" followed by return). If @sc{cvs} is working
+correctly, it will respond with
+
+@example
+cvs [pserver aborted]: bad auth protocol start: foo
+@end example
+
+If this fails to work, then make sure inetd is working
+right. Change the invocation in inetd.conf to run the
+echo program instead of cvs. For example:
+
+@example
+2401 stream tcp nowait root /bin/echo echo hello
+@end example
+
+After making that change and instructing inetd to
+re-read its configuration file, "telnet servername
+2401" should show you the text hello and then the
+server should close the connection. If this doesn't
+work, you need to fix it before you can worry about
+@sc{cvs} problems.
+
+On AIX systems, the system will often have its own
+program trying to use port 2401. This is AIX's problem
+in the sense that port 2401 is registered for use with
+@sc{cvs}. I hear that there is an AIX patch available
+to address this problem.
+@end table
+
@node Other problems
@appendixsec Other common problems
-Here is a list of problems which cannot be readily
-looked up based on an error message. They are in no
-particular order.
+Here is a list of problems which do not fit into the
+above categories. They are in no particular order.
@itemize @bullet
@item
diff --git a/contrib/cvs/doc/cvsclient.texi b/contrib/cvs/doc/cvsclient.texi
index 35fe39b34a63..56577c29e2bd 100644
--- a/contrib/cvs/doc/cvsclient.texi
+++ b/contrib/cvs/doc/cvsclient.texi
@@ -309,7 +309,7 @@ out what the other supports before exchanging large amounts of data
(such as file contents).
@c Hmm, having 3 sections in this menu makes a certain amount of sense
-@c but that structure gets lots in the printed manual (not sure about
+@c but that structure gets lost in the printed manual (not sure about
@c HTML). Perhaps there is a better way.
@menu
diff --git a/contrib/cvs/lib/ChangeLog b/contrib/cvs/lib/ChangeLog
index 3d858ab8659d..623c292aca24 100644
--- a/contrib/cvs/lib/ChangeLog
+++ b/contrib/cvs/lib/ChangeLog
@@ -1,3 +1,25 @@
+1998-02-20 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * regex.c: Partial merge with version from emacs 20.2. Brings
+ over some trivial changes (whitespace and so on) (most such
+ changes I didn't bother with, for this time). Don't cast to int
+ before comparing old_regend[r] to regstart[r] (this is the point
+ of bothering; the old code was broken for 64 bit machines.
+ Reported by Paul Vixie).
+
+Tue Feb 17 18:33:26 1998 Ian Lance Taylor <ian@cygnus.com>
+
+ * memmove.c: New file, resurrecting the old one.
+ * Makefile.in (SOURCES): Add memmove.c.
+
+1998-02-03 Tim Pierce <twp@skepsis.com>
+
+ * system.h (CVS_LSTAT): New macro.
+
+Sat Feb 7 17:33:39 1998 Ian Lance Taylor <ian@cygnus.com>
+
+ * getline.h (getstr): Declare.
+
13 Jan 1998 Jim Kingdon
* fncase.c: Include config.h before system.h.
diff --git a/contrib/cvs/lib/Makefile.in b/contrib/cvs/lib/Makefile.in
index 9c32d9fd1a28..92122c7a09ae 100644
--- a/contrib/cvs/lib/Makefile.in
+++ b/contrib/cvs/lib/Makefile.in
@@ -35,6 +35,7 @@ SOURCES = \
getopt1.c \
hostname.c \
md5.c \
+ memmove.c \
mkdir.c \
regex.c \
rename.c \
diff --git a/contrib/cvs/lib/getline.h b/contrib/cvs/lib/getline.h
index 30bcc258373d..d061b7cf1b93 100644
--- a/contrib/cvs/lib/getline.h
+++ b/contrib/cvs/lib/getline.h
@@ -11,5 +11,8 @@
int
getline __PROTO ((char **_lineptr, size_t *_n, FILE *_stream));
+int
+ getstr __PROTO ((char **_lineptr, size_t *_n, FILE *_stream,
+ char _terminator, int _offset));
#endif /* _getline_h_ */
diff --git a/contrib/cvs/lib/regex.c b/contrib/cvs/lib/regex.c
index ddeca2a2af10..38b31f43afdd 100644
--- a/contrib/cvs/lib/regex.c
+++ b/contrib/cvs/lib/regex.c
@@ -1,6 +1,5 @@
-/* Extended regular expression matching and search library,
- version 0.12.
- (Implements POSIX draft P10003.2/D11.2, except for
+/* Extended regular expression matching and search library, version
+ 0.12. (Implements POSIX draft P10003.2/D11.2, except for
internationalization features.)
Copyright (C) 1993, 1994, 1995, 1996 Free Software Foundation, Inc.
@@ -39,10 +38,16 @@
#include "lisp.h"
#include "buffer.h"
+
+/* Make syntax table lookup grant data in gl_state. */
+#define SYNTAX_ENTRY_VIA_PROPERTY
+
#include "syntax.h"
+#include "charset.h"
+#include "category.h"
-/* Emacs uses `NULL' as a predicate. */
-#undef NULL
+#define malloc xmalloc
+#define free xfree
#else /* not emacs */
@@ -160,7 +165,7 @@ init_syntax_once ()
/* We remove any previous definition of `SIGN_EXTEND_CHAR',
since ours (we hope) works properly with all combinations of
machines, compilers, `char' and `unsigned char' argument types.
- (Per Bothner suggested the basic approach.) */
+ (Per Bothner suggested the basic approach.) */
#undef SIGN_EXTEND_CHAR
#if __STDC__
#define SIGN_EXTEND_CHAR(c) ((signed char) (c))
@@ -3807,15 +3812,15 @@ re_match_2 (bufp, string1, size1, string2, size2, pos, regs, stop)
EVER_MATCHED_SOMETHING (reg_info[*p]) = 0;
/* Restore this and inner groups' (if any) registers. */
- for (r = *p; r < *p + *(p + 1); r++)
- {
- regstart[r] = old_regstart[r];
-
- /* xx why this test? */
- if ((int) old_regend[r] >= (int) regstart[r])
- regend[r] = old_regend[r];
- }
- }
+ for (r = *p; r < *p + *(p + 1); r++)
+ {
+ regstart[r] = old_regstart[r];
+
+ /* xx why this test? */
+ if (old_regend[r] >= regstart[r])
+ regend[r] = old_regend[r];
+ }
+ }
p1++;
EXTRACT_NUMBER_AND_INCR (mcnt, p1);
PUSH_FAILURE_POINT (p1 + mcnt, d, -2);
diff --git a/contrib/cvs/lib/system.h b/contrib/cvs/lib/system.h
index 7648b76355cf..8beedf0eda78 100644
--- a/contrib/cvs/lib/system.h
+++ b/contrib/cvs/lib/system.h
@@ -453,6 +453,13 @@ extern int errno;
#define CVS_STAT stat
#endif
+/* Open question: should CVS_STAT be lstat by default? We need
+ to use lstat in order to handle symbolic links correctly with
+ the PreservePermissions option. -twp */
+#ifndef CVS_LSTAT
+#define CVS_LSTAT lstat
+#endif
+
#ifndef CVS_UNLINK
#define CVS_UNLINK unlink
#endif
diff --git a/contrib/cvs/src/ChangeLog b/contrib/cvs/src/ChangeLog
index de2f535ac39f..96e170123292 100644
--- a/contrib/cvs/src/ChangeLog
+++ b/contrib/cvs/src/ChangeLog
@@ -1,5 +1,490 @@
+1998-03-04 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * entries.c, cvs.h (Entries_Open): New argument update_dir; use it
+ in error message.
+ * add.c, checkout.c, client.c, find_names.c, import.c, recurse.c,
+ update.c: Pass it (as NULL except in call_in_directory).
+ * entries.c (Subdirs_Known): Just return if there is no CVSADM
+ directory (as in subdir_record).
+ * sanity.sh (conflicts3): New tests conflicts3-20a and
+ conflicts3-23 test for these fixes.
+
+ * commit.c (commit): Only set up hardlist if preserve_perms.
+
+ * commit.c, import.c, no_diff.c, parseinfo.c, rcs.c, rcscmds.c,
+ update.c: Omit the preserve_perms code if
+ PRESERVE_PERMISSIONS_SUPPORT is not defined. Much of that code
+ won't even compile on non-unix systems.
+
+ * hardlink.c, hardlink.h: Use the 'standard' copyright (as found
+ in server.c).
+ * commit.c, rcs.c: Minor whitespace changes to Tim's submission.
+ * commit.c (check_fileproc), update.c (get_linkinfo_proc): Remove
+ unused variable delta.
+ * hardlink.c (set_hardlink_field_proc), update.c
+ (get_linkinfo_proc): Return a value rather than falling off the
+ end of the function.
+
+1998-03-02 Tim Pierce <twp@skepsis.com>
+
+ * update.c (special_file_mismatch): Compare the hard links of the
+ two revisions.
+
+ * rcs.c (RCS_checkout):
+
+ * hardlink.c, hardlink.h: New files.
+ (hardlink_info): New struct.
+ (hardlist, working_dir): New variables.
+ (list_files_proc, cache_hardlinks_proc, set_hardlink_field_proc,
+ lookup_file_by_inode, update_hardlink_info, list_files_linked_to):
+ New functions.
+
+ * Makefile.in (SOURCES): Add hardlink.c.
+ (OBJECTS): Add hardlink.o.
+ (HEADERS): Add hardlink.h.
+ * commit.c: Include hardlink.h.
+ (commit): Save the working directory before recursing. Walk the
+ hardlink list, calling set_hardlink_field_proc on each node.
+ (check_fileproc): Add each file's link information to hardlist.
+ * rcs.c: Include hardlink.h.
+ (RCS_checkin): Save list of hardlinks in delta node.
+ (RCS_checkout): Look up the file's `hardlinks' delta field, and
+ see if any of the files linked to it have been checked out
+ already. Link to one of those files if so.
+ * update.c: Include hardlink.h.
+ (get_linkinfo_proc): New function.
+ (do_update): Extra recursion to collect hardlink info.
+ (special_file_mismatch): Reparse the RCS file if necessary.
+
+ fsortcmp is now used by several files, so let's make it extern.
+ * hash.c, hash.h (fsortcmp): New function.
+ * find_names.c (fsortcmp): Removed.
+ * lock.c (fsortcmp): Removed.
+
+1998-03-03 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * sanity.sh (conflicts3): New tests conflicts3-14a,
+ conflicts3-14b, and conflicts3-21, conflicts3-22 test that we can
+ skip over a working directory with a CVSADM directory missing.
+
+1998-02-26 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * sanity.sh (conflicts3): Tests conflicts3-16 and conflicts3-20
+ test that we include update_dir in messages. Rename test
+ conflicts3-14 to fix typo.
+
+Sun Feb 22 23:14:25 1998 Steve Cameron <steve.cameron@compaq.com>
+ and Ian Lance Taylor <ian@cygnus.com>
+
+ * update.c (tag_update_dir): New static variable.
+ (update_dirent_proc): If no tag or date were specified when
+ creating a subdirectory, use the tag and/or date of the parent
+ directory.
+ (update_dirleave_proc): If we set the tag and/or date in
+ update_dirent_proc, reset them when we leave the directory.
+ * sanity.sh (branches2): New set of tests for above patch, and
+ related behaviour.
+
+Sun Feb 22 13:31:51 1998 Ian Lance Taylor <ian@cygnus.com>
+
+ * commit.c (lock_RCS): Don't call RCS_rewrite.
+
+ * update.c (patch_file): If the revision is dead, let
+ checkout_file handle it.
+ * sanity.sh (death2): Add test for above patch: add
+ death2-10a, death2-10b, death2-13a, and adjust
+ death2-{2,4,5,11,14,diff-11,diff-12,19}.
+
+ * cvs.h (RCS_FLAGS_KEEPFILE): Define.
+ * rcs.c (RCS_checkin): If RCS_FLAGS_KEEPFILE is set in the flags
+ parameter, don't unlink the working file.
+ * checkin.c (Checkin): Don't copy the file. Instead pass
+ RCS_FLAGS_KEEPFILE to RCS_checkin, and only check the file out
+ again if it has changed.
+
+1998-02-21 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * rcs.c (rcs_internal_unlockfile, RCS_rewrite): Don't assume errno
+ means anything just because ferror is set.
+
+Sat Feb 21 20:02:24 1998 Ian Lance Taylor <ian@cygnus.com>
+
+ * Makefile.in (clean): Change "/bin/rm" to "rm".
+
+ * buffer.c (buf_append_buffer): Correct typo in comment.
+ * rcs.c (RCS_putadmin): Likewise.
+
+Fri Feb 20 17:53:06 1998 Ian Lance Taylor <ian@cygnus.com>
+
+ * rcs.c (rcs_internal_unlockfile): Pass errno when calling error
+ because ferror is true.
+
+1998-02-20 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * sanity.sh (abspath): Don't assume that we can't write to /; this
+ is the kind of thing that is sure to break sooner or later
+ (especially on Windows).
+
+ * sanity.sh: Add summary of which modules tests are which (at
+ "modules"). Move cvsadm, abspath, and toplevel next to modules.
+ Add comments to clarify the structure (such as it is).
+
+Fri Feb 20 12:47:14 1998 Larry Jones <larry.jones@sdrc.com>
+
+ * admin.c (admin_fileproc): Better fix for -b.
+
+ * rcs.c (RCS_whatbranch): Back out previous change.
+ (RCS_getversion): Ditto.
+ (RCS_setbranch): Treat an empty revision string like a null pointer.
+
+1998-02-18 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * rcs.c (RCS_whatbranch): Fix indentation.
+
+ * patch.c (patch_fileproc): Check for errors from fclose; check
+ for errors from fopen properly.
+
+Wed Feb 18 16:03:37 1998 Larry Jones <larry.jones@sdrc.com>
+
+ * admin.c (admin_fileproc): Convert -b argument from symbolic name
+ to revision number before storing in the RCS file.
+ * rcs.c (RCS_whatbranch): Allow numeric as well as symbolic revision.
+ (RCS_getversion): Take advantage of above.
+ * sanity.sh (admin): Add/revise/renumber admin-10c, admin-11a,
+ admin-12, and admin-12a to check above.
+
+ * commmit.c (lock_RCS): Minor clean-up.
+
+ * sanity.sh (abspath-6a): Don't depend on the sepcific contents of
+ CVSROOT, it depends on which other tests have been run.
+
+Wed Feb 18 01:56:04 1998 Ian Lance Taylor <ian@cygnus.com>
+
+ * rcs.c (putsymbol_proc): Use putc and fputs rather than fprintf.
+ (RCS_putadmin): Don't call RCS_symbols if the symbols have not yet
+ been converted to a list.
+
+ * rcs.c (rcsbuf_cache, rcsbuf_cache_open, rcsbuf_cache_close): New
+ static functions to avoid closing and reopening the RCS file.
+ (cached_rcs, cached_rcsbuf): New static variables.
+ (RCS_parse): Call rcsbuf_cache_close. Don't call fclose.
+ (RCS_parsercsfile): Likewise.
+ (RCS_parsercsfile_i): Call rcsbuf_cache rather than
+ rcsbuf_close. Call fclose on error. Remove comment about
+ inefficiency of opening file twice.
+ (RCS_reparsercsfile): Call rcsbuf_cache_open rather than fopen and
+ rcsbuf_open. Call rcsbuf_cache rather than rcsbuf_close and
+ fclose.
+ (RCS_fully_parse, RCS_checkout, RCS_deltas): Likewise.
+ (RCS_rewrite): Likewise.
+ (RCS_checkin): Call rcsbuf_cache_close.
+
+ * rcs.c (RCS_copydeltas): Fix code which checks for an extra
+ newline in buffered data.
+
+ * rcs.c (rcsbuf_getkey): Save an indirection by using start rather
+ than *valp when trimming trailing whitespace from value.
+
+ * rcs.c (rcsbuf_get_buffered): New static function.
+ (RCS_copydeltas): After we have done all the required special
+ actions, and inserted any new revision, just copy the file bytes
+ directly, rather than interpreting all the data.
+ (count_delta_actions): New static function.
+ * sanity.sh (rcs): Add rcs-6a and rcs-6b to commit a new branch
+ revision, to force CVS to interpret all the data, rather than just
+ copying it. Adjust rcs-5 to add a branch tag. Adjust rcs-8a and
+ rcs-14 for the changes created by rcs-6b.
+
+Tue Feb 17 18:34:01 1998 Ian Lance Taylor <ian@cygnus.com>
+
+ * sanity.sh (cvsadm, diffmerge2): Remove directories at the end of
+ the test.
+
+ * import.c (expand_at_signs): Rewrite to use memchr and fwrite
+ rather than putc.
+
+ Rewrite RCS file reading routines for speed:
+ * rcs.c (struct rcsbuffer): Define.
+ (rcsbuf_open, rcsbuf_close, rcsbuf_getkey, rcsbuf_getrevnum,
+ rcsbuf_fill, rcsbuf_valcopy, rcsbuf_valpolish,
+ rcsbuf_valpolish_internal, rcsbuf_ftell): New static functions.
+ (getrcskey, getrcsrev, getrevnum): Remove.
+ (many functions): Change to use new rcsbuf functions instead of
+ old getrcskey/getrcsrev/getrevnum functions.
+ (RCS_reparsercsfile): Add rcsbufp parameter. Change all callers.
+ (RCS_deltas): Add rcsbuf parameter. Change all callers.
+ (getdelta): Change fp parameter to rcsbuf parameter. Change all
+ callers.
+ (RCS_getdeltatext): Add rcsbuf parameter. Change all callers.
+ (RCS_copydeltas): Add rcsbufin parameter. Change all callers.
+ * rcs.h (RCS_reparsercsfile): Update declaration.
+ * admin.c (admin_fileproc): Update calls to RCS_reparsercsfile for
+ new parameters.
+
+1998-02-17 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * sanity.sh (toplevel): Also clean up second-dir (not a new
+ bug, but triggered by running tests as "toplevel abspath").
+
+ * create_adm.c (Create_Admin): Just print update_dir to tell the
+ user where we are; not the whole xgetwd. Cleaner than
+ Noel's change (which also had problems in errno handling).
+ * sanity.sh (toplevel-12): Update accordingly.
+
+Tue Feb 17 02:32:21 1998 Noel Cragg <noel@swish.red-bean.com>
+
+ [These mods make "checkout" work with "-d /absolute/pathname"
+ once again.]
+
+ * checkout.c (checkout_proc): the -d flag on the command line
+ should override the -d flag in the modules file if the latter is
+ an absolute path. The loop that assembles the list of directories
+ to build has been reorganized slightly to prepare for rewriting
+ with last_component rather than assuming '/' as a path separator.
+ Also added to that loop was some code to handle absolute
+ pathnames.
+ (build_dirs_and_chdir): add a new argument that tells this routine
+ whether or not to check before it creates and populates
+ directories or not.
+
+ * filesubr.c (last_component): return the top-level directory when
+ asked about the top-level directory.
+
+ * sanity.sh (toplevel-12): change test to reflect the new style of
+ this error message.
+
+ * create_adm.c (Create_Admin): include the directory in the error
+ message.
+
+1998-02-16 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * diff.c (diff_fileproc), import.c (import, add_rcs_file), rcs.c
+ (RCS_cmp_file): Don't ignore errors from CVS_UNLINK and fclose.
+
+ * patch.c (patch_fileproc): Check for errors from fclose; if we
+ get -1 from getline check for end of file vs. error.
+
+ * rcs.c (RCS_checkout): Comment return value (0/1, not -1).
+ * commit.c, diff.c, mkmodules.c, patch.c, rcs.c, update.c: Update
+ to match this convention. Don't suppress errors based on
+ quiet or really_quiet variables.
+
+ Fix a longstanding bug which also makes stamps-8kw in make
+ remotecheck work again (it stopped working with Ian's 8 Feb 98
+ checkin):
+ * client.c, client.h (change_mode): If new argument respect_umask
+ is set, then honor the umask.
+ * client.c, server.c: Update callers.
+
+ Cleanups to Tim's checkin:
+ * rcs.c (RCS_checkout): Use existence_error not ENOENT.
+ * commit.c (checkaddfile): Remove comment about whether we want to
+ check for errors from fclose; there is no reason not to.
+ * rcs.c (RCS_checkout), update.c (special_file_mismatch): sscanf
+ on %ld requires an unsigned long, not a dev_t.
+ * update.c (special_file_mismatch): Remove unused variable
+ check_devnums.
+ * mkmodules.c (config_contents): Between two settings, use a blank
+ line not a "#" line.
+
+1998-02-15 Tim Pierce <twp@skepsis.com>
+
+ [This is the code as submitted. I'll be checking in my cleanups
+ shortly. This work sponsored by Abbott Labs. -kingdon]
+
+ Support for device special files, symbolic links, user and group
+ ownerships, and file permissions.
+
+ * parseinfo.c: (parse_config): Handle new config variable
+ `PreservePermissions'.
+ * mkmodules.c (config_contents): Add new PreservePermissions var.
+
+ * rcs.c, rcs.h (preserve_perms): New variable.
+ (RCS_checkout, RCS_checkin): Support for newphrases `owner',
+ `group', `permissions', `special', `symlink'.
+ (RCS_checkout): If `workfile' and `sout' are symlinks, remove them
+ before attempting to open them for writing.
+ * import.c (add_rcs_file): Support for newphrases. Do not attempt
+ to read data from special files or symlinks. Error message
+ `cannot fstat' is now `cannot lstat'.
+
+ New metrics for deciding when two files are different:
+
+ * update.c, cvs.h (special_file_mismatch): New function.
+ (merge_file, join_file): Call it.
+ * no_diff.c (No_Difference): Call it.
+
+ * filesubr.c (xcmp): Consider files to be different if they are of
+ different types; if they are symlinks which link to different
+ pathnames; or if they are devices with different device numbers.
+ Error message is now `cannot lstat'.
+ * rcs.c (RCS_cmp_file): Use `xcmp' to compare files, simplifying
+ the special handling for nonregular files.
+
+ * rcscmds.c (diff_exec, diff_execv): If asked to obtain diffs for
+ special files, report no differences.
+
+ Miscellaneous changes to make special file support possible:
+
+ * commit.c (fix_rcs_modes): Don't attempt to `fix' permissions on
+ a symlink.
+
+ * import.c (add_rcs_file): Don't try to close fpuser if it was
+ never opened (e.g. when operating on a symlink).
+
+ * filesubr.c, cvs.h (isdevice, xreadlink): New functions.
+ * filesubr.c (copy_file): Handle special files and symlinks.
+ (xchmod): Do nothing if `preserve_perms' is set.
+
+ * commit.c (checkaddfile): Replace `copy_file (DEVNULL, ...)' with
+ fopen/fclose calls. Copy_file no longer attempts to read data
+ from device files.
+
+ * filesubr.c (islink): Use CVS_LSTAT, not lstat.
+ * vers_ts.c (time_stamp, time_stamp_server): Use CVS_LSTAT, not stat,
+ to get symlinks right.
+ * subr.c (get_file): Same. Don't attempt to read from special
+ files or symlinks.
+
+ * classify.c (Classify_File): Doc fix.
+
+Fri Feb 13 17:07:32 1998 Eric Mumpower <nocturne@cygnus.com>
+ and Ian Lance Taylor <ian@cygnus.com>
+
+ Fix some file system ordering problems found on Irix 6.4:
+ * sanity.sh (basic2): Use dotest_sort for test 56.
+ (importb): Use dotest_sort for tests importb-1 and importb-2.
+ (head): Use dotest_sort for test head-1.
+
+Thu Feb 12 15:15:33 1998 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * import.c (add_rcs_file): If add_logfp is NULL, don't call fperror.
+
+11 Feb 1998 Andy Piper
+
+ * server.c (cvs_output_binary): Use OPEN_BINARY not _O_BINARY.
+
+Mon Feb 9 18:34:39 1998 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ Tweaks to Ian's checkin:
+ * update.c (merge_file): Remove comment about sending file to
+ client before the message. It doesn't apply to this code any more
+ (it does apply to checkout_file, but I'm not sure it is important
+ to have such a comment anyway).
+ * buffer.c (buf_default_memory_error, buf_length): Reindent.
+ * server.h: Declare struct buffer before use.
+
+Mon Feb 9 21:05:28 1998 Ian Lance Taylor <ian@cygnus.com>
+
+ * rcs.c (RCS_fully_parse): Call getrevnum rather than getrcsrev.
+ Don't bother with ungetc.
+
+ * rcs.c (getrcsrev): Rewrite to simply call getrevnum.
+
+Sun Feb 8 15:49:39 1998 Ian Lance Taylor <ian@cygnus.com>
+
+ Don't have the server check out a revision into a file and then
+ immediately read the file; just read into a buffer instead.
+ * update.c: Include buffer.h.
+ (update_fileproc): Let checkout_file call server_updated.
+ (checkout_file): Add merging and update_server parameters. Change
+ all callers. If server_active, don't mess with backup files. If
+ server_active, copy the revision into a buffer rather than a file
+ when possible. If update_server, call server_updated. Fix
+ handling of error status.
+ (checkout_to_buffer): New static function used by checkout_file.
+ (merge_file): Let checkout_file call server_updated.
+ (join_file): Likewise.
+ * server.c (server_updated): Change file_info parameter to mode
+ parameter. Add filebuf parameter. Change all callers. If
+ filebuf is not NULL, don't read the file.
+ * server.h (server_updated): Update declaration.
+ * buffer.c (buf_free): New function.
+ (buf_append_buffer): New function.
+ (buf_length): New function.
+ * buffer.h (buf_free, buf_append_buffer, buf_length): Declare.
+
+ * buffer.c: (buf_initialize): If the memory parameter is NULL, use
+ buf_default_memory_error.
+ (buf_default_memory_error): New static function.
+ * buffer.h (BUFMEMERRPROC): Define typedef.
+ * client.c (buf_memory_error): Remove.
+ (start_server): Pass NULL rather than buf_memory_error as buffer
+ memory error function.
+
+Sat Feb 7 16:27:30 1998 Ian Lance Taylor <ian@cygnus.com>
+
+ * rcs.c (RCS_parsercsfile_i): Read the expand keyword from the RCS
+ file. We do this because Version_TS calls RCS_getexpand in many
+ common cases, and we don't want to reopen the file just for that.
+ (RCS_reparsercsfile): Skip the expand keyword.
+ (RCS_getexpand): Don't call RCS_reparsercsfile.
+
+ * rcs.c (STREQ): New macro. In all string equality tests in the
+ file, replace strcmp with STREQ.
+
+Fri Feb 6 16:14:49 1998 Ian Lance Taylor <ian@cygnus.com>
+
+ * update.c (checkout_file): If we've already removed the backup
+ file once, don't try to remove it again.
+
+ * filesubr.c (unlink_file_dir): Call stat rather than isdir, and
+ don't call unlink if the file does not exist.
+
+ * myndbm.c (mydbm_load_file): Rename line_len to line_size. Call
+ getstr rather than getline, to avoid any confusion between \n and
+ \012. Use the line length returned by getstr rather than calling
+ strlen. Remove local variable len.
+
+Fri Feb 6 13:23:46 1998 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * rcs.c (RCS_parsercsfile_i): Don't suppress errors on
+ really_quiet.
+ (RCS_parsercsfile_i, RCS_reparsercsfile, RCS_fully_parse,
+ RCS_deltas, getdelta, getrcskey, RCS_getdeltatext):
+ Check for errors. Include errno in error messages. Include
+ filename in error messages. Pass new argument to getrcskey.
+ (getrcskey): New argument NAME, so we can report errors ourself.
+
+Fri Feb 6 12:10:18 1998 Ian Lance Taylor <ian@cygnus.com>
+
+ * rcs.c (RCS_reparsercsfile): Don't use ftell/fseek; just keep
+ track of whether we've already read a key/value pair. Use sizeof
+ rather than strlen for a constant string. Pass the current key
+ and value to getdelta, and get them back as well.
+ (getdelta): Add keyp and valp parameters. Don't use ftell/fseek;
+ just return the key/value pair to the caller. Don't allocate
+ vnode before we know we need it. Check one getrcskey return
+ value. Use sizeof rather than strlen for a constant string.
+
+ * rcs.c (getrcskey): Correct comment describing return value.
+
+Thu Feb 5 22:51:13 1998 Ian Lance Taylor <ian@cygnus.com>
+
+ * subr.c (getcaller): Cache the result, so that we don't keep
+ searching the password file.
+
+Wed Feb 4 23:31:08 1998 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * rcs.c (max_rev): Don't prototype. Interesting that noone
+ complained about this until now.
+
+4 Feb 1998 Jim Kingdon
+
+ * rcs.c (RCS_checkin): When adding a new file, read it
+ with "rb" if binary.
+
+Fri Jan 30 11:32:41 1998 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * sanity.sh: Also test "first-dir" as the regexp in loginfo in
+ addition to ALL.
+
+ * main.c (main): Update year in copyright notice to 1998.
+
Thu Jan 29 00:01:05 1998 Jim Kingdon <kingdon@harvey.cyclic.com>
+ * version.c: Change version number to 1.9.25.
+
* Version 1.9.24.
* sanity.sh (multibranch2): File file2 and tests multibranch2-13
diff --git a/contrib/cvs/src/Makefile.in b/contrib/cvs/src/Makefile.in
index 296554a264a0..61b4fe26d30a 100644
--- a/contrib/cvs/src/Makefile.in
+++ b/contrib/cvs/src/Makefile.in
@@ -39,7 +39,7 @@ LIBS = @LIBS@
SOURCES = add.c admin.c buffer.c checkin.c checkout.c classify.c client.c \
commit.c create_adm.c cvsrc.c diff.c edit.c entries.c error.c expand_path.c \
-fileattr.c find_names.c hash.c history.c ignore.c import.c \
+fileattr.c find_names.c hardlink.c hash.c history.c ignore.c import.c \
lock.c log.c login.c logmsg.c main.c mkmodules.c modules.c myndbm.c no_diff.c \
parseinfo.c patch.c rcs.c rcscmds.c recurse.c release.c remove.c repos.c \
root.c rtag.c scramble.c server.c status.c subr.c filesubr.c run.c \
@@ -47,14 +47,14 @@ tag.c update.c watch.c wrapper.c vers_ts.c version.c zlib.c
OBJECTS = add.o admin.o buffer.o checkin.o checkout.o classify.o client.o \
commit.o create_adm.o cvsrc.o diff.o edit.o entries.o expand_path.o \
-fileattr.o find_names.o hash.o history.o ignore.o import.o \
+fileattr.o find_names.o hardlink.o hash.o history.o ignore.o import.o \
lock.o log.o login.o logmsg.o main.o mkmodules.o modules.o myndbm.o no_diff.o \
parseinfo.o patch.o rcs.o rcscmds.o recurse.o release.o remove.o repos.o \
root.o rtag.o scramble.o server.o status.o tag.o update.o \
watch.o wrapper.o vers_ts.o \
subr.o filesubr.o run.o version.o error.o zlib.o
-HEADERS = buffer.h cvs.h rcs.h hash.h myndbm.h \
+HEADERS = buffer.h cvs.h rcs.h hardlink.h hash.h myndbm.h \
update.h server.h client.h error.h fileattr.h edit.h watch.h
TAGFILES = $(HEADERS) options.h.in $(SOURCES)
@@ -125,7 +125,7 @@ ls:
.PHONY: ls
clean:
- /bin/rm -f $(PROGS) *.o core check.log check.plog
+ rm -f $(PROGS) *.o core check.log check.plog
.PHONY: clean
distclean: clean
diff --git a/contrib/cvs/src/add.c b/contrib/cvs/src/add.c
index ab667df39a82..d44435a3d592 100644
--- a/contrib/cvs/src/add.c
+++ b/contrib/cvs/src/add.c
@@ -264,7 +264,7 @@ add (argc, argv)
/* Find the repository associated with our current dir. */
repository = Name_Repository (NULL, finfo.update_dir);
- entries = Entries_Open (0);
+ entries = Entries_Open (0, NULL);
finfo.repository = repository;
finfo.entries = entries;
diff --git a/contrib/cvs/src/admin.c b/contrib/cvs/src/admin.c
index a78badacc4db..3c50f4a57ac2 100644
--- a/contrib/cvs/src/admin.c
+++ b/contrib/cvs/src/admin.c
@@ -478,7 +478,7 @@ admin_fileproc (callerdat, finfo)
rcs = vers->srcfile;
if (rcs->flags & PARTIAL)
- RCS_reparsercsfile (rcs, NULL);
+ RCS_reparsercsfile (rcs, (FILE **) NULL, (struct rcsbuffer *) NULL);
status = 0;
@@ -490,9 +490,23 @@ admin_fileproc (callerdat, finfo)
}
if (admin_data->branch != NULL)
- RCS_setbranch (rcs, (admin_data->branch[2] == '\0'
- ? NULL
- : admin_data->branch + 2));
+ {
+ char *branch = &admin_data->branch[2];
+ if (*branch != '\0' && ! isdigit (*branch))
+ {
+ branch = RCS_whatbranch (rcs, admin_data->branch + 2);
+ if (branch == NULL)
+ {
+ error (0, 0, "%s: Symbolic name %s is undefined.",
+ rcs->path, admin_data->branch + 2);
+ status = 1;
+ }
+ }
+ if (status == 0)
+ RCS_setbranch (rcs, branch);
+ if (branch != NULL && branch != &admin_data->branch[2])
+ free (branch);
+ }
if (admin_data->comment != NULL)
{
if (rcs->comment != NULL)
@@ -793,7 +807,7 @@ admin_fileproc (callerdat, finfo)
RCS data structure. Forcing a reparse does the trick,
but leaks memory and is kludgey. Should we export
free_rcsnode_contents for this purpose? */
- RCS_reparsercsfile (rcs, NULL);
+ RCS_reparsercsfile (rcs, (FILE **) NULL, (struct rcsbuffer *) NULL);
}
exitfunc:
diff --git a/contrib/cvs/src/buffer.c b/contrib/cvs/src/buffer.c
index 0042e4c3be5c..8f9054506640 100644
--- a/contrib/cvs/src/buffer.c
+++ b/contrib/cvs/src/buffer.c
@@ -16,6 +16,7 @@
static struct buffer_data *free_buffer_data;
/* Local functions. */
+static void buf_default_memory_error PROTO ((struct buffer *));
static void allocate_buffer_datas PROTO((void));
static struct buffer_data *get_buffer_data PROTO((void));
@@ -42,11 +43,25 @@ buf_initialize (input, output, flush, block, shutdown, memory, closure)
buf->flush = flush;
buf->block = block;
buf->shutdown = shutdown;
- buf->memory_error = memory;
+ buf->memory_error = memory ? memory : buf_default_memory_error;
buf->closure = closure;
return buf;
}
+/* Free a buffer structure. */
+
+void
+buf_free (buf)
+ struct buffer *buf;
+{
+ if (buf->data != NULL)
+ {
+ buf->last->next = free_buffer_data;
+ free_buffer_data = buf->data;
+ }
+ free (buf);
+}
+
/* Initialize a buffer structure which is not to be used for I/O. */
struct buffer *
@@ -63,6 +78,15 @@ buf_nonio_initialize (memory)
(void *) NULL));
}
+/* Default memory error handler. */
+
+static void
+buf_default_memory_error (buf)
+ struct buffer *buf;
+{
+ error (1, 0, "out of memory");
+}
+
/* Allocate more buffer_data structures. */
static void
@@ -470,6 +494,19 @@ buf_append_data (buf, data, last)
}
}
+/* Append the data on one buffer to another. This removes the data
+ from the source buffer. */
+
+void
+buf_append_buffer (to, from)
+ struct buffer *to;
+ struct buffer *from;
+{
+ buf_append_data (to, from->data, from->last);
+ from->data = NULL;
+ from->last = NULL;
+}
+
/*
* Copy the contents of file F into buffer_data structures. We can't
* copy directly into an buffer, because we want to handle failure and
@@ -621,6 +658,15 @@ buf_chain_length (buf)
return size;
}
+/* Return the number of bytes in a buffer. */
+
+int
+buf_length (buf)
+ struct buffer *buf;
+{
+ return buf_chain_length (buf->data);
+}
+
/*
* Read an arbitrary amount of data into an input buffer. The buffer
* will be in nonblocking mode, and we just grab what we can. Return
diff --git a/contrib/cvs/src/buffer.h b/contrib/cvs/src/buffer.h
index c632490c041a..0556781d0c8c 100644
--- a/contrib/cvs/src/buffer.h
+++ b/contrib/cvs/src/buffer.h
@@ -96,6 +96,9 @@ struct buffer_data
/* The size we allocate for each buffer_data structure. */
#define BUFFER_DATA_SIZE (4096)
+/* The type of a function passed as a memory error handler. */
+typedef void (*BUFMEMERRPROC) PROTO ((struct buffer *));
+
extern struct buffer *buf_initialize PROTO((int (*) (void *, char *, int,
int, int *),
int (*) (void *, const char *,
@@ -105,6 +108,7 @@ extern struct buffer *buf_initialize PROTO((int (*) (void *, char *, int,
int (*) (void *),
void (*) (struct buffer *),
void *));
+extern void buf_free PROTO((struct buffer *));
extern struct buffer *buf_nonio_initialize PROTO((void (*) (struct buffer *)));
extern struct buffer *stdio_buffer_initialize
PROTO((FILE *, int, void (*) (struct buffer *)));
@@ -127,6 +131,7 @@ extern int buf_send_special_count PROTO((struct buffer *, int));
extern void buf_append_data PROTO((struct buffer *,
struct buffer_data *,
struct buffer_data *));
+extern void buf_append_buffer PROTO((struct buffer *, struct buffer *));
extern int buf_read_file PROTO((FILE *, long, struct buffer_data **,
struct buffer_data **));
extern int buf_read_file_to_eof PROTO((FILE *, struct buffer_data **,
@@ -137,6 +142,7 @@ extern int buf_read_data PROTO((struct buffer *, int, char **, int *));
extern void buf_copy_lines PROTO((struct buffer *, struct buffer *, int));
extern int buf_copy_counted PROTO((struct buffer *, struct buffer *, int *));
extern int buf_chain_length PROTO((struct buffer_data *));
+extern int buf_length PROTO((struct buffer *));
extern int buf_shutdown PROTO((struct buffer *));
#ifdef SERVER_FLOWCONTROL
diff --git a/contrib/cvs/src/checkin.c b/contrib/cvs/src/checkin.c
index 1d89ae12cd90..70fb74cb1cb9 100644
--- a/contrib/cvs/src/checkin.c
+++ b/contrib/cvs/src/checkin.c
@@ -29,7 +29,6 @@ Checkin (type, finfo, rcs, rev, tag, options, message)
char *options;
char *message;
{
- char *fname;
Vers_TS *vers;
int set_time;
char *tocvsPath = NULL;
@@ -42,69 +41,63 @@ Checkin (type, finfo, rcs, rev, tag, options, message)
cvs_output (finfo->fullname, 0);
cvs_output (";\n", 0);
- fname = xmalloc (strlen (finfo->file) + 80);
- (void) sprintf (fname, "%s/%s%s", CVSADM, CVSPREFIX, finfo->file);
-
- /*
- * Move the user file to a backup file, so as to preserve its
- * modification times, then place a copy back in the original file name
- * for the checkin and checkout.
- */
-
tocvsPath = wrap_tocvs_process_file (finfo->file);
-
if (!noexec)
{
if (tocvsPath)
{
- copy_file (tocvsPath, fname);
if (unlink_file_dir (finfo->file) < 0)
if (! existence_error (errno))
error (1, errno, "cannot remove %s", finfo->fullname);
- copy_file (tocvsPath, finfo->file);
- }
- else
- {
- copy_file (finfo->file, fname);
+ rename_file (tocvsPath, finfo->file);
}
}
if (finfo->rcs == NULL)
finfo->rcs = RCS_parse (finfo->file, finfo->repository);
- switch (RCS_checkin (finfo->rcs, NULL, message, rev, 0))
+ switch (RCS_checkin (finfo->rcs, NULL, message, rev, RCS_FLAGS_KEEPFILE))
{
case 0: /* everything normal */
- /*
- * The checkin succeeded, so now check the new file back out and
- * see if it matches exactly with the one we checked in. If it
- * does, just move the original user file back, thus preserving
- * the modes; otherwise, we have no recourse but to leave the
- * newly checkout file as the user file and remove the old
- * original user file.
- */
+ /* The checkin succeeded. If checking the file out again
+ would not cause any changes, we are done. Otherwise,
+ we need to check out the file, which will change the
+ modification time of the file.
+
+ The only way checking out the file could cause any
+ changes is if the file contains RCS keywords. So we if
+ we are not expanding RCS keywords, we are done. */
if (strcmp (options, "-V4") == 0) /* upgrade to V5 now */
options[0] = '\0';
- /* FIXME: should be checking for errors. */
- (void) RCS_checkout (finfo->rcs, finfo->file, rev,
- (char *) NULL, options, RUN_TTY,
- (RCSCHECKOUTPROC) NULL, (void *) NULL);
-
- xchmod (finfo->file, 1);
- if (xcmp (finfo->file, fname) == 0)
+ /* FIXME: If PreservePermissions is on, RCS_cmp_file is
+ going to call RCS_checkout into a temporary file
+ anyhow. In that case, it would be more efficient to
+ call RCS_checkout here, compare the resulting files
+ using xcmp, and rename if necessary. I think this
+ should be fixed in RCS_cmp_file. */
+ if ((! preserve_perms
+ && options != NULL
+ && (strcmp (options, "-ko") == 0
+ || strcmp (options, "-kb") == 0))
+ || RCS_cmp_file (finfo->rcs, rev, options, finfo->file) == 0)
{
- rename_file (fname, finfo->file);
- /* the time was correct, so leave it alone */
+ /* The existing file is correct. We don't have to do
+ anything. */
set_time = 0;
}
else
{
- if (unlink_file (fname) < 0)
- error (0, errno, "cannot remove %s", fname);
- /* sync up with the time from the RCS file */
+ /* The existing file is incorrect. We need to check
+ out the correct file contents. */
+ if (RCS_checkout (finfo->rcs, finfo->file, rev, (char *) NULL,
+ options, RUN_TTY, (RCSCHECKOUTPROC) NULL,
+ (void *) NULL) != 0)
+ error (1, 0, "failed when checking out new copy of %s",
+ finfo->fullname);
+ xchmod (finfo->file, 1);
set_time = 1;
}
@@ -140,25 +133,19 @@ Checkin (type, finfo, rcs, rev, tag, options, message)
if (!noexec)
error (1, errno, "could not check in %s -- fork failed",
finfo->fullname);
- free (fname);
return (1);
default: /* ci failed */
- /*
- * The checkin failed, for some unknown reason, so we restore the
- * original user file, print an error, and return an error
- */
+ /* The checkin failed, for some unknown reason, so we
+ print an error, and return an error. We assume that
+ the original file has not been touched. */
if (tocvsPath)
if (unlink_file_dir (tocvsPath) < 0)
error (0, errno, "cannot remove %s", tocvsPath);
if (!noexec)
- {
- rename_file (fname, finfo->file);
error (0, 0, "could not check in %s", finfo->fullname);
- }
- free (fname);
return (1);
}
@@ -179,7 +166,8 @@ Checkin (type, finfo, rcs, rev, tag, options, message)
if (set_time)
/* Need to update the checked out file on the client side. */
server_updated (finfo, vers, SERVER_UPDATED,
- NULL, NULL);
+ (mode_t) -1, (unsigned char *) NULL,
+ (struct buffer *) NULL);
else
server_checked_in (finfo->file, finfo->update_dir, finfo->repository);
}
@@ -188,6 +176,5 @@ Checkin (type, finfo, rcs, rev, tag, options, message)
mark_up_to_date (finfo->file);
freevers_ts (&vers);
- free (fname);
return (0);
}
diff --git a/contrib/cvs/src/checkout.c b/contrib/cvs/src/checkout.c
index 3c1eef7fe11b..46151eb99694 100644
--- a/contrib/cvs/src/checkout.c
+++ b/contrib/cvs/src/checkout.c
@@ -33,6 +33,7 @@
* edited by the user, if necessary (when the repository is moved, e.g.)
*/
+#include <assert.h>
#include "cvs.h"
static char *findslash PROTO((char *start, char *p));
@@ -431,7 +432,7 @@ struct dir_to_build
};
static int build_dirs_and_chdir PROTO ((struct dir_to_build *list,
- int sticky));
+ int sticky, int check_existing_dirs));
static void build_one_dir PROTO ((char *, char *, int));
@@ -580,7 +581,11 @@ checkout_proc (pargc, argv, where_orig, mwhere, mfile, shorten,
(void) strcat (where, "/");
}
- if (mwhere != NULL)
+ /* If the -d flag in the modules file specified an absolute
+ directory, let the user override it with the command-line
+ -d option. */
+
+ if ((mwhere != NULL) && (! isabsolute (mwhere)))
(void) strcat (where, mwhere);
else
(void) strcat (where, argv[0]);
@@ -723,19 +728,39 @@ internal error: %s doesn't start with %s in checkout_proc",
/* Make a copy of the repository name to play with. */
reposcopy = xstrdup (repository);
- /* FIXME: this should be written in terms of last_component instead
- of hardcoding '/'. This presumably affects OS/2, NT, &c, if
- the user specifies '\'. Likewise for the call to findslash. */
- cp = strrchr (where, '/');
- while (cp != NULL)
+ /* FIXME: this should be written in terms of last_component
+ instead of hardcoding '/'. This presumably affects OS/2,
+ NT, &c, if the user specifies '\'. Likewise for the call
+ to findslash. */
+ cp = where + strlen (where);
+ while (1)
{
struct dir_to_build *new;
+
+ cp = findslash (where, cp - 1);
+ if (cp == NULL)
+ break; /* we're done */
+
new = (struct dir_to_build *)
xmalloc (sizeof (struct dir_to_build));
new->dirpath = xmalloc (strlen (where));
- strncpy (new->dirpath, where, cp - where);
- new->dirpath[cp - where] = '\0';
+ /* If the user specified an absolute path for where, the
+ last path element we create should be the top-level
+ directory. */
+
+ if (cp - where)
+ {
+ strncpy (new->dirpath, where, cp - where);
+ new->dirpath[cp - where] = '\0';
+ }
+ else
+ {
+ /* where should always be at least one character long. */
+ assert (strlen (where));
+ strcpy (new->dirpath, "/");
+ }
+
/* Now figure out what repository directory to generate.
The most complete case would be something like this:
@@ -813,31 +838,41 @@ internal error: %s doesn't start with %s in checkout_proc",
new->next = head;
head = new;
-
- cp = findslash (where, cp - 1);
}
/* clean up */
free (reposcopy);
- /* The top-level CVSADM directory should always be
- CVSroot_directory. Create it.
-
- It may be argued that we shouldn't set any sticky bits for
- the top-level repository. FIXME? */
- build_one_dir (CVSroot_directory, ".", *pargc <= 1);
-
- /*
- * build dirs on the path if necessary and leave us in the bottom
- * directory (where if where was specified) doesn't contain a CVS
- * subdir yet, but all the others contain CVS and Entries.Static
- * files
- */
- if (build_dirs_and_chdir (head, *pargc <= 1) != 0)
{
- error (0, 0, "ignoring module %s", omodule);
- err = 1;
- goto out;
+ int where_is_absolute = isabsolute (where);
+
+ /* The top-level CVSADM directory should always be
+ CVSroot_directory. Create it, but only if WHERE is
+ relative. If WHERE is absolute, our current directory
+ may not have a thing to do with where the sources are
+ being checked out. If it does, build_dirs_and_chdir
+ will take care of creating adm files here. */
+
+ if (! where_is_absolute)
+ {
+ /* It may be argued that we shouldn't set any sticky
+ bits for the top-level repository. FIXME? */
+ build_one_dir (CVSroot_directory, ".", *pargc <= 1);
+ }
+
+
+ /* Build dirs on the path if necessary and leave us in the
+ bottom directory (where if where was specified) doesn't
+ contain a CVS subdir yet, but all the others contain
+ CVS and Entries.Static files */
+
+ if (build_dirs_and_chdir (head, *pargc <= 1,
+ where_is_absolute) != 0)
+ {
+ error (0, 0, "ignoring module %s", omodule);
+ err = 1;
+ goto out;
+ }
}
/* set up the repository (or make sure the old one matches) */
@@ -969,7 +1004,7 @@ internal error: %s doesn't start with %s in checkout_proc",
List *entries;
/* we are only doing files, so register them */
- entries = Entries_Open (0);
+ entries = Entries_Open (0, NULL);
for (i = 1; i < *pargc; i++)
{
char *line;
@@ -1066,13 +1101,22 @@ emptydir_name ()
return repository;
}
-/* Build all the dirs along the path to DIRS with CVS subdirs with appropriate
- repositories. If ->repository is NULL, do not create a CVSADM directory
- for that subdirectory; just CVS_CHDIR into it. */
+
+/* Build all the dirs along the path to DIRS with CVS subdirs with
+ appropriate repositories. If ->repository is NULL, do not create a
+ CVSADM directory for that subdirectory; just CVS_CHDIR into it. If
+ check_existing_dirs is nonzero, don't create directories if they
+ already exist, and don't try to write adm files in directories
+ where we don't have write permission. We use this last option
+ primarily when a user has specified an absolute path for checkout
+ -- we will often not have permission to top-level directories, so
+ we shouldn't complain. */
+
static int
-build_dirs_and_chdir (dirs, sticky)
+build_dirs_and_chdir (dirs, sticky, check_existing_dirs)
struct dir_to_build *dirs;
int sticky;
+ int check_existing_dirs;
{
int retval = 0;
struct dir_to_build *nextdir;
@@ -1080,20 +1124,31 @@ build_dirs_and_chdir (dirs, sticky)
while (dirs != NULL)
{
char *dir = last_component (dirs->dirpath);
+ int dir_is_writeable;
+
+ if ((! check_existing_dirs) || (! isdir (dir)))
+ mkdir_if_needed (dir);
- mkdir_if_needed (dir);
Subdir_Register (NULL, NULL, dir);
+
+ /* This is an expensive call -- only make it if necessary. */
+ if (check_existing_dirs)
+ dir_is_writeable = iswritable (dir);
+
if (CVS_CHDIR (dir) < 0)
{
error (0, errno, "cannot chdir to %s", dir);
retval = 1;
goto out;
}
- if (dirs->repository != NULL)
+
+ if ((dirs->repository != NULL)
+ && ((! check_existing_dirs) || dir_is_writeable))
{
build_one_dir (dirs->repository, dirs->dirpath, sticky);
free (dirs->repository);
}
+
nextdir = dirs->next;
free (dirs->dirpath);
free (dirs);
diff --git a/contrib/cvs/src/classify.c b/contrib/cvs/src/classify.c
index 57c23cdc9b31..b33c94537780 100644
--- a/contrib/cvs/src/classify.c
+++ b/contrib/cvs/src/classify.c
@@ -302,6 +302,8 @@ conflict: %s created independently by second party",
* has changed. If the sticky tag has changed, we just need
* to re-register the entry
*/
+ /* TODO: decide whether we need to check file permissions
+ for a mismatch, and return T_CONFLICT if so. */
if (vers->entdata->options &&
strcmp (vers->entdata->options, vers->options) != 0)
ret = T_CHECKOUT;
@@ -377,7 +379,7 @@ conflict: %s created independently by second party",
* The user file is still unmodified, so just get it as well
*/
#ifdef SERVER_SUPPORT
- if (strcmp (vers->entdata->options ?
+ if (strcmp (vers->entdata->options ?
vers->entdata->options : "", vers->options) != 0
|| (vers->srcfile != NULL
&& (vers->srcfile->flags & INATTIC) != 0))
diff --git a/contrib/cvs/src/client.c b/contrib/cvs/src/client.c
index ca6f464f3b15..aa897d5c7196 100644
--- a/contrib/cvs/src/client.c
+++ b/contrib/cvs/src/client.c
@@ -134,7 +134,6 @@ static void handle_e PROTO((char *, int));
static void handle_f PROTO((char *, int));
static void handle_notified PROTO((char *, int));
-static void buf_memory_error PROTO((struct buffer *));
static size_t try_read_from_server PROTO ((char *, size_t));
#endif /* CLIENT_SUPPORT */
@@ -182,11 +181,13 @@ mode_to_string (mode)
/*
* Change mode of FILENAME to MODE_STRING.
* Returns 0 for success or errno code.
+ * If RESPECT_UMASK is set, then honor the umask.
*/
int
-change_mode (filename, mode_string)
+change_mode (filename, mode_string, respect_umask)
char *filename;
char *mode_string;
+ int respect_umask;
{
#ifdef CHMOD_BROKEN
char *p;
@@ -217,6 +218,10 @@ change_mode (filename, mode_string)
++p;
}
+ /* xchmod honors the umask for us. In the !respect_umask case, we
+ don't try to cope with it (probably to handle that well, the server
+ needs to deal with modes in data structures, rather than via the
+ modes in temporary files). */
xchmod (filename, writeable);
return 0;
@@ -224,6 +229,7 @@ change_mode (filename, mode_string)
char *p;
mode_t mode = 0;
+ mode_t oumask;
p = mode_string;
while (*p != '\0')
@@ -277,6 +283,13 @@ change_mode (filename, mode_string)
++p;
}
+ if (respect_umask)
+ {
+ oumask = umask (0);
+ (void) umask (oumask);
+ mode &= ~oumask;
+ }
+
if (chmod (filename, mode) < 0)
return errno;
return 0;
@@ -305,16 +318,6 @@ static FILE *from_server_fp;
static int rsh_pid = -1;
-/* This routine is called when one of the buffer routines runs out of
- memory. */
-
-static void
-buf_memory_error (buf)
- struct buffer *buf;
-{
- error (1, 0, "out of memory");
-}
-
/* We want to be able to log data sent between us and the server. We
do it using log buffers. Each log buffer has another buffer which
handles the actual I/O, and a file to log information to.
@@ -1138,7 +1141,7 @@ warning: server is not creating directories one at a time");
if (strcmp (command_name, "export") != 0)
{
- last_entries = Entries_Open (0);
+ last_entries = Entries_Open (0, dir_name);
/* If this is a newly created directory, we will record
all subdirectory information, so call Subdirs_Known in
@@ -1928,8 +1931,7 @@ update_entries (data_arg, ent_list, short_pathname, filename)
}
{
- /* FIXME: we should be respecting the umask. */
- int status = change_mode (filename, mode_string);
+ int status = change_mode (filename, mode_string, 1);
if (status != 0)
error (0, status, "cannot change mode of %s", short_pathname);
}
@@ -1939,7 +1941,7 @@ update_entries (data_arg, ent_list, short_pathname, filename)
}
if (stored_mode_valid)
- change_mode (filename, stored_mode);
+ change_mode (filename, stored_mode, 1);
stored_mode_valid = 0;
if (stored_modtime_valid)
@@ -4012,9 +4014,9 @@ the :server: access method is not supported by this port of CVS");
if (use_socket_style)
{
to_server = socket_buffer_initialize (server_sock, 0,
- buf_memory_error);
+ (BUFMEMERRPROC) NULL);
from_server = socket_buffer_initialize (server_sock, 1,
- buf_memory_error);
+ (BUFMEMERRPROC) NULL);
}
else
#endif /* NO_SOCKET_TO_FD */
@@ -4038,13 +4040,13 @@ the :server: access method is not supported by this port of CVS");
if (to_server_fp == NULL)
error (1, errno, "cannot fdopen %d for write", tofd);
to_server = stdio_buffer_initialize (to_server_fp, 0,
- buf_memory_error);
+ (BUFMEMERRPROC) NULL);
from_server_fp = fdopen (fromfd, FOPEN_BINARY_READ);
if (from_server_fp == NULL)
error (1, errno, "cannot fdopen %d for read", fromfd);
from_server = stdio_buffer_initialize (from_server_fp, 1,
- buf_memory_error);
+ (BUFMEMERRPROC) NULL);
}
/* Set up logfiles, if any. */
@@ -4071,7 +4073,7 @@ the :server: access method is not supported by this port of CVS");
error (0, errno, "opening to-server logfile %s", buf);
else
to_server = log_buffer_initialize (to_server, fp, 0,
- buf_memory_error);
+ (BUFMEMERRPROC) NULL);
strcpy (p, ".out");
fp = open_file (buf, "wb");
@@ -4079,7 +4081,7 @@ the :server: access method is not supported by this port of CVS");
error (0, errno, "opening from-server logfile %s", buf);
else
from_server = log_buffer_initialize (from_server, fp, 1,
- buf_memory_error);
+ (BUFMEMERRPROC) NULL);
free (buf);
}
@@ -4240,10 +4242,10 @@ the :server: access method is not supported by this port of CVS");
send_to_server ("Kerberos-encrypt\012", 0);
to_server = krb_encrypt_buffer_initialize (to_server, 0, sched,
kblock,
- buf_memory_error);
+ (BUFMEMERRPROC) NULL);
from_server = krb_encrypt_buffer_initialize (from_server, 1,
sched, kblock,
- buf_memory_error);
+ (BUFMEMERRPROC) NULL);
}
else
#endif /* HAVE_KERBEROS */
@@ -4255,10 +4257,12 @@ the :server: access method is not supported by this port of CVS");
send_to_server ("Gssapi-encrypt\012", 0);
to_server = cvs_gssapi_wrap_buffer_initialize (to_server, 0,
gcontext,
- buf_memory_error);
+ ((BUFMEMERRPROC)
+ NULL));
from_server = cvs_gssapi_wrap_buffer_initialize (from_server, 1,
gcontext,
- buf_memory_error);
+ ((BUFMEMERRPROC)
+ NULL));
cvs_gssapi_encrypt = 1;
}
else
@@ -4283,10 +4287,10 @@ the :server: access method is not supported by this port of CVS");
compressed. */
to_server = compress_buffer_initialize (to_server, 0, gzip_level,
- buf_memory_error);
+ (BUFMEMERRPROC) NULL);
from_server = compress_buffer_initialize (from_server, 1,
gzip_level,
- buf_memory_error);
+ (BUFMEMERRPROC) NULL);
}
#ifndef NO_CLIENT_GZIP_PROCESS
else if (supported_request ("gzip-file-contents"))
@@ -4327,10 +4331,12 @@ the :server: access method is not supported by this port of CVS");
send_to_server ("Gssapi-authenticate\012", 0);
to_server = cvs_gssapi_wrap_buffer_initialize (to_server, 0,
gcontext,
- buf_memory_error);
+ ((BUFMEMERRPROC)
+ NULL));
from_server = cvs_gssapi_wrap_buffer_initialize (from_server, 1,
gcontext,
- buf_memory_error);
+ ((BUFMEMERRPROC)
+ NULL));
}
else
error (1, 0, "Stream authentication is only supported when using GSSAPI");
@@ -5093,7 +5099,7 @@ send_file_names (argc, argv, flags)
command line, not the case of the
directory in the filesystem. This
is correct behavior. */
- entries = Entries_Open (0);
+ entries = Entries_Open (0, NULL);
node = findnode_fn (entries, p);
if (node != NULL)
{
diff --git a/contrib/cvs/src/client.h b/contrib/cvs/src/client.h
index 119168ccde6a..996dc63366c9 100644
--- a/contrib/cvs/src/client.h
+++ b/contrib/cvs/src/client.h
@@ -2,7 +2,7 @@
/* Stuff shared with the server. */
extern char *mode_to_string PROTO((mode_t));
-extern int change_mode PROTO((char *, char *));
+extern int change_mode PROTO((char *, char *, int));
extern int gzip_level;
extern int file_gzip_level;
diff --git a/contrib/cvs/src/commit.c b/contrib/cvs/src/commit.c
index a7fec5f9bb84..4aa243868a91 100644
--- a/contrib/cvs/src/commit.c
+++ b/contrib/cvs/src/commit.c
@@ -19,6 +19,7 @@
#include "getline.h"
#include "edit.h"
#include "fileattr.h"
+#include "hardlink.h"
static Dtype check_direntproc PROTO ((void *callerdat, char *dir,
char *repos, char *update_dir,
@@ -81,7 +82,6 @@ static List *mulist;
static char *message;
static time_t last_register_time;
-
static const char *const commit_usage[] =
{
"Usage: %s %s [-nRlf] [-m msg | -F logfile] [-r rev] files...\n",
@@ -622,10 +622,23 @@ commit (argc, argv)
lock_tree_for_write (argc, argv, local, aflag);
/*
- * Set up the master update list
+ * Set up the master update list and hard link list
*/
mulist = getlist ();
+#ifdef PRESERVE_PERMISSIONS_SUPPORT
+ if (preserve_perms)
+ {
+ hardlist = getlist ();
+
+ /*
+ * We need to save the working directory so that
+ * check_fileproc can construct a full pathname for each file.
+ */
+ working_dir = xgetwd();
+ }
+#endif
+
/*
* Run the recursion processor to verify the files are all up-to-date
*/
@@ -638,6 +651,17 @@ commit (argc, argv)
error (1, 0, "correct above errors first!");
}
+#ifdef PRESERVE_PERMISSIONS_SUPPORT
+ if (preserve_perms)
+ {
+ /* hardlist now includes a complete index of the files
+ to be committed, indexed by inode. For each inode,
+ compile a list of the files that are linked to it,
+ and save this list in each file's hardlink_info node. */
+ (void) walklist (hardlist, cache_hardlinks_proc, NULL);
+ }
+#endif
+
/*
* Run the recursion processor to commit the files
*/
@@ -992,6 +1016,43 @@ warning: file `%s' seems to still contain conflict indicators",
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. */
+ fullpath = xmalloc (strlen(working_dir) +
+ strlen(finfo->fullname) + 2);
+ sprintf (fullpath, "%s/%s", working_dir, finfo->fullname);
+
+ /* To permit following links in subdirectories, files
+ are keyed on finfo->fullname, not on finfo->name. */
+ linkp = lookup_file_by_inode (fullpath);
+
+ /* If linkp is NULL, the file doesn't exist... maybe
+ 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;
+ hlinfo->links = NULL;
+ linkp->data = (char *) hlinfo;
+ }
+ }
+#endif
+
break;
case T_UNKNOWN:
error (0, 0, "nothing known about `%s'", finfo->fullname);
@@ -1280,8 +1341,9 @@ commit_fileproc (callerdat, finfo)
/* Doesn't matter, it won't get checked. */
SERVER_UPDATED,
- (struct stat *) NULL,
- (unsigned char *) NULL);
+ (mode_t) -1,
+ (unsigned char *) NULL,
+ (struct buffer *) NULL);
}
#endif
}
@@ -1645,9 +1707,8 @@ remove_file (finfo, tag, message)
(RCSCHECKOUTPROC) NULL, (void *) NULL);
if (retcode != 0)
{
- if (!quiet)
- error (0, retcode == -1 ? errno : 0,
- "failed to check out `%s'", finfo->fullname);
+ error (0, 0,
+ "failed to check out `%s'", finfo->fullname);
return (1);
}
@@ -1981,13 +2042,21 @@ internal error: `%s' didn't move out of the attic",
if (tag && newfile)
{
char *tmp;
+ FILE *fp;
/* move the new file out of the way. */
fname = xmalloc (strlen (file) + sizeof (CVSADM)
+ sizeof (CVSPREFIX) + 10);
(void) sprintf (fname, "%s/%s%s", CVSADM, CVSPREFIX, file);
rename_file (file, fname);
- copy_file (DEVNULL, file);
+
+ /* Create empty FILE. Can't use copy_file with a DEVNULL
+ argument -- copy_file now ignores device files. */
+ fp = fopen (file, "w");
+ if (fp == NULL)
+ error (1, errno, "cannot open %s for writing", file);
+ if (fclose (fp) < 0)
+ error (0, errno, "cannot close %s", file);
tmp = xmalloc (strlen (file) + strlen (tag) + 80);
/* commit a dead revision. */
@@ -2156,18 +2225,31 @@ lock_RCS (user, rcs, rev, repository)
{
(void) RCS_lock(rcs, rev, 1);
}
- RCS_rewrite (rcs, NULL, NULL);
+
+ /* We used to call RCS_rewrite here, and that might seem
+ appropriate in order to write out the locked revision
+ information. However, such a call would actually serve no
+ purpose. CVS locks will prevent any interference from other
+ CVS processes. The comment above rcs_internal_lockfile
+ explains that it is already unsafe to use RCS and CVS
+ simultaneously. It follows that writing out the locked
+ revision information here would add no additional security.
+
+ If we ever do care about it, the proper fix is to create the
+ RCS lock file before calling this function, and maintain it
+ until the checkin is complete.
+
+ The call to RCS_lock is still required at present, since in
+ some cases RCS_checkin will determine which revision to check
+ in by looking for a lock. FIXME: This is rather roundabout,
+ and a more straightforward approach would probably be easier to
+ understand. */
if (err == 0)
{
if (sbranch != NULL)
free (sbranch);
- if (branch)
- {
- sbranch = branch;
- }
- else
- sbranch = NULL;
+ sbranch = branch;
return (0);
}
@@ -2182,7 +2264,8 @@ lock_RCS (user, rcs, rev, repository)
/* Called when "add"ing files to the RCS respository. It doesn't seem to
be possible to get RCS to use the right mode, so we change it after
- the fact. */
+ the fact. TODO: now that RCS has been librarified, we have the power
+ to change this. */
static void
fix_rcs_modes (rcs, user)
@@ -2192,6 +2275,12 @@ fix_rcs_modes (rcs, user)
struct stat sb;
mode_t rcs_mode;
+#ifdef PRESERVE_PERMISSIONS_SUPPORT
+ /* Do ye nothing to the modes on a symbolic link. */
+ if (preserve_perms && islink (user))
+ return;
+#endif
+
if (CVS_STAT (user, &sb) < 0)
{
/* FIXME: Should be ->fullname. */
@@ -2201,6 +2290,9 @@ fix_rcs_modes (rcs, user)
/* Now we compute the new mode.
+ TODO: decide whether this whole thing can/should be skipped
+ when `preserve_perms' is set. Almost certainly so. -twp
+
The algorithm that we use is:
Write permission is always off (this is what RCS and CVS have always
diff --git a/contrib/cvs/src/create_adm.c b/contrib/cvs/src/create_adm.c
index c1772b216f60..c51785c317c4 100644
--- a/contrib/cvs/src/create_adm.c
+++ b/contrib/cvs/src/create_adm.c
@@ -59,6 +59,12 @@ Create_Admin (dir, update_dir, repository, tag, date, nonbranch, warn)
if (CVS_MKDIR (tmp, 0777) < 0)
{
+ /* We want to print out the entire update_dir, since a lot of
+ our code calls this function with dir == "." or dir ==
+ NULL. I hope that gives enough information in cases like
+ absolute pathnames; printing out xgetwd or something would
+ be way too verbose in the common cases. */
+
if (warn)
{
/* The reason that this is a warning, rather than silently
@@ -66,11 +72,14 @@ Create_Admin (dir, update_dir, repository, tag, date, nonbranch, warn)
CVS's behavior to vary subtly based on factors (like directory
permissions) which are not made clear to the user. With
the warning at least we let them know what is going on. */
- error (0, errno, "warning: cannot make directory %s", tmp);
+ error (0, errno, "warning: cannot make directory %s in %s",
+ CVSADM, update_dir);
+ free (tmp);
return 1;
}
else
- error (1, errno, "cannot make directory %s", tmp);
+ error (1, errno, "cannot make directory %s in %s",
+ CVSADM, update_dir);
}
/* record the current cvs root for later use */
diff --git a/contrib/cvs/src/cvs.h b/contrib/cvs/src/cvs.h
index 5d735f1b5a16..3a7e9e4febd0 100644
--- a/contrib/cvs/src/cvs.h
+++ b/contrib/cvs/src/cvs.h
@@ -402,6 +402,7 @@ int RCS_merge PROTO((RCSNode *, char *, char *, char *, char *, char *));
#define RCS_FLAGS_DEAD 2
#define RCS_FLAGS_QUIET 4
#define RCS_FLAGS_MODTIME 8
+#define RCS_FLAGS_KEEPFILE 16
extern int RCS_exec_rcsdiff PROTO ((RCSNode *rcsfile,
char *opts, char *options,
@@ -422,7 +423,7 @@ DBM *open_module PROTO((void));
FILE *open_file PROTO((const char *, const char *));
List *Find_Directories PROTO((char *repository, int which, List *entries));
void Entries_Close PROTO((List *entries));
-List *Entries_Open PROTO((int aflag));
+List *Entries_Open PROTO ((int aflag, char *update_dir));
void Subdirs_Known PROTO((List *entries));
void Subdir_Register PROTO((List *, const char *, const char *));
void Subdir_Deregister PROTO((List *, const char *, const char *));
@@ -461,10 +462,12 @@ int SIG_register PROTO((int sig, SIGCLEANUPPROC sigcleanup));
int isdir PROTO((const char *file));
int isfile PROTO((const char *file));
int islink PROTO((const char *file));
+int isdevice PROTO ((const char *));
int isreadable PROTO((const char *file));
int iswritable PROTO((const char *file));
int isaccessible PROTO((const char *file, const int mode));
int isabsolute PROTO((const char *filename));
+char *xreadlink PROTO((const char *link));
char *last_component PROTO((char *path));
char *get_homedir PROTO ((void));
char *cvs_temp_name PROTO ((void));
@@ -727,6 +730,9 @@ void freevers_ts PROTO ((Vers_TS ** versp));
int Checkin PROTO ((int type, struct file_info *finfo, char *rcs, char *rev,
char *tag, char *options, char *message));
int No_Difference PROTO ((struct file_info *finfo, Vers_TS *vers));
+/* TODO: can the finfo argument to special_file_mismatch be changed? -twp */
+int special_file_mismatch PROTO ((struct file_info *finfo,
+ char *rev1, char *rev2));
/* CVSADM_BASEREV stuff, from entries.c. */
extern char *base_get PROTO ((struct file_info *));
diff --git a/contrib/cvs/src/diff.c b/contrib/cvs/src/diff.c
index a894c5b4eb6c..7a4c105b9d3d 100644
--- a/contrib/cvs/src/diff.c
+++ b/contrib/cvs/src/diff.c
@@ -636,13 +636,11 @@ RCS file: ", 0);
: vers->options),
tmp, (RCSCHECKOUTPROC) NULL,
(void *) NULL);
- if (retcode == -1)
+ if (retcode != 0)
{
- (void) CVS_UNLINK (tmp);
- error (1, errno, "fork failed during checkout of %s",
- vers->srcfile->path);
+ diff_mark_errors (err);
+ return err;
}
- /* FIXME: what if retcode > 0? */
status = diff_exec (DEVNULL, tmp, opts, RUN_TTY);
}
@@ -657,13 +655,11 @@ RCS file: ", 0);
*options ? options : vers->options,
tmp, (RCSCHECKOUTPROC) NULL,
(void *) NULL);
- if (retcode == -1)
+ if (retcode != 0)
{
- (void) CVS_UNLINK (tmp);
- error (1, errno, "fork failed during checkout of %s",
- vers->srcfile->path);
+ diff_mark_errors (err);
+ return err;
}
- /* FIXME: what if retcode > 0? */
status = diff_exec (tmp, DEVNULL, opts, RUN_TTY);
}
@@ -719,7 +715,8 @@ RCS file: ", 0);
if (empty_file == DIFF_REMOVED
|| (empty_file == DIFF_ADDED && use_rev2 != NULL))
{
- (void) CVS_UNLINK (tmp);
+ if (CVS_UNLINK (tmp) < 0)
+ error (0, errno, "cannot remove %s", tmp);
free (tmp);
}
diff --git a/contrib/cvs/src/entries.c b/contrib/cvs/src/entries.c
index 538cd2126421..aeab31356c35 100644
--- a/contrib/cvs/src/entries.c
+++ b/contrib/cvs/src/entries.c
@@ -450,12 +450,15 @@ fputentent(fp, p)
}
-/*
- * Read the entries file into a list, hashing on the file name.
- */
+/* Read the entries file into a list, hashing on the file name.
+
+ UPDATE_DIR is the name of the current directory, for use in error
+ messages, or NULL if not known (that is, noone has gotten around
+ to updating the caller to pass in the information). */
List *
-Entries_Open (aflag)
+Entries_Open (aflag, update_dir)
int aflag;
+ char *update_dir;
{
List *entries;
struct stickydirtag *sdtp = NULL;
@@ -492,7 +495,11 @@ Entries_Open (aflag)
fpin = CVS_FOPEN (CVSADM_ENT, "r");
if (fpin == NULL)
+ {
+ if (update_dir != NULL)
+ error (0, 0, "in directory %s:", update_dir);
error (0, errno, "cannot open %s for reading", CVSADM_ENT);
+ }
else
{
while ((ent = fgetentent (fpin, (char *) NULL, &sawdir)) != NULL)
@@ -797,9 +804,22 @@ Subdirs_Known (entries)
if (!noexec)
{
/* Create Entries.Log so that Entries_Close will do something. */
- fp = open_file (CVSADM_ENTLOG, "a");
- if (fclose (fp) == EOF)
- error (1, errno, "cannot close %s", CVSADM_ENTLOG);
+ fp = CVS_FOPEN (CVSADM_ENTLOG, "a");
+ if (fp == NULL)
+ {
+ int save_errno = errno;
+
+ /* As in subdir_record, just silently skip the whole thing
+ if there is no CVSADM directory. */
+ if (! isdir (CVSADM))
+ return;
+ error (1, save_errno, "cannot open %s", entfilename);
+ }
+ else
+ {
+ if (fclose (fp) == EOF)
+ error (1, errno, "cannot close %s", CVSADM_ENTLOG);
+ }
}
}
}
diff --git a/contrib/cvs/src/filesubr.c b/contrib/cvs/src/filesubr.c
index b5121a7d216a..0ccc3df91801 100644
--- a/contrib/cvs/src/filesubr.c
+++ b/contrib/cvs/src/filesubr.c
@@ -43,46 +43,66 @@ copy_file (from, to)
if (noexec)
return;
- if ((fdin = open (from, O_RDONLY)) < 0)
- error (1, errno, "cannot open %s for copying", from);
- if (fstat (fdin, &sb) < 0)
- error (1, errno, "cannot fstat %s", from);
- if ((fdout = creat (to, (int) sb.st_mode & 07777)) < 0)
- error (1, errno, "cannot create %s for copying", to);
- if (sb.st_size > 0)
+ /* If the file to be copied is a link or a device, then just create
+ the new link or device appropriately. */
+ if (islink (from))
{
- char buf[BUFSIZ];
- int n;
+ char *source = xreadlink (from);
+ symlink (source, to);
+ free (source);
+ return;
+ }
- for (;;)
+ if (isdevice (from))
+ {
+ if (stat (from, &sb) < 0)
+ error (1, errno, "cannot stat %s", from);
+ mknod (to, sb.st_mode, sb.st_rdev);
+ }
+ else
+ {
+ /* Not a link or a device... probably a regular file. */
+ if ((fdin = open (from, O_RDONLY)) < 0)
+ error (1, errno, "cannot open %s for copying", from);
+ if (fstat (fdin, &sb) < 0)
+ error (1, errno, "cannot fstat %s", from);
+ if ((fdout = creat (to, (int) sb.st_mode & 07777)) < 0)
+ error (1, errno, "cannot create %s for copying", to);
+ if (sb.st_size > 0)
{
- n = read (fdin, buf, sizeof(buf));
- if (n == -1)
+ char buf[BUFSIZ];
+ int n;
+
+ for (;;)
{
+ n = read (fdin, buf, sizeof(buf));
+ if (n == -1)
+ {
#ifdef EINTR
- if (errno == EINTR)
- continue;
+ if (errno == EINTR)
+ continue;
#endif
- error (1, errno, "cannot read file %s for copying", from);
- }
- else if (n == 0)
- break;
-
- if (write(fdout, buf, n) != n) {
- error (1, errno, "cannot write file %s for copying", to);
+ error (1, errno, "cannot read file %s for copying", from);
+ }
+ else if (n == 0)
+ break;
+
+ if (write(fdout, buf, n) != n) {
+ error (1, errno, "cannot write file %s for copying", to);
+ }
}
- }
#ifdef HAVE_FSYNC
- if (fsync (fdout))
- error (1, errno, "cannot fsync file %s after copying", to);
+ if (fsync (fdout))
+ error (1, errno, "cannot fsync file %s after copying", to);
#endif
- }
+ }
- if (close (fdin) < 0)
- error (0, errno, "cannot close %s", from);
- if (close (fdout) < 0)
- error (1, errno, "cannot close %s", to);
+ if (close (fdin) < 0)
+ error (0, errno, "cannot close %s", from);
+ if (close (fdout) < 0)
+ error (1, errno, "cannot close %s", to);
+ }
/* now, set the times for the copied file to match those of the original */
memset ((char *) &t, 0, sizeof (t));
@@ -119,7 +139,7 @@ islink (file)
#ifdef S_ISLNK
struct stat sb;
- if (lstat (file, &sb) < 0)
+ if (CVS_LSTAT (file, &sb) < 0)
return (0);
return (S_ISLNK (sb.st_mode));
#else
@@ -128,6 +148,29 @@ islink (file)
}
/*
+ * Returns non-zero if the argument file is a block or
+ * character special device.
+ */
+int
+isdevice (file)
+ const char *file;
+{
+ struct stat sb;
+
+ if (CVS_LSTAT (file, &sb) < 0)
+ return (0);
+#ifdef S_ISBLK
+ if (S_ISBLK (sb.st_mode))
+ return 1;
+#endif
+#ifdef S_ISCHR
+ if (S_ISCHR (sb.st_mode))
+ return 1;
+#endif
+ return 0;
+}
+
+/*
* Returns non-zero if the argument file exists.
*/
int
@@ -298,6 +341,9 @@ mkdir_if_needed (name)
/*
* Change the mode of a file, either adding write permissions, or removing
* all write permissions. Either change honors the current umask setting.
+ *
+ * Don't do anything if PreservePermissions is set to `yes'. This may
+ * have unexpected consequences for some uses of xchmod.
*/
void
xchmod (fname, writable)
@@ -307,6 +353,9 @@ xchmod (fname, writable)
struct stat sb;
mode_t mode, oumask;
+ if (preserve_perms)
+ return;
+
if (stat (fname, &sb) < 0)
{
if (!noexec)
@@ -417,6 +466,8 @@ int
unlink_file_dir (f)
const char *f;
{
+ struct stat sb;
+
if (trace
#ifdef SERVER_SUPPORT
/* This is called by the server parent process in contexts where
@@ -433,20 +484,23 @@ unlink_file_dir (f)
/* For at least some unices, if root tries to unlink() a directory,
instead of doing something rational like returning EISDIR,
the system will gleefully go ahead and corrupt the filesystem.
- So we first call isdir() to see if it is OK to call unlink(). This
+ So we first call stat() to see if it is OK to call unlink(). This
doesn't quite work--if someone creates a directory between the
- call to isdir() and the call to unlink(), we'll still corrupt
+ call to stat() and the call to unlink(), we'll still corrupt
the filesystem. Where is the Unix Haters Handbook when you need
it? */
- if (isdir(f))
- return deep_remove_dir(f);
- else
+ if (stat (f, &sb) < 0)
{
- if (unlink (f) != 0)
+ if (existence_error (errno))
+ {
+ /* The file or directory doesn't exist anyhow. */
return -1;
+ }
}
- /* We were able to remove the file from the disk */
- return 0;
+ else if (S_ISDIR (sb.st_mode))
+ return deep_remove_dir (f);
+
+ return unlink (f);
}
/* Remove a directory and everything it contains. Returns 0 for
@@ -557,6 +611,8 @@ block_read (fd, buf, nchars)
/*
* Compare "file1" to "file2". Return non-zero if they don't compare exactly.
+ * If FILE1 and FILE2 are special files, compare their salient characteristics
+ * (i.e. major/minor device numbers, links, etc.
*/
int
xcmp (file1, file2)
@@ -568,14 +624,42 @@ xcmp (file1, file2)
int fd1, fd2;
int ret;
+ if (CVS_LSTAT (file1, &sb1) < 0)
+ error (1, errno, "cannot lstat %s", file1);
+ if (CVS_LSTAT (file2, &sb2) < 0)
+ error (1, errno, "cannot lstat %s", file2);
+
+ /* If FILE1 and FILE2 are not the same file type, they are unequal. */
+ if ((sb1.st_mode & S_IFMT) != (sb2.st_mode & S_IFMT))
+ return 1;
+
+ /* If FILE1 and FILE2 are symlinks, they are equal if they point to
+ the same thing. */
+ if (S_ISLNK (sb1.st_mode) && S_ISLNK (sb2.st_mode))
+ {
+ int result;
+ buf1 = xreadlink (file1);
+ buf2 = xreadlink (file2);
+ result = (strcmp (buf1, buf2) == 0);
+ free (buf1);
+ free (buf2);
+ return result;
+ }
+
+ /* If FILE1 and FILE2 are devices, they are equal if their device
+ numbers match. */
+ if (S_ISBLK (sb1.st_mode) || S_ISCHR (sb1.st_mode))
+ {
+ if (sb1.st_rdev == sb2.st_rdev)
+ return 0;
+ else
+ return 1;
+ }
+
if ((fd1 = open (file1, O_RDONLY)) < 0)
error (1, errno, "cannot open file %s for comparing", file1);
if ((fd2 = open (file2, O_RDONLY)) < 0)
error (1, errno, "cannot open file %s for comparing", file2);
- if (fstat (fd1, &sb1) < 0)
- error (1, errno, "cannot fstat %s", file1);
- if (fstat (fd2, &sb2) < 0)
- error (1, errno, "cannot fstat %s", file2);
/* A generic file compare routine might compare st_dev & st_ino here
to see if the two files being compared are actually the same file.
@@ -677,6 +761,39 @@ isabsolute (filename)
return filename[0] == '/';
}
+/*
+ * Return a string (dynamically allocated) with the name of the file to which
+ * LINK is symlinked.
+ */
+char *
+xreadlink (link)
+ const char *link;
+{
+ char *file = NULL;
+ int buflen = BUFSIZ;
+
+ if (!islink (link))
+ return NULL;
+
+ /* Get the name of the file to which `from' is linked.
+ FIXME: what portability issues arise here? Are readlink &
+ ENAMETOOLONG defined on all systems? -twp */
+ do
+ {
+ file = xrealloc (file, buflen);
+ errno = 0;
+ readlink (link, file, buflen);
+ buflen *= 2;
+ }
+ while (errno == ENAMETOOLONG);
+
+ if (errno)
+ error (1, errno, "cannot readlink %s", link);
+
+ return file;
+}
+
+
/* Return a pointer into PATH's last component. */
char *
@@ -684,8 +801,8 @@ last_component (path)
char *path;
{
char *last = strrchr (path, '/');
-
- if (last)
+
+ if (last && (last != path))
return last + 1;
else
return path;
diff --git a/contrib/cvs/src/find_names.c b/contrib/cvs/src/find_names.c
index ed6c5c44b14e..4fa795a1f27d 100644
--- a/contrib/cvs/src/find_names.c
+++ b/contrib/cvs/src/find_names.c
@@ -50,18 +50,6 @@ add_entries_proc (node, closure)
return (0);
}
-/*
- * compare two files list node (for sort)
- */
-static int fsortcmp PROTO ((const Node *, const Node *));
-static int
-fsortcmp (p, q)
- const Node *p;
- const Node *q;
-{
- return (strcmp (p->key, q->key));
-}
-
List *
Find_Names (repository, which, aflag, optentries)
char *repository;
@@ -79,7 +67,7 @@ Find_Names (repository, which, aflag, optentries)
if (which & W_LOCAL)
{
/* parse the entries file (if it exists) */
- entries = Entries_Open (aflag);
+ entries = Entries_Open (aflag, NULL);
if (entries != NULL)
{
/* walk the entries file adding elements to the files list */
@@ -182,7 +170,7 @@ Find_Directories (repository, which, entries)
if (entries != NULL)
tmpentries = entries;
else if (isfile (CVSADM_ENT))
- tmpentries = Entries_Open (0);
+ tmpentries = Entries_Open (0, NULL);
else
tmpentries = NULL;
diff --git a/contrib/cvs/src/hardlink.c b/contrib/cvs/src/hardlink.c
new file mode 100644
index 000000000000..51bd2a60567b
--- /dev/null
+++ b/contrib/cvs/src/hardlink.c
@@ -0,0 +1,298 @@
+/* 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. */
+
+/* Collect and manage hardlink info associated with a particular file. */
+
+#include "cvs.h"
+#include "hardlink.h"
+
+/* The structure currently used to manage hardlink info is a list.
+ Therefore, most of the functions which manipulate hardlink data
+ are walklist procedures. This is not a very efficient implementation;
+ if someone decides to use a real hash table (for instance), then
+ much of this code can be rewritten to be a little less arcane.
+
+ Each element of `hardlist' represents an inode. It is keyed on the
+ inode number, and points to a list of files. This is to make it
+ easy to find out what files are linked to a given file FOO: find
+ FOO's inode, look it up in hardlist, and retrieve the list of files
+ associated with that inode.
+
+ Each file node, in turn, is represented by a `hardlink_info' struct,
+ which includes `status' and `links' fields. The `status' field should
+ be used by a procedure like commit_fileproc or update_fileproc to
+ record each file's status; that way, after all file links have been
+ recorded, CVS can check the linkage of files which are in doubt
+ (i.e. T_NEEDS_MERGE files).
+
+ TODO: a diagram of an example hardlist would help here. */
+
+/* TODO: change this to something with a marginal degree of
+ efficiency, like maybe a hash table. Yeah. */
+
+List *hardlist; /* Record hardlink information for working files */
+char *working_dir; /* The top-level working directory, used for
+ constructing full pathnames. */
+
+/* For check_link_proc: list all of the files named in an inode list. */
+static int
+list_files_proc (node, vstrp)
+ Node *node;
+ void *vstrp;
+{
+ char **strp, *file;
+ int len;
+
+ /* Get the file's basename. This is because -- VERY IMPORTANT --
+ the `hardlinks' field is presently defined only to include links
+ within a directory. So the hardlinks field might be `foo' or
+ `mumble grump flink', but not `foo bar com/baz' or `wham ../bam
+ ../thank/you'. Someday it would be nice to extend this to
+ permit cross-directory links, but the issues involved are
+ hideous. */
+
+ file = strrchr (node->key, '/');
+ if (file)
+ ++file;
+ else
+ file = node->key;
+
+ /* Is it safe to cast vstrp to (char **) here, and then play with
+ the contents? I think so, since vstrp will have started out
+ a char ** to begin with, so we should not have alignment bugs. */
+ strp = (char **) vstrp;
+ len = (*strp == NULL ? 0 : strlen (*strp));
+ *strp = (char *) xrealloc (*strp, len + strlen (file) + 2);
+ if (*strp == NULL)
+ {
+ error (0, errno, "could not allocate memory");
+ return 1;
+ }
+ if (sprintf (*strp + len, "%s ", file) < 0)
+ {
+ error (0, errno, "could not compile file list");
+ return 1;
+ }
+
+ return 0;
+}
+
+/* Set the link field of each hardlink_info node to `data', which is a
+ list of linked files. */
+static int
+set_hardlink_field_proc (node, data)
+ Node *node;
+ void *data;
+{
+ struct hardlink_info *hlinfo = (struct hardlink_info *) node->data;
+ hlinfo->links = xstrdup ((char *) data);
+
+ return 0;
+}
+
+/* For each file being checked in, compile a list of the files linked
+ to it, and cache the list in the file's hardlink_info field. */
+int
+cache_hardlinks_proc (node, data)
+ Node *node;
+ void *data;
+{
+ List *inode_links;
+ char *p, *linked_files = NULL;
+ int err;
+
+ inode_links = (List *) node->data;
+
+ /* inode->data is a list of hardlink_info structures: all the
+ files linked to this inode. We compile a string of each file
+ named in this list, in alphabetical order, separated by spaces.
+ Then store this string in the `links' field of each
+ hardlink_info structure, so that RCS_checkin can easily add
+ it to the `hardlinks' field of a new delta node. */
+
+ sortlist (inode_links, fsortcmp);
+ err = walklist (inode_links, list_files_proc, &linked_files);
+ if (err)
+ return err;
+
+ /* Trim trailing whitespace. */
+ p = linked_files + strlen(linked_files) - 1;
+ while (p > linked_files && isspace (*p))
+ *p-- = '\0';
+
+ err = walklist (inode_links, set_hardlink_field_proc, linked_files);
+ return err;
+}
+
+/* Return a pointer to FILEPATH's node in the hardlist. This means
+ looking up its inode, retrieving the list of files linked to that
+ inode, and then looking up FILE in that list. If the file doesn't
+ seem to exist, return NULL. */
+Node *
+lookup_file_by_inode (filepath)
+ const char *filepath;
+{
+ char *inodestr, *file;
+ struct stat sb;
+ Node *hp, *p;
+
+ /* Get file's basename, so that we can stat it. */
+ file = strrchr (filepath, '/');
+ if (file)
+ ++file;
+ else
+ file = (char *) filepath;
+
+ /* inodestr contains the hexadecimal representation of an
+ inode, so it requires two bytes of text to represent
+ each byte of the inode number. */
+ inodestr = (char *) xmalloc (2*sizeof(ino_t)*sizeof(char) + 1);
+ if (stat (file, &sb) < 0)
+ {
+ if (errno == ENOENT)
+ {
+ /* The file doesn't exist; we may be doing an update on a
+ file that's been removed. A nonexistent file has no
+ link information, so return without changing hardlist. */
+ free (inodestr);
+ return NULL;
+ }
+ error (1, errno, "cannot stat %s", file);
+ }
+
+ sprintf (inodestr, "%lx", (unsigned long) sb.st_ino);
+
+ /* Find out if this inode is already in the hardlist, adding
+ a new entry to the list if not. */
+ hp = findnode (hardlist, inodestr);
+ if (hp == NULL)
+ {
+ hp = getnode ();
+ hp->type = UNKNOWN;
+ hp->key = inodestr;
+ hp->data = (char *) getlist();
+ hp->delproc = dellist;
+ (void) addnode (hardlist, hp);
+ }
+ else
+ {
+ free (inodestr);
+ }
+
+ p = findnode ((List *) hp->data, filepath);
+ if (p == NULL)
+ {
+ p = getnode();
+ p->type = UNKNOWN;
+ p->key = xstrdup (filepath);
+ p->data = NULL;
+ (void) addnode ((List *) hp->data, p);
+ }
+
+ return p;
+}
+
+/* After a file has been checked out, add a node for it to the hardlist
+ (if necessary) and mark it as checked out. */
+void
+update_hardlink_info (file)
+ const char *file;
+{
+ char *path;
+ Node *n;
+ struct hardlink_info *hlinfo;
+
+ if (file[0] == '/')
+ {
+ path = xstrdup (file);
+ }
+ else
+ {
+ /* file is a relative pathname; assume it's from the current
+ working directory. */
+ char *dir = xgetwd();
+ path = xmalloc (sizeof(char) * (strlen(dir) + strlen(file) + 2));
+ sprintf (path, "%s/%s", dir, file);
+ free (dir);
+ }
+
+ n = lookup_file_by_inode (path);
+ if (n == NULL)
+ {
+ /* Something is *really* wrong if the file doesn't exist here;
+ update_hardlink_info should be called only when a file has
+ just been checked out to a working directory. */
+ error (1, 0, "lost hardlink info for %s", file);
+ }
+
+ if (n->data == NULL)
+ n->data = (char *) xmalloc (sizeof (struct hardlink_info));
+ hlinfo = (struct hardlink_info *) n->data;
+ hlinfo->status = T_UPTODATE;
+ hlinfo->checked_out = 1;
+ hlinfo->links = NULL;
+}
+
+/* Return a string listing all the files known to be linked to FILE in
+ the working directory. Used by special_file_mismatch, to determine
+ whether it is safe to merge two files. */
+char *
+list_files_linked_to (file)
+ const char *file;
+{
+ char *inodestr, *filelist, *path;
+ struct stat sb;
+ Node *n;
+ int err;
+
+ /* If hardlist is NULL, we have not been doing an operation that
+ would permit us to know anything about the file's hardlinks
+ (cvs update, cvs commit, etc). Return an empty string. */
+ if (hardlist == NULL)
+ return xstrdup ("");
+
+ /* Get the full pathname of file (assuming the working directory) */
+ if (file[0] == '/')
+ path = xstrdup (file);
+ else
+ {
+ char *dir = xgetwd();
+ path = (char *) xmalloc (sizeof(char) *
+ (strlen(dir) + strlen(file) + 2));
+ sprintf (path, "%s/%s", dir, file);
+ free (dir);
+ }
+
+ /* We do an extra lookup_file here just to make sure that there
+ is a node for `path' in the hardlist. If that were not so,
+ comparing the working directory linkage against the repository
+ linkage for a file would always fail. */
+ (void) lookup_file_by_inode (path);
+
+ if (stat (path, &sb) < 0)
+ error (1, errno, "cannot stat %s", file);
+ /* inodestr contains the hexadecimal representation of an
+ inode, so it requires two bytes of text to represent
+ each byte of the inode number. */
+ inodestr = (char *) xmalloc (2*sizeof(ino_t)*sizeof(char) + 1);
+ sprintf (inodestr, "%lx", (unsigned long) sb.st_ino);
+
+ /* Make sure the files linked to this inode are sorted. */
+ n = findnode (hardlist, inodestr);
+ sortlist ((List *) n->data, fsortcmp);
+
+ filelist = NULL;
+ err = walklist ((List *) n->data, list_files_proc, &filelist);
+ if (err)
+ error (1, 0, "cannot get list of hardlinks for %s", file);
+
+ free (inodestr);
+ return filelist;
+}
diff --git a/contrib/cvs/src/hardlink.h b/contrib/cvs/src/hardlink.h
new file mode 100644
index 000000000000..cce3f33a265e
--- /dev/null
+++ b/contrib/cvs/src/hardlink.h
@@ -0,0 +1,33 @@
+/* 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. */
+
+/* Data type definitions and declarations for hardlink management. */
+
+/* This file should be #included in CVS source files after cvs.h
+ since it relies on types and macros defined there. */
+
+/* The `checked_out' member of a hardlink_info struct is used only
+ when files are being checked out or updated. It is used only when
+ hardlinked files are being checked out. */
+
+struct hardlink_info
+{
+ Ctype status; /* as returned from Classify_File() */
+ int checked_out; /* has this file been checked out lately? */
+ char *links; /* contents of `hardlinks' RCS field */
+};
+
+extern List *hardlist;
+extern char *working_dir;
+
+int cache_hardlinks_proc PROTO ((Node *, void *));
+Node *lookup_file_by_inode PROTO ((const char *));
+void update_hardlink_info PROTO ((const char *));
+char *list_files_linked_to PROTO ((const char *));
diff --git a/contrib/cvs/src/hash.c b/contrib/cvs/src/hash.c
index ff3f122b7472..af000acdee9c 100644
--- a/contrib/cvs/src/hash.c
+++ b/contrib/cvs/src/hash.c
@@ -436,6 +436,17 @@ sortlist (list, comp)
free (array);
}
+/*
+ * compare two files list node (for sort)
+ */
+int
+fsortcmp (p, q)
+ const Node *p;
+ const Node *q;
+{
+ return (strcmp (p->key, q->key));
+}
+
/* Debugging functions. Quite useful to call from within gdb. */
static char *nodetypestring PROTO ((Ntype));
diff --git a/contrib/cvs/src/hash.h b/contrib/cvs/src/hash.h
index d29c757db4b9..06867a721c62 100644
--- a/contrib/cvs/src/hash.h
+++ b/contrib/cvs/src/hash.h
@@ -56,3 +56,4 @@ void dellist PROTO((List ** listp));
void delnode PROTO((Node * p));
void freenode PROTO((Node * p));
void sortlist PROTO((List * list, int (*)(const Node *, const Node *)));
+int fsortcmp PROTO((const Node * p, const Node * q));
diff --git a/contrib/cvs/src/import.c b/contrib/cvs/src/import.c
index 0a2af7f595ec..d79a10e31464 100644
--- a/contrib/cvs/src/import.c
+++ b/contrib/cvs/src/import.c
@@ -260,7 +260,11 @@ import (argc, argv)
tmpfile = cvs_temp_name ();
if ((logfp = CVS_FOPEN (tmpfile, "w+")) == NULL)
error (1, errno, "cannot create temporary file `%s'", tmpfile);
- (void) CVS_UNLINK (tmpfile); /* to be sure it goes away */
+ /* On systems where we can unlink an open file, do so, so it will go
+ away no matter how we exit. FIXME-maybe: Should be checking for
+ errors but I'm not sure which error(s) we get if we are on a system
+ where one can't unlink open files. */
+ (void) CVS_UNLINK (tmpfile);
(void) fprintf (logfp, "\nVendor Tag:\t%s\n", argv[1]);
(void) fprintf (logfp, "Release Tags:\t");
for (i = 2; i < argc; i++)
@@ -320,11 +324,13 @@ import (argc, argv)
(void) addnode (ulist, p);
Update_Logfile (repository, message, logfp, ulist);
dellist (&ulist);
- (void) fclose (logfp);
+ 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. */
- CVS_UNLINK (tmpfile);
+ if (CVS_UNLINK (tmpfile) < 0 && !existence_error (errno))
+ error (0, errno, "cannot remove %s", tmpfile);
free (tmpfile);
if (message)
@@ -491,7 +497,7 @@ process_import_file (message, vfile, vtag, targc, targv)
/* Reading all the entries for each file is fairly silly, and
probably slow. But I am too lazy at the moment to do
anything else. */
- entries = Entries_Open (0);
+ entries = Entries_Open (0, NULL);
node = findnode_fn (entries, vfile);
if (node != NULL)
{
@@ -977,6 +983,7 @@ add_rcs_file (message, rcs, user, add_vhead, key_opt,
char *userfile;
char *local_opt = key_opt;
char *free_opt = NULL;
+ mode_t file_type;
if (noexec)
return (0);
@@ -1004,18 +1011,39 @@ add_rcs_file (message, rcs, user, add_vhead, key_opt,
which does not depend on what the client or server OS is, as
documented in cvsclient.texi), but as long as the server just
runs on unix it is a moot point. */
- fpuser = CVS_FOPEN (userfile,
- ((local_opt != NULL && strcmp (local_opt, "b") == 0)
- ? "rb"
- : "r")
- );
- if (fpuser == NULL)
+
+ /* If PreservePermissions is set, then make sure that the file
+ is a plain file before trying to open it. Longstanding (although
+ often unpopular) CVS behavior has been to follow symlinks, so we
+ maintain that behavior if PreservePermissions is not on.
+
+ NOTE: this error message used to be `cannot fstat', but is now
+ `cannot lstat'. I don't see a way around this, since we must
+ stat the file before opening it. -twp */
+
+ if (CVS_LSTAT (userfile, &sb) < 0)
+ error (1, errno, "cannot lstat %s", user);
+ file_type = sb.st_mode & S_IFMT;
+
+ fpuser = NULL;
+ if (!preserve_perms || file_type == S_IFREG)
{
- /* not fatal, continue import */
- fperror (add_logfp, 0, errno, "ERROR: cannot read file %s", userfile);
- error (0, errno, "ERROR: cannot read file %s", userfile);
- goto read_error;
+ fpuser = CVS_FOPEN (userfile,
+ ((local_opt != NULL && strcmp (local_opt, "b") == 0)
+ ? "rb"
+ : "r")
+ );
+ if (fpuser == NULL)
+ {
+ /* not fatal, continue import */
+ if (add_logfp != NULL)
+ fperror (add_logfp, 0, errno,
+ "ERROR: cannot read file %s", userfile);
+ error (0, errno, "ERROR: cannot read file %s", userfile);
+ goto read_error;
+ }
}
+
fprcs = CVS_FOPEN (rcs, "w+b");
if (fprcs == NULL)
{
@@ -1082,10 +1110,6 @@ add_rcs_file (message, rcs, user, add_vhead, key_opt,
if (fprintf (fprcs, "\012") < 0)
goto write_error;
- /* Get information on modtime and mode. */
- if (fstat (fileno (fpuser), &sb) < 0)
- error (1, errno, "cannot fstat %s", user);
-
/* Write the revision(s), with the date and author and so on
(that is "delta" rather than "deltatext" from rcsfile(5)). */
if (add_vhead != NULL)
@@ -1118,13 +1142,102 @@ add_rcs_file (message, rcs, user, add_vhead, key_opt,
if (fprintf (fprcs, "next ;\012") < 0)
goto write_error;
+
+#ifdef PRESERVE_PERMISSIONS_SUPPORT
+ /* Store initial permissions if necessary. */
+ if (preserve_perms)
+ {
+ if (file_type == S_IFLNK)
+ {
+ char *link = xreadlink (userfile);
+ if (fprintf (fprcs, "symlink\t@") < 0 ||
+ expand_at_signs (link, strlen (link), fprcs) < 0 ||
+ fprintf (fprcs, "@;\012") < 0)
+ goto write_error;
+ free (link);
+ }
+ else
+ {
+ if (fprintf (fprcs, "owner\t%u;\012", sb.st_uid) < 0)
+ goto write_error;
+ if (fprintf (fprcs, "group\t%u;\012", sb.st_gid) < 0)
+ goto write_error;
+ if (fprintf (fprcs, "permissions\t%o;\012",
+ sb.st_mode & 07777) < 0)
+ goto write_error;
+ switch (file_type)
+ {
+ case S_IFREG: break;
+ case S_IFCHR:
+ case S_IFBLK:
+ if (fprintf (fprcs, "special\t%s %lu;\012",
+ (file_type == S_IFCHR
+ ? "character"
+ : "block"),
+ (unsigned long) sb.st_rdev) < 0)
+ goto write_error;
+ break;
+ default:
+ error (0, 0,
+ "can't import %s: unknown kind of special file",
+ userfile);
+ }
+ }
+ }
+#endif
+
if (add_vbranch != NULL)
{
if (fprintf (fprcs, "\012%s.1\012", add_vbranch) < 0 ||
fprintf (fprcs, "date %s; author %s; state Exp;\012",
altdate1, author) < 0 ||
fprintf (fprcs, "branches ;\012") < 0 ||
- fprintf (fprcs, "next ;\012\012") < 0)
+ fprintf (fprcs, "next ;\012") < 0)
+ goto write_error;
+
+#ifdef PRESERVE_PERMISSIONS_SUPPORT
+ /* Store initial permissions if necessary. */
+ if (preserve_perms)
+ {
+ if (file_type == S_IFLNK)
+ {
+ char *link = xreadlink (userfile);
+ if (fprintf (fprcs, "symlink\t@") < 0 ||
+ expand_at_signs (link, strlen (link), fprcs) < 0 ||
+ fprintf (fprcs, "@;\012") < 0)
+ goto write_error;
+ free (link);
+ }
+ else
+ {
+ if (fprintf (fprcs, "owner\t%u;\012", sb.st_uid) < 0 ||
+ fprintf (fprcs, "group\t%u;\012", sb.st_gid) < 0 ||
+ fprintf (fprcs, "permissions\t%o;\012",
+ sb.st_mode & 07777) < 0)
+ goto write_error;
+
+ switch (file_type)
+ {
+ case S_IFREG: break;
+ case S_IFCHR:
+ case S_IFBLK:
+ if (fprintf (fprcs, "special\t%s %lu;\012",
+ (file_type == S_IFCHR
+ ? "character"
+ : "block"),
+ (unsigned long) sb.st_rdev) < 0)
+ goto write_error;
+ break;
+ default:
+ error (0, 0,
+ "cannot import %s: special file of unknown type",
+ userfile);
+ }
+ }
+ }
+#endif
+
+ if (fprintf (fprcs, "\012") < 0)
goto write_error;
}
}
@@ -1170,7 +1283,9 @@ add_rcs_file (message, rcs, user, add_vhead, key_opt,
goto write_error;
}
- /* Now copy over the contents of the file, expanding at signs. */
+ /* Now copy over the contents of the file, expanding at signs.
+ If preserve_perms is set, do this only for regular files. */
+ if (!preserve_perms || file_type == S_IFREG)
{
char buf[8192];
unsigned int len;
@@ -1208,7 +1323,12 @@ add_rcs_file (message, rcs, user, add_vhead, key_opt,
ierrno = errno;
goto write_error_noclose;
}
- (void) fclose (fpuser);
+ /* Close fpuser only if we opened it to begin with. */
+ if (fpuser != NULL)
+ {
+ if (fclose (fpuser) < 0)
+ error (0, errno, "cannot close %s", user);
+ }
/*
* Fix the modes on the RCS files. The user modes of the original
@@ -1224,8 +1344,9 @@ add_rcs_file (message, rcs, user, add_vhead, key_opt,
if (chmod (rcs, mode) < 0)
{
ierrno = errno;
- fperror (add_logfp, 0, ierrno,
- "WARNING: cannot change mode of file %s", rcs);
+ if (add_logfp != NULL)
+ fperror (add_logfp, 0, ierrno,
+ "WARNING: cannot change mode of file %s", rcs);
error (0, ierrno, "WARNING: cannot change mode of file %s", rcs);
err++;
}
@@ -1238,15 +1359,20 @@ add_rcs_file (message, rcs, user, add_vhead, key_opt,
write_error:
ierrno = errno;
- (void) fclose (fprcs);
+ if (fclose (fprcs) < 0)
+ error (0, errno, "cannot close %s", rcs);
write_error_noclose:
- (void) fclose (fpuser);
- fperror (add_logfp, 0, ierrno, "ERROR: cannot write file %s", rcs);
+ if (fclose (fpuser) < 0)
+ error (0, errno, "cannot close %s", user);
+ if (add_logfp != NULL)
+ fperror (add_logfp, 0, ierrno, "ERROR: cannot write file %s", rcs);
error (0, ierrno, "ERROR: cannot write file %s", rcs);
if (ierrno == ENOSPC)
{
- (void) CVS_UNLINK (rcs);
- fperror (add_logfp, 0, 0, "ERROR: out of space - aborting");
+ if (CVS_UNLINK (rcs) < 0)
+ error (0, errno, "cannot remove %s", rcs);
+ if (add_logfp != NULL)
+ fperror (add_logfp, 0, 0, "ERROR: out of space - aborting");
error (1, 0, "ERROR: out of space - aborting");
}
read_error:
@@ -1271,20 +1397,27 @@ expand_at_signs (buf, size, fp)
off_t size;
FILE *fp;
{
- char *cp, *end;
+ register char *cp, *next;
- errno = 0;
- for (cp = buf, end = buf + size; cp < end; cp++)
+ cp = buf;
+ while ((next = memchr (cp, '@', size)) != NULL)
{
- if (*cp == '@')
- {
- if (putc ('@', fp) == EOF && errno != 0)
- return EOF;
- }
- if (putc (*cp, fp) == EOF && errno != 0)
- return (EOF);
+ int len;
+
+ ++next;
+ len = next - cp;
+ if (fwrite (cp, 1, len, fp) != len)
+ return EOF;
+ if (putc ('@', fp) == EOF)
+ return EOF;
+ cp = next;
+ size -= len;
}
- return (1);
+
+ if (fwrite (cp, 1, size, fp) != size)
+ return EOF;
+
+ return 1;
}
/*
diff --git a/contrib/cvs/src/lock.c b/contrib/cvs/src/lock.c
index be2dd1cb7ff1..72818f33b5a4 100644
--- a/contrib/cvs/src/lock.c
+++ b/contrib/cvs/src/lock.c
@@ -704,7 +704,6 @@ lock_obtained (repos)
static int lock_filesdoneproc PROTO ((void *callerdat, int err,
char *repository, char *update_dir,
List *entries));
-static int fsortcmp PROTO((const Node * p, const Node * q));
/*
* Create a list of repositories to lock
@@ -733,17 +732,6 @@ lock_filesdoneproc (callerdat, err, repository, update_dir, entries)
return (err);
}
-/*
- * compare two lock list nodes (for sort)
- */
-static int
-fsortcmp (p, q)
- const Node *p;
- const Node *q;
-{
- return (strcmp (p->key, q->key));
-}
-
void
lock_tree_for_write (argc, argv, local, aflag)
int argc;
diff --git a/contrib/cvs/src/main.c b/contrib/cvs/src/main.c
index 2397d7eaeb03..9465be615783 100644
--- a/contrib/cvs/src/main.c
+++ b/contrib/cvs/src/main.c
@@ -522,7 +522,7 @@ main (argc, argv)
(void) fputs (config_string, stdout);
(void) fputs ("\n", stdout);
(void) fputs ("\
-Copyright (c) 1989-1997 Brian Berliner, david d `zoo' zuhn, \n\
+Copyright (c) 1989-1998 Brian Berliner, david d `zoo' zuhn, \n\
Jeff Polk, and other authors\n", stdout);
(void) fputs ("\n", stdout);
(void) fputs ("CVS may be copied only under the terms of the GNU General Public License,\n", stdout);
diff --git a/contrib/cvs/src/mkmodules.c b/contrib/cvs/src/mkmodules.c
index dab5b3f78b72..c3c530d803c5 100644
--- a/contrib/cvs/src/mkmodules.c
+++ b/contrib/cvs/src/mkmodules.c
@@ -279,6 +279,10 @@ static const char *const modules_contents[] = {
static const char *const config_contents[] = {
"# Set this to \"no\" if pserver shouldn't check system users/passwords\n",
"#SystemAuth=no\n",
+ "\n",
+ "# Set `PreservePermissions' to `yes' to save file status information\n",
+ "# in the repository.\n",
+ "#PreservePermissions=no\n",
NULL
};
@@ -538,7 +542,7 @@ checkout_file (file, temp)
(RCSCHECKOUTPROC) NULL, (void *) NULL);
if (retcode != 0)
{
- error (0, retcode == -1 ? errno : 0, "failed to check out %s file",
+ error (0, 0, "failed to check out %s file",
file);
}
freercsnode (&rcsnode);
diff --git a/contrib/cvs/src/myndbm.c b/contrib/cvs/src/myndbm.c
index 949ac7898fa8..6b15e77412e5 100644
--- a/contrib/cvs/src/myndbm.c
+++ b/contrib/cvs/src/myndbm.c
@@ -200,27 +200,34 @@ mydbm_load_file (fp, list)
List *list;
{
char *line = NULL;
- size_t line_len;
+ size_t line_size;
char *value;
size_t value_allocated;
char *cp, *vp;
- int len, cont;
+ int cont;
int line_length;
value_allocated = 1;
value = xmalloc (value_allocated);
- for (cont = 0; (line_length = getline (&line, &line_len, fp)) >= 0;)
+ cont = 0;
+ while ((line_length = getstr (&line, &line_size, fp, '\012', 0)) >= 0)
{
- if ((cp = strrchr (line, '\012')) != NULL)
- *cp = '\0'; /* strip the newline */
- cp = line + strlen (line);
- if (cp > line && cp[-1] == '\015')
+ if (line_length > 0 && line[line_length - 1] == '\012')
+ {
+ /* Strip the newline. */
+ --line_length;
+ line[line_length] = '\0';
+ }
+ if (line_length > 0 && line[line_length - 1] == '\015')
+ {
/* If the file (e.g. modules) was written on an NT box, it will
contain CRLF at the ends of lines. Strip them (we can't do
this by opening the file in text mode because we might be
running on unix). */
- cp[-1] = '\0';
+ --line_length;
+ line[line_length] = '\0';
+ }
/*
* Add the line to the value, at the end if this is a continuation
@@ -234,15 +241,15 @@ mydbm_load_file (fp, list)
* See if the line we read is a continuation line, and strip the
* backslash if so.
*/
- len = strlen (line);
- if (len > 0)
- cp = &line[len - 1];
+ if (line_length > 0)
+ cp = &line[line_length - 1];
else
cp = line;
if (*cp == '\\')
{
cont = 1;
*cp = '\0';
+ --line_length;
}
else
{
@@ -250,7 +257,7 @@ mydbm_load_file (fp, list)
}
expand_string (&value,
&value_allocated,
- strlen (value) + strlen (line) + 5);
+ strlen (value) + line_length + 5);
strcat (value, line);
if (value[0] == '#')
diff --git a/contrib/cvs/src/no_diff.c b/contrib/cvs/src/no_diff.c
index 6d6a6fbf1018..078343ea6ed3 100644
--- a/contrib/cvs/src/no_diff.c
+++ b/contrib/cvs/src/no_diff.c
@@ -36,6 +36,13 @@ No_Difference (finfo, vers)
if (!vers->srcfile || !vers->srcfile->path)
return (-1); /* different since we couldn't tell */
+#ifdef PRESERVE_PERMISSIONS_SUPPORT
+ /* If special files are in use, then any mismatch of file metadata
+ information also means that the files should be considered different. */
+ if (preserve_perms && special_file_mismatch (finfo, vers->vn_user, NULL))
+ return 1;
+#endif
+
if (vers->entdata && vers->entdata->options)
options = xstrdup (vers->entdata->options);
else
diff --git a/contrib/cvs/src/parseinfo.c b/contrib/cvs/src/parseinfo.c
index c492be002670..a8a1b4a39180 100644
--- a/contrib/cvs/src/parseinfo.c
+++ b/contrib/cvs/src/parseinfo.c
@@ -326,6 +326,26 @@ parse_config (cvsroot)
goto error_return;
}
}
+ else if (strcmp (line, "PreservePermissions") == 0)
+ {
+ if (strcmp (p, "no") == 0)
+ preserve_perms = 0;
+ else if (strcmp (p, "yes") == 0)
+ {
+#ifdef PRESERVE_PERMISSIONS_SUPPORT
+ preserve_perms = 1;
+#else
+ error (0, 0, "\
+warning: this CVS does not support PreservePermissions");
+#endif
+ }
+ else
+ {
+ error (0, 0, "unrecognized value '%s' for PreservePermissions",
+ p);
+ goto error_return;
+ }
+ }
else
{
/* We may be dealing with a keyword which was added in a
diff --git a/contrib/cvs/src/patch.c b/contrib/cvs/src/patch.c
index 9d091c34b6fc..9f56b627bfaf 100644
--- a/contrib/cvs/src/patch.c
+++ b/contrib/cvs/src/patch.c
@@ -499,23 +499,43 @@ patch_fileproc (callerdat, finfo)
ret = 0;
goto out2;
}
+
+ /* Create 3 empty files. I'm not really sure there is any advantage
+ to doing so now rather than just waiting until later. */
tmpfile1 = cvs_temp_name ();
- if ((fp1 = CVS_FOPEN (tmpfile1, "w+")) != NULL)
- (void) fclose (fp1);
+ fp1 = CVS_FOPEN (tmpfile1, "w+");
+ if (fp1 == NULL)
+ {
+ error (0, errno, "cannot create temporary file %s", tmpfile1);
+ ret = 1;
+ goto out;
+ }
+ else
+ if (fclose (fp1) < 0)
+ error (0, errno, "warning: cannot close %s", tmpfile1);
tmpfile2 = cvs_temp_name ();
- if ((fp2 = CVS_FOPEN (tmpfile2, "w+")) != NULL)
- (void) fclose (fp2);
+ fp2 = CVS_FOPEN (tmpfile2, "w+");
+ if (fp2 == NULL)
+ {
+ error (0, errno, "cannot create temporary file %s", tmpfile2);
+ ret = 1;
+ goto out;
+ }
+ else
+ if (fclose (fp2) < 0)
+ error (0, errno, "warning: cannot close %s", tmpfile2);
tmpfile3 = cvs_temp_name ();
- if ((fp3 = CVS_FOPEN (tmpfile3, "w+")) != NULL)
- (void) fclose (fp3);
- if (fp1 == NULL || fp2 == NULL || fp3 == NULL)
+ fp3 = CVS_FOPEN (tmpfile3, "w+");
+ if (fp3 == NULL)
{
- /* FIXME: should be printing a proper error message, with errno-based
- message, and the filename which we could not create. */
- error (0, 0, "cannot create temporary files");
+ error (0, errno, "cannot create temporary file %s", tmpfile3);
ret = 1;
goto out;
}
+ else
+ if (fclose (fp3) < 0)
+ error (0, errno, "warning: cannot close %s", tmpfile3);
+
if (vers_tag != NULL)
{
retcode = RCS_checkout (rcsfile, (char *) NULL, vers_tag,
@@ -523,9 +543,8 @@ patch_fileproc (callerdat, finfo)
(RCSCHECKOUTPROC) NULL, (void *) NULL);
if (retcode != 0)
{
- if (!really_quiet)
- error (retcode == -1 ? 1 : 0, retcode == -1 ? errno : 0,
- "co of revision %s in %s failed", vers_tag, rcs);
+ error (0, 0,
+ "cannot check out revision %s of %s", vers_tag, rcs);
ret = 1;
goto out;
}
@@ -548,9 +567,8 @@ patch_fileproc (callerdat, finfo)
(RCSCHECKOUTPROC) NULL, (void *) NULL);
if (retcode != 0)
{
- if (!really_quiet)
- error (retcode == -1 ? 1 : 0, retcode == -1 ? errno : 0,
- "co of revision %s in %s failed", vers_head, rcs);
+ error (0, 0,
+ "cannot check out revision %s of %s", vers_head, rcs);
ret = 1;
goto out;
}
@@ -584,10 +602,16 @@ patch_fileproc (callerdat, finfo)
if (getline (&line1, &line1_chars_allocated, fp) < 0 ||
getline (&line2, &line2_chars_allocated, fp) < 0)
{
- error (0, errno, "failed to read diff file header %s for %s",
- tmpfile3, rcs);
+ if (feof (fp))
+ error (0, 0, "\
+failed to read diff file header %s for %s: end of file", tmpfile3, rcs);
+ else
+ error (0, errno,
+ "failed to read diff file header %s for %s",
+ tmpfile3, rcs);
ret = 1;
- (void) fclose (fp);
+ if (fclose (fp) < 0)
+ error (0, errno, "error closing %s", tmpfile3);
goto out;
}
if (!unidiff)
@@ -599,7 +623,8 @@ patch_fileproc (callerdat, finfo)
{
error (0, 0, "invalid diff header for %s", rcs);
ret = 1;
- (void) fclose (fp);
+ if (fclose (fp) < 0)
+ error (0, errno, "error closing %s", tmpfile3);
goto out;
}
}
@@ -612,7 +637,8 @@ patch_fileproc (callerdat, finfo)
{
error (0, 0, "invalid unidiff header for %s", rcs);
ret = 1;
- (void) fclose (fp);
+ if (fclose (fp) < 0)
+ error (0, errno, "error closing %s", tmpfile3);
goto out;
}
}
diff --git a/contrib/cvs/src/rcs.c b/contrib/cvs/src/rcs.c
index c134d67360d4..89af760725aa 100644
--- a/contrib/cvs/src/rcs.c
+++ b/contrib/cvs/src/rcs.c
@@ -11,6 +11,9 @@
#include <assert.h>
#include "cvs.h"
#include "edit.h"
+#include "hardlink.h"
+
+int preserve_perms = 0;
/* The RCS -k options, and a set of enums that must match the array.
These come first so that we can use enum kflag in function
@@ -19,11 +22,57 @@ static const char *const kflags[] =
{"kv", "kvl", "k", "v", "o", "b", (char *) NULL};
enum kflag { KFLAG_KV = 0, KFLAG_KVL, KFLAG_K, KFLAG_V, KFLAG_O, KFLAG_B };
+/* A structure we use to buffer the contents of an RCS file. The
+ various fields are only referenced directly by the rcsbuf_*
+ functions. We declare the struct here so that we can allocate it
+ on the stack, rather than in memory. */
+
+struct rcsbuffer
+{
+ /* Points to the current position in the buffer. */
+ char *ptr;
+ /* Points just after the last valid character in the buffer. */
+ char *ptrend;
+ /* The file. */
+ FILE *fp;
+ /* The name of the file, used for error messages. */
+ const char *filename;
+ /* The starting file position of the data in the buffer. */
+ unsigned long pos;
+ /* The length of the value. */
+ size_t vlen;
+ /* Whether the value contains an '@' string. If so, we can not
+ compress whitespace characters. */
+ int at_string;
+ /* The number of embedded '@' characters in an '@' string. If
+ this is non-zero, we must search the string for pairs of '@'
+ and convert them to a single '@'. */
+ int embedded_at;
+};
+
static RCSNode *RCS_parsercsfile_i PROTO((FILE * fp, const char *rcsfile));
static char *RCS_getdatebranch PROTO((RCSNode * rcs, char *date, char *branch));
-static int getrcskey PROTO((FILE * fp, char **keyp, char **valp,
- size_t *lenp));
-static void getrcsrev PROTO ((FILE *fp, char **revp));
+static void rcsbuf_open PROTO ((struct rcsbuffer *, FILE *fp,
+ const char *filename, unsigned long pos));
+static void rcsbuf_close PROTO ((struct rcsbuffer *));
+static int rcsbuf_getkey PROTO ((struct rcsbuffer *, char **keyp,
+ char **valp));
+static int rcsbuf_getrevnum PROTO ((struct rcsbuffer *, char **revp));
+static char *rcsbuf_fill PROTO ((struct rcsbuffer *, char *ptr, char **keyp,
+ char **valp));
+static char *rcsbuf_valcopy PROTO ((struct rcsbuffer *, char *val, int polish,
+ size_t *lenp));
+static void rcsbuf_valpolish PROTO ((struct rcsbuffer *, char *val, int polish,
+ size_t *lenp));
+static void rcsbuf_valpolish_internal PROTO ((struct rcsbuffer *, char *to,
+ const char *from, size_t *lenp));
+static unsigned long rcsbuf_ftell PROTO ((struct rcsbuffer *));
+static void rcsbuf_get_buffered PROTO ((struct rcsbuffer *, char **datap,
+ size_t *lenp));
+static void rcsbuf_cache PROTO ((RCSNode *, struct rcsbuffer *));
+static void rcsbuf_cache_close PROTO ((void));
+static void rcsbuf_cache_open PROTO ((RCSNode *, long, FILE **,
+ struct rcsbuffer *));
static int checkmagic_proc PROTO((Node *p, void *closure));
static void do_branches PROTO((List * list, char *val));
static void do_symbols PROTO((List * list, char *val));
@@ -40,12 +89,15 @@ static void expand_keywords PROTO((RCSNode *, RCSVers *, const char *,
static void cmp_file_buffer PROTO((void *, const char *, size_t));
enum rcs_delta_op {RCS_ANNOTATE, RCS_FETCH};
-static void RCS_deltas PROTO ((RCSNode *, FILE *, char *, enum rcs_delta_op,
- char **, size_t *, char **, size_t *));
+static void RCS_deltas PROTO ((RCSNode *, FILE *, struct rcsbuffer *, char *,
+ enum rcs_delta_op, char **, size_t *,
+ char **, size_t *));
/* Routines for reading, parsing and writing RCS files. */
-static RCSVers *getdelta PROTO ((FILE *, char *));
-static Deltatext *RCS_getdeltatext PROTO ((RCSNode *, FILE *));
+static RCSVers *getdelta PROTO ((struct rcsbuffer *, char *, char **,
+ char **));
+static Deltatext *RCS_getdeltatext PROTO ((RCSNode *, FILE *,
+ struct rcsbuffer *));
static void freedeltatext PROTO ((Deltatext *));
static void RCS_putadmin PROTO ((RCSNode *, FILE *));
@@ -54,13 +106,21 @@ static void RCS_putdesc PROTO ((RCSNode *, FILE *));
static void putdelta PROTO ((RCSVers *, FILE *));
static int putrcsfield_proc PROTO ((Node *, void *));
static int putsymbol_proc PROTO ((Node *, void *));
-static void RCS_copydeltas PROTO ((RCSNode *, FILE *, FILE *, Deltatext *, char *));
+static void RCS_copydeltas PROTO ((RCSNode *, FILE *, struct rcsbuffer *,
+ FILE *, Deltatext *, char *));
+static int count_delta_actions PROTO ((Node *, void *));
static void putdeltatext PROTO ((FILE *, Deltatext *));
static FILE *rcs_internal_lockfile PROTO ((char *));
static void rcs_internal_unlockfile PROTO ((FILE *, char *));
static char *rcs_lockfilename PROTO ((char *));
+/* The RCS file reading functions are called a lot, and they do some
+ string comparisons. This macro speeds things up a bit by skipping
+ the function call when the first characters are different. It
+ evaluates its arguments multiple times. */
+#define STREQ(a, b) ((a)[0] == (b)[0] && strcmp ((a), (b)) == 0)
+
/*
* We don't want to use isspace() from the C library because:
*
@@ -90,7 +150,6 @@ static const char spacetab[] = {
#define whitespace(c) (spacetab[(unsigned char)c] != 0)
-
/* Parse an rcsfile given a user file name and a repository. If there is
an error, we print an error message and return NULL. If the file
does not exist, we return NULL without printing anything (I'm not
@@ -106,6 +165,10 @@ RCS_parse (file, repos)
RCSNode *retval;
char *rcsfile;
+ /* We're creating a new RCSNode, so there is no hope of finding it
+ in the cache. */
+ rcsbuf_cache_close ();
+
rcsfile = xmalloc (strlen (repos) + strlen (file)
+ sizeof (RCSEXT) + sizeof (CVSATTIC) + 10);
(void) sprintf (rcsfile, "%s/%s%s", repos, file, RCSEXT);
@@ -115,7 +178,6 @@ RCS_parse (file, repos)
if (rcs != NULL)
rcs->flags |= VALID;
- fclose (fp);
retval = rcs;
goto out;
}
@@ -136,7 +198,6 @@ RCS_parse (file, repos)
rcs->flags |= VALID;
}
- fclose (fp);
retval = rcs;
goto out;
}
@@ -165,7 +226,6 @@ RCS_parse (file, repos)
if (rcs != NULL)
rcs->flags |= VALID;
- fclose (fp);
free (rcs->path);
rcs->path = found_path;
retval = rcs;
@@ -189,7 +249,6 @@ RCS_parse (file, repos)
rcs->flags |= VALID;
}
- fclose (fp);
free (rcs->path);
rcs->path = found_path;
retval = rcs;
@@ -221,6 +280,10 @@ RCS_parsercsfile (rcsfile)
FILE *fp;
RCSNode *rcs;
+ /* We're creating a new RCSNode, so there is no hope of finding it
+ in the cache. */
+ rcsbuf_cache_close ();
+
/* open the rcsfile */
if ((fp = CVS_FOPEN (rcsfile, FOPEN_BINARY_READ)) == NULL)
{
@@ -230,7 +293,6 @@ RCS_parsercsfile (rcsfile)
rcs = RCS_parsercsfile_i (fp, rcsfile);
- fclose (fp);
return (rcs);
}
@@ -243,6 +305,7 @@ RCS_parsercsfile_i (fp, rcsfile)
const char *rcsfile;
{
RCSNode *rdata;
+ struct rcsbuffer rcsbuf;
char *key, *value;
/* make a node */
@@ -251,40 +314,32 @@ RCS_parsercsfile_i (fp, rcsfile)
rdata->refcount = 1;
rdata->path = xstrdup (rcsfile);
- /* Process HEAD and BRANCH keywords from the RCS header.
+ /* Process HEAD, BRANCH, and EXPAND keywords from the RCS header.
Most cvs operations on the main branch don't need any more
information. Those that do call RCS_reparsercsfile to parse
- the rest of the header and the deltas.
-
- People often wonder whether this is inefficient, to open the
- file once here and once in RCS_reparsercsfile. Well, it might
- help a little bit if we kept the file open (I haven't tried
- timing this myself), but basically the common case, which we
- want to optimize, is the one in which we call
- RCS_parsercsfile_i and not RCS_reparsercsfile (for example,
- "cvs update" on a lot of files most of which are unmodified).
- So making the case in which we call RCS_reparsercsfile fast is
- not as important. */
-
- if (getrcskey (fp, &key, &value, NULL) == -1 || key == NULL)
+ the rest of the header and the deltas. */
+
+ rcsbuf_open (&rcsbuf, fp, rcsfile, 0);
+
+ if (! rcsbuf_getkey (&rcsbuf, &key, &value))
goto l_error;
- if (strcmp (key, RCSDESC) == 0)
+ if (STREQ (key, RCSDESC))
goto l_error;
- if (strcmp (RCSHEAD, key) == 0 && value != NULL)
- rdata->head = xstrdup (value);
+ if (STREQ (RCSHEAD, key) && value != NULL)
+ rdata->head = rcsbuf_valcopy (&rcsbuf, value, 0, (size_t *) NULL);
- if (getrcskey (fp, &key, &value, NULL) == -1 || key == NULL)
+ if (! rcsbuf_getkey (&rcsbuf, &key, &value))
goto l_error;
- if (strcmp (key, RCSDESC) == 0)
+ if (STREQ (key, RCSDESC))
goto l_error;
- if (strcmp (RCSBRANCH, key) == 0 && value != NULL)
+ if (STREQ (RCSBRANCH, key) && value != NULL)
{
char *cp;
- rdata->branch = xstrdup (value);
+ rdata->branch = rcsbuf_valcopy (&rcsbuf, value, 0, (size_t *) NULL);
if ((numdots (rdata->branch) & 1) != 0)
{
/* turn it into a branch if it's a revision */
@@ -293,23 +348,43 @@ RCS_parsercsfile_i (fp, rcsfile)
}
}
- rdata->flags |= PARTIAL;
- return rdata;
-
-l_error:
- if (!really_quiet)
+ /* Look ahead for expand, stopping when we see desc or a revision
+ number. */
+ while (1)
{
- if (ferror(fp))
- {
- error (1, 0, "error reading `%s'", rcsfile);
- }
- else
+ char *cp;
+
+ if (STREQ (RCSEXPAND, key))
{
- error (0, 0, "`%s' does not appear to be a valid rcs file",
- rcsfile);
+ rdata->expand = rcsbuf_valcopy (&rcsbuf, value, 0,
+ (size_t *) NULL);
+ break;
}
+
+ for (cp = key; (isdigit (*cp) || *cp == '.') && *cp != '\0'; cp++)
+ /* do nothing */ ;
+ if (*cp == '\0')
+ break;
+
+ if (STREQ (RCSDESC, key))
+ break;
+
+ if (! rcsbuf_getkey (&rcsbuf, &key, &value))
+ break;
}
+
+ rdata->flags |= PARTIAL;
+
+ rcsbuf_cache (rdata, &rcsbuf);
+
+ return rdata;
+
+l_error:
+ error (0, 0, "`%s' does not appear to be a valid rcs file",
+ rcsfile);
+ rcsbuf_close (&rcsbuf);
freercsnode (&rdata);
+ fclose (fp);
return (NULL);
}
@@ -321,25 +396,24 @@ l_error:
If PFP is NULL, close the file when done. Otherwise, leave it open
and store the FILE * in *PFP. */
void
-RCS_reparsercsfile (rdata, pfp)
+RCS_reparsercsfile (rdata, pfp, rcsbufp)
RCSNode *rdata;
FILE **pfp;
+ struct rcsbuffer *rcsbufp;
{
FILE *fp;
char *rcsfile;
-
+ struct rcsbuffer rcsbuf;
Node *q, *kv;
RCSVers *vnode;
- long fpos;
+ int gotkey;
char *cp;
char *key, *value;
assert (rdata != NULL);
rcsfile = rdata->path;
- fp = CVS_FOPEN (rcsfile, FOPEN_BINARY_READ);
- if (fp == NULL)
- error (1, 0, "unable to reopen `%s'", rcsfile);
+ rcsbuf_cache_open (rdata, 0, &fp, &rcsbuf);
/* make a node */
/* This probably shouldn't be done until later: if a file has an
@@ -351,66 +425,68 @@ RCS_reparsercsfile (rdata, pfp)
* process all the special header information, break out when we get to
* the first revision delta
*/
+ gotkey = 0;
for (;;)
{
- fpos = ftell (fp);
-
/* get the next key/value pair */
-
- /* if key is NULL here, then the file is missing some headers
- or we had trouble reading the file. */
- if (getrcskey (fp, &key, &value, NULL) == -1 || key == NULL)
+ if (!gotkey)
{
- if (ferror(fp))
- {
- error (1, 0, "error reading `%s'", rcsfile);
- }
- else
+ if (! rcsbuf_getkey (&rcsbuf, &key, &value))
{
error (1, 0, "`%s' does not appear to be a valid rcs file",
rcsfile);
}
}
- /* Skip head and branch tags; we already have them. */
- if (strcmp (key, RCSHEAD) == 0 || strcmp (key, RCSBRANCH) == 0)
+ gotkey = 0;
+
+ /* Skip head, branch and expand tags; we already have them. */
+ if (STREQ (key, RCSHEAD)
+ || STREQ (key, RCSBRANCH)
+ || STREQ (key, RCSEXPAND))
+ {
continue;
+ }
- if (strcmp (key, "access") == 0)
+ if (STREQ (key, "access"))
{
if (value != NULL)
- rdata->access = xstrdup (value);
+ {
+ /* We pass the POLISH parameter as 1 because
+ RCS_addaccess expects nothing but spaces. FIXME:
+ It would be easy and more efficient to change
+ RCS_addaccess. */
+ rdata->access = rcsbuf_valcopy (&rcsbuf, value, 1,
+ (size_t *) NULL);
+ }
continue;
}
/* We always save lock information, so that we can handle
-kkvl correctly when checking out a file. */
- if (strcmp (key, "locks") == 0)
+ if (STREQ (key, "locks"))
{
if (value != NULL)
- rdata->locks_data = xstrdup (value);
- fpos = ftell (fp);
- if (getrcskey (fp, &key, &value, NULL) >= 0 &&
- strcmp (key, "strict") == 0 &&
- value == NULL)
+ rdata->locks_data = rcsbuf_valcopy (&rcsbuf, value, 0,
+ (size_t *) NULL);
+ if (! rcsbuf_getkey (&rcsbuf, &key, &value))
+ {
+ error (1, 0, "premature end of file reading %s", rcsfile);
+ }
+ if (STREQ (key, "strict") && value == NULL)
{
rdata->strict_locks = 1;
}
else
- (void) fseek (fp, fpos, SEEK_SET);
+ gotkey = 1;
continue;
}
- if (strcmp (RCSSYMBOLS, key) == 0)
+ if (STREQ (RCSSYMBOLS, key))
{
if (value != NULL)
- rdata->symbols_data = xstrdup(value);
- continue;
- }
-
- if (strcmp (RCSEXPAND, key) == 0)
- {
- rdata->expand = xstrdup (value);
+ rdata->symbols_data = rcsbuf_valcopy (&rcsbuf, value, 0,
+ (size_t *) NULL);
continue;
}
@@ -421,15 +497,19 @@ RCS_reparsercsfile (rdata, pfp)
*/
for (cp = key; (isdigit (*cp) || *cp == '.') && *cp != '\0'; cp++)
/* do nothing */ ;
- if (*cp == '\0' && strncmp (RCSDATE, value, strlen (RCSDATE)) == 0)
+ /* Note that when comparing with RCSDATE, we are not massaging
+ VALUE from the string found in the RCS file. This is OK
+ since we know exactly what to expect. */
+ if (*cp == '\0' && strncmp (RCSDATE, value, (sizeof RCSDATE) - 1) == 0)
break;
- if (strcmp (key, RCSDESC) == 0)
+ if (STREQ (key, RCSDESC))
break;
- if (strcmp (key, "comment") == 0)
+ if (STREQ (key, "comment"))
{
- rdata->comment = xstrdup (value);
+ rdata->comment = rcsbuf_valcopy (&rcsbuf, value, 0,
+ (size_t *) NULL);
continue;
}
if (rdata->other == NULL)
@@ -437,7 +517,7 @@ RCS_reparsercsfile (rdata, pfp)
kv = getnode ();
kv->type = RCSFIELD;
kv->key = xstrdup (key);
- kv->data = xstrdup (value);
+ kv->data = rcsbuf_valcopy (&rcsbuf, value, 1, (size_t *) NULL);
if (addnode (rdata->other, kv) != 0)
{
error (0, 0, "warning: duplicate key `%s' in RCS file `%s'",
@@ -448,15 +528,11 @@ RCS_reparsercsfile (rdata, pfp)
/* if we haven't grabbed it yet, we didn't want it */
}
- /*
- * we got out of the loop, so we have the first part of the first
- * revision delta in our hand key=the revision and value=the date key and
- * its value
- */
- /* First, seek back to the start of the delta block. */
- (void) fseek (fp, fpos, SEEK_SET);
+ /* We got out of the loop, so we have the first part of the first
+ revision delta in KEY (the revision) and VALUE (the date key
+ and its value). This is what getdelta expects to receive. */
- while ((vnode = getdelta (fp, rcsfile)) != NULL)
+ while ((vnode = getdelta (&rcsbuf, rcsfile, &key, &value)) != NULL)
{
/* get the node */
q = getnode ();
@@ -476,8 +552,9 @@ RCS_reparsercsfile (rdata, pfp)
}
}
- (void) getrcskey (fp, &key, &value, NULL);
- if (key != NULL && strcmp (key, RCSDESC) == 0)
+ /* Here KEY and VALUE are whatever caused getdelta to return NULL. */
+
+ if (STREQ (key, RCSDESC))
{
if (rdata->desc != NULL)
{
@@ -486,19 +563,17 @@ RCS_reparsercsfile (rdata, pfp)
key, rcsfile);
free (rdata->desc);
}
- rdata->desc = xstrdup (value);
+ rdata->desc = rcsbuf_valcopy (&rcsbuf, value, 0, (size_t *) NULL);
}
- rdata->delta_pos = ftell (fp);
+ rdata->delta_pos = rcsbuf_ftell (&rcsbuf);
if (pfp == NULL)
- {
- if (fclose (fp) < 0)
- error (0, errno, "cannot close %s", rcsfile);
- }
+ rcsbuf_cache (rdata, &rcsbuf);
else
{
*pfp = fp;
+ *rcsbufp = rcsbuf;
}
rdata->flags &= ~PARTIAL;
}
@@ -520,31 +595,21 @@ RCS_fully_parse (rcs)
RCSNode *rcs;
{
FILE *fp;
+ struct rcsbuffer rcsbuf;
- RCS_reparsercsfile (rcs, &fp);
+ RCS_reparsercsfile (rcs, &fp, &rcsbuf);
while (1)
{
- int c;
char *key, *value;
- size_t vallen;
Node *vers;
RCSVers *vnode;
/* Rather than try to keep track of how much information we
have read, just read to the end of the file. */
- do
- {
- c = getc (fp);
- if (c == EOF)
- break;
- } while (whitespace (c));
- if (c == EOF)
+ if (! rcsbuf_getrevnum (&rcsbuf, &key))
break;
- if (ungetc (c, fp) == EOF)
- error (1, errno, "ungetc failed");
- getrcsrev (fp, &key);
vers = findnode (rcs->versions, key);
if (vers == NULL)
error (1, 0,
@@ -553,9 +618,9 @@ RCS_fully_parse (rcs)
vnode = (RCSVers *) vers->data;
- while (getrcskey (fp, &key, &value, &vallen) >= 0)
+ while (rcsbuf_getkey (&rcsbuf, &key, &value))
{
- if (strcmp (key, "text") != 0)
+ if (! STREQ (key, "text"))
{
Node *kv;
@@ -564,7 +629,7 @@ RCS_fully_parse (rcs)
kv = getnode ();
kv->type = RCSFIELD;
kv->key = xstrdup (key);
- kv->data = xstrdup (value);
+ kv->data = rcsbuf_valcopy (&rcsbuf, value, 1, (size_t *) NULL);
if (addnode (vnode->other, kv) != 0)
{
error (0, 0,
@@ -577,7 +642,7 @@ warning: duplicate key `%s' in version `%s' of RCS file `%s'",
continue;
}
- if (strcmp (vnode->version, rcs->head) != 0)
+ if (! STREQ (vnode->version, rcs->head))
{
unsigned long add, del;
char buf[50];
@@ -589,8 +654,10 @@ warning: duplicate key `%s' in version `%s' of RCS file `%s'",
del = 0;
if (value != NULL)
{
+ size_t vallen;
const char *cp;
+ rcsbuf_valpolish (&rcsbuf, value, 0, &vallen);
cp = value;
while (cp < value + vallen)
{
@@ -670,8 +737,7 @@ warning: duplicate key `%s' in version `%s' of RCS file `%s'",
}
}
- if (fclose (fp) < 0)
- error (0, errno, "cannot close %s", rcs->path);
+ rcsbuf_cache (rcs, &rcsbuf);
}
/*
@@ -766,353 +832,877 @@ rcsvers_delproc (p)
{
free_rcsvers_contents ((RCSVers *) p->data);
}
+
+/* These functions retrieve keys and values from an RCS file using a
+ buffer. We use this somewhat complex approach because it turns out
+ that for many common operations, CVS spends most of its time
+ reading keys, so it's worth doing some fairly hairy optimization. */
-/*
- * getrcskey - fill in the key and value from the rcs file the algorithm is
- * as follows
- *
- * o skip whitespace
- * o fill in key with everything up to next white
- * space or semicolon
- * o if key == "desc" then key and data are NULL and return -1
- * o if key wasn't terminated by a semicolon, skip white space and fill
- * in value with everything up to a semicolon
- * o compress all whitespace down to a single space
- * o if a word starts with @, do funky rcs processing
- * o strip whitespace off end of value or set value to NULL if it empty
- * o return 0 since we found something besides "desc"
- *
- * Sets *KEYP and *VALUEP to point to storage managed by the getrcskey
- * function; the contents are only valid until the next call to
- * getrcskey or getrcsrev. If LENP is not NULL, this sets *LENP to
- * the length of *VALUEP; this is needed if the string might contain
- * binary data.
- */
+/* The number of bytes we try to read each time we need more data. */
-static char *key = NULL;
-static char *value = NULL;
-static size_t keysize = 0;
-static size_t valsize = 0;
+#define RCSBUF_BUFSIZE (8192)
-static int
-getrcskey (fp, keyp, valp, lenp)
+/* The buffer we use to store data. This grows as needed. */
+
+static char *rcsbuf_buffer = NULL;
+static size_t rcsbuf_buffer_size = 0;
+
+/* Whether rcsbuf_buffer is in use. This is used as a sanity check. */
+
+static int rcsbuf_inuse;
+
+/* Set up to start gathering keys and values from an RCS file. This
+ initializes RCSBUF. */
+
+static void
+rcsbuf_open (rcsbuf, fp, filename, pos)
+ struct rcsbuffer *rcsbuf;
FILE *fp;
+ const char *filename;
+ unsigned long pos;
+{
+ if (rcsbuf_inuse)
+ error (1, 0, "rcsbuf_open: internal error");
+ rcsbuf_inuse = 1;
+
+ if (rcsbuf_buffer_size < RCSBUF_BUFSIZE)
+ expand_string (&rcsbuf_buffer, &rcsbuf_buffer_size, RCSBUF_BUFSIZE);
+
+ rcsbuf->ptr = rcsbuf_buffer;
+ rcsbuf->ptrend = rcsbuf_buffer;
+ rcsbuf->fp = fp;
+ rcsbuf->filename = filename;
+ rcsbuf->pos = pos;
+ rcsbuf->vlen = 0;
+ rcsbuf->at_string = 0;
+ rcsbuf->embedded_at = 0;
+}
+
+/* Stop gathering keys from an RCS file. */
+
+static void
+rcsbuf_close (rcsbuf)
+ struct rcsbuffer *rcsbuf;
+{
+ if (! rcsbuf_inuse)
+ error (1, 0, "rcsbuf_close: internal error");
+ rcsbuf_inuse = 0;
+}
+
+/* Read a key/value pair from an RCS file. This sets *KEYP to point
+ to the key, and *VALUEP to point to the value. A missing or empty
+ value is indicated by setting *VALUEP to NULL.
+
+ This function returns 1 on success, or 0 on EOF. If there is an
+ error reading the file, or an EOF in an unexpected location, it
+ gives a fatal error.
+
+ This sets *KEYP and *VALUEP to point to storage managed by
+ rcsbuf_getkey. Moreover, *VALUEP has not been massaged from the
+ RCS format: it may contain embedded whitespace and embedded '@'
+ characters. Call rcsbuf_valcopy or rcsbuf_valpolish to do
+ appropriate massaging. */
+
+static int
+rcsbuf_getkey (rcsbuf, keyp, valp)
+ struct rcsbuffer *rcsbuf;
char **keyp;
char **valp;
- size_t *lenp;
{
- char *cur, *max;
- int c;
- int just_string;
+ register const char * const my_spacetab = spacetab;
+ register char *ptr, *ptrend;
+ char c;
- if (lenp != NULL)
- *lenp = 0;
+#define my_whitespace(c) (my_spacetab[(unsigned char)c] != 0)
- /* skip leading whitespace */
- do
+ rcsbuf->vlen = 0;
+ rcsbuf->at_string = 0;
+ rcsbuf->embedded_at = 0;
+
+ ptr = rcsbuf->ptr;
+ ptrend = rcsbuf->ptrend;
+
+ /* Sanity check. */
+ if (ptr < rcsbuf_buffer || ptr > rcsbuf_buffer + rcsbuf_buffer_size)
+ abort ();
+
+ /* If the pointer is more than RCSBUF_BUFSIZE bytes into the
+ buffer, move back to the start of the buffer. This keeps the
+ buffer from growing indefinitely. */
+ if (ptr - rcsbuf_buffer >= RCSBUF_BUFSIZE)
{
- c = getc (fp);
- if (c == EOF)
- {
- *keyp = (char *) NULL;
- *valp = (char *) NULL;
- return (-1);
- }
- } while (whitespace (c));
+ int len;
+
+ len = ptrend - ptr;
- /* fill in key */
- cur = key;
- max = key + keysize;
- while (!whitespace (c) && c != ';')
+ /* Sanity check: we don't read more than RCSBUF_BUFSIZE bytes
+ at a time, so we can't have more bytes than that past PTR. */
+ if (len > RCSBUF_BUFSIZE)
+ abort ();
+
+ /* Update the POS field, which holds the file offset of the
+ first byte in the RCSBUF_BUFFER buffer. */
+ rcsbuf->pos += ptr - rcsbuf_buffer;
+
+ memcpy (rcsbuf_buffer, ptr, len);
+ ptr = rcsbuf_buffer;
+ ptrend = ptr + len;
+ rcsbuf->ptrend = ptrend;
+ }
+
+ /* Skip leading whitespace. */
+
+ while (1)
{
- if (cur >= max)
+ if (ptr >= ptrend)
{
- size_t curoff = cur - key;
- expand_string (&key, &keysize, keysize + 1);
- cur = key + curoff;
- max = key + keysize;
+ ptr = rcsbuf_fill (rcsbuf, ptr, (char **) NULL, (char **) NULL);
+ if (ptr == NULL)
+ return 0;
+ ptrend = rcsbuf->ptrend;
}
- *cur++ = c;
- c = getc (fp);
- if (c == EOF)
+ c = *ptr;
+ if (! my_whitespace (c))
+ break;
+
+ ++ptr;
+ }
+
+ /* We've found the start of the key. */
+
+ *keyp = ptr;
+
+ if (c != ';')
+ {
+ while (1)
{
- *keyp = (char *) NULL;
- *valp = (char *) NULL;
- return (-1);
+ ++ptr;
+ if (ptr >= ptrend)
+ {
+ ptr = rcsbuf_fill (rcsbuf, ptr, keyp, (char **) NULL);
+ if (ptr == NULL)
+ error (1, 0, "EOF in key in RCS file %s",
+ rcsbuf->filename);
+ ptrend = rcsbuf->ptrend;
+ }
+ c = *ptr;
+ if (c == ';' || my_whitespace (c))
+ break;
}
}
- if (cur >= max)
+
+ /* Here *KEYP points to the key in the buffer, C is the character
+ we found at the of the key, and PTR points to the location in
+ the buffer where we found C. We must set *PTR to \0 in order
+ to terminate the key. If the key ended with ';', then there is
+ no value. */
+
+ *ptr = '\0';
+ ++ptr;
+
+ if (c == ';')
{
- size_t curoff = cur - key;
- expand_string (&key, &keysize, keysize + 1);
- cur = key + curoff;
- max = key + keysize;
+ *valp = NULL;
+ rcsbuf->ptr = ptr;
+ return 1;
}
- *cur = '\0';
- /* skip whitespace between key and val */
- while (whitespace (c))
+ /* C must be whitespace. Skip whitespace between the key and the
+ value. If we find ';' now, there is no value. */
+
+ while (1)
{
- c = getc (fp);
- if (c == EOF)
+ if (ptr >= ptrend)
{
- *keyp = (char *) NULL;
- *valp = (char *) NULL;
- return (-1);
+ ptr = rcsbuf_fill (rcsbuf, ptr, keyp, (char **) NULL);
+ if (ptr == NULL)
+ error (1, 0, "EOF while looking for value in RCS file %s",
+ rcsbuf->filename);
+ ptrend = rcsbuf->ptrend;
}
- }
+ c = *ptr;
+ if (c == ';')
+ {
+ *valp = NULL;
+ rcsbuf->ptr = ptr + 1;
+ return 1;
+ }
+ if (! my_whitespace (c))
+ break;
+ ++ptr;
+ }
- /* if we ended key with a semicolon, there is no value */
- if (c == ';')
+ /* Now PTR points to the start of the value, and C is the first
+ character of the value. */
+
+ if (c != '@')
+ *valp = ptr;
+ else
{
- *keyp = key;
- *valp = (char *) NULL;
- return (0);
- }
+ char *pat;
+ size_t vlen;
- /* otherwise, there might be a value, so fill it in */
- cur = value;
- max = value + valsize;
+ /* Optimize the common case of a value composed of a single
+ '@' string. */
- just_string = (strcmp (key, RCSDESC) == 0
- || strcmp (key, "text") == 0
- || strcmp (key, "log") == 0);
+ rcsbuf->at_string = 1;
- /* process the value */
- for (;;)
- {
- /* handle RCS "strings" */
- if (c == '@')
+ ++ptr;
+
+ *valp = ptr;
+
+ while (1)
{
- for (;;)
+ while ((pat = memchr (ptr, '@', ptrend - ptr)) == NULL)
{
- c = getc (fp);
- if (c == EOF)
- {
- *keyp = (char *) NULL;
- *valp = (char *) NULL;
- return (-1);
- }
+ /* Note that we pass PTREND as the PTR value to
+ rcsbuf_fill, so that we will wind up setting PTR to
+ the location corresponding to the old PTREND, so
+ that we don't search the same bytes again. */
+ ptr = rcsbuf_fill (rcsbuf, ptrend, keyp, valp);
+ if (ptr == NULL)
+ error (1, 0,
+ "EOF while looking for end of string in RCS file %s",
+ rcsbuf->filename);
+ ptrend = rcsbuf->ptrend;
+ }
- if (c == '@')
+ /* Handle the special case of an '@' right at the end of
+ the known bytes. */
+ if (pat + 1 >= ptrend)
+ {
+ /* Note that we pass PAT, not PTR, here. */
+ pat = rcsbuf_fill (rcsbuf, pat, keyp, valp);
+ if (pat == NULL)
{
- c = getc (fp);
- if (c == EOF)
- {
- *keyp = (char *) NULL;
- *valp = (char *) NULL;
- return (-1);
- }
-
- if (c != '@')
- break;
- }
+ /* EOF here is OK; it just means that the last
+ character of the file was an '@' terminating a
+ value for a key type which does not require a
+ trailing ';'. */
+ pat = rcsbuf->ptrend - 1;
- if (cur >= max)
- {
- size_t curoff = cur - value;
- expand_string (&value, &valsize, valsize + 1);
- cur = value + curoff;
- max = value + valsize;
}
- *cur++ = c;
+ ptrend = rcsbuf->ptrend;
+
+ /* Note that the value of PTR is bogus here. This is
+ OK, because we don't use it. */
}
+
+ if (pat + 1 >= ptrend || pat[1] != '@')
+ break;
+
+ /* We found an '@' pair in the string. Keep looking. */
+ ++rcsbuf->embedded_at;
+ ptr = pat + 2;
}
- /* The syntax for some key-value pairs is different; they
- don't end with a semicolon. */
- if (just_string)
- break;
+ /* Here PAT points to the final '@' in the string. */
+
+ *pat = '\0';
+
+ vlen = pat - *valp;
+ if (vlen == 0)
+ *valp = NULL;
+ rcsbuf->vlen = vlen;
+
+ ptr = pat + 1;
+ }
+
+ /* Certain keywords only have a '@' string. If there is no '@'
+ string, then the old getrcskey function assumed that they had
+ no value, and we do the same. */
- /* compress whitespace down to a single space */
- if (whitespace (c))
+ {
+ char *k;
+
+ k = *keyp;
+ if (STREQ (k, RCSDESC)
+ || STREQ (k, "text")
+ || STREQ (k, "log"))
{
- do {
- c = getc (fp);
- if (c == EOF)
- {
- *keyp = (char *) NULL;
- *valp = (char *) NULL;
- return (-1);
- }
- } while (whitespace (c));
+ if (c != '@')
+ *valp = NULL;
+ rcsbuf->ptr = ptr;
+ return 1;
+ }
+ }
- /* Do not include any trailing whitespace in the value. */
- if (c != ';')
+ /* If we've already gathered a '@' string, try to skip whitespace
+ and find a ';'. */
+ if (c == '@')
+ {
+ while (1)
+ {
+ char n;
+
+ if (ptr >= ptrend)
{
- if (cur >= max)
- {
- size_t curoff = cur - value;
- expand_string (&value, &valsize, valsize + 1);
- cur = value + curoff;
- max = value + valsize;
- }
- *cur++ = ' ';
+ ptr = rcsbuf_fill (rcsbuf, ptr, keyp, valp);
+ if (ptr == NULL)
+ error (1, 0, "EOF in value in RCS file %s",
+ rcsbuf->filename);
+ ptrend = rcsbuf->ptrend;
+ }
+ n = *ptr;
+ if (n == ';')
+ {
+ /* We're done. We already set everything up for this
+ case above. */
+ rcsbuf->ptr = ptr + 1;
+ return 1;
}
+ if (! my_whitespace (n))
+ break;
+ ++ptr;
}
- /* if we got a semi-colon we are done with the entire value */
- if (c == ';')
- break;
+ /* The value extends past the '@' string. We need to undo the
+ closing of the '@' done in the default case above. This
+ case never happens in a plain RCS file, but it can happen
+ if user defined phrases are used. */
+ if (rcsbuf->vlen != 0)
+ (*valp)[rcsbuf->vlen] = ' ';
+ else
+ *valp = ptr;
+ }
- if (cur >= max)
+ /* Here we have a value which is not a simple '@' string. We need
+ to gather up everything until the next ';', including any '@'
+ strings. *VALP points to the start of the value. If
+ RCSBUF->VLEN is not zero, then we have already read an '@'
+ string, and PTR points to the data following the '@' string.
+ Otherwise, PTR points to the start of the value. */
+
+ while (1)
+ {
+ char *start, *psemi, *pat;
+
+ /* Find the ';' which must end the value. */
+ start = ptr;
+ while ((psemi = memchr (ptr, ';', ptrend - ptr)) == NULL)
{
- size_t curoff = cur - value;
- expand_string (&value, &valsize, valsize + 1);
- cur = value + curoff;
- max = value + valsize;
+ int slen;
+
+ /* Note that we pass PTREND as the PTR value to
+ rcsbuf_fill, so that we will wind up setting PTR to the
+ location corresponding to the old PTREND, so that we
+ don't search the same bytes again. */
+ slen = start - *valp;
+ ptr = rcsbuf_fill (rcsbuf, ptrend, keyp, valp);
+ if (ptr == NULL)
+ error (1, 0, "EOF in value in RCS file %s", rcsbuf->filename);
+ start = *valp + slen;
+ ptrend = rcsbuf->ptrend;
}
- *cur++ = c;
- c = getc (fp);
- if (c == EOF)
+ /* See if there are any '@' strings in the value. */
+ pat = memchr (start, '@', psemi - start);
+
+ if (pat == NULL)
{
- *keyp = (char *) NULL;
- *valp = (char *) NULL;
- return (-1);
+ size_t vlen;
+
+ /* We're done with the value. Trim any trailing
+ whitespace. */
+
+ rcsbuf->ptr = psemi + 1;
+
+ start = *valp;
+ while (psemi > start && my_whitespace (psemi[-1]))
+ --psemi;
+ *psemi = '\0';
+
+ vlen = psemi - start;
+ if (vlen == 0)
+ *valp = NULL;
+ rcsbuf->vlen = vlen;
+
+ return 1;
}
+
+ /* We found an '@' string in the value. We set
+ RCSBUF->AT_STRING, which means that we won't be able to
+ compress whitespace correctly for this type of value.
+ Since this type of value never arises in a normal RCS file,
+ this should not be a big deal. It means that if anybody
+ adds a phrase which can have both an '@' string and regular
+ text, they will have to handle whitespace compression
+ themselves. */
+
+ rcsbuf->at_string = 1;
+
+ *pat = ' ';
+
+ ptr = pat + 1;
+
+ while (1)
+ {
+ while ((pat = memchr (ptr, '@', ptrend - ptr)) == NULL)
+ {
+ /* Note that we pass PTREND as the PTR value to
+ rcsbuff_fill, so that we will wind up setting PTR
+ to the location corresponding to the old PTREND, so
+ that we don't search the same bytes again. */
+ ptr = rcsbuf_fill (rcsbuf, ptrend, keyp, valp);
+ if (ptr == NULL)
+ error (1, 0,
+ "EOF while looking for end of string in RCS file %s",
+ rcsbuf->filename);
+ ptrend = rcsbuf->ptrend;
+ }
+
+ /* Handle the special case of an '@' right at the end of
+ the known bytes. */
+ if (pat + 1 >= ptrend)
+ {
+ ptr = rcsbuf_fill (rcsbuf, ptr, keyp, valp);
+ if (ptr == NULL)
+ error (1, 0, "EOF in value in RCS file %s",
+ rcsbuf->filename);
+ ptrend = rcsbuf->ptrend;
+ }
+
+ if (pat[1] != '@')
+ break;
+
+ /* We found an '@' pair in the string. Keep looking. */
+ ++rcsbuf->embedded_at;
+ ptr = pat + 2;
+ }
+
+ /* Here PAT points to the final '@' in the string. */
+
+ *pat = ' ';
+
+ ptr = pat + 1;
}
- /* terminate the string */
- if (cur >= max)
+#undef my_whitespace
+}
+
+/* Read an RCS revision number from an RCS file. This sets *REVP to
+ point to the revision number; it will point to space that is
+ managed by the rcsbuf functions, and is only good until the next
+ call to rcsbuf_getkey or rcsbuf_getrevnum.
+
+ This function returns 1 on success, or 0 on EOF. If there is an
+ error reading the file, or an EOF in an unexpected location, it
+ gives a fatal error. */
+
+static int
+rcsbuf_getrevnum (rcsbuf, revp)
+ struct rcsbuffer *rcsbuf;
+ char **revp;
+{
+ char *ptr, *ptrend;
+ char c;
+
+ ptr = rcsbuf->ptr;
+ ptrend = rcsbuf->ptrend;
+
+ *revp = NULL;
+
+ /* Skip leading whitespace. */
+
+ while (1)
{
- size_t curoff = cur - value;
- expand_string (&value, &valsize, valsize + 1);
- cur = value + curoff;
- max = value + valsize;
+ if (ptr >= ptrend)
+ {
+ ptr = rcsbuf_fill (rcsbuf, ptr, (char **) NULL, (char **) NULL);
+ if (ptr == NULL)
+ return 0;
+ ptrend = rcsbuf->ptrend;
+ }
+
+ c = *ptr;
+ if (! whitespace (c))
+ break;
+
+ ++ptr;
}
- *cur = '\0';
- /* if the string is empty, make it null */
- if (value && cur != value)
+ if (! isdigit (c) && c != '.')
+ error (1, 0,
+ "unexpected `%c' reading revision number in RCS file %s",
+ c, rcsbuf->filename);
+
+ *revp = ptr;
+
+ do
+ {
+ ++ptr;
+ if (ptr >= ptrend)
+ {
+ ptr = rcsbuf_fill (rcsbuf, ptr, revp, (char **) NULL);
+ if (ptr == NULL)
+ error (1, 0,
+ "unexpected EOF reading revision number in RCS file %s",
+ rcsbuf->filename);
+ ptrend = rcsbuf->ptrend;
+ }
+
+ c = *ptr;
+ }
+ while (isdigit (c) || c == '.');
+
+ if (! whitespace (c))
+ error (1, 0, "unexpected `%c' reading revision number in RCS file %s",
+ c, rcsbuf->filename);
+
+ *ptr = '\0';
+
+ rcsbuf->ptr = ptr + 1;
+
+ return 1;
+}
+
+/* Fill RCSBUF_BUFFER with bytes from the file associated with RCSBUF,
+ updating PTR and the PTREND field. If KEYP and *KEYP are not NULL,
+ then *KEYP points into the buffer, and must be adjusted if the
+ buffer is changed. Likewise for VALP. Returns the new value of
+ PTR, or NULL on error. */
+
+static char *
+rcsbuf_fill (rcsbuf, ptr, keyp, valp)
+ struct rcsbuffer *rcsbuf;
+ char *ptr;
+ char **keyp;
+ char **valp;
+{
+ int got;
+
+ if (rcsbuf->ptrend - rcsbuf_buffer + RCSBUF_BUFSIZE > rcsbuf_buffer_size)
+ {
+ int poff, peoff, koff, voff;
+
+ poff = ptr - rcsbuf_buffer;
+ peoff = rcsbuf->ptrend - rcsbuf_buffer;
+ if (keyp != NULL && *keyp != NULL)
+ koff = *keyp - rcsbuf_buffer;
+ if (valp != NULL && *valp != NULL)
+ voff = *valp - rcsbuf_buffer;
+
+ expand_string (&rcsbuf_buffer, &rcsbuf_buffer_size,
+ rcsbuf_buffer_size + RCSBUF_BUFSIZE);
+
+ ptr = rcsbuf_buffer + poff;
+ rcsbuf->ptrend = rcsbuf_buffer + peoff;
+ if (keyp != NULL && *keyp != NULL)
+ *keyp = rcsbuf_buffer + koff;
+ if (valp != NULL && *valp != NULL)
+ *valp = rcsbuf_buffer + voff;
+ }
+
+ got = fread (rcsbuf->ptrend, 1, RCSBUF_BUFSIZE, rcsbuf->fp);
+ if (got == 0)
+ {
+ if (ferror (rcsbuf->fp))
+ error (1, errno, "cannot read %s", rcsbuf->filename);
+ return NULL;
+ }
+
+ rcsbuf->ptrend += got;
+
+ return ptr;
+}
+
+/* Copy the value VAL returned by rcsbuf_getkey into a memory buffer,
+ returning the memory buffer. Polish the value like
+ rcsbuf_valpolish, q.v. */
+
+static char *
+rcsbuf_valcopy (rcsbuf, val, polish, lenp)
+ struct rcsbuffer *rcsbuf;
+ char *val;
+ int polish;
+ size_t *lenp;
+{
+ size_t vlen;
+ int embedded_at;
+ char *ret;
+
+ if (val == NULL)
{
- *valp = value;
if (lenp != NULL)
- *lenp = cur - value;
+ *lenp = 0;
+ return NULL;
}
- else
- *valp = NULL;
- *keyp = key;
- return (0);
+
+ vlen = rcsbuf->vlen;
+ embedded_at = rcsbuf->embedded_at;
+
+ ret = xmalloc (vlen - embedded_at + 1);
+
+ if (rcsbuf->at_string ? embedded_at == 0 : ! polish)
+ {
+ /* No special action to take. */
+ memcpy (ret, val, vlen + 1);
+ if (lenp != NULL)
+ *lenp = vlen;
+ return ret;
+ }
+
+ rcsbuf_valpolish_internal (rcsbuf, ret, val, lenp);
+ return ret;
}
-/* Read an RCS revision number from FP. Put a pointer to it in *REVP;
- it points to space managed by getrcsrev which is only good until
- the next call to getrcskey or getrcsrev. */
+/* Polish the value VAL returned by rcsbuf_getkey. The POLISH
+ parameter is non-zero if multiple embedded whitespace characters
+ should be compressed into a single whitespace character. Note that
+ leading and trailing whitespace was already removed by
+ rcsbuf_getkey. Within an '@' string, pairs of '@' characters are
+ compressed into a single '@' character regardless of the value of
+ POLISH. If LENP is not NULL, set *LENP to the length of the value. */
+
static void
-getrcsrev (fp, revp)
- FILE *fp;
- char **revp;
+rcsbuf_valpolish (rcsbuf, val, polish, lenp)
+ struct rcsbuffer *rcsbuf;
+ char *val;
+ int polish;
+ size_t *lenp;
{
- char *cur;
- char *max;
- int c;
+ if (val == NULL)
+ {
+ if (lenp != NULL)
+ *lenp= 0;
+ return;
+ }
- do {
- c = getc (fp);
- if (c == EOF)
+ if (rcsbuf->at_string ? rcsbuf->embedded_at == 0 : ! polish)
+ {
+ /* No special action to take. */
+ if (lenp != NULL)
+ *lenp = rcsbuf->vlen;
+ return;
+ }
+
+ rcsbuf_valpolish_internal (rcsbuf, val, val, lenp);
+}
+
+/* Internal polishing routine, called from rcsbuf_valcopy and
+ rcsbuf_valpolish. */
+
+static void
+rcsbuf_valpolish_internal (rcsbuf, to, from, lenp)
+ struct rcsbuffer *rcsbuf;
+ char *to;
+ const char *from;
+ size_t *lenp;
+{
+ size_t len;
+
+ len = rcsbuf->vlen;
+
+ if (! rcsbuf->at_string)
+ {
+ char *orig_to;
+ size_t clen;
+
+ orig_to = to;
+
+ for (clen = len; clen > 0; ++from, --clen)
{
- /* FIXME: should be including filename in error message. */
- if (ferror (fp))
- error (1, errno, "cannot read rcs file");
- else
- error (1, 0, "unexpected end of file reading rcs file");
+ char c;
+
+ c = *from;
+ if (whitespace (c))
+ {
+ /* Note that we know that clen can not drop to zero
+ while we have whitespace, because we know there is
+ no trailing whitespace. */
+ while (whitespace (from[1]))
+ {
+ ++from;
+ --clen;
+ }
+ c = ' ';
+ }
+ *to++ = c;
}
- } while (whitespace (c));
- if (!(isdigit (c) || c == '.'))
- /* FIXME: should be including filename in error message. */
- error (1, 0, "error reading rcs file; revision number expected");
+ *to = '\0';
- cur = key;
- max = key + keysize;
- while (isdigit (c) || c == '.')
+ if (lenp != NULL)
+ *lenp = to - orig_to;
+ }
+ else
{
- if (cur >= max)
+ const char *orig_from;
+ char *orig_to;
+ int embedded_at;
+ size_t clen;
+
+ orig_from = from;
+ orig_to = to;
+
+ embedded_at = rcsbuf->embedded_at;
+
+ if (lenp != NULL)
+ *lenp = len - embedded_at;
+
+ for (clen = len; clen > 0; ++from, --clen)
{
- size_t curoff = cur - key;
- expand_string (&key, &keysize, keysize + 1);
- cur = key + curoff;
- max = key + keysize;
+ char c;
+
+ c = *from;
+ *to++ = c;
+ if (c == '@')
+ {
+ ++from;
+
+ /* Sanity check. */
+ if (*from != '@' || clen == 0)
+ abort ();
+
+ --clen;
+
+ --embedded_at;
+ if (embedded_at == 0)
+ {
+ /* We've found all the embedded '@' characters.
+ We can just memcpy the rest of the buffer after
+ this '@' character. */
+ if (orig_to != orig_from)
+ memcpy (to, from + 1, clen - 1);
+ else
+ memmove (to, from + 1, clen - 1);
+ from += clen;
+ to += clen - 1;
+ break;
+ }
+ }
}
- *cur++ = c;
- c = getc (fp);
- if (c == EOF)
+ /* Sanity check. */
+ if (from != orig_from + len
+ || to != orig_to + (len - rcsbuf->embedded_at))
{
- /* FIXME: should be including filename in error message. */
- if (ferror (fp))
- error (1, errno, "cannot read rcs file");
- else
- error (1, 0, "unexpected end of file reading rcs file");
+ abort ();
}
- }
- if (cur >= max)
- {
- size_t curoff = cur - key;
- expand_string (&key, &keysize, keysize + 1);
- cur = key + curoff;
- max = key + keysize;
+ *to = '\0';
}
- *cur = '\0';
- *revp = key;
}
-/* Like getrcsrev, but don't die on error. Return the last character
- read (last call to getc, which may be EOF). TODO: implement getrcsrev
- in terms of this function. */
-static int
-getrevnum (fp, revp)
- FILE *fp;
- char **revp;
+/* Return the current position of an rcsbuf. */
+
+static unsigned long
+rcsbuf_ftell (rcsbuf)
+ struct rcsbuffer *rcsbuf;
{
- char *cur;
- char *max;
- int c;
+ return rcsbuf->pos + (rcsbuf->ptr - rcsbuf_buffer);
+}
- *revp = NULL;
- do {
- c = getc (fp);
- if (c == EOF)
- return c;
- } while (whitespace (c));
+/* Return a pointer to any data buffered for RCSBUF, along with the
+ length. */
+
+static void
+rcsbuf_get_buffered (rcsbuf, datap, lenp)
+ struct rcsbuffer *rcsbuf;
+ char **datap;
+ size_t *lenp;
+{
+ *datap = rcsbuf->ptr;
+ *lenp = rcsbuf->ptrend - rcsbuf->ptr;
+}
- if (!(isdigit (c) || c == '.'))
- return c;
+/* CVS optimizes by quickly reading some header information from a
+ file. If it decides it needs to do more with the file, it reopens
+ it. We speed that up here by maintaining a cache of a single open
+ file, to save the time it takes to reopen the file in the common
+ case. */
- cur = key;
- max = key + keysize;
- while (isdigit (c) || c == '.')
+static RCSNode *cached_rcs;
+static struct rcsbuffer cached_rcsbuf;
+
+/* Cache RCS and RCSBUF. This takes responsibility for closing
+ RCSBUF->FP. */
+
+static void
+rcsbuf_cache (rcs, rcsbuf)
+ RCSNode *rcs;
+ struct rcsbuffer *rcsbuf;
+{
+ if (cached_rcs != NULL)
+ rcsbuf_cache_close ();
+ cached_rcs = rcs;
+ ++rcs->refcount;
+ cached_rcsbuf = *rcsbuf;
+}
+
+/* If there is anything in the cache, close it. */
+
+static void
+rcsbuf_cache_close ()
+{
+ if (cached_rcs != NULL)
{
- if (cur >= max)
+ if (fclose (cached_rcsbuf.fp) != 0)
+ error (0, errno, "cannot close %s", cached_rcsbuf.filename);
+ rcsbuf_close (&cached_rcsbuf);
+ freercsnode (&cached_rcs);
+ cached_rcs = NULL;
+ }
+}
+
+/* Open an rcsbuffer for RCS, getting it from the cache if possible.
+ Set *FPP to the file, and *RCSBUFP to the rcsbuf. The file should
+ be put at position POS. */
+
+static void
+rcsbuf_cache_open (rcs, pos, pfp, prcsbuf)
+ RCSNode *rcs;
+ long pos;
+ FILE **pfp;
+ struct rcsbuffer *prcsbuf;
+{
+ if (cached_rcs == rcs)
+ {
+ if (rcsbuf_ftell (&cached_rcsbuf) != pos)
{
- size_t curoff = cur - key;
- expand_string (&key, &keysize, keysize + 1);
- cur = key + curoff;
- max = key + keysize;
+ if (fseek (cached_rcsbuf.fp, pos, SEEK_SET) != 0)
+ error (1, 0, "cannot fseek RCS file %s",
+ cached_rcsbuf.filename);
+ cached_rcsbuf.ptr = rcsbuf_buffer;
+ cached_rcsbuf.ptrend = rcsbuf_buffer;
+ cached_rcsbuf.pos = pos;
}
- *cur = c;
+ *pfp = cached_rcsbuf.fp;
- c = getc (fp);
- if (c == EOF)
- break;
- cur++;
- }
+ /* When RCS_parse opens a file using fopen_case, it frees the
+ filename which we cached in CACHED_RCSBUF and stores a new
+ file name in RCS->PATH. We avoid problems here by always
+ copying the filename over. FIXME: This is hackish. */
+ cached_rcsbuf.filename = rcs->path;
+
+ *prcsbuf = cached_rcsbuf;
+
+ cached_rcs = NULL;
- if (cur >= max)
+ /* Removing RCS from the cache removes a reference to it. */
+ --rcs->refcount;
+ if (rcs->refcount <= 0)
+ error (1, 0, "rcsbuf_cache_open: internal error");
+ }
+ else
{
- size_t curoff = cur - key;
- expand_string (&key, &keysize, keysize + 1);
- cur = key + curoff;
- max = key + keysize;
+ if (cached_rcs != NULL)
+ rcsbuf_cache_close ();
+
+ *pfp = CVS_FOPEN (rcs->path, FOPEN_BINARY_READ);
+ if (*pfp == NULL)
+ error (1, 0, "unable to reopen `%s'", rcs->path);
+ if (pos != 0)
+ {
+ if (fseek (*pfp, pos, SEEK_SET) != 0)
+ error (1, 0, "cannot fseek RCS file %s", rcs->path);
+ }
+ rcsbuf_open (prcsbuf, *pfp, rcs->path, pos);
}
- *cur = '\0';
- *revp = key;
- return c;
}
+
/*
* process the symbols list of the rcs file
*/
@@ -1312,10 +1902,10 @@ RCS_gettag (rcs, symtag, force_tag_match, simple_tag)
/* XXX this is probably not necessary, --jtc */
if (rcs->flags & PARTIAL)
- RCS_reparsercsfile (rcs, NULL);
+ RCS_reparsercsfile (rcs, (FILE **) NULL, (struct rcsbuffer *) NULL);
/* If tag is "HEAD", special case to get head RCS revision */
- if (tag && (strcmp (tag, TAG_HEAD) == 0 || *tag == '\0'))
+ if (tag && (STREQ (tag, TAG_HEAD) || *tag == '\0'))
#if 0 /* This #if 0 is only in the Cygnus code. Why? Death support? */
if (force_tag_match && (rcs->flags & VALID) && (rcs->flags & INATTIC))
return ((char *) NULL); /* head request for removed file */
@@ -1501,7 +2091,7 @@ checkmagic_proc (p, closure)
Node *p;
void *closure;
{
- if (strcmp (check_rev, p->data) == 0)
+ if (STREQ (check_rev, p->data))
return (1);
else
return (0);
@@ -1655,7 +2245,7 @@ RCS_getbranch (rcs, tag, force_tag_match)
assert (rcs != NULL);
if (rcs->flags & PARTIAL)
- RCS_reparsercsfile (rcs, NULL);
+ RCS_reparsercsfile (rcs, (FILE **) NULL, (struct rcsbuffer *) NULL);
/* find out if the tag contains a dot, or is on the trunk */
cp = strrchr (tag, '.');
@@ -1873,7 +2463,7 @@ RCS_getdate (rcs, date, force_tag_match)
assert (rcs != NULL);
if (rcs->flags & PARTIAL)
- RCS_reparsercsfile (rcs, NULL);
+ RCS_reparsercsfile (rcs, (FILE **) NULL, (struct rcsbuffer *) NULL);
/* if the head is on a branch, try the branch first */
if (rcs->branch != NULL)
@@ -1911,7 +2501,7 @@ RCS_getdate (rcs, date, force_tag_match)
*/
/* if we found what we're looking for, and it's not 1.1 return it */
- if (cur_rev != NULL && strcmp (cur_rev, "1.1") != 0)
+ if (cur_rev != NULL && ! STREQ (cur_rev, "1.1"))
return (xstrdup (cur_rev));
/* look on the vendor branch */
@@ -1960,7 +2550,7 @@ RCS_getdatebranch (rcs, date, branch)
assert (rcs != NULL);
if (rcs->flags & PARTIAL)
- RCS_reparsercsfile (rcs, NULL);
+ RCS_reparsercsfile (rcs, (FILE **) NULL, (struct rcsbuffer *) NULL);
p = findnode (rcs->versions, xrev);
free (xrev);
@@ -2057,7 +2647,7 @@ RCS_getrevtime (rcs, rev, date, fudge)
assert (rcs != NULL);
if (rcs->flags & PARTIAL)
- RCS_reparsercsfile (rcs, NULL);
+ RCS_reparsercsfile (rcs, (FILE **) NULL, (struct rcsbuffer *) NULL);
/* look up the revision */
p = findnode (rcs->versions, rev);
@@ -2109,7 +2699,7 @@ RCS_getlocks (rcs)
assert(rcs != NULL);
if (rcs->flags & PARTIAL)
- RCS_reparsercsfile (rcs, NULL);
+ RCS_reparsercsfile (rcs, (FILE **) NULL, (struct rcsbuffer *) NULL);
if (rcs->locks_data) {
rcs->locks = getlist ();
@@ -2128,7 +2718,7 @@ RCS_symbols(rcs)
assert(rcs != NULL);
if (rcs->flags & PARTIAL)
- RCS_reparsercsfile (rcs, NULL);
+ RCS_reparsercsfile (rcs, (FILE **) NULL, (struct rcsbuffer *) NULL);
if (rcs->symbols_data) {
rcs->symbols = getlist ();
@@ -2150,7 +2740,7 @@ translate_symtag (rcs, tag)
const char *tag;
{
if (rcs->flags & PARTIAL)
- RCS_reparsercsfile (rcs, NULL);
+ RCS_reparsercsfile (rcs, (FILE **) NULL, (struct rcsbuffer *) NULL);
if (rcs->symbols != NULL)
{
@@ -2234,7 +2824,7 @@ RCS_check_kflag (arg)
{
for (cpp = kflags; *cpp != NULL; cpp++)
{
- if (strcmp (arg, *cpp) == 0)
+ if (STREQ (arg, *cpp))
break;
}
}
@@ -2292,7 +2882,7 @@ RCS_isdead (rcs, tag)
RCSVers *version;
if (rcs->flags & PARTIAL)
- RCS_reparsercsfile (rcs, NULL);
+ RCS_reparsercsfile (rcs, (FILE **) NULL, (struct rcsbuffer *) NULL);
p = findnode (rcs->versions, tag);
if (p == NULL)
@@ -2312,8 +2902,6 @@ RCS_getexpand (rcs)
RCSNode *rcs;
{
assert (rcs != NULL);
- if (rcs->flags & PARTIAL)
- RCS_reparsercsfile (rcs, NULL);
return rcs->expand;
}
@@ -2902,7 +3490,16 @@ expand_keywords (rcs, ver, name, log, loglen, expand, buf, len, retbuf, retlen)
OPTIONS is a string such as "-kb" or "-kv" for keyword expansion
options. It may be NULL to use the default expansion mode of the
- file, typically "-kkv". */
+ file, typically "-kkv".
+
+ On an error which prevented checking out the file, either print a
+ nonfatal error and return 1, or give a fatal error. On success,
+ return 0. */
+
+/* This function mimics the behavior of `rcs co' almost exactly. The
+ chief difference is in its support for preserving file ownership,
+ permissions, and special files across checkin and checkout -- see
+ comments in RCS_checkin for some issues about this. -twp */
int
RCS_checkout (rcs, workfile, rev, nametag, options, sout, pfn, callerdat)
@@ -2917,15 +3514,27 @@ RCS_checkout (rcs, workfile, rev, nametag, options, sout, pfn, callerdat)
{
int free_rev = 0;
enum kflag expand;
- FILE *fp;
+ FILE *fp, *ofp;
struct stat sb;
+ struct rcsbuffer rcsbuf;
char *key;
char *value;
size_t len;
int free_value = 0;
char *log = NULL;
size_t loglen;
- FILE *ofp;
+ Node *vp = NULL;
+#ifdef PRESERVE_PERMISSIONS_SUPPORT
+ uid_t rcs_owner;
+ gid_t rcs_group;
+ mode_t rcs_mode;
+ int change_rcs_owner = 0;
+ int change_rcs_group = 0;
+ int change_rcs_mode = 0;
+ int special_file = 0;
+ unsigned long devnum_long;
+ dev_t devnum = 0;
+#endif
if (trace)
{
@@ -2962,34 +3571,25 @@ RCS_checkout (rcs, workfile, rev, nametag, options, sout, pfn, callerdat)
free_rev = 1;
}
- if (rev == NULL || strcmp (rev, rcs->head) == 0)
+ if (rev == NULL || STREQ (rev, rcs->head))
{
int gothead;
/* We want the head revision. Try to read it directly. */
if (rcs->flags & PARTIAL)
- RCS_reparsercsfile (rcs, &fp);
+ RCS_reparsercsfile (rcs, &fp, &rcsbuf);
else
- {
- fp = CVS_FOPEN (rcs->path, FOPEN_BINARY_READ);
- if (fp == NULL)
- error (1, 0, "unable to reopen `%s'", rcs->path);
- if (fseek (fp, rcs->delta_pos, SEEK_SET) != 0)
- error (1, 0, "cannot fseek RCS file");
- }
+ rcsbuf_cache_open (rcs, rcs->delta_pos, &fp, &rcsbuf);
gothead = 0;
- getrcsrev (fp, &key);
- while (getrcskey (fp, &key, &value, &len) >= 0)
+ if (! rcsbuf_getrevnum (&rcsbuf, &key))
+ error (1, 0, "unexpected EOF reading %s", rcs->path);
+ while (rcsbuf_getkey (&rcsbuf, &key, &value))
{
- if (strcmp (key, "log") == 0)
- {
- log = xmalloc (len);
- memcpy (log, value, len);
- loglen = len;
- }
- if (strcmp (key, "text") == 0)
+ if (STREQ (key, "log"))
+ log = rcsbuf_valcopy (&rcsbuf, value, 0, &loglen);
+ else if (STREQ (key, "text"))
{
gothead = 1;
break;
@@ -3004,20 +3604,23 @@ RCS_checkout (rcs, workfile, rev, nametag, options, sout, pfn, callerdat)
return 1;
}
+ rcsbuf_valpolish (&rcsbuf, value, 0, &len);
+
if (fstat (fileno (fp), &sb) < 0)
error (1, errno, "cannot fstat %s", rcs->path);
- if (fclose (fp) < 0)
- error (0, errno, "cannot close %s", rcs->path);
+ rcsbuf_cache (rcs, &rcsbuf);
}
else
{
+ struct rcsbuffer *rcsbufp;
+
/* It isn't the head revision of the trunk. We'll need to
walk through the deltas. */
fp = NULL;
if (rcs->flags & PARTIAL)
- RCS_reparsercsfile (rcs, &fp);
+ RCS_reparsercsfile (rcs, &fp, &rcsbuf);
if (fp == NULL)
{
@@ -3025,14 +3628,17 @@ RCS_checkout (rcs, workfile, rev, nametag, options, sout, pfn, callerdat)
here too. Probably should change it thusly.... */
if (stat (rcs->path, &sb) < 0)
error (1, errno, "cannot stat %s", rcs->path);
+ rcsbufp = NULL;
}
else
{
if (fstat (fileno (fp), &sb) < 0)
error (1, errno, "cannot fstat %s", rcs->path);
+ rcsbufp = &rcsbuf;
}
- RCS_deltas (rcs, fp, rev, RCS_FETCH, &value, &len, &log, &loglen);
+ RCS_deltas (rcs, fp, rcsbufp, rev, RCS_FETCH, &value, &len,
+ &log, &loglen);
free_value = 1;
}
@@ -3055,7 +3661,7 @@ RCS_checkout (rcs, workfile, rev, nametag, options, sout, pfn, callerdat)
ouroptions = rcs->expand;
for (cpp = kflags; *cpp != NULL; cpp++)
- if (strcmp (*cpp, ouroptions) == 0)
+ if (STREQ (*cpp, ouroptions))
break;
if (*cpp != NULL)
@@ -3069,17 +3675,186 @@ RCS_checkout (rcs, workfile, rev, nametag, options, sout, pfn, callerdat)
}
}
- if (expand != KFLAG_O && expand != KFLAG_B)
+#ifdef PRESERVE_PERMISSIONS_SUPPORT
+ /* Handle special files and permissions, if that is desired. */
+ if (preserve_perms)
{
- Node *p;
- char *newvalue;
+ RCSVers *vers;
+ Node *info;
+ struct hardlink_info *hlinfo;
- p = findnode (rcs->versions, rev == NULL ? rcs->head : rev);
- if (p == NULL)
+ vp = findnode (rcs->versions, rev == NULL ? rcs->head : rev);
+ if (vp == NULL)
error (1, 0, "internal error: no revision information for %s",
rev == NULL ? rcs->head : rev);
+ vers = (RCSVers *) vp->data;
+
+ /* First we look for symlinks, which are simplest to handle. */
+ info = findnode (vers->other_delta, "symlink");
+ if (info != NULL)
+ {
+ char *dest;
+
+ if (pfn != NULL || (workfile == NULL && sout == RUN_TTY))
+ error (1, 0, "symbolic link %s:%s cannot be piped",
+ rcs->path, vers->version);
+ if (workfile == NULL)
+ dest = sout;
+ else
+ dest = workfile;
+
+ /* Remove `dest', just in case. It's okay to get ENOENT here,
+ since we just want the file not to be there. (TODO: decide
+ whether it should be considered an error for `dest' to exist
+ at this point. If so, the unlink call should be removed and
+ `symlink' should signal the error. -twp) */
+ if (unlink (dest) < 0 && existence_error (errno))
+ error (1, errno, "cannot remove %s", dest);
+ if (symlink (info->data, dest) < 0)
+ error (1, errno, "cannot create symbolic link from %s to %s",
+ dest, info->data);
+ if (free_value)
+ free (value);
+ if (free_rev)
+ free (rev);
+ return 0;
+ }
+
+ /* Next, we look at this file's hardlinks field, and see whether
+ it is linked to any other file that has been checked out.
+ If so, we don't do anything else -- just link it to that file.
- expand_keywords (rcs, (RCSVers *) p->data, nametag, log, loglen,
+ If we are checking out a file to a pipe or temporary storage,
+ none of this should matter. Hence the `workfile != NULL'
+ wrapper around the whole thing. -twp */
+
+ if (workfile != NULL)
+ {
+ info = findnode (vers->other_delta, "hardlinks");
+ if (info != NULL)
+ {
+ char *links = xstrdup (info->data);
+ char *working_dir = xgetwd();
+ char *p, *file = NULL;
+ Node *n, *uptodate_link;
+
+ /* For each file in the hardlinks field, check to see
+ if it exists, and if so, if it has been checked out
+ this iteration. */
+ uptodate_link = NULL;
+ for (p = strtok (links, " ");
+ p != NULL && uptodate_link == NULL;
+ p = strtok (NULL, " "))
+ {
+ file = (char *)
+ xmalloc (sizeof(char) *
+ (strlen(working_dir) + strlen(p) + 2));
+ sprintf (file, "%s/%s", working_dir, p);
+ n = lookup_file_by_inode (file);
+ if (n == NULL)
+ {
+ if (strcmp (p, workfile) != 0)
+ {
+ /* One of the files that WORKFILE should be
+ linked to is not even in the working directory.
+ The user should probably be warned. */
+ error (0, 0,
+ "warning: %s should be hardlinked to %s, but is missing",
+ p, workfile);
+ }
+ free (file);
+ continue;
+ }
+
+ /* hlinfo may be NULL if, for instance, a file is being
+ removed. */
+ hlinfo = (struct hardlink_info *) n->data;
+ if (hlinfo && hlinfo->checked_out)
+ uptodate_link = n;
+ free (file);
+ }
+ free (links);
+ free (working_dir);
+
+ /* If we've found a file that `workfile' is supposed to be
+ linked to, and it has been checked out since CVS was
+ invoked, then simply link workfile to that file.
+
+ If one of these conditions is not met, then we're
+ checking out workfile to a temp file or stdout, or
+ workfile is the first one in its hardlink group to be
+ checked out. Either way we must continue with a full
+ checkout. */
+
+ if (uptodate_link != NULL)
+ {
+ if (link (uptodate_link->key, workfile) < 0)
+ error (1, errno, "cannot link %s to %s",
+ workfile, uptodate_link->key);
+ hlinfo->checked_out = 1; /* probably unnecessary */
+ if (free_value)
+ free (value);
+ if (free_rev)
+ free (rev);
+ return 0;
+ }
+ }
+ }
+
+ info = findnode (vers->other_delta, "owner");
+ if (info != NULL)
+ {
+ change_rcs_owner = 1;
+ rcs_owner = (uid_t) strtoul (info->data, NULL, 10);
+ }
+ info = findnode (vers->other_delta, "group");
+ if (info != NULL)
+ {
+ change_rcs_group = 1;
+ rcs_group = (gid_t) strtoul (info->data, NULL, 10);
+ }
+ info = findnode (vers->other_delta, "permissions");
+ if (info != NULL)
+ {
+ change_rcs_mode = 1;
+ rcs_mode = (mode_t) strtoul (info->data, NULL, 8);
+ }
+ info = findnode (vers->other_delta, "special");
+ if (info != NULL)
+ {
+ /* If the size of `devtype' changes, fix the sscanf call also */
+ char devtype[16];
+
+ if (sscanf (info->data, "%16s %lu",
+ devtype, &devnum_long) < 2)
+ error (1, 0, "%s:%s has bad `special' newphrase %s",
+ workfile, vers->version, info->data);
+ devnum = devnum_long;
+ if (strcmp (devtype, "character") == 0)
+ special_file = S_IFCHR;
+ else if (strcmp (devtype, "block") == 0)
+ special_file = S_IFBLK;
+ else
+ error (0, 0, "%s is a special file of unsupported type `%s'",
+ workfile, info->data);
+ }
+ }
+#endif
+
+ if (expand != KFLAG_O && expand != KFLAG_B)
+ {
+ char *newvalue;
+
+ /* Don't fetch the delta node again if we already have it. */
+ if (vp == NULL)
+ {
+ vp = findnode (rcs->versions, rev == NULL ? rcs->head : rev);
+ if (vp == NULL)
+ error (1, 0, "internal error: no revision information for %s",
+ rev == NULL ? rcs->head : rev);
+ }
+
+ expand_keywords (rcs, (RCSVers *) vp->data, nametag, log, loglen,
expand, value, len, &newvalue, &len);
if (newvalue != value)
@@ -3099,19 +3874,55 @@ RCS_checkout (rcs, workfile, rev, nametag, options, sout, pfn, callerdat)
if (pfn != NULL)
{
+#ifdef PRESERVE_PERMISSIONS_SUPPORT
+ if (special_file)
+ error (1, 0, "special file %s cannot be piped to anything",
+ rcs->path);
+#endif
/* The PFN interface is very simple to implement right now, as
we always have the entire file in memory. */
if (len != 0)
pfn (callerdat, value, len);
}
+#ifdef PRESERVE_PERMISSIONS_SUPPORT
+ else if (special_file)
+ {
+ char *dest;
+
+ /* Can send either to WORKFILE or to SOUT, as long as SOUT is
+ not RUN_TTY. */
+ dest = workfile;
+ if (dest == NULL)
+ {
+ if (sout == RUN_TTY)
+ error (1, 0, "special file %s cannot be written to stdout",
+ rcs->path);
+ dest = sout;
+ }
+
+ /* Unlink `dest', just in case. It's okay if this provokes a
+ ENOENT error. */
+ if (unlink (dest) < 0 && existence_error (errno))
+ error (1, errno, "cannot remove %s", dest);
+ if (mknod (dest, special_file, devnum) < 0)
+ error (1, errno, "could not create special file %s",
+ dest);
+ }
+#endif
else
{
+ /* Not a special file: write to WORKFILE or SOUT. */
if (workfile == NULL)
{
if (sout == RUN_TTY)
ofp = stdout;
else
{
+ /* Symbolic links should be removed before replacement, so that
+ `fopen' doesn't follow the link and open the wrong file. */
+ if (islink (sout))
+ if (unlink_file (sout) < 0)
+ error (1, errno, "cannot remove %s", sout);
ofp = CVS_FOPEN (sout, expand == KFLAG_B ? "wb" : "w");
if (ofp == NULL)
error (1, errno, "cannot open %s", sout);
@@ -3119,6 +3930,11 @@ RCS_checkout (rcs, workfile, rev, nametag, options, sout, pfn, callerdat)
}
else
{
+ /* Output is supposed to go to WORKFILE, so we should open that
+ file. Symbolic links should be removed first (see above). */
+ if (islink (workfile))
+ if (unlink_file (workfile) < 0)
+ error (1, errno, "cannot remove %s", workfile);
ofp = CVS_FOPEN (workfile, expand == KFLAG_B ? "wb" : "w");
if (ofp == NULL)
error (1, errno, "cannot open %s", workfile);
@@ -3163,22 +3979,56 @@ RCS_checkout (rcs, workfile, rev, nametag, options, sout, pfn, callerdat)
nstep = nleft;
}
}
+ }
- if (workfile != NULL)
+ if (workfile != NULL)
+ {
+ int ret;
+
+#ifdef PRESERVE_PERMISSIONS_SUPPORT
+ if (!special_file && fclose (ofp) < 0)
+ error (1, errno, "cannot close %s", workfile);
+
+ if (change_rcs_owner || change_rcs_group)
{
- if (fclose (ofp) < 0)
- error (1, errno, "cannot close %s", workfile);
- if (chmod (workfile,
- sb.st_mode & ~(S_IWRITE | S_IWGRP | S_IWOTH)) < 0)
- error (0, errno, "cannot change mode of file %s",
+ if (chown (workfile, rcs_owner, rcs_group) < 0)
+ error (0, errno, "could not change file ownership on %s",
workfile);
}
- else if (sout != RUN_TTY)
+
+ ret = chmod (workfile,
+ change_rcs_mode
+ ? rcs_mode
+ : sb.st_mode & ~(S_IWRITE | S_IWGRP | S_IWOTH));
+#else
+ if (fclose (ofp) < 0)
+ error (1, errno, "cannot close %s", workfile);
+
+ ret = chmod (workfile,
+ sb.st_mode & ~(S_IWRITE | S_IWGRP | S_IWOTH));
+#endif
+ if (ret < 0)
{
- if (fclose (ofp) < 0)
- error (1, errno, "cannot close %s", sout);
+ error (0, errno, "cannot change mode of file %s",
+ workfile);
}
}
+ else if (sout != RUN_TTY)
+ {
+ if (
+#ifdef PRESERVE_PERMISSIONS_SUPPORT
+ !special_file &&
+#endif
+ fclose (ofp) < 0)
+ error (1, errno, "cannot close %s", sout);
+ }
+
+#ifdef PRESERVE_PERMISSIONS_SUPPORT
+ /* If we are in the business of preserving hardlinks, then
+ mark this file as having been checked out. */
+ if (preserve_perms && workfile != NULL)
+ update_hardlink_info (workfile);
+#endif
if (free_value)
free (value);
@@ -3227,7 +4077,7 @@ RCS_findlock_or_tip (rcs)
lock = NULL;
for (p = locklist->list->next; p != locklist->list; p = p->next)
{
- if (strcmp (p->data, user) == 0)
+ if (STREQ (p->data, user))
{
if (lock != NULL)
{
@@ -3333,8 +4183,11 @@ compare_truncated_revnums (r, s)
FIXME: isn't the max rev always the last one?
If so, we don't even need a loop. */
+static char *max_rev PROTO ((const RCSVers *));
+
static char *
-max_rev (const RCSVers *branchnode)
+max_rev (branchnode)
+ const RCSVers *branchnode;
{
Node *head;
Node *bp;
@@ -3469,22 +4322,27 @@ RCS_addbranch (rcs, branch)
return newrevnum;
}
-/* Check in to RCSFILE with revision REV (which must be greater than the
- largest revision) and message MESSAGE (which is checked for legality).
- If FLAGS & RCS_FLAGS_DEAD, check in a dead revision. If FLAGS &
- RCS_FLAGS_QUIET, tell ci to be quiet. If FLAGS & RCS_FLAGS_MODTIME,
- use the working file's modification time for the checkin time.
- WORKFILE is the working file to check in from, or NULL to use the usual
- RCS rules for deriving it from the RCSFILE.
+/* Check in to RCSFILE with revision REV (which must be greater than
+ the largest revision) and message MESSAGE (which is checked for
+ legality). If FLAGS & RCS_FLAGS_DEAD, check in a dead revision.
+ If FLAGS & RCS_FLAGS_QUIET, tell ci to be quiet. If FLAGS &
+ RCS_FLAGS_MODTIME, use the working file's modification time for the
+ checkin time. WORKFILE is the working file to check in from, or
+ NULL to use the usual RCS rules for deriving it from the RCSFILE.
+ If FLAGS & RCS_FLAGS_KEEPFILE, don't unlink the working file;
+ unlinking the working file is standard RCS behavior, but is rarely
+ appropriate for CVS.
+
+ This function should almost exactly mimic the behavior of `rcs ci'. The
+ principal point of difference is the support here for preserving file
+ ownership and permissions in the delta nodes. This is not a clean
+ solution -- precisely because it diverges from RCS's behavior -- but
+ it doesn't seem feasible to do this anywhere else in the code. [-twp]
Return value is -1 for error (and errno is set to indicate the
error), positive for error (and an error message has been printed),
or zero for success. */
-/* TODO: RCS_checkin always unlinks the working file after checkin --
- then RCS_checkout checks it out again. The logic should probably
- be reversed here. */
-
int
RCS_checkin (rcs, workfile, message, rev, flags)
RCSNode *rcs;
@@ -3507,7 +4365,7 @@ RCS_checkin (rcs, workfile, message, rev, flags)
commitpt = NULL;
if (rcs->flags & PARTIAL)
- RCS_reparsercsfile (rcs, NULL);
+ RCS_reparsercsfile (rcs, (FILE **) NULL, (struct rcsbuffer *) NULL);
/* Get basename of working file. Is there a library function to
do this? I couldn't find one. -twp */
@@ -3561,6 +4419,92 @@ RCS_checkin (rcs, workfile, message, rev, flags)
else
delta->state = xstrdup ("Exp");
+#ifdef PRESERVE_PERMISSIONS_SUPPORT
+ /* If permissions should be preserved on this project, then
+ save the permission info. */
+ if (preserve_perms)
+ {
+ Node *np;
+ struct stat sb;
+ char buf[64]; /* static buffer should be safe: see usage. -twp */
+ char *fullpath;
+
+ delta->other_delta = getlist();
+
+ if (CVS_LSTAT (workfile, &sb) < 0)
+ error (1, 1, "cannot lstat %s", workfile);
+
+ if (S_ISLNK (sb.st_mode))
+ {
+ np = getnode();
+ np->key = xstrdup ("symlink");
+ np->data = xreadlink (workfile);
+ addnode (delta->other_delta, np);
+ }
+ else
+ {
+ (void) sprintf (buf, "%u", sb.st_uid);
+ np = getnode();
+ np->key = xstrdup ("owner");
+ np->data = xstrdup (buf);
+ addnode (delta->other_delta, np);
+
+ (void) sprintf (buf, "%u", sb.st_gid);
+ np = getnode();
+ np->key = xstrdup ("group");
+ np->data = xstrdup (buf);
+ addnode (delta->other_delta, np);
+
+ (void) sprintf (buf, "%o", sb.st_mode & 07777);
+ np = getnode();
+ np->key = xstrdup ("permissions");
+ np->data = xstrdup (buf);
+ addnode (delta->other_delta, np);
+
+ /* Save device number. */
+ switch (sb.st_mode & S_IFMT)
+ {
+ case S_IFREG: break;
+ case S_IFCHR:
+ case S_IFBLK:
+ np = getnode();
+ np->key = xstrdup ("special");
+ sprintf (buf, "%s %lu",
+ ((sb.st_mode & S_IFMT) == S_IFCHR
+ ? "character" : "block"),
+ (unsigned long) sb.st_rdev);
+ np->data = xstrdup (buf);
+ addnode (delta->other_delta, np);
+ break;
+
+ default:
+ error (0, 0, "special file %s has unknown type", workfile);
+ }
+
+ /* Save hardlinks. */
+ fullpath = xgetwd();
+ fullpath = xrealloc (fullpath,
+ strlen(fullpath) + strlen(workfile) + 2);
+ sprintf (fullpath + strlen(fullpath), "/%s", workfile);
+
+ np = lookup_file_by_inode (fullpath);
+ if (np == NULL)
+ {
+ error (1, 0, "lost information on %s's linkage", workfile);
+ }
+ else
+ {
+ struct hardlink_info *hlinfo;
+ hlinfo = (struct hardlink_info *) np->data;
+ np = getnode();
+ np->key = xstrdup ("hardlinks");
+ np->data = xstrdup (hlinfo->links);
+ (void) addnode (delta->other_delta, np);
+ }
+ }
+ }
+#endif
+
/* Create a new deltatext node. */
dtext = (Deltatext *) xmalloc (sizeof (Deltatext));
memset (dtext, 0, sizeof (Deltatext));
@@ -3599,7 +4543,9 @@ RCS_checkin (rcs, workfile, message, rev, flags)
dtext->version = xstrdup (newrev);
bufsize = 0;
- get_file(workfile, workfile, "r", &dtext->text, &bufsize, &dtext->len);
+ get_file (workfile, workfile,
+ rcs->expand != NULL && STREQ (rcs->expand, "b") ? "rb" : "r",
+ &dtext->text, &bufsize, &dtext->len);
if (!checkin_quiet)
{
@@ -3608,6 +4554,9 @@ RCS_checkin (rcs, workfile, message, rev, flags)
cvs_output ("\n", 1);
}
+ /* We are probably about to invalidate any cached file. */
+ rcsbuf_cache_close ();
+
fout = rcs_internal_lockfile (rcs->path);
RCS_putadmin (rcs, fout);
RCS_putdtree (rcs, rcs->head, fout);
@@ -3619,12 +4568,12 @@ RCS_checkin (rcs, workfile, message, rev, flags)
rcs_internal_unlockfile (fout, rcs->path);
freedeltatext (dtext);
- /* Removing the file here is an RCS user-visible behavior which
- we almost surely do not need in the CVS case. In fact, getting
- rid of it should clean up link_file and friends in import.c. */
- if (unlink_file (workfile) < 0)
- /* FIXME-update-dir: message does not include update_dir. */
- error (0, errno, "cannot remove %s", workfile);
+ if ((flags & RCS_FLAGS_KEEPFILE) == 0)
+ {
+ if (unlink_file (workfile) < 0)
+ /* FIXME-update-dir: message does not include update_dir. */
+ error (0, errno, "cannot remove %s", workfile);
+ }
if (!checkin_quiet)
cvs_output ("done\n", 5);
@@ -3664,7 +4613,7 @@ RCS_checkin (rcs, workfile, message, rev, flags)
goto checkin_done;
}
else if (commitpt->next == NULL
- || strcmp (commitpt->version, rcs->head) == 0)
+ || STREQ (commitpt->version, rcs->head))
delta->version = increment_revnum (commitpt->version);
else
delta->version = RCS_addbranch (rcs, commitpt->version);
@@ -3768,7 +4717,7 @@ RCS_checkin (rcs, workfile, message, rev, flags)
nodep = findnode (RCS_getlocks (rcs), commitpt->version);
if (nodep != NULL)
{
- if (strcmp (nodep->data, delta->author) != 0)
+ if (! STREQ (nodep->data, delta->author))
{
error (0, 0, "%s: revision %s locked by %s",
rcs->path,
@@ -3790,13 +4739,13 @@ RCS_checkin (rcs, workfile, message, rev, flags)
tmpfile = cvs_temp_name();
status = RCS_checkout (rcs, NULL, commitpt->version, NULL,
((rcs->expand != NULL
- && strcmp (rcs->expand, "b") == 0)
+ && STREQ (rcs->expand, "b"))
? "-kb"
: "-ko"),
tmpfile,
(RCSCHECKOUTPROC)0, NULL);
if (status != 0)
- error (1, status < 0 ? errno : 0,
+ error (1, 0,
"could not check out revision %s of `%s'",
commitpt->version, rcs->path);
@@ -3807,18 +4756,18 @@ RCS_checkin (rcs, workfile, message, rev, flags)
/* Diff options should include --binary if the RCS file has -kb set
in its `expand' field. */
- diffopts = (rcs->expand != NULL && strcmp (rcs->expand, "b") == 0
+ diffopts = (rcs->expand != NULL && STREQ (rcs->expand, "b")
? "-a -n --binary"
: "-a -n");
- if (strcmp (commitpt->version, rcs->head) == 0 &&
+ if (STREQ (commitpt->version, rcs->head) &&
numdots (delta->version) == 1)
{
/* If this revision is being inserted on the trunk, the change text
for the new delta should be the contents of the working file ... */
bufsize = 0;
get_file (workfile, workfile,
- rcs->expand != NULL && strcmp (rcs->expand, "b") == 0 ? "rb" : "r",
+ rcs->expand != NULL && STREQ (rcs->expand, "b") ? "rb" : "r",
&dtext->text, &bufsize, &dtext->len);
/* ... and the change text for the old delta should be a diff. */
@@ -3854,7 +4803,7 @@ RCS_checkin (rcs, workfile, message, rev, flags)
This should cause no harm, but doesn't strike me as
immensely clean. */
get_file (changefile, changefile,
- rcs->expand != NULL && strcmp (rcs->expand, "b") == 0 ? "rb" : "r",
+ rcs->expand != NULL && STREQ (rcs->expand, "b") ? "rb" : "r",
&commitpt->text->text, &bufsize, &commitpt->text->len);
/* If COMMITPT->TEXT->TEXT is NULL, it means that CHANGEFILE
@@ -3891,7 +4840,7 @@ RCS_checkin (rcs, workfile, message, rev, flags)
/* See the comment above, at the other get_file invocation,
regarding binary vs. text. */
get_file (changefile, changefile,
- rcs->expand != NULL && strcmp (rcs->expand, "b") == 0 ? "rb" : "r",
+ rcs->expand != NULL && STREQ (rcs->expand, "b") ? "rb" : "r",
&dtext->text, &bufsize,
&dtext->len);
if (dtext->text == NULL)
@@ -3915,7 +4864,7 @@ RCS_checkin (rcs, workfile, message, rev, flags)
if (numdots (commitpt->version) == numdots (delta->version))
{
- if (strcmp (commitpt->version, rcs->head) == 0)
+ if (STREQ (commitpt->version, rcs->head))
{
delta->next = rcs->head;
rcs->head = xstrdup (delta->version);
@@ -3945,12 +4894,12 @@ RCS_checkin (rcs, workfile, message, rev, flags)
RCS_rewrite (rcs, dtext, commitpt->version);
- /* Removing the file here is an RCS user-visible behavior which
- we almost surely do not need in the CVS case. In fact, getting
- rid of it should clean up link_file and friends in import.c. */
- if (unlink_file (workfile) < 0)
- /* FIXME-update-dir: message does not include update_dir. */
- error (1, errno, "cannot remove %s", workfile);
+ if ((flags & RCS_FLAGS_KEEPFILE) == 0)
+ {
+ if (unlink_file (workfile) < 0)
+ /* FIXME-update-dir: message does not include update_dir. */
+ error (1, errno, "cannot remove %s", workfile);
+ }
if (unlink_file (tmpfile) < 0)
error (0, errno, "cannot remove %s", tmpfile);
if (unlink_file (changefile) < 0)
@@ -4003,42 +4952,70 @@ RCS_cmp_file (rcs, rev, options, filename)
int retcode;
if (options != NULL && options[0] != '\0')
- binary = (strcmp (options, "-kb") == 0);
+ binary = STREQ (options, "-kb");
else
{
char *expand;
expand = RCS_getexpand (rcs);
- if (expand != NULL && strcmp (expand, "b") == 0)
+ if (expand != NULL && STREQ (expand, "b"))
binary = 1;
else
binary = 0;
}
- fp = CVS_FOPEN (filename, binary ? FOPEN_BINARY_READ : "r");
-
- data.filename = filename;
- data.fp = fp;
- data.different = 0;
-
- retcode = RCS_checkout (rcs, (char *) NULL, rev, (char *) NULL,
- options, RUN_TTY, cmp_file_buffer,
- (void *) &data);
+#ifdef PRESERVE_PERMISSIONS_SUPPORT
+ /* If CVS is to deal properly with special files (when
+ PreservePermissions is on), the best way is to check out the
+ revision to a temporary file and call `xcmp' on the two disk
+ files. xcmp needs to handle non-regular files properly anyway,
+ so calling it simplifies RCS_cmp_file. We *could* just yank
+ the delta node out of the version tree and look for device
+ numbers, but writing to disk and calling xcmp is a better
+ abstraction (therefore probably more robust). -twp */
- /* If we have not yet found a difference, make sure that we are at
- the end of the file. */
- if (! data.different)
+ if (preserve_perms)
{
- if (getc (fp) != EOF)
- data.different = 1;
- }
+ char *tmp;
- fclose (fp);
+ tmp = cvs_temp_name();
+ retcode = RCS_checkout(rcs, NULL, rev, NULL, options, tmp, NULL, NULL);
+ if (retcode != 0)
+ return 1;
- if (retcode != 0)
- return 1;
+ retcode = xcmp (tmp, filename);
+ if (CVS_UNLINK (tmp) < 0)
+ error (0, errno, "cannot remove %s", tmp);
+ return retcode;
+ }
+ else
+#endif
+ {
+ fp = CVS_FOPEN (filename, binary ? FOPEN_BINARY_READ : "r");
+
+ data.filename = filename;
+ data.fp = fp;
+ data.different = 0;
+
+ retcode = RCS_checkout (rcs, (char *) NULL, rev, (char *) NULL,
+ options, RUN_TTY, cmp_file_buffer,
+ (void *) &data);
+
+ /* If we have not yet found a difference, make sure that we are at
+ the end of the file. */
+ if (! data.different)
+ {
+ if (getc (fp) != EOF)
+ data.different = 1;
+ }
+
+ fclose (fp);
- return data.different;
+ if (retcode != 0)
+ return 1;
+
+ return data.different;
+ }
}
/* This is a subroutine of RCS_cmp_file. It is passed to
@@ -4106,12 +5083,12 @@ RCS_settag (rcs, tag, rev)
Node *node;
if (rcs->flags & PARTIAL)
- RCS_reparsercsfile (rcs, NULL);
+ RCS_reparsercsfile (rcs, (FILE **) NULL, (struct rcsbuffer *) NULL);
/* FIXME: This check should be moved to RCS_check_tag. There is no
reason for it to be here. */
- if (strcmp (tag, TAG_BASE) == 0
- || strcmp (tag, TAG_HEAD) == 0)
+ if (STREQ (tag, TAG_BASE)
+ || STREQ (tag, TAG_HEAD))
{
/* Print the name of the tag might be considered redundant
with the caller, which also prints it. Perhaps this helps
@@ -4165,7 +5142,7 @@ RCS_deltag (rcs, tag)
List *symbols;
Node *node;
if (rcs->flags & PARTIAL)
- RCS_reparsercsfile (rcs, NULL);
+ RCS_reparsercsfile (rcs, (FILE **) NULL, (struct rcsbuffer *) NULL);
symbols = RCS_symbols (rcs);
if (symbols == NULL)
@@ -4188,11 +5165,14 @@ RCS_setbranch (rcs, rev)
const char *rev;
{
if (rcs->flags & PARTIAL)
- RCS_reparsercsfile (rcs, NULL);
+ RCS_reparsercsfile (rcs, (FILE **) NULL, (struct rcsbuffer *) NULL);
+
+ if (rev && ! *rev)
+ rev = NULL;
if (rev == NULL && rcs->branch == NULL)
return 0;
- if (rev != NULL && rcs->branch != NULL && strcmp (rev, rcs->branch) == 0)
+ if (rev != NULL && rcs->branch != NULL && STREQ (rev, rcs->branch))
return 0;
if (rcs->branch != NULL)
@@ -4222,7 +5202,7 @@ RCS_lock (rcs, rev, lock_quiet)
char *xrev = NULL;
if (rcs->flags & PARTIAL)
- RCS_reparsercsfile (rcs, NULL);
+ RCS_reparsercsfile (rcs, (FILE **) NULL, (struct rcsbuffer *) NULL);
locks = RCS_getlocks (rcs);
if (locks == NULL)
@@ -4266,7 +5246,7 @@ RCS_lock (rcs, rev, lock_quiet)
p = findnode (locks, xrev);
if (p != NULL)
{
- if (strcmp (p->data, user) == 0)
+ if (STREQ (p->data, user))
{
/* We already own the lock on this revision, so do nothing. */
free (xrev);
@@ -4318,7 +5298,7 @@ RCS_unlock (rcs, rev, unlock_quiet)
user = getcaller();
if (rcs->flags & PARTIAL)
- RCS_reparsercsfile (rcs, NULL);
+ RCS_reparsercsfile (rcs, (FILE **) NULL, (struct rcsbuffer *) NULL);
/* If rev is NULL, unlock the latest revision (first in
rcs->locks) held by the caller. */
@@ -4345,7 +5325,7 @@ RCS_unlock (rcs, rev, unlock_quiet)
lock = NULL;
for (p = locks->list->next; p != locks->list; p = p->next)
{
- if (strcmp (p->data, user) == 0)
+ if (STREQ (p->data, user))
{
if (lock != NULL)
{
@@ -4384,7 +5364,7 @@ RCS_unlock (rcs, rev, unlock_quiet)
return 0;
}
- if (strcmp (lock->data, user) != 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
@@ -4420,7 +5400,7 @@ RCS_addaccess (rcs, user)
char *access, *a;
if (rcs->flags & PARTIAL)
- RCS_reparsercsfile (rcs, NULL);
+ RCS_reparsercsfile (rcs, (FILE **) NULL, (struct rcsbuffer *) NULL);
if (rcs->access == NULL)
rcs->access = xstrdup (user);
@@ -4429,7 +5409,7 @@ RCS_addaccess (rcs, user)
access = xstrdup (rcs->access);
for (a = strtok (access, " "); a != NULL; a = strtok (NULL, " "))
{
- if (strcmp (a, user) == 0)
+ if (STREQ (a, user))
{
free (access);
return;
@@ -4453,7 +5433,7 @@ RCS_delaccess (rcs, user)
int ulen;
if (rcs->flags & PARTIAL)
- RCS_reparsercsfile (rcs, NULL);
+ RCS_reparsercsfile (rcs, (FILE **) NULL, (struct rcsbuffer *) NULL);
if (rcs->access == NULL)
return;
@@ -4484,7 +5464,7 @@ RCS_getaccess (rcs)
RCSNode *rcs;
{
if (rcs->flags & PARTIAL)
- RCS_reparsercsfile (rcs, NULL);
+ RCS_reparsercsfile (rcs, (FILE **) NULL, (struct rcsbuffer *) NULL);
return rcs->access;
}
@@ -4500,7 +5480,7 @@ findtag (node, arg)
{
char *rev = (char *)arg;
- if (strcmp (node->data, rev) == 0)
+ if (STREQ (node->data, rev))
return 1;
else
return 0;
@@ -4593,7 +5573,7 @@ RCS_delete_revs (rcs, tag1, tag2, inclusive)
{
/* A range consisting of a branch number means the latest revision
on that branch. */
- if (RCS_isbranch (rcs, rev1) && strcmp (rev1, rev2) == 0)
+ if (RCS_isbranch (rcs, rev1) && STREQ (rev1, rev2))
rev1 = rev2 = RCS_getbranch (rcs, rev1, 0);
else
{
@@ -4684,12 +5664,12 @@ RCS_delete_revs (rcs, tag1, tag2, inclusive)
*bp = '.';
}
}
- else if (strcmp (rev1, branchpoint) != 0)
+ else if (! STREQ (rev1, branchpoint))
{
/* Walk deltas from BRANCHPOINT on, looking for REV1. */
nodep = findnode (rcs->versions, branchpoint);
revp = (RCSVers *) nodep->data;
- while (revp->next != NULL && strcmp (revp->next, rev1) != 0)
+ while (revp->next != NULL && ! STREQ (revp->next, rev1))
{
revp = (RCSVers *) nodep->data;
nodep = findnode (rcs->versions, revp->next);
@@ -4738,7 +5718,7 @@ RCS_delete_revs (rcs, tag1, tag2, inclusive)
revp = (RCSVers *) nodep->data;
if (rev2 != NULL)
- found = (strcmp (revp->version, rev2) == 0);
+ found = STREQ (revp->version, rev2);
next = revp->next;
if ((!found && next != NULL) || rev2_inclusive || rev2 == NULL)
@@ -4835,13 +5815,6 @@ RCS_delete_revs (rcs, tag1, tag2, inclusive)
if (status > 0)
goto delrev_done;
- else if (status < 0)
- {
- error (0, errno,
- "cannot check out revision %s of %s", after, rcs->path);
- goto delrev_done;
- }
-
if (before == NULL)
{
/* We are deleting revisions from the head of the tree,
@@ -4869,12 +5842,6 @@ RCS_delete_revs (rcs, tag1, tag2, inclusive)
(RCSCHECKOUTPROC)0, NULL);
if (status > 0)
goto delrev_done;
- else if (status < 0)
- {
- error (0, errno, "cannot check out revision %s of %s",
- before, rcs->path);
- goto delrev_done;
- }
outfile = cvs_temp_name();
status = diff_exec (beforefile, afterfile, "-n", outfile);
@@ -4919,7 +5886,7 @@ RCS_delete_revs (rcs, tag1, tag2, inclusive)
outdated. (FIXME: would it be safe to use the `dead' field for
this? Doubtful.) */
for (next = rev1;
- next != NULL && (after == NULL || strcmp (next, after) != 0);
+ next != NULL && (after == NULL || ! STREQ (next, after));
next = revp->next)
{
nodep = findnode (rcs->versions, next);
@@ -4944,13 +5911,13 @@ RCS_delete_revs (rcs, tag1, tag2, inclusive)
/* beforep's ->next field already should be equal to after,
which I think is always NULL in this case. */
;
- else if (strcmp (rev1, branchpoint) == 0)
+ else if (STREQ (rev1, branchpoint))
{
nodep = findnode (rcs->versions, before);
revp = (RCSVers *) nodep->data;
nodep = revp->branches->list->next;
while (nodep != revp->branches->list &&
- strcmp (nodep->key, rev1) != 0)
+ ! STREQ (nodep->key, rev1))
nodep = nodep->next;
assert (nodep != revp->branches->list);
if (after == NULL)
@@ -5470,9 +6437,10 @@ rcs_change_text (name, textbuf, textlen, diffbuf, difflen, retbuf, retlen)
On error, give a fatal error. */
static void
-RCS_deltas (rcs, fp, version, op, text, len, log, loglen)
+RCS_deltas (rcs, fp, rcsbuf, version, op, text, len, log, loglen)
RCSNode *rcs;
FILE *fp;
+ struct rcsbuffer *rcsbuf;
char *version;
enum rcs_delta_op op;
char **text;
@@ -5480,6 +6448,7 @@ RCS_deltas (rcs, fp, version, op, text, len, log, loglen)
char **log;
size_t *loglen;
{
+ struct rcsbuffer rcsbuf_local;
char *branchversion;
char *cpversion;
char *key;
@@ -5489,7 +6458,6 @@ RCS_deltas (rcs, fp, version, op, text, len, log, loglen)
RCSVers *prev_vers;
RCSVers *trunk_vers;
char *next;
- int n;
int ishead, isnext, isversion, onbranch;
Node *node;
struct linevector headlines;
@@ -5499,11 +6467,8 @@ RCS_deltas (rcs, fp, version, op, text, len, log, loglen)
if (fp == NULL)
{
- fp = CVS_FOPEN (rcs->path, FOPEN_BINARY_READ);
- if (fp == NULL)
- error (1, 0, "unable to reopen `%s'", rcs->path);
- if (fseek (fp, rcs->delta_pos, SEEK_SET) != 0)
- error (1, 0, "cannot fseek RCS file");
+ rcsbuf_cache_open (rcs, rcs->delta_pos, &fp, &rcsbuf_local);
+ rcsbuf = &rcsbuf_local;
}
ishead = 1;
@@ -5530,9 +6495,10 @@ RCS_deltas (rcs, fp, version, op, text, len, log, loglen)
*cpversion = '\0';
do {
- getrcsrev (fp, &key);
+ if (! rcsbuf_getrevnum (rcsbuf, &key))
+ error (1, 0, "unexpected EOF reading RCS file %s", rcs->path);
- if (next != NULL && strcmp (next, key) != 0)
+ if (next != NULL && ! STREQ (next, key))
{
/* This is not the next version we need. It is a branch
version which we want to ignore. */
@@ -5557,27 +6523,30 @@ RCS_deltas (rcs, fp, version, op, text, len, log, loglen)
next = vers->next;
/* Compare key and trunkversion now, because key points to
- storage controlled by getrcskey. */
- if (strcmp (branchversion, key) == 0)
+ storage controlled by rcsbuf_getkey. */
+ if (STREQ (branchversion, key))
isversion = 1;
else
isversion = 0;
}
- while ((n = getrcskey (fp, &key, &value, &vallen)) >= 0)
+ while (1)
{
+ if (! rcsbuf_getkey (rcsbuf, &key, &value))
+ error (1, 0, "%s does not appear to be a valid rcs file",
+ rcs->path);
+
if (log != NULL
&& isversion
- && strcmp (key, "log") == 0
- && strcmp (branchversion, version) == 0)
+ && STREQ (key, "log")
+ && STREQ (branchversion, version))
{
- *log = xmalloc (vallen);
- memcpy (*log, value, vallen);
- *loglen = vallen;
+ *log = rcsbuf_valcopy (rcsbuf, value, 0, loglen);
}
- if (strcmp (key, "text") == 0)
+ if (STREQ (key, "text"))
{
+ rcsbuf_valpolish (rcsbuf, value, 0, &vallen);
if (ishead)
{
if (! linevector_add (&curlines, value, vallen, NULL, 0))
@@ -5596,14 +6565,12 @@ RCS_deltas (rcs, fp, version, op, text, len, log, loglen)
break;
}
}
- if (n < 0)
- goto l_error;
if (isversion)
{
/* This is either the version we want, or it is the
branchpoint to the version we want. */
- if (strcmp (branchversion, version) == 0)
+ if (STREQ (branchversion, version))
{
/* This is the version we want. */
linevector_copy (&headlines, &curlines);
@@ -5683,9 +6650,8 @@ RCS_deltas (rcs, fp, version, op, text, len, log, loglen)
} while (next != NULL);
free (branchversion);
-
- if (fclose (fp) < 0)
- error (0, errno, "cannot close %s", rcs->path);
+
+ rcsbuf_cache (rcs, rcsbuf);
if (! foundhead)
error (1, 0, "could not find desired version %s in %s",
@@ -5789,125 +6755,148 @@ RCS_deltas (rcs, fp, version, op, text, len, log, loglen)
linevector_free (&trunklines);
return;
-
- l_error:
- if (ferror (fp))
- error (1, errno, "cannot read %s", rcs->path);
- else
- error (1, 0, "%s does not appear to be a valid rcs file",
- rcs->path);
}
+/* Read the information for a single delta from the RCS buffer RCSBUF,
+ whose name is RCSFILE. *KEYP and *VALP are either NULL, or the
+ first key/value pair to read, as set by rcsbuf_getkey. Return NULL
+ if there are no more deltas. Store the key/value pair which
+ terminated the read in *KEYP and *VALP. */
+
static RCSVers *
-getdelta (fp, rcsfile)
- FILE *fp;
+getdelta (rcsbuf, rcsfile, keyp, valp)
+ struct rcsbuffer *rcsbuf;
char *rcsfile;
+ char **keyp;
+ char **valp;
{
RCSVers *vnode;
char *key, *value, *cp;
- long fpos;
Node *kv;
- vnode = (RCSVers *) xmalloc (sizeof (RCSVers));
- memset (vnode, 0, sizeof (RCSVers));
-
- /* Get revision number. This uses getrcskey because it doesn't
- croak when encountering unexpected input. As a result, we have
- to play unholy games with `key' and `value'. */
- fpos = ftell (fp);
- getrcskey (fp, &key, &value, NULL);
+ /* Get revision number if it wasn't passed in. This uses
+ rcsbuf_getkey because it doesn't croak when encountering
+ unexpected input. As a result, we have to play unholy games
+ with `key' and `value'. */
+ if (*keyp != NULL)
+ {
+ key = *keyp;
+ value = *valp;
+ }
+ else
+ {
+ if (! rcsbuf_getkey (rcsbuf, &key, &value))
+ error (1, 0, "%s: unexpected EOF", rcsfile);
+ }
/* Make sure that it is a revision number and not a cabbage
or something. */
for (cp = key; (isdigit (*cp) || *cp == '.') && *cp != '\0'; cp++)
/* do nothing */ ;
- if (*cp != '\0' || strncmp (RCSDATE, value, strlen (RCSDATE)) != 0)
+ /* Note that when comparing with RCSDATE, we are not massaging
+ VALUE from the string found in the RCS file. This is OK since
+ we know exactly what to expect. */
+ if (*cp != '\0' || strncmp (RCSDATE, value, (sizeof RCSDATE) - 1) != 0)
{
- (void) fseek (fp, fpos, SEEK_SET);
- free (vnode);
+ *keyp = key;
+ *valp = value;
return NULL;
}
+
+ vnode = (RCSVers *) xmalloc (sizeof (RCSVers));
+ memset (vnode, 0, sizeof (RCSVers));
+
vnode->version = xstrdup (key);
- /* grab the value of the date from value */
- cp = value + strlen (RCSDATE);/* skip the "date" keyword */
+ /* Grab the value of the date from value. Note that we are not
+ massaging VALUE from the string found in the RCS file. */
+ cp = value + (sizeof RCSDATE) - 1; /* skip the "date" keyword */
while (whitespace (*cp)) /* take space off front of value */
cp++;
vnode->date = xstrdup (cp);
/* Get author field. */
- (void) getrcskey (fp, &key, &value, NULL);
- /* FIXME: should be using errno in case of ferror. */
- if (key == NULL || strcmp (key, "author") != 0)
+ if (! rcsbuf_getkey (rcsbuf, &key, &value))
+ {
+ error (1, 0, "unexpected end of file reading %s", rcsfile);
+ }
+ if (! STREQ (key, "author"))
error (1, 0, "\
-unable to parse rcs file; `author' not in the expected place");
- vnode->author = xstrdup (value);
+unable to parse %s; `author' not in the expected place", rcsfile);
+ vnode->author = rcsbuf_valcopy (rcsbuf, value, 0, (size_t *) NULL);
/* Get state field. */
- (void) getrcskey (fp, &key, &value, NULL);
- /* FIXME: should be using errno in case of ferror. */
- if (key == NULL || strcmp (key, "state") != 0)
+ if (! rcsbuf_getkey (rcsbuf, &key, &value))
+ {
+ error (1, 0, "unexpected end of file reading %s", rcsfile);
+ }
+ if (! STREQ (key, "state"))
error (1, 0, "\
-unable to parse rcs file; `state' not in the expected place");
- vnode->state = xstrdup (value);
- if (strcmp (value, "dead") == 0)
+unable to parse %s; `state' not in the expected place", rcsfile);
+ vnode->state = rcsbuf_valcopy (rcsbuf, value, 0, (size_t *) NULL);
+ if (STREQ (value, "dead"))
{
vnode->dead = 1;
}
/* Note that "branches" and "next" are in fact mandatory, according
- to doc/RCSFILES. We perhaps should be giving an error if they
- are not there. */
+ to doc/RCSFILES. */
/* fill in the branch list (if any branches exist) */
- fpos = ftell (fp);
- (void) getrcskey (fp, &key, &value, NULL);
- /* FIXME: should be handling various error conditions better. */
- if (key != NULL && strcmp (key, RCSDESC) == 0)
+ if (! rcsbuf_getkey (rcsbuf, &key, &value))
+ {
+ error (1, 0, "unexpected end of file reading %s", rcsfile);
+ }
+ if (STREQ (key, RCSDESC))
{
- (void) fseek (fp, fpos, SEEK_SET);
+ *keyp = key;
+ *valp = value;
+ /* Probably could/should be a fatal error. */
+ error (0, 0, "warning: 'branches' keyword missing from %s", rcsfile);
return vnode;
}
if (value != (char *) NULL)
{
vnode->branches = getlist ();
+ /* Note that we are not massaging VALUE from the string found
+ in the RCS file. */
do_branches (vnode->branches, value);
}
/* fill in the next field if there is a next revision */
- fpos = ftell (fp);
- (void) getrcskey (fp, &key, &value, NULL);
- /* FIXME: should be handling various error conditions better. */
- if (key != NULL && strcmp (key, RCSDESC) == 0)
+ if (! rcsbuf_getkey (rcsbuf, &key, &value))
{
- (void) fseek (fp, fpos, SEEK_SET);
+ error (1, 0, "unexpected end of file reading %s", rcsfile);
+ }
+ if (STREQ (key, RCSDESC))
+ {
+ *keyp = key;
+ *valp = value;
+ /* Probably could/should be a fatal error. */
+ error (0, 0, "warning: 'next' keyword missing from %s", rcsfile);
return vnode;
}
if (value != (char *) NULL)
- vnode->next = xstrdup (value);
+ vnode->next = rcsbuf_valcopy (rcsbuf, value, 0, (size_t *) NULL);
/*
* XXX - this is where we put the symbolic link stuff???
* (into newphrases in the deltas).
*/
- /* FIXME: Does not correctly handle errors, e.g. from stdio. */
while (1)
{
- fpos = ftell (fp);
- if (getrcskey (fp, &key, &value, NULL) < 0)
- break;
+ if (! rcsbuf_getkey (rcsbuf, &key, &value))
+ error (1, 0, "unexpected end of file reading %s", rcsfile);
- assert (key != NULL);
-
- if (strcmp (key, RCSDESC) == 0)
+ if (STREQ (key, RCSDESC))
break;
/* Enable use of repositories created by certain obsolete
versions of CVS. This code should remain indefinately;
there is no procedure for converting old repositories, and
checking for it is harmless. */
- if (strcmp(key, RCSDEAD) == 0)
+ if (STREQ (key, RCSDEAD))
{
vnode->dead = 1;
if (vnode->state != NULL)
@@ -5918,6 +6907,9 @@ unable to parse rcs file; `state' not in the expected place");
/* if we have a new revision number, we're done with this delta */
for (cp = key; (isdigit (*cp) || *cp == '.') && *cp != '\0'; cp++)
/* do nothing */ ;
+ /* Note that when comparing with RCSDATE, we are not massaging
+ VALUE from the string found in the RCS file. This is OK
+ since we know exactly what to expect. */
if (*cp == '\0' && strncmp (RCSDATE, value, strlen (RCSDATE)) == 0)
break;
@@ -5928,7 +6920,7 @@ unable to parse rcs file; `state' not in the expected place");
kv = getnode ();
kv->type = RCSFIELD;
kv->key = xstrdup (key);
- kv->data = xstrdup (value);
+ kv->data = rcsbuf_valcopy (rcsbuf, value, 1, (size_t *) NULL);
if (addnode (vnode->other_delta, kv) != 0)
{
/* Complaining about duplicate keys in newphrases seems
@@ -5940,11 +6932,11 @@ unable to parse rcs file; `state' not in the expected place");
key, rcsfile);
freenode (kv);
}
- }
+ }
- /* We got here because we read beyond the end of a delta. Seek back
- to the beginning of the erroneous read. */
- (void) fseek (fp, fpos, SEEK_SET);
+ /* Return the key which caused us to fail back to the caller. */
+ *keyp = key;
+ *valp = value;
return vnode;
}
@@ -5965,25 +6957,21 @@ freedeltatext (d)
}
static Deltatext *
-RCS_getdeltatext (rcs, fp)
+RCS_getdeltatext (rcs, fp, rcsbuf)
RCSNode *rcs;
FILE *fp;
+ struct rcsbuffer *rcsbuf;
{
char *num;
char *key, *value;
- int n;
Node *p;
Deltatext *d;
- size_t textlen;
/* Get the revision number. */
- n = getrevnum (fp, &num);
- if (ferror (fp))
- error (1, errno, "%s: cannot read", rcs->path);
- if (n == EOF)
+ if (! rcsbuf_getrevnum (rcsbuf, &num))
{
- /* If n == EOF and num == NULL, it means we reached EOF
- naturally. That's fine. */
+ /* If num == NULL, it means we reached EOF naturally. That's
+ fine. */
if (num == NULL)
return NULL;
else
@@ -5999,36 +6987,36 @@ RCS_getdeltatext (rcs, fp)
d->version = xstrdup (num);
/* Get the log message. */
- if (getrcskey (fp, &key, &value, NULL) < 0)
+ if (! rcsbuf_getkey (rcsbuf, &key, &value))
error (1, 0, "%s, delta %s: unexpected EOF", rcs->path, num);
- if (strcmp (key, "log") != 0)
+ if (! STREQ (key, "log"))
error (1, 0, "%s, delta %s: expected `log', got `%s'",
rcs->path, num, key);
- d->log = xstrdup (value);
+ d->log = rcsbuf_valcopy (rcsbuf, value, 0, (size_t *) NULL);
/* Get random newphrases. */
d->other = getlist();
- for (n = getrcskey (fp, &key, &value, &textlen);
- n >= 0 && strcmp (key, "text") != 0;
- n = getrcskey (fp, &key, &value, &textlen))
+ while (1)
{
+ if (! rcsbuf_getkey (rcsbuf, &key, &value))
+ error (1, 0, "%s, delta %s: unexpected EOF", rcs->path, num);
+
+ if (STREQ (key, "text"))
+ break;
+
p = getnode();
p->type = RCSFIELD;
p->key = xstrdup (key);
- p->data = xstrdup (value);
+ p->data = rcsbuf_valcopy (rcsbuf, value, 1, (size_t *) NULL);
if (addnode (d->other, p) < 0)
{
error (0, 0, "warning: %s, delta %s: duplicate field `%s'",
rcs->path, num, key);
}
}
- if (n < 0)
- error (1, 0, "%s, delta %s: unexpected EOF", rcs->path, num);
/* Get the change text. We already know that this key is `text'. */
- d->text = (char *) malloc (textlen + 1);
- d->len = textlen;
- memcpy (d->text, value, textlen);
+ d->text = rcsbuf_valcopy (rcsbuf, value, 0, &d->len);
return d;
}
@@ -6045,11 +7033,23 @@ RCS_getdeltatext (rcs, fp)
not get corrupted. */
static int
-putsymbol_proc (symnode, fp)
+putsymbol_proc (symnode, fparg)
Node *symnode;
- void *fp;
+ void *fparg;
{
- return fprintf ((FILE *) fp, "\n\t%s:%s", symnode->key, symnode->data);
+ FILE *fp = (FILE *) fparg;
+
+ /* A fiddly optimization: this code used to just call fprintf, but
+ in an old repository with hundreds of tags this can get called
+ hundreds of thousands of times when doing a cvs tag. Since
+ tagging is a relatively common operation, and using putc and
+ fputs is just as comprehensible, the change is worthwhile. */
+ putc ('\n', fp);
+ putc ('\t', fp);
+ fputs (symnode->key, fp);
+ putc (':', fp);
+ fputs (symnode->data, fp);
+ return 0;
}
static int putlock_proc PROTO ((Node *, void *));
@@ -6101,9 +7101,9 @@ putrcsfield_proc (node, vfp)
/* desc, log and text fields should not be terminated with semicolon;
all other fields should be. */
- if (strcmp (node->key, "desc") != 0 &&
- strcmp (node->key, "log") != 0 &&
- strcmp (node->key, "text") != 0)
+ if (! STREQ (node->key, "desc") &&
+ ! STREQ (node->key, "log") &&
+ ! STREQ (node->key, "text"))
{
putc (';', fp);
}
@@ -6133,7 +7133,15 @@ RCS_putadmin (rcs, fp)
fputs (";\n", fp);
fputs (RCSSYMBOLS, fp);
- walklist (RCS_symbols(rcs), putsymbol_proc, (void *) fp);
+ /* If we haven't had to convert the symbols to a list yet, don't
+ force a conversion now; just write out the string. */
+ if (rcs->symbols == NULL && rcs->symbols_data != NULL)
+ {
+ fputs ("\n\t", fp);
+ fputs (rcs->symbols_data, fp);
+ }
+ else
+ walklist (RCS_symbols (rcs), putsymbol_proc, (void *) fp);
fputs (";\n", fp);
fputs ("locks", fp);
@@ -6151,7 +7159,7 @@ RCS_putadmin (rcs, fp)
expand_at_signs (rcs->comment, (off_t) strlen (rcs->comment), fp);
fputs ("@;\n", fp);
}
- if (rcs->expand && strcmp (rcs->expand, "kv") != 0)
+ if (rcs->expand && ! STREQ (rcs->expand, "kv"))
fprintf (fp, "%s\t@%s@;\n", RCSEXPAND, rcs->expand);
walklist (rcs->other, putrcsfield_proc, (void *) fp);
@@ -6273,40 +7281,68 @@ putdeltatext (fp, d)
increasing order.) */
static void
-RCS_copydeltas (rcs, fin, fout, newdtext, insertpt)
+RCS_copydeltas (rcs, fin, rcsbufin, fout, newdtext, insertpt)
RCSNode *rcs;
FILE *fin;
+ struct rcsbuffer *rcsbufin;
FILE *fout;
Deltatext *newdtext;
char *insertpt;
{
- Deltatext *dtext;
+ int actions;
RCSVers *dadmin;
Node *np;
int insertbefore, found;
+ char *bufrest;
+ int nls;
+ size_t buflen;
+ char buf[8192];
+ int got;
+
+ /* Count the number of versions for which we have to do some
+ special operation. */
+ actions = walklist (rcs->versions, count_delta_actions, (void *) NULL);
/* Make a note of whether NEWDTEXT should be inserted
before or after its INSERTPT. */
insertbefore = (newdtext != NULL && numdots (newdtext->version) == 1);
- found = 0;
- while ((dtext = RCS_getdeltatext (rcs, fin)) != NULL)
+ while (actions != 0 || newdtext != NULL)
{
- found = (insertpt != NULL && strcmp (dtext->version, insertpt) == 0);
+ Deltatext *dtext;
+
+ dtext = RCS_getdeltatext (rcs, fin, rcsbufin);
+
+ /* We shouldn't hit EOF here, because that would imply that
+ some action was not taken, or that we could not insert
+ NEWDTEXT. */
+ if (dtext == NULL)
+ error (1, 0, "internal error: EOF too early in RCS_copydeltas");
+
+ found = (insertpt != NULL && STREQ (dtext->version, insertpt));
if (found && insertbefore)
+ {
putdeltatext (fout, newdtext);
+ newdtext = NULL;
+ insertpt = NULL;
+ }
np = findnode (rcs->versions, dtext->version);
dadmin = (RCSVers *) np->data;
/* If this revision has been outdated, just skip it. */
if (dadmin->outdated)
+ {
+ --actions;
continue;
+ }
/* Update the change text for this delta. New change text
data may come from cvs admin -m, cvs admin -o, or cvs ci. */
if (dadmin->text != NULL)
{
+ if (dadmin->text->log != NULL || dadmin->text->text != NULL)
+ --actions;
if (dadmin->text->log != NULL)
{
free (dtext->log);
@@ -6325,9 +7361,92 @@ RCS_copydeltas (rcs, fin, fout, newdtext, insertpt)
freedeltatext (dtext);
if (found && !insertbefore)
+ {
putdeltatext (fout, newdtext);
+ newdtext = NULL;
+ insertpt = NULL;
+ }
+ }
+
+ /* Copy the rest of the file directly, without bothering to
+ interpret it. The caller will handle error checking by calling
+ ferror.
+
+ We just wrote a newline to the file, either in putdeltatext or
+ in the caller. However, we may not have read the corresponding
+ newline from the file, because rcsbuf_getkey returns as soon as
+ it finds the end of the '@' string for the desc or text key.
+ Therefore, we may read three newlines when we should really
+ only write two, and we check for that case here. This is not
+ an semantically important issue; we only do it to make our RCS
+ files look traditional. */
+
+ nls = 3;
+
+ rcsbuf_get_buffered (rcsbufin, &bufrest, &buflen);
+ if (buflen > 0)
+ {
+ if (bufrest[0] != '\n'
+ || strncmp (bufrest, "\n\n\n", buflen < 3 ? buflen : 3) != 0)
+ {
+ nls = 0;
+ }
+ else
+ {
+ if (buflen < 3)
+ nls -= buflen;
+ else
+ {
+ ++bufrest;
+ --buflen;
+ nls = 0;
+ }
+ }
+
+ fwrite (bufrest, 1, buflen, fout);
+ }
+
+ while ((got = fread (buf, 1, sizeof buf, fin)) != 0)
+ {
+ if (nls > 0
+ && got >= nls
+ && buf[0] == '\n'
+ && strncmp (buf, "\n\n\n", nls) == 0)
+ {
+ fwrite (buf + 1, 1, got - 1, fout);
+ }
+ else
+ {
+ fwrite (buf, 1, got, fout);
+ }
+
+ nls = 0;
+ }
+}
+
+/* A helper procedure for RCS_copydeltas. This is called via walklist
+ to count the number of RCS revisions for which some special action
+ is required. */
+
+int
+count_delta_actions (np, ignore)
+ Node *np;
+ void *ignore;
+{
+ RCSVers *dadmin;
+
+ dadmin = (RCSVers *) np->data;
+
+ if (dadmin->outdated)
+ return 1;
+
+ if (dadmin->text != NULL
+ && (dadmin->text->log != NULL || dadmin->text->text != NULL))
+ {
+ return 1;
}
- putc ('\n', fout);
+
+ return 0;
}
/* RCS_internal_lockfile and RCS_internal_unlockfile perform RCS-style
@@ -6435,6 +7554,12 @@ rcs_internal_unlockfile (fp, rcsfile)
corrupting the repository. */
if (ferror (fp))
+ /* The only case in which using errno here would be meaningful
+ is if we happen to have left errno unmolested since the call
+ which produced the error (e.g. fprintf). That is pretty
+ fragile even if it happens to sometimes be true. The real
+ solution is to check each call to fprintf rather than waiting
+ until the end like this. */
error (1, 0, "error writing to lock file %s", lockfile);
if (fclose (fp) == EOF)
error (1, errno, "error closing lock file %s", lockfile);
@@ -6478,6 +7603,7 @@ RCS_rewrite (rcs, newdtext, insertpt)
char *insertpt;
{
FILE *fin, *fout;
+ struct rcsbuffer rcsbufin;
if (noexec)
return;
@@ -6489,10 +7615,7 @@ RCS_rewrite (rcs, newdtext, insertpt)
RCS_putdesc (rcs, fout);
/* Open the original RCS file and seek to the first delta text. */
- if ((fin = CVS_FOPEN (rcs->path, FOPEN_BINARY_READ)) == NULL)
- error (1, errno, "cannot open RCS file `%s' for reading", rcs->path);
- if (fseek (fin, rcs->delta_pos, SEEK_SET) < 0)
- error (1, errno, "cannot fseek in RCS file %s", rcs->path);
+ rcsbuf_cache_open (rcs, rcs->delta_pos, &fin, &rcsbufin);
/* Update delta_pos to the current position in the output file.
Do NOT move these statements: they must be done after fin has
@@ -6502,10 +7625,19 @@ RCS_rewrite (rcs, newdtext, insertpt)
if (rcs->delta_pos == -1)
error (1, errno, "cannot ftell in RCS file %s", rcs->path);
- RCS_copydeltas (rcs, fin, fout, newdtext, insertpt);
+ RCS_copydeltas (rcs, fin, &rcsbufin, fout, newdtext, insertpt);
+ /* We don't want to call rcsbuf_cache here, since we're about to
+ delete the file. */
+ rcsbuf_close (&rcsbufin);
if (ferror (fin))
- error (0, errno, "warning: when closing RCS file `%s'", rcs->path);
+ /* The only case in which using errno here would be meaningful
+ is if we happen to have left errno unmolested since the call
+ which produced the error (e.g. fread). That is pretty
+ fragile even if it happens to sometimes be true. The real
+ solution is to make sure that all the code which reads
+ from fin checks for errors itself (some does, some doesn't). */
+ error (0, 0, "warning: when closing RCS file `%s'", rcs->path);
if (fclose (fin) < 0)
error (0, errno, "warning: closing RCS file `%s'", rcs->path);
@@ -6530,13 +7662,18 @@ annotate_fileproc (callerdat, finfo)
struct file_info *finfo;
{
FILE *fp = NULL;
+ struct rcsbuffer *rcsbufp = NULL;
+ struct rcsbuffer rcsbuf;
char *version;
if (finfo->rcs == NULL)
return (1);
if (finfo->rcs->flags & PARTIAL)
- RCS_reparsercsfile (finfo->rcs, &fp);
+ {
+ RCS_reparsercsfile (finfo->rcs, &fp, &rcsbuf);
+ rcsbufp = &rcsbuf;
+ }
version = RCS_getversion (finfo->rcs, tag, date, force_tag_match,
(int *) NULL);
@@ -6549,7 +7686,7 @@ annotate_fileproc (callerdat, finfo)
cvs_outerr (finfo->fullname, 0);
cvs_outerr ("\n***************\n", 0);
- RCS_deltas (finfo->rcs, fp, version, RCS_ANNOTATE, (char **) NULL,
+ RCS_deltas (finfo->rcs, fp, rcsbufp, version, RCS_ANNOTATE, (char **) NULL,
(size_t) NULL, (char **) NULL, (size_t *) NULL);
free (version);
return 0;
diff --git a/contrib/cvs/src/rcs.h b/contrib/cvs/src/rcs.h
index d0f47bba381c..400d1a0a13f1 100644
--- a/contrib/cvs/src/rcs.h
+++ b/contrib/cvs/src/rcs.h
@@ -169,13 +169,17 @@ typedef struct rcsversnode RCSVers;
/* The type of a function passed to RCS_checkout. */
typedef void (*RCSCHECKOUTPROC) PROTO ((void *, const char *, size_t));
+#ifdef __STDC__
+struct rcsbuffer;
+#endif
+
/*
* exported interfaces
*/
RCSNode *RCS_parse PROTO((const char *file, const char *repos));
RCSNode *RCS_parsercsfile PROTO((char *rcsfile));
void RCS_fully_parse PROTO((RCSNode *));
-void RCS_reparsercsfile PROTO((RCSNode *, FILE **));
+void RCS_reparsercsfile PROTO((RCSNode *, FILE **, struct rcsbuffer *));
char *RCS_check_kflag PROTO((const char *arg));
char *RCS_getdate PROTO((RCSNode * rcs, char *date, int force_tag_match));
@@ -217,6 +221,8 @@ int rcs_change_text PROTO ((const char *, char *, size_t, const char *,
size_t, char **, size_t *));
char *make_file_label PROTO ((char *, char *, RCSNode *));
+extern int preserve_perms;
+
/* From import.c. */
extern int add_rcs_file PROTO ((char *, char *, char *, char *, char *,
char *, char *, int, char **,
diff --git a/contrib/cvs/src/rcscmds.c b/contrib/cvs/src/rcscmds.c
index 9a237a9be96c..3086b57e10c7 100644
--- a/contrib/cvs/src/rcscmds.c
+++ b/contrib/cvs/src/rcscmds.c
@@ -487,7 +487,42 @@ diff_exec (file1, file2, options, out)
char *options;
char *out;
{
- char *args = xmalloc (strlen (options) + 10);
+ char *args;
+
+#ifdef PRESERVE_PERMISSIONS_SUPPORT
+ /* If either file1 or file2 are special files, pretend they are
+ /dev/null. Reason: suppose a file that represents a block
+ special device in one revision becomes a regular file. CVS
+ must find the `difference' between these files, but a special
+ file contains no data useful for calculating this metric. The
+ safe thing to do is to treat the special file as an empty file,
+ thus recording the regular file's full contents. Doing so will
+ create extremely large deltas at the point of transition
+ between device files and regular files, but this is probably
+ very rare anyway.
+
+ There may be ways around this, but I think they are fraught
+ with danger. -twp */
+
+ if (preserve_perms &&
+ strcmp (file1, DEVNULL) != 0 &&
+ strcmp (file2, DEVNULL) != 0)
+ {
+ struct stat sb1, sb2;
+
+ if (CVS_LSTAT (file1, &sb1) < 0)
+ error (1, errno, "cannot get file information for %s", file1);
+ if (CVS_LSTAT (file2, &sb2) < 0)
+ error (1, errno, "cannot get file information for %s", file2);
+
+ if (!S_ISREG (sb1.st_mode) && !S_ISDIR (sb1.st_mode))
+ file1 = DEVNULL;
+ if (!S_ISREG (sb2.st_mode) && !S_ISDIR (sb2.st_mode))
+ file2 = DEVNULL;
+ }
+#endif
+
+ args = xmalloc (strlen (options) + 10);
/* The first word in this string is used only for error reporting. */
sprintf (args, "diff %s", options);
call_diff_setup (args);
@@ -507,7 +542,31 @@ diff_execv (file1, file2, label1, label2, options, out)
char *options;
char *out;
{
- char *args = xmalloc (strlen (options) + 10);
+ char *args;
+
+#ifdef PRESERVE_PERMISSIONS_SUPPORT
+ /* Pretend that special files are /dev/null for purposes of making
+ diffs. See comments in diff_exec. */
+
+ if (preserve_perms &&
+ strcmp (file1, DEVNULL) != 0 &&
+ strcmp (file2, DEVNULL) != 0)
+ {
+ struct stat sb1, sb2;
+
+ if (CVS_LSTAT (file1, &sb1) < 0)
+ error (1, errno, "cannot get file information for %s", file1);
+ if (CVS_LSTAT (file2, &sb2) < 0)
+ error (1, errno, "cannot get file information for %s", file2);
+
+ if (!S_ISREG (sb1.st_mode) && !S_ISDIR (sb1.st_mode))
+ file1 = DEVNULL;
+ if (!S_ISREG (sb2.st_mode) && !S_ISDIR (sb2.st_mode))
+ file2 = DEVNULL;
+ }
+#endif
+
+ args = xmalloc (strlen (options) + 10);
/* The first word in this string is used only for error reporting. */
/* I guess we are pretty confident that options starts with a space. */
sprintf (args, "diff%s", options);
diff --git a/contrib/cvs/src/recurse.c b/contrib/cvs/src/recurse.c
index d2fae6ff75f6..3896bc737a58 100644
--- a/contrib/cvs/src/recurse.c
+++ b/contrib/cvs/src/recurse.c
@@ -448,7 +448,7 @@ do_recursion (frame)
{
/* we will process files, so pre-parse entries */
if (frame->which & W_LOCAL)
- entries = Entries_Open (frame->aflag);
+ entries = Entries_Open (frame->aflag, NULL);
}
}
diff --git a/contrib/cvs/src/sanity.sh b/contrib/cvs/src/sanity.sh
index e407cbd04aba..1d877fee0b26 100755
--- a/contrib/cvs/src/sanity.sh
+++ b/contrib/cvs/src/sanity.sh
@@ -548,20 +548,29 @@ RCSINIT=; export RCSINIT
# tests.
if test x"$*" = x; then
+ # Basic/miscellaneous functionality
tests="basica basicb basicc basic1 deep basic2"
- tests="${tests} rdiff death death2 branches"
+ # Branching, tagging, removing, adding, multiple directories
+ tests="${tests} rdiff death death2 branches branches2"
tests="${tests} rcslib multibranch import importb join join2 join3"
tests="${tests} new newb conflicts conflicts2 conflicts3"
+ # Checking out various places (modules, checkout -d, &c)
tests="${tests} modules modules2 modules3 modules4"
+ tests="${tests} cvsadm abspath toplevel"
+ # Log messages, error messages.
tests="${tests} mflag editor errmsg1 errmsg2"
+ # Watches, binary files, history browsing, &c.
tests="${tests} devcom devcom2 devcom3 watch4"
tests="${tests} ignore binfiles binfiles2 mcopy binwrap binwrap2"
tests="${tests} binwrap3 mwrap info config"
tests="${tests} serverpatch log log2 ann crerepos rcs big modes stamps"
+ # More tag and branch tests, keywords.
tests="${tests} sticky keyword keywordlog"
- tests="${tests} toplevel head tagdate multibranch2"
+ tests="${tests} head tagdate multibranch2"
+ # "cvs admin", reserved checkouts.
tests="${tests} admin reserved"
- tests="${tests} cvsadm diffmerge1 diffmerge2"
+ # Nuts and bolts of diffing/merging (diff library, &c)
+ tests="${tests} diffmerge1 diffmerge2"
else
tests="$*"
fi
@@ -1903,20 +1912,21 @@ done"
# interrupt, while we've got a clean 1.1 here, let's import it
# into a couple of other modules.
cd export-dir
- dotest 56 "${testcvs} import -m first-import second-dir first-immigration immigration1 immigration1_0" \
-"N second-dir/file14
-N second-dir/file6
-N second-dir/file7
-${PROG} [a-z]*: Importing ${TESTDIR}/cvsroot/second-dir/dir1
-N second-dir/dir1/file14
-N second-dir/dir1/file6
-N second-dir/dir1/file7
-${PROG} [a-z]*: Importing ${TESTDIR}/cvsroot/second-dir/dir1/dir2
+ dotest_sort 56 "${testcvs} import -m first-import second-dir first-immigration immigration1 immigration1_0" \
+"
+
N second-dir/dir1/dir2/file14
N second-dir/dir1/dir2/file6
N second-dir/dir1/dir2/file7
-
-No conflicts created by this import"
+N second-dir/dir1/file14
+N second-dir/dir1/file6
+N second-dir/dir1/file7
+N second-dir/file14
+N second-dir/file6
+N second-dir/file7
+No conflicts created by this import
+${PROG} [a-z]*: Importing ${TESTDIR}/cvsroot/second-dir/dir1
+${PROG} [a-z]*: Importing ${TESTDIR}/cvsroot/second-dir/dir1/dir2"
cd ..
if ${CVS} export -r HEAD second-dir ; then
@@ -2532,11 +2542,13 @@ U first-dir/file3'
cd first-dir
- # Add a file on the trunk.
+ # Add two files on the trunk.
echo "first revision" > file1
- dotest death2-2 "${testcvs} add file1" \
+ echo "file4 first revision" > file4
+ dotest death2-2 "${testcvs} add file1 file4" \
"${PROG}"' [a-z]*: scheduling file `file1'\'' for addition
-'"${PROG}"' [a-z]*: use .'"${PROG}"' commit. to add this file permanently'
+'"${PROG}"' [a-z]*: scheduling file `file4'\'' for addition
+'"${PROG}"' [a-z]*: use .'"${PROG}"' commit. to add these files permanently'
dotest death2-3 "${testcvs} -q commit -m add" \
"RCS file: ${TESTDIR}/cvsroot/first-dir/file1,v
@@ -2544,11 +2556,21 @@ done
Checking in file1;
${TESTDIR}/cvsroot/first-dir/file1,v <-- file1
initial revision: 1\.1
+done
+RCS file: ${TESTDIR}/cvsroot/first-dir/file4,v
+done
+Checking in file4;
+${TESTDIR}/cvsroot/first-dir/file4,v <-- file4
+initial revision: 1\.1
done"
# Make a branch and a non-branch tag.
- dotest death2-4 "${testcvs} -q tag -b branch" 'T file1'
- dotest death2-5 "${testcvs} -q tag tag" 'T file1'
+ dotest death2-4 "${testcvs} -q tag -b branch" \
+'T file1
+T file4'
+ dotest death2-5 "${testcvs} -q tag tag" \
+'T file1
+T file4'
# Switch over to the branch.
dotest death2-6 "${testcvs} -q update -r branch" ''
@@ -2659,8 +2681,20 @@ ${TESTDIR}/cvsroot/first-dir/file1,v <-- file1
new revision: 1\.1\.2\.2; previous revision: 1\.1\.2\.1
done"
+ # Delete file4 from the branch
+ dotest death2-10a "${testcvs} rm -f file4" \
+"${PROG} [a-z]*: scheduling .file4. for removal
+${PROG} [a-z]*: use .${PROG} commit. to remove this file permanently"
+ dotest death2-10b "${testcvs} -q ci -m removed" \
+"Removing file4;
+${TESTDIR}/cvsroot/first-dir/file4,v <-- file4
+new revision: delete; previous revision: 1\.1\.2
+done"
+
# Back to the trunk.
- dotest death2-11 "${testcvs} -q update -A" 'U file1' 'P file1'
+ dotest death2-11 "${testcvs} -q update -A" \
+"[UP] file1
+U file4"
# Add another file on the trunk.
echo "first revision" > file2
@@ -2675,15 +2709,22 @@ ${TESTDIR}/cvsroot/first-dir/file2,v <-- file2
initial revision: 1\.1
done"
+ # Modify file4 on the trunk.
+ echo "new file4 revision" > file4
+ dotest death2-13a "${testcvs} -q commit -m mod" \
+"Checking in file4;
+${TESTDIR}/cvsroot/first-dir/file4,v <-- file4
+new revision: 1\.2; previous revision: 1\.1
+done"
+
# Back to the branch.
# The ``no longer in the repository'' message doesn't really
# look right to me, but that's what CVS currently prints for
# this case.
dotest death2-14 "${testcvs} -q update -r branch" \
-"U file1
-${PROG} [a-z]*: file2 is no longer in the repository" \
-"P file1
-${PROG} [a-z]*: file2 is no longer in the repository"
+"[UP] file1
+${PROG} [a-z]*: file2 is no longer in the repository
+${PROG} [a-z]*: warning: file4 is not (any longer) pertinent"
# Add a file on the branch with the same name.
echo "branch revision" > file2
@@ -2740,7 +2781,8 @@ diff -c -r1\.1 -r1\.1\.2\.2
--- 1 ----
! second revision
${PROG} [a-z]*: tag tag is not in file file2
-${PROG} [a-z]*: tag tag is not in file file3"
+${PROG} [a-z]*: tag tag is not in file file3
+${PROG} [a-z]*: file4 no longer exists, no comparison available"
dotest_fail death2-diff-12 "${testcvs} -q diff -rtag -c -N ." \
"Index: file1
@@ -2775,16 +2817,24 @@ diff -N file3
\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*
\*\*\* 0 \*\*\*\*
--- 1 ----
-${PLUS} first revision"
+${PLUS} first revision
+Index: file4
+===================================================================
+RCS file: file4
+diff -N file4
+\*\*\* ${tempname}[ ][ ]*[a-zA-Z0-9: ]*
+--- /dev/null[ ][ ]*[a-zA-Z0-9: ]*
+\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*
+\*\*\* 1 \*\*\*\*
+- file4 first revision
+--- 0 ----"
# Switch to the nonbranch tag.
dotest death2-19 "${testcvs} -q update -r tag" \
-"U file1
-${PROG} [a-z]*: file2 is no longer in the repository
-${PROG} [a-z]*: file3 is no longer in the repository" \
-"P file1
+"[UP] file1
${PROG} [a-z]*: file2 is no longer in the repository
-${PROG} [a-z]*: file3 is no longer in the repository"
+${PROG} [a-z]*: file3 is no longer in the repository
+U file4"
dotest_fail death2-20 "test -f file2"
@@ -3036,6 +3086,272 @@ done"
rm -r first-dir
;;
+ branches2)
+ # More branch tests.
+ # Test that when updating a new subdirectory in a directory
+ # which was checked out on a branch, the new subdirectory is
+ # created on the appropriate branch. Test this when joining
+ # as well.
+
+ mkdir ${CVSROOT_DIRNAME}/first-dir
+ mkdir trunk; cd trunk
+
+ # Create a file.
+ dotest branches2-1 "${testcvs} -q co first-dir"
+ cd first-dir
+ echo "file1 first revision" > file1
+ dotest branches2-2 "${testcvs} add file1" \
+"${PROG} [a-z]*: scheduling file .file1. for addition
+${PROG} [a-z]*: use .${PROG} commit. to add this file permanently"
+ dotest branches2-3 "${testcvs} commit -m add file1" \
+"RCS file: ${TESTDIR}/cvsroot/first-dir/file1,v
+done
+Checking in file1;
+${TESTDIR}/cvsroot/first-dir/file1,v <-- file1
+initial revision: 1\.1
+done"
+
+ # Tag the file.
+ dotest branches2-4 "${testcvs} -q tag tag1" 'T file1'
+
+ # Make two branches.
+ dotest branches2-5 "${testcvs} -q rtag -b -r tag1 b1 first-dir" ''
+ dotest branches2-6 "${testcvs} -q rtag -b -r tag1 b2 first-dir" ''
+
+ # Create some files and a subdirectory on branch b1.
+ cd ../..
+ mkdir b1; cd b1
+ dotest branches2-7 "${testcvs} -q co -r b1 first-dir" \
+"U first-dir/file1"
+ cd first-dir
+ echo "file2 first revision" > file2
+ dotest branches2-8 "${testcvs} add file2" \
+"${PROG}"' [a-z]*: scheduling file `file2'\'' for addition on branch `b1'\''
+'"${PROG}"' [a-z]*: use .'"${PROG}"' commit. to add this file permanently'
+ mkdir dir1
+ dotest branches2-9 "${testcvs} add dir1" \
+"Directory ${TESTDIR}/cvsroot/first-dir/dir1 added to the repository
+--> Using per-directory sticky tag "'`'"b1'"
+ echo "file3 first revision" > dir1/file3
+ dotest branches2-10 "${testcvs} add dir1/file3" \
+"${PROG}"' [a-z]*: scheduling file `dir1/file3'\'' for addition on branch `b1'\''
+'"${PROG}"' [a-z]*: use .'"${PROG}"' commit. to add this file permanently'
+ dotest branches2-11 "${testcvs} -q ci -madd ." \
+"RCS file: ${TESTDIR}/cvsroot/first-dir/Attic/file2,v
+done
+Checking in file2;
+${TESTDIR}/cvsroot/first-dir/Attic/file2,v <-- file2
+new revision: 1\.1\.2\.1; previous revision: 1\.1
+done
+RCS file: ${TESTDIR}/cvsroot/first-dir/dir1/Attic/file3,v
+done
+Checking in dir1/file3;
+${TESTDIR}/cvsroot/first-dir/dir1/Attic/file3,v <-- file3
+new revision: 1\.1\.2\.1; previous revision: 1\.1
+done"
+
+ # Check out the second branch, and update the working
+ # directory to the first branch, to make sure the right
+ # happens with dir1.
+ cd ../..
+ mkdir b2; cd b2
+ dotest branches2-12 "${testcvs} -q co -r b2 first-dir" \
+'U first-dir/file1'
+ cd first-dir
+ dotest branches2-13 "${testcvs} update -d -r b1 dir1" \
+"${PROG} [a-z]*: Updating dir1
+U dir1/file3"
+ dotest branches2-14 "${testcvs} -q status" \
+"===================================================================
+File: file1 Status: Up-to-date
+
+ Working revision: 1\.1.*
+ Repository revision: 1\.1 ${TESTDIR}/cvsroot/first-dir/file1,v
+ Sticky Tag: b2 (branch: 1\.1\.4)
+ Sticky Date: (none)
+ Sticky Options: (none)
+
+===================================================================
+File: file3 Status: Up-to-date
+
+ Working revision: 1\.1\.2\.1.*
+ Repository revision: 1\.1\.2\.1 ${TESTDIR}/cvsroot/first-dir/dir1/Attic/file3,v
+ Sticky Tag: b1 (branch: 1\.1\.2)
+ Sticky Date: (none)
+ Sticky Options: (none)"
+
+ # FIXME: Just clobbering the directory like this is a bit
+ # tacky, although people generally expect it to work. Maybe
+ # we should release it instead. We do it a few other places
+ # below as well.
+ rm -r dir1
+ dotest branches2-15 "${testcvs} update -d -j b1 dir1" \
+"${PROG} [a-z]*: Updating dir1
+U dir1/file3"
+ # FIXCVS: The `No revision control file' stuff seems to be
+ # CVS's way of telling us that we're adding the file on a
+ # branch, and the file is not on that branch yet. This
+ # should be nicer.
+ dotest branches2-16 "${testcvs} -q status" \
+"===================================================================
+File: file1 Status: Up-to-date
+
+ Working revision: 1\.1.*
+ Repository revision: 1\.1 ${TESTDIR}/cvsroot/first-dir/file1,v
+ Sticky Tag: b2 (branch: 1\.1\.4)
+ Sticky Date: (none)
+ Sticky Options: (none)
+
+===================================================================
+File: file3 Status: Locally Added
+
+ Working revision: New file!
+ Repository revision: No revision control file
+ Sticky Tag: b2 - MISSING from RCS file!
+ Sticky Date: (none)
+ Sticky Options: (none)"
+
+ cd ../../trunk/first-dir
+ dotest branches2-17 "${testcvs} update -d -P dir1" \
+"${PROG} [a-z]*: Updating dir1"
+ dotest_fail branches2-18 "test -d dir1"
+ dotest branches2-19 "${testcvs} update -d -P -r b1 dir1" \
+"${PROG} [a-z]*: Updating dir1
+U dir1/file3"
+ dotest branches2-20 "${testcvs} -q status" \
+"===================================================================
+File: file1 Status: Up-to-date
+
+ Working revision: 1\.1.*
+ Repository revision: 1\.1 ${TESTDIR}/cvsroot/first-dir/file1,v
+ Sticky Tag: (none)
+ Sticky Date: (none)
+ Sticky Options: (none)
+
+===================================================================
+File: file3 Status: Up-to-date
+
+ Working revision: 1\.1\.2\.1.*
+ Repository revision: 1\.1\.2\.1 ${TESTDIR}/cvsroot/first-dir/dir1/Attic/file3,v
+ Sticky Tag: b1 (branch: 1\.1\.2)
+ Sticky Date: (none)
+ Sticky Options: (none)"
+
+ rm -r dir1
+ dotest branches2-21 "${testcvs} update -d -P -j b1 dir1" \
+"${PROG} [a-z]*: Updating dir1
+U dir1/file3"
+ dotest branches2-22 "${testcvs} -q status" \
+"===================================================================
+File: file1 Status: Up-to-date
+
+ Working revision: 1\.1.*
+ Repository revision: 1\.1 ${TESTDIR}/cvsroot/first-dir/file1,v
+ Sticky Tag: (none)
+ Sticky Date: (none)
+ Sticky Options: (none)
+
+===================================================================
+File: file3 Status: Locally Added
+
+ Working revision: New file!
+ Repository revision: 1\.1 ${TESTDIR}/cvsroot/first-dir/dir1/Attic/file3,v
+ Sticky Tag: (none)
+ Sticky Date: (none)
+ Sticky Options: (none)"
+
+ cd ../..
+ rm -r b1 b2
+
+ # Check out branch b1 twice. Crate a new directory in one
+ # working directory, then do a cvs update in the other
+ # working directory and see if the tags are right.
+ mkdir b1a
+ mkdir b1b
+ cd b1b
+ dotest branches2-23 "${testcvs} -q co -r b1 first-dir" \
+'U first-dir/file1
+U first-dir/file2
+U first-dir/dir1/file3'
+ cd ../b1a
+ dotest branches2-24 "${testcvs} -q co -r b1 first-dir" \
+'U first-dir/file1
+U first-dir/file2
+U first-dir/dir1/file3'
+ cd first-dir
+ mkdir dir2
+ dotest branches2-25 "${testcvs} add dir2" \
+"Directory ${TESTDIR}/cvsroot/first-dir/dir2 added to the repository
+--> Using per-directory sticky tag "'`'"b1'"
+ echo "file4 first revision" > dir2/file4
+ dotest branches2-26 "${testcvs} add dir2/file4" \
+"${PROG}"' [a-z]*: scheduling file `dir2/file4'\'' for addition on branch `b1'\''
+'"${PROG}"' [a-z]*: use .'"${PROG}"' commit. to add this file permanently'
+ dotest branches2-27 "${testcvs} -q commit -madd" \
+"RCS file: ${TESTDIR}/cvsroot/first-dir/dir2/Attic/file4,v
+done
+Checking in dir2/file4;
+${TESTDIR}/cvsroot/first-dir/dir2/Attic/file4,v <-- file4
+new revision: 1\.1\.2\.1; previous revision: 1\.1
+done"
+
+ cd ../../b1b/first-dir
+ dotest branches2-28 "${testcvs} update -d dir2" \
+"${PROG} [a-z]*: Updating dir2
+U dir2/file4"
+ cd dir2
+ dotest branches2-29 "${testcvs} -q status" \
+"===================================================================
+File: file4 Status: Up-to-date
+
+ Working revision: 1\.1\.2\.1.*
+ Repository revision: 1\.1\.2\.1 ${TESTDIR}/cvsroot/first-dir/dir2/Attic/file4,v
+ Sticky Tag: b1 (branch: 1\.1\.2)
+ Sticky Date: (none)
+ Sticky Options: (none)"
+ dotest branches2-30 "cat CVS/Tag" 'Tb1'
+
+ # Test update -A on a subdirectory
+ cd ..
+ rm -r dir2
+ dotest branches2-31 "${testcvs} update -A -d dir2" \
+"${PROG} [a-z]*: Updating dir2"
+ cd dir2
+ dotest branches2-32 "${testcvs} -q status" ''
+ dotest_fail branches2-33 "test -f CVS/Tag"
+
+ # Add a file on the trunk.
+ echo "file5 first revision" > file5
+ dotest branches2-34 "${testcvs} add file5" \
+"${PROG}"' [a-z]*: scheduling file `file5'\'' for addition
+'"${PROG}"' [a-z]*: use .'"${PROG}"' commit. to add this file permanently'
+ dotest branches2-35 "${testcvs} -q commit -madd" \
+"RCS file: ${TESTDIR}/cvsroot/first-dir/dir2/file5,v
+done
+Checking in file5;
+${TESTDIR}/cvsroot/first-dir/dir2/file5,v <-- file5
+initial revision: 1\.1
+done"
+
+ cd ../../../trunk/first-dir
+ dotest branches2-36 "${testcvs} -q update -d dir2" 'U dir2/file5'
+ cd dir2
+ dotest branches2-37 "${testcvs} -q status" \
+"===================================================================
+File: file5 Status: Up-to-date
+
+ Working revision: 1\.1.*
+ Repository revision: 1\.1 ${TESTDIR}/cvsroot/first-dir/dir2/file5,v
+ Sticky Tag: (none)
+ Sticky Date: (none)
+ Sticky Options: (none)"
+ dotest_fail branches2-38 "test -f CVS/status"
+
+ cd ../../..
+ rm -rf ${CVSROOT_DIRNAME}/first-dir
+ rm -r trunk b1a b1b
+ ;;
+
rcslib)
# Test librarification of RCS.
# First: test whether `cvs diff' handles $Name expansion
@@ -3567,11 +3883,12 @@ rev 2 of file 2
cd imp-dir
echo 'OpenMunger sources' >file1
echo 'OpenMunger sources' >file2
- dotest importb-1 \
+ dotest_sort importb-1 \
"${testcvs} import -m add first-dir openmunger openmunger-1_0" \
-"N first-dir/file1
-N first-dir/file2
+"
+N first-dir/file1
+N first-dir/file2
No conflicts created by this import"
cd ..
rm -r imp-dir
@@ -3583,15 +3900,16 @@ No conflicts created by this import"
echo 'FreeMunger sources' >file2
# Not completely sure how the conflict detection is supposed to
# be working here (haven't really thought about it).
- dotest importb-2 \
+ dotest_sort importb-2 \
"${testcvs} import -m add -b 1.1.3 first-dir freemunger freemunger-1_0" \
-"C first-dir/file1
-C first-dir/file2
+"
-2 conflicts created by this import.
-Use the following command to help the merge:
- ${PROG} checkout -jfreemunger:yesterday -jfreemunger first-dir"
+ ${PROG} checkout -jfreemunger:yesterday -jfreemunger first-dir
+2 conflicts created by this import.
+C first-dir/file1
+C first-dir/file2
+Use the following command to help the merge:"
cd ..
rm -r imp-dir
@@ -4920,8 +5238,20 @@ ${PROG} [a-z]*: warning: file2 is not (any longer) pertinent"
# OK, now add a directory to both working directories
# and see that CVS doesn't lose its mind.
mkdir sdir
- dotest conficts3-14 "${testcvs} add sdir" \
+ dotest conflicts3-14 "${testcvs} add sdir" \
"Directory ${TESTDIR}/cvsroot/first-dir/sdir added to the repository"
+ touch sdir/sfile
+ dotest conflicts3-14a "${testcvs} add sdir/sfile" \
+"${PROG} [a-z]*: scheduling file .sdir/sfile. for addition
+${PROG} [a-z]*: use .${PROG} commit. to add this file permanently"
+ dotest conflicts3-14b "${testcvs} -q ci -m add" \
+"RCS file: ${TESTDIR}/cvsroot/first-dir/sdir/sfile,v
+done
+Checking in sdir/sfile;
+${TESTDIR}/cvsroot/first-dir/sdir/sfile,v <-- sfile
+initial revision: 1\.1
+done"
+
cd ../../2/first-dir
# Create a CVS directory without the proper administrative
@@ -4946,6 +5276,11 @@ ${PROG} [a-z]*: ignoring sdir (CVS/Repository missing)"
dotest conflicts3-16 "${testcvs} -q update" \
"${QUESTION} sdir
${PROG} [a-z]*: ignoring sdir (CVS/Entries missing)"
+ cd ..
+ dotest conflicts3-16a "${testcvs} -q update first-dir" \
+"${QUESTION} first-dir/sdir
+${PROG} [a-z]*: ignoring first-dir/sdir (CVS/Entries missing)"
+ cd first-dir
fi
rm -r sdir
@@ -4960,8 +5295,40 @@ ${PROG} [a-z]*: ignoring sdir (CVS/Entries missing)"
touch newdir/CVS/Repository
dotest conflicts3-19 "${testcvs} -q update" \
"${PROG} [a-z]*: ignoring newdir (CVS/Entries missing)"
+ cd ..
+ dotest conflicts3-20 "${testcvs} -q update first-dir" \
+"${PROG} [a-z]*: ignoring first-dir/newdir (CVS/Entries missing)"
+ cd first-dir
rm -r newdir
+ # The previous tests have left CVS/Entries in something of a mess.
+ # While we "should" be able to deal with that (maybe), for now
+ # we just start over.
+ cd ..
+ rm -r first-dir
+ dotest conflicts3-20a "${testcvs} -q co -l first-dir" ''
+ cd first-dir
+
+ dotest conflicts3-21 "${testcvs} -q update -d sdir" "U sdir/sfile"
+ rm -r sdir/CVS
+ dotest conflicts3-22 "${testcvs} -q update" "? sdir"
+ if test "x$remote" = xyes; then
+ # It isn't particularly swift that CVS prints this
+ # "cannot open CVS/Entries" where it has already printed
+ # "? sdir". At least I don't think so. But do note: (1)
+ # non-fatal error, and (2) tells us which directory has
+ # the problem.
+ dotest_fail conflicts3-23 "${testcvs} -q update -PdA" \
+"${QUESTION} sdir
+${PROG} update: in directory sdir:
+${PROG} update: cannot open CVS/Entries for reading: No such file or directory
+${PROG} update: move away sdir/sfile; it is in the way
+C sdir/sfile"
+ else
+ dotest conflicts3-23 "${testcvs} -q update -PdA" \
+"${QUESTION} sdir"
+ fi
+
cd ../..
rm -r 1 2
@@ -4970,6 +5337,17 @@ ${PROG} [a-z]*: ignoring sdir (CVS/Entries missing)"
modules)
# Tests of various ways to define and use modules.
+ # Roadmap to various modules tests:
+ # -a:
+ # error on incorrect placement: modules
+ # error combining with other options: modules2-a*
+ # use to specify a file more than once: modules3
+ # use with ! feature: modules4
+ # regular modules: modules, modules2, cvsadm
+ # ampersand modules: modules2
+ # -s: modules.
+ # -d: modules, modules3, cvsadm
+ # slashes in module names: modules3
############################################################
# These tests are to make sure that administrative files get
@@ -5805,6 +6183,1652 @@ add-it
rm -rf ${CVSROOT_DIRNAME}/first-dir
;;
+ cvsadm)
+ # These test check the content of CVS' administrative
+ # files as they are checked out in various configurations.
+ # (As a side note, I'm not using the "-q" flag in any of
+ # this code, which should provide some extra checking for
+ # those messages which don't seem to be checked thoroughly
+ # anywhere else.) To do a thorough test, we need to make
+ # a bunch of modules in various configurations.
+ #
+ # <1mod> is a directory at the top level of cvsroot
+ # ``foo bar''
+ # <2mod> is a directory at the second level of cvsroot
+ # ``foo bar/baz''
+ # <1d1mod> is a directory at the top level which is
+ # checked out into another directory
+ # ``foo -d bar baz''
+ # <1d2mod> is a directory at the second level which is
+ # checked out into another directory
+ # ``foo -d bar baz/quux''
+ # <2d1mod> is a directory at the top level which is
+ # checked out into a directory that is two deep
+ # ``foo -d bar/baz quux''
+ # <2d2mod> is a directory at the second level which is
+ # checked out into a directory that is two deep
+ # ``foo -d bar/baz quux''
+ #
+ # The tests do each of these types separately and in twos.
+ # We also repeat each test -d flag for 1-deep and 2-deep
+ # directories.
+ #
+ # Each test should check the output for the Repository
+ # file, since that is the one which varies depending on
+ # the directory and how it was checked out.
+ #
+ # Yes, this is verbose, but at least it's very thorough.
+
+ # convenience variables
+ REP=${CVSROOT}
+
+ # First, check out the modules file and edit it.
+ mkdir 1; cd 1
+ dotest cvsadm-1 "${testcvs} co CVSROOT/modules" \
+"U CVSROOT/modules"
+
+ # Try to determine whether RELATIVE_REPOS is defined
+ # so that we can make the following a lot less
+ # verbose.
+
+ echo "${CVSROOT_DIRNAME}/." > ${TESTDIR}/dotest.abs
+ echo "." > ${TESTDIR}/dotest.rel
+ if cmp ${TESTDIR}/dotest.abs CVS/Repository >/dev/null 2>&1; then
+ AREP="${CVSROOT_DIRNAME}/"
+ elif cmp ${TESTDIR}/dotest.rel CVS/Repository >/dev/null 2>&1; then
+ AREP=""
+ else
+ fail "Cannot figure out if RELATIVE_REPOS is defined."
+ fi
+
+ # Test CVS/Root once. Since there is only one part of
+ # the code which writes CVS/Root files (Create_Admin),
+ # there is no point in testing this every time.
+ dotest cvsadm-1a "cat CVS/Root" ${REP}
+ dotest cvsadm-1b "cat CVS/Repository" \
+"${AREP}\."
+ dotest cvsadm-1c "cat CVSROOT/CVS/Root" ${REP}
+ dotest cvsadm-1d "cat CVSROOT/CVS/Repository" \
+"${AREP}CVSROOT"
+ # All of the defined module names begin with a number.
+ # All of the top-level directory names begin with "dir".
+ # All of the subdirectory names begin with "sub".
+ # All of the top-level modules begin with "mod".
+ echo "# Module defs for cvsadm tests" > CVSROOT/modules
+ echo "1mod mod1" >> CVSROOT/modules
+ echo "1mod-2 mod1-2" >> CVSROOT/modules
+ echo "2mod mod2/sub2" >> CVSROOT/modules
+ echo "2mod-2 mod2-2/sub2-2" >> CVSROOT/modules
+ echo "1d1mod -d dir1d1 mod1" >> CVSROOT/modules
+ echo "1d1mod-2 -d dir1d1-2 mod1-2" >> CVSROOT/modules
+ echo "1d2mod -d dir1d2 mod2/sub2" >> CVSROOT/modules
+ echo "1d2mod-2 -d dir1d2-2 mod2-2/sub2-2" >> CVSROOT/modules
+ echo "2d1mod -d dir2d1/sub2d1 mod1" >> CVSROOT/modules
+ echo "2d1mod-2 -d dir2d1-2/sub2d1-2 mod1-2" >> CVSROOT/modules
+ echo "2d2mod -d dir2d2/sub2d2 mod2/sub2" >> CVSROOT/modules
+ echo "2d2mod-2 -d dir2d2-2/sub2d2-2 mod2-2/sub2-2" >> CVSROOT/modules
+ dotest cvsadm-1e "${testcvs} ci -m add-modules" \
+"${PROG} [a-z]*: Examining .
+${PROG} [a-z]*: Examining CVSROOT
+Checking in CVSROOT/modules;
+${CVSROOT_DIRNAME}/CVSROOT/modules,v <-- modules
+new revision: 1\.[0-9]*; previous revision: 1\.[0-9]*
+done
+${PROG} [a-z]*: Rebuilding administrative file database"
+ rm -rf CVS CVSROOT;
+
+ # Create the various modules
+ mkdir ${CVSROOT_DIRNAME}/mod1
+ mkdir ${CVSROOT_DIRNAME}/mod1-2
+ mkdir ${CVSROOT_DIRNAME}/mod2
+ mkdir ${CVSROOT_DIRNAME}/mod2/sub2
+ mkdir ${CVSROOT_DIRNAME}/mod2-2
+ mkdir ${CVSROOT_DIRNAME}/mod2-2/sub2-2
+ dotest cvsadm-2 "${testcvs} co mod1 mod1-2 mod2 mod2-2" \
+"${PROG} [a-z]*: Updating mod1
+${PROG} [a-z]*: Updating mod1-2
+${PROG} [a-z]*: Updating mod2
+${PROG} [a-z]*: Updating mod2/sub2
+${PROG} [a-z]*: Updating mod2-2
+${PROG} [a-z]*: Updating mod2-2/sub2-2"
+
+ # Populate the directories for the halibut
+ echo "file1" > mod1/file1
+ echo "file1-2" > mod1-2/file1-2
+ echo "file2" > mod2/sub2/file2
+ echo "file2-2" > mod2-2/sub2-2/file2-2
+ dotest cvsadm-2a "${testcvs} add mod1/file1 mod1-2/file1-2 mod2/sub2/file2 mod2-2/sub2-2/file2-2" \
+"${PROG} [a-z]*: scheduling file .mod1/file1. for addition
+${PROG} [a-z]*: scheduling file .mod1-2/file1-2. for addition
+${PROG} [a-z]*: scheduling file .mod2/sub2/file2. for addition
+${PROG} [a-z]*: scheduling file .mod2-2/sub2-2/file2-2. for addition
+${PROG} [a-z]*: use '${PROG} commit' to add these files permanently"
+
+ dotest cvsadm-2b "${testcvs} ci -m yup mod1 mod1-2 mod2 mod2-2" \
+"${PROG} [a-z]*: Examining mod1
+${PROG} [a-z]*: Examining mod1-2
+${PROG} [a-z]*: Examining mod2
+${PROG} [a-z]*: Examining mod2/sub2
+${PROG} [a-z]*: Examining mod2-2
+${PROG} [a-z]*: Examining mod2-2/sub2-2
+RCS file: ${CVSROOT_DIRNAME}/mod1/file1,v
+done
+Checking in mod1/file1;
+${CVSROOT_DIRNAME}/mod1/file1,v <-- file1
+initial revision: 1.1
+done
+RCS file: ${CVSROOT_DIRNAME}/mod1-2/file1-2,v
+done
+Checking in mod1-2/file1-2;
+${CVSROOT_DIRNAME}/mod1-2/file1-2,v <-- file1-2
+initial revision: 1.1
+done
+RCS file: ${CVSROOT_DIRNAME}/mod2/sub2/file2,v
+done
+Checking in mod2/sub2/file2;
+${CVSROOT_DIRNAME}/mod2/sub2/file2,v <-- file2
+initial revision: 1.1
+done
+RCS file: ${CVSROOT_DIRNAME}/mod2-2/sub2-2/file2-2,v
+done
+Checking in mod2-2/sub2-2/file2-2;
+${CVSROOT_DIRNAME}/mod2-2/sub2-2/file2-2,v <-- file2-2
+initial revision: 1.1
+done"
+ # Finished creating the modules -- clean up.
+ rm -rf CVS mod1 mod1-2 mod2 mod2-2
+ # Done.
+
+ ##################################################
+ ## Start the dizzying array of possibilities.
+ ## Begin with each module type separately.
+ ##################################################
+
+ # Pattern -- after each checkout, first check the top-level
+ # CVS directory. Then, check the directories in numerical
+ # order.
+
+ dotest cvsadm-3 "${testcvs} co 1mod" \
+"${PROG} [a-z]*: Updating 1mod
+U 1mod/file1"
+ dotest cvsadm-3b "cat CVS/Repository" \
+"${AREP}\."
+ dotest cvsadm-3d "cat 1mod/CVS/Repository" \
+"${AREP}mod1"
+ rm -rf CVS 1mod
+
+ dotest cvsadm-4 "${testcvs} co 2mod" \
+"${PROG} [a-z]*: Updating 2mod
+U 2mod/file2"
+ dotest cvsadm-4b "cat CVS/Repository" \
+"${AREP}\."
+ dotest cvsadm-4d "cat 2mod/CVS/Repository" \
+"${AREP}mod2/sub2"
+ rm -rf CVS 2mod
+
+ dotest cvsadm-5 "${testcvs} co 1d1mod" \
+"${PROG} [a-z]*: Updating dir1d1
+U dir1d1/file1"
+ dotest cvsadm-5b "cat CVS/Repository" \
+"${AREP}\."
+ dotest cvsadm-5d "cat dir1d1/CVS/Repository" \
+"${AREP}mod1"
+ rm -rf CVS dir1d1
+
+ dotest cvsadm-6 "${testcvs} co 1d2mod" \
+"${PROG} [a-z]*: Updating dir1d2
+U dir1d2/file2"
+ dotest cvsadm-6b "cat CVS/Repository" \
+"${AREP}\."
+ dotest cvsadm-6d "cat dir1d2/CVS/Repository" \
+"${AREP}mod2/sub2"
+ rm -rf CVS dir1d2
+
+ dotest cvsadm-7 "${testcvs} co 2d1mod" \
+"${PROG} [a-z]*: Updating dir2d1/sub2d1
+U dir2d1/sub2d1/file1"
+ dotest cvsadm-7b "cat CVS/Repository" \
+"${AREP}\."
+ dotest cvsadm-7d "cat dir2d1/CVS/Repository" \
+"${AREP}CVSROOT/Emptydir"
+ dotest cvsadm-7f "cat dir2d1/sub2d1/CVS/Repository" \
+"${AREP}mod1"
+ rm -rf CVS dir2d1
+
+ dotest cvsadm-8 "${testcvs} co 2d2mod" \
+"${PROG} [a-z]*: Updating dir2d2/sub2d2
+U dir2d2/sub2d2/file2"
+ dotest cvsadm-8b "cat CVS/Repository" \
+"${AREP}\."
+ dotest cvsadm-8d "cat dir2d2/CVS/Repository" \
+"${AREP}CVSROOT/Emptydir"
+ dotest cvsadm-8f "cat dir2d2/sub2d2/CVS/Repository" \
+"${AREP}mod2/sub2"
+ rm -rf CVS dir2d2
+
+ ##################################################
+ ## You are in a shell script of twisted little
+ ## module combination statements, all alike.
+ ##################################################
+
+ ### 1mod
+
+ dotest cvsadm-9 "${testcvs} co 1mod 1mod-2" \
+"${PROG} [a-z]*: Updating 1mod
+U 1mod/file1
+${PROG} [a-z]*: Updating 1mod-2
+U 1mod-2/file1-2"
+ # the usual for the top level
+ dotest cvsadm-9b "cat CVS/Repository" \
+"${AREP}\."
+ # the usual for 1mod
+ dotest cvsadm-9d "cat 1mod/CVS/Repository" \
+"${AREP}mod1"
+ # the usual for 1mod copy
+ dotest cvsadm-9f "cat 1mod-2/CVS/Repository" \
+"${AREP}mod1-2"
+ rm -rf CVS 1mod 1mod-2
+
+ # 1mod 2mod redmod bluemod
+ dotest cvsadm-10 "${testcvs} co 1mod 2mod" \
+"${PROG} [a-z]*: Updating 1mod
+U 1mod/file1
+${PROG} [a-z]*: Updating 2mod
+U 2mod/file2"
+ # the usual for the top level
+ dotest cvsadm-10b "cat CVS/Repository" \
+"${AREP}\."
+ # the usual for 1mod
+ dotest cvsadm-10d "cat 1mod/CVS/Repository" \
+"${AREP}mod1"
+ # the usual for 2dmod
+ dotest cvsadm-10f "cat 2mod/CVS/Repository" \
+"${AREP}mod2/sub2"
+ rm -rf CVS 1mod 2mod
+
+ dotest cvsadm-11 "${testcvs} co 1mod 1d1mod" \
+"${PROG} [a-z]*: Updating 1mod
+U 1mod/file1
+${PROG} [a-z]*: Updating dir1d1
+U dir1d1/file1"
+ # the usual for the top level
+ dotest cvsadm-11b "cat CVS/Repository" \
+"${AREP}\."
+ # the usual for 1mod
+ dotest cvsadm-11d "cat 1mod/CVS/Repository" \
+"${AREP}mod1"
+ # the usual for 1d1mod
+ dotest cvsadm-11f "cat dir1d1/CVS/Repository" \
+"${AREP}mod1"
+ rm -rf CVS 1mod dir1d1
+
+ dotest cvsadm-12 "${testcvs} co 1mod 1d2mod" \
+"${PROG} [a-z]*: Updating 1mod
+U 1mod/file1
+${PROG} [a-z]*: Updating dir1d2
+U dir1d2/file2"
+ # the usual for the top level
+ dotest cvsadm-12b "cat CVS/Repository" \
+"${AREP}\."
+ # the usual for 1mod
+ dotest cvsadm-12d "cat 1mod/CVS/Repository" \
+"${AREP}mod1"
+ # the usual for 1d2mod
+ dotest cvsadm-12f "cat dir1d2/CVS/Repository" \
+"${AREP}mod2/sub2"
+ rm -rf CVS 1mod dir1d2
+
+ dotest cvsadm-13 "${testcvs} co 1mod 2d1mod" \
+"${PROG} [a-z]*: Updating 1mod
+U 1mod/file1
+${PROG} [a-z]*: Updating dir2d1/sub2d1
+U dir2d1/sub2d1/file1"
+ # the usual for the top level
+ dotest cvsadm-13b "cat CVS/Repository" \
+"${AREP}\."
+ # the usual for 1mod
+ dotest cvsadm-13d "cat 1mod/CVS/Repository" \
+"${AREP}mod1"
+ # the usual for 2d1mod
+ dotest cvsadm-13f "cat dir2d1/CVS/Repository" \
+"${AREP}CVSROOT/Emptydir"
+ dotest cvsadm-13h "cat dir2d1/sub2d1/CVS/Repository" \
+"${AREP}mod1"
+ rm -rf CVS 1mod dir2d1
+
+ dotest cvsadm-14 "${testcvs} co 1mod 2d2mod" \
+"${PROG} [a-z]*: Updating 1mod
+U 1mod/file1
+${PROG} [a-z]*: Updating dir2d2/sub2d2
+U dir2d2/sub2d2/file2"
+ # the usual for the top level
+ dotest cvsadm-14b "cat CVS/Repository" \
+"${AREP}\."
+ # the usual for 1mod
+ dotest cvsadm-14d "cat 1mod/CVS/Repository" \
+"${AREP}mod1"
+ # the usual for 2d2mod
+ dotest cvsadm-14f "cat dir2d2/CVS/Repository" \
+"${AREP}CVSROOT/Emptydir"
+ dotest cvsadm-14h "cat dir2d2/sub2d2/CVS/Repository" \
+"${AREP}mod2/sub2"
+ rm -rf CVS 1mod dir2d2
+
+
+ ### 2mod
+
+ dotest cvsadm-15 "${testcvs} co 2mod 2mod-2" \
+"${PROG} [a-z]*: Updating 2mod
+U 2mod/file2
+${PROG} [a-z]*: Updating 2mod-2
+U 2mod-2/file2-2"
+ # the usual for the top level
+ dotest cvsadm-15b "cat CVS/Repository" \
+"${AREP}\."
+ # the usual for 2mod
+ dotest cvsadm-15d "cat 2mod/CVS/Repository" \
+"${AREP}mod2/sub2"
+ # the usual for 2mod copy
+ dotest cvsadm-15f "cat 2mod-2/CVS/Repository" \
+"${AREP}mod2-2/sub2-2"
+ rm -rf CVS 2mod 2mod-2
+
+
+ dotest cvsadm-16 "${testcvs} co 2mod 1d1mod" \
+"${PROG} [a-z]*: Updating 2mod
+U 2mod/file2
+${PROG} [a-z]*: Updating dir1d1
+U dir1d1/file1"
+ # the usual for the top level
+ dotest cvsadm-16b "cat CVS/Repository" \
+"${AREP}\."
+ # the usual for 2mod
+ dotest cvsadm-16d "cat 2mod/CVS/Repository" \
+"${AREP}mod2/sub2"
+ # the usual for 1d1mod
+ dotest cvsadm-16f "cat dir1d1/CVS/Repository" \
+"${AREP}mod1"
+ rm -rf CVS 2mod dir1d1
+
+ dotest cvsadm-17 "${testcvs} co 2mod 1d2mod" \
+"${PROG} [a-z]*: Updating 2mod
+U 2mod/file2
+${PROG} [a-z]*: Updating dir1d2
+U dir1d2/file2"
+ # the usual for the top level
+ dotest cvsadm-17b "cat CVS/Repository" \
+"${AREP}\."
+ # the usual for 2mod
+ dotest cvsadm-17d "cat 2mod/CVS/Repository" \
+"${AREP}mod2/sub2"
+ # the usual for 1d2mod
+ dotest cvsadm-17f "cat dir1d2/CVS/Repository" \
+"${AREP}mod2/sub2"
+ rm -rf CVS 2mod dir1d2
+
+ dotest cvsadm-18 "${testcvs} co 2mod 2d1mod" \
+"${PROG} [a-z]*: Updating 2mod
+U 2mod/file2
+${PROG} [a-z]*: Updating dir2d1/sub2d1
+U dir2d1/sub2d1/file1"
+ # the usual for the top level
+ dotest cvsadm-18b "cat CVS/Repository" \
+"${AREP}\."
+ # the usual for 2mod
+ dotest cvsadm-18d "cat 2mod/CVS/Repository" \
+"${AREP}mod2/sub2"
+ # the usual for 2d1mod
+ dotest cvsadm-18f "cat dir2d1/CVS/Repository" \
+"${AREP}CVSROOT/Emptydir"
+ dotest cvsadm-18h "cat dir2d1/sub2d1/CVS/Repository" \
+"${AREP}mod1"
+ rm -rf CVS 2mod dir2d1
+
+ dotest cvsadm-19 "${testcvs} co 2mod 2d2mod" \
+"${PROG} [a-z]*: Updating 2mod
+U 2mod/file2
+${PROG} [a-z]*: Updating dir2d2/sub2d2
+U dir2d2/sub2d2/file2"
+ # the usual for the top level
+ dotest cvsadm-19b "cat CVS/Repository" \
+"${AREP}\."
+ # the usual for 2mod
+ dotest cvsadm-19d "cat 2mod/CVS/Repository" \
+"${AREP}mod2/sub2"
+ # the usual for 2d2mod
+ dotest cvsadm-19f "cat dir2d2/CVS/Repository" \
+"${AREP}CVSROOT/Emptydir"
+ dotest cvsadm-19h "cat dir2d2/sub2d2/CVS/Repository" \
+"${AREP}mod2/sub2"
+ rm -rf CVS 2mod dir2d2
+
+
+ ### 1d1mod
+
+ dotest cvsadm-20 "${testcvs} co 1d1mod 1d1mod-2" \
+"${PROG} [a-z]*: Updating dir1d1
+U dir1d1/file1
+${PROG} [a-z]*: Updating dir1d1-2
+U dir1d1-2/file1-2"
+ # the usual for the top level
+ dotest cvsadm-20b "cat CVS/Repository" \
+"${AREP}\."
+ # the usual for 1d1mod
+ dotest cvsadm-20d "cat dir1d1/CVS/Repository" \
+"${AREP}mod1"
+ # the usual for 1d1mod copy
+ dotest cvsadm-20f "cat dir1d1-2/CVS/Repository" \
+"${AREP}mod1-2"
+ rm -rf CVS dir1d1 dir1d1-2
+
+ dotest cvsadm-21 "${testcvs} co 1d1mod 1d2mod" \
+"${PROG} [a-z]*: Updating dir1d1
+U dir1d1/file1
+${PROG} [a-z]*: Updating dir1d2
+U dir1d2/file2"
+ # the usual for the top level
+ dotest cvsadm-21b "cat CVS/Repository" \
+"${AREP}\."
+ # the usual for 1d1mod
+ dotest cvsadm-21d "cat dir1d1/CVS/Repository" \
+"${AREP}mod1"
+ # the usual for 1d2mod
+ dotest cvsadm-21f "cat dir1d2/CVS/Repository" \
+"${AREP}mod2/sub2"
+ rm -rf CVS dir1d1 dir1d2
+
+ dotest cvsadm-22 "${testcvs} co 1d1mod 2d1mod" \
+"${PROG} [a-z]*: Updating dir1d1
+U dir1d1/file1
+${PROG} [a-z]*: Updating dir2d1/sub2d1
+U dir2d1/sub2d1/file1"
+ # the usual for the top level
+ dotest cvsadm-22b "cat CVS/Repository" \
+"${AREP}\."
+ # the usual for 1d1mod
+ dotest cvsadm-22d "cat dir1d1/CVS/Repository" \
+"${AREP}mod1"
+ # the usual for 2d1mod
+ dotest cvsadm-22f "cat dir2d1/CVS/Repository" \
+"${AREP}CVSROOT/Emptydir"
+ dotest cvsadm-22h "cat dir2d1/sub2d1/CVS/Repository" \
+"${AREP}mod1"
+ rm -rf CVS dir1d1 dir2d1
+
+ dotest cvsadm-23 "${testcvs} co 1d1mod 2d2mod" \
+"${PROG} [a-z]*: Updating dir1d1
+U dir1d1/file1
+${PROG} [a-z]*: Updating dir2d2/sub2d2
+U dir2d2/sub2d2/file2"
+ # the usual for the top level
+ dotest cvsadm-23b "cat CVS/Repository" \
+"${AREP}\."
+ # the usual for 1d1mod
+ dotest cvsadm-23d "cat dir1d1/CVS/Repository" \
+"${AREP}mod1"
+ # the usual for 2d2mod
+ dotest cvsadm-23f "cat dir2d2/CVS/Repository" \
+"${AREP}CVSROOT/Emptydir"
+ dotest cvsadm-23h "cat dir2d2/sub2d2/CVS/Repository" \
+"${AREP}mod2/sub2"
+ rm -rf CVS dir1d1 dir2d2
+
+
+ ### 1d2mod
+
+ dotest cvsadm-24 "${testcvs} co 1d2mod 1d2mod-2" \
+"${PROG} [a-z]*: Updating dir1d2
+U dir1d2/file2
+${PROG} [a-z]*: Updating dir1d2-2
+U dir1d2-2/file2-2"
+ # the usual for the top level
+ dotest cvsadm-24b "cat CVS/Repository" \
+"${AREP}\."
+ # the usual for 1d2mod
+ dotest cvsadm-24d "cat dir1d2/CVS/Repository" \
+"${AREP}mod2/sub2"
+ # the usual for 1d2mod copy
+ dotest cvsadm-24f "cat dir1d2-2/CVS/Repository" \
+"${AREP}mod2-2/sub2-2"
+ rm -rf CVS dir1d2 dir1d2-2
+
+ dotest cvsadm-25 "${testcvs} co 1d2mod 2d1mod" \
+"${PROG} [a-z]*: Updating dir1d2
+U dir1d2/file2
+${PROG} [a-z]*: Updating dir2d1/sub2d1
+U dir2d1/sub2d1/file1"
+ # the usual for the top level
+ dotest cvsadm-25b "cat CVS/Repository" \
+"${AREP}\."
+ # the usual for 1d2mod
+ dotest cvsadm-25d "cat dir1d2/CVS/Repository" \
+"${AREP}mod2/sub2"
+ # the usual for 2d1mod
+ dotest cvsadm-25f "cat dir2d1/CVS/Repository" \
+"${AREP}CVSROOT/Emptydir"
+ dotest cvsadm-25h "cat dir2d1/sub2d1/CVS/Repository" \
+"${AREP}mod1"
+ rm -rf CVS dir1d2 dir2d1
+
+ dotest cvsadm-26 "${testcvs} co 1d2mod 2d2mod" \
+"${PROG} [a-z]*: Updating dir1d2
+U dir1d2/file2
+${PROG} [a-z]*: Updating dir2d2/sub2d2
+U dir2d2/sub2d2/file2"
+ # the usual for the top level
+ dotest cvsadm-26b "cat CVS/Repository" \
+"${AREP}\."
+ # the usual for 1d2mod
+ dotest cvsadm-26d "cat dir1d2/CVS/Repository" \
+"${AREP}mod2/sub2"
+ # the usual for 2d2mod
+ dotest cvsadm-26f "cat dir2d2/CVS/Repository" \
+"${AREP}CVSROOT/Emptydir"
+ dotest cvsadm-26h "cat dir2d2/sub2d2/CVS/Repository" \
+"${AREP}mod2/sub2"
+ rm -rf CVS dir1d2 dir2d2
+
+
+ # 2d1mod
+
+ dotest cvsadm-27 "${testcvs} co 2d1mod 2d1mod-2" \
+"${PROG} [a-z]*: Updating dir2d1/sub2d1
+U dir2d1/sub2d1/file1
+${PROG} [a-z]*: Updating dir2d1-2/sub2d1-2
+U dir2d1-2/sub2d1-2/file1-2"
+ # the usual for the top level
+ dotest cvsadm-27b "cat CVS/Repository" \
+"${AREP}\."
+ # the usual for 2d1mod
+ dotest cvsadm-27d "cat dir2d1/CVS/Repository" \
+"${AREP}CVSROOT/Emptydir"
+ dotest cvsadm-27f "cat dir2d1/sub2d1/CVS/Repository" \
+"${AREP}mod1"
+ # the usual for 2d1mod
+ dotest cvsadm-27h "cat dir2d1-2/CVS/Repository" \
+"${AREP}CVSROOT/Emptydir"
+ dotest cvsadm-27j "cat dir2d1-2/sub2d1-2/CVS/Repository" \
+"${AREP}mod1-2"
+ rm -rf CVS dir2d1 dir2d1-2
+
+ dotest cvsadm-28 "${testcvs} co 2d1mod 2d2mod" \
+"${PROG} [a-z]*: Updating dir2d1/sub2d1
+U dir2d1/sub2d1/file1
+${PROG} [a-z]*: Updating dir2d2/sub2d2
+U dir2d2/sub2d2/file2"
+ # the usual for the top level
+ dotest cvsadm-28b "cat CVS/Repository" \
+"${AREP}\."
+ # the usual for 2d1mod
+ dotest cvsadm-28d "cat dir2d1/CVS/Repository" \
+"${AREP}CVSROOT/Emptydir"
+ dotest cvsadm-28f "cat dir2d1/sub2d1/CVS/Repository" \
+"${AREP}mod1"
+ # the usual for 2d2mod
+ dotest cvsadm-28h "cat dir2d2/CVS/Repository" \
+"${AREP}CVSROOT/Emptydir"
+ dotest cvsadm-28j "cat dir2d2/sub2d2/CVS/Repository" \
+"${AREP}mod2/sub2"
+ rm -rf CVS dir2d1 dir2d2
+
+
+ # 2d2mod
+
+ dotest cvsadm-29 "${testcvs} co 2d2mod 2d2mod-2" \
+"${PROG} [a-z]*: Updating dir2d2/sub2d2
+U dir2d2/sub2d2/file2
+${PROG} [a-z]*: Updating dir2d2-2/sub2d2-2
+U dir2d2-2/sub2d2-2/file2-2"
+ # the usual for the top level
+ dotest cvsadm-29b "cat CVS/Repository" \
+"${AREP}\."
+ # the usual for 2d2mod
+ dotest cvsadm-29d "cat dir2d2/CVS/Repository" \
+"${AREP}CVSROOT/Emptydir"
+ dotest cvsadm-29f "cat dir2d2/sub2d2/CVS/Repository" \
+"${AREP}mod2/sub2"
+ # the usual for 2d2mod
+ dotest cvsadm-29h "cat dir2d2-2/CVS/Repository" \
+"${AREP}CVSROOT/Emptydir"
+ dotest cvsadm-29j "cat dir2d2-2/sub2d2-2/CVS/Repository" \
+"${AREP}mod2-2/sub2-2"
+ rm -rf CVS dir2d2 dir2d2-2
+
+ ##################################################
+ ## And now, all of that again using the "-d" flag
+ ## on the command line.
+ ##################################################
+
+ dotest cvsadm-1d3 "${testcvs} co -d dir 1mod" \
+"${PROG} [a-z]*: Updating dir
+U dir/file1"
+ dotest cvsadm-1d3b "cat CVS/Repository" \
+"${AREP}\."
+ dotest cvsadm-1d3d "cat dir/CVS/Repository" \
+"${AREP}mod1"
+ rm -rf CVS dir
+
+ dotest cvsadm-1d4 "${testcvs} co -d dir 2mod" \
+"${PROG} [a-z]*: Updating dir
+U dir/file2"
+ dotest cvsadm-1d4b "cat CVS/Repository" \
+"${AREP}\."
+ dotest cvsadm-1d4d "cat dir/CVS/Repository" \
+"${AREP}mod2/sub2"
+ rm -rf CVS dir
+
+ dotest cvsadm-1d5 "${testcvs} co -d dir 1d1mod" \
+"${PROG} [a-z]*: Updating dir
+U dir/file1"
+ dotest cvsadm-1d5b "cat CVS/Repository" \
+"${AREP}\."
+ dotest cvsadm-1d5d "cat dir/CVS/Repository" \
+"${AREP}mod1"
+ rm -rf CVS dir
+
+ dotest cvsadm-1d6 "${testcvs} co -d dir 1d2mod" \
+"${PROG} [a-z]*: Updating dir
+U dir/file2"
+ dotest cvsadm-1d6b "cat CVS/Repository" \
+"${AREP}\."
+ dotest cvsadm-1d6d "cat dir/CVS/Repository" \
+"${AREP}mod2/sub2"
+ rm -rf CVS dir
+
+ dotest cvsadm-1d7 "${testcvs} co -d dir 2d1mod" \
+"${PROG} [a-z]*: Updating dir
+U dir/file1"
+ dotest cvsadm-1d7b "cat CVS/Repository" \
+"${AREP}\."
+ dotest cvsadm-1d7d "cat dir/CVS/Repository" \
+"${AREP}mod1"
+ rm -rf CVS dir
+
+ dotest cvsadm-1d8 "${testcvs} co -d dir 2d2mod" \
+"${PROG} [a-z]*: Updating dir
+U dir/file2"
+ dotest cvsadm-1d8b "cat CVS/Repository" \
+"${AREP}\."
+ dotest cvsadm-1d8d "cat dir/CVS/Repository" \
+"${AREP}mod2/sub2"
+ rm -rf CVS dir
+
+ ##################################################
+ ## Los Combonaciones
+ ##################################################
+
+ ### 1mod
+
+ dotest cvsadm-1d9 "${testcvs} co -d dir 1mod 1mod-2" \
+"${PROG} [a-z]*: Updating dir/1mod
+U dir/1mod/file1
+${PROG} [a-z]*: Updating dir/1mod-2
+U dir/1mod-2/file1-2"
+ # the usual for the top level
+ dotest cvsadm-1d9b "cat CVS/Repository" \
+"${AREP}\."
+ # the usual for the dir level
+ dotest cvsadm-1d9d "cat dir/CVS/Repository" \
+"${AREP}\."
+ # the usual for 1mod
+ dotest cvsadm-1d9f "cat dir/1mod/CVS/Repository" \
+"${AREP}mod1"
+ # the usual for 1mod copy
+ dotest cvsadm-1d9h "cat dir/1mod-2/CVS/Repository" \
+"${AREP}mod1-2"
+ rm -rf CVS dir
+
+ # 1mod 2mod redmod bluemod
+ dotest cvsadm-1d10 "${testcvs} co -d dir 1mod 2mod" \
+"${PROG} [a-z]*: Updating dir/1mod
+U dir/1mod/file1
+${PROG} [a-z]*: Updating dir/2mod
+U dir/2mod/file2"
+ dotest cvsadm-1d10b "cat CVS/Repository" \
+"${AREP}\."
+ # the usual for the dir level
+ dotest cvsadm-1d10d "cat dir/CVS/Repository" \
+"${AREP}\."
+ # the usual for 1mod
+ dotest cvsadm-1d10f "cat dir/1mod/CVS/Repository" \
+"${AREP}mod1"
+ # the usual for 2dmod
+ dotest cvsadm-1d10h "cat dir/2mod/CVS/Repository" \
+"${AREP}mod2/sub2"
+ rm -rf CVS dir
+
+ dotest cvsadm-1d11 "${testcvs} co -d dir 1mod 1d1mod" \
+"${PROG} [a-z]*: Updating dir/1mod
+U dir/1mod/file1
+${PROG} [a-z]*: Updating dir/dir1d1
+U dir/dir1d1/file1"
+ dotest cvsadm-1d11b "cat CVS/Repository" \
+"${AREP}\."
+ # the usual for the dir level
+ dotest cvsadm-1d11d "cat dir/CVS/Repository" \
+"${AREP}\."
+ # the usual for 1mod
+ dotest cvsadm-1d11f "cat dir/1mod/CVS/Repository" \
+"${AREP}mod1"
+ # the usual for 1d1mod
+ dotest cvsadm-1d11h "cat dir/dir1d1/CVS/Repository" \
+"${AREP}mod1"
+ rm -rf CVS dir
+
+ dotest cvsadm-1d12 "${testcvs} co -d dir 1mod 1d2mod" \
+"${PROG} [a-z]*: Updating dir/1mod
+U dir/1mod/file1
+${PROG} [a-z]*: Updating dir/dir1d2
+U dir/dir1d2/file2"
+ dotest cvsadm-1d12b "cat CVS/Repository" \
+"${AREP}\."
+ # the usual for the dir level
+ dotest cvsadm-1d12d "cat dir/CVS/Repository" \
+"${AREP}\."
+ # the usual for 1mod
+ dotest cvsadm-1d12f "cat dir/1mod/CVS/Repository" \
+"${AREP}mod1"
+ # the usual for 1d2mod
+ dotest cvsadm-1d12h "cat dir/dir1d2/CVS/Repository" \
+"${AREP}mod2/sub2"
+ rm -rf CVS dir
+
+ dotest cvsadm-1d13 "${testcvs} co -d dir 1mod 2d1mod" \
+"${PROG} [a-z]*: Updating dir/1mod
+U dir/1mod/file1
+${PROG} [a-z]*: Updating dir/dir2d1/sub2d1
+U dir/dir2d1/sub2d1/file1"
+ dotest cvsadm-1d13b "cat CVS/Repository" \
+"${AREP}\."
+ # the usual for the dir level
+ dotest cvsadm-1d13d "cat dir/CVS/Repository" \
+"${AREP}\."
+ # the usual for 1mod
+ dotest cvsadm-1d13f "cat dir/1mod/CVS/Repository" \
+"${AREP}mod1"
+ # the usual for 2d1mod
+ dotest cvsadm-1d13h "cat dir/dir2d1/CVS/Repository" \
+"${AREP}CVSROOT/Emptydir"
+ dotest cvsadm-1d13j "cat dir/dir2d1/sub2d1/CVS/Repository" \
+"${AREP}mod1"
+ rm -rf CVS dir
+
+ dotest cvsadm-1d14 "${testcvs} co -d dir 1mod 2d2mod" \
+"${PROG} [a-z]*: Updating dir/1mod
+U dir/1mod/file1
+${PROG} [a-z]*: Updating dir/dir2d2/sub2d2
+U dir/dir2d2/sub2d2/file2"
+ dotest cvsadm-1d14b "cat CVS/Repository" \
+"${AREP}\."
+ # the usual for the dir level
+ dotest cvsadm-1d14d "cat dir/CVS/Repository" \
+"${AREP}\."
+ # the usual for 1mod
+ dotest cvsadm-1d14f "cat dir/1mod/CVS/Repository" \
+"${AREP}mod1"
+ # the usual for 2d2mod
+ dotest cvsadm-1d14h "cat dir/dir2d2/CVS/Repository" \
+"${AREP}CVSROOT/Emptydir"
+ dotest cvsadm-1d14j "cat dir/dir2d2/sub2d2/CVS/Repository" \
+"${AREP}mod2/sub2"
+ rm -rf CVS dir
+
+
+ ### 2mod
+
+ dotest cvsadm-1d15 "${testcvs} co -d dir 2mod 2mod-2" \
+"${PROG} [a-z]*: Updating dir/2mod
+U dir/2mod/file2
+${PROG} [a-z]*: Updating dir/2mod-2
+U dir/2mod-2/file2-2"
+ dotest cvsadm-1d15b "cat CVS/Repository" \
+"${AREP}\."
+ # the usual for the dir level
+ dotest cvsadm-1d15d "cat dir/CVS/Repository" \
+"${AREP}\."
+ # the usual for 2mod
+ dotest cvsadm-1d15f "cat dir/2mod/CVS/Repository" \
+"${AREP}mod2/sub2"
+ # the usual for 2mod copy
+ dotest cvsadm-1d15h "cat dir/2mod-2/CVS/Repository" \
+"${AREP}mod2-2/sub2-2"
+ rm -rf CVS dir
+
+ dotest cvsadm-1d16 "${testcvs} co -d dir 2mod 1d1mod" \
+"${PROG} [a-z]*: Updating dir/2mod
+U dir/2mod/file2
+${PROG} [a-z]*: Updating dir/dir1d1
+U dir/dir1d1/file1"
+ dotest cvsadm-1d16b "cat CVS/Repository" \
+"${AREP}\."
+ # the usual for the dir level
+ dotest cvsadm-1d16d "cat dir/CVS/Repository" \
+"${AREP}\."
+ # the usual for 2mod
+ dotest cvsadm-1d16f "cat dir/2mod/CVS/Repository" \
+"${AREP}mod2/sub2"
+ # the usual for 1d1mod
+ dotest cvsadm-1d16h "cat dir/dir1d1/CVS/Repository" \
+"${AREP}mod1"
+ rm -rf CVS dir
+
+ dotest cvsadm-1d17 "${testcvs} co -d dir 2mod 1d2mod" \
+"${PROG} [a-z]*: Updating dir/2mod
+U dir/2mod/file2
+${PROG} [a-z]*: Updating dir/dir1d2
+U dir/dir1d2/file2"
+ dotest cvsadm-1d17b "cat CVS/Repository" \
+"${AREP}\."
+ # the usual for the dir level
+ dotest cvsadm-1d17d "cat dir/CVS/Repository" \
+"${AREP}\."
+ # the usual for 2mod
+ dotest cvsadm-1d17f "cat dir/2mod/CVS/Repository" \
+"${AREP}mod2/sub2"
+ # the usual for 1d2mod
+ dotest cvsadm-1d17h "cat dir/dir1d2/CVS/Repository" \
+"${AREP}mod2/sub2"
+ rm -rf CVS dir
+
+ dotest cvsadm-1d18 "${testcvs} co -d dir 2mod 2d1mod" \
+"${PROG} [a-z]*: Updating dir/2mod
+U dir/2mod/file2
+${PROG} [a-z]*: Updating dir/dir2d1/sub2d1
+U dir/dir2d1/sub2d1/file1"
+ dotest cvsadm-1d18b "cat CVS/Repository" \
+"${AREP}\."
+ # the usual for the dir level
+ dotest cvsadm-1d18d "cat dir/CVS/Repository" \
+"${AREP}\."
+ # the usual for 2mod
+ dotest cvsadm-1d18f "cat dir/2mod/CVS/Repository" \
+"${AREP}mod2/sub2"
+ # the usual for 2d1mod
+ dotest cvsadm-1d18h "cat dir/dir2d1/CVS/Repository" \
+"${AREP}CVSROOT/Emptydir"
+ dotest cvsadm-1d18j "cat dir/dir2d1/sub2d1/CVS/Repository" \
+"${AREP}mod1"
+ rm -rf CVS dir
+
+ dotest cvsadm-1d19 "${testcvs} co -d dir 2mod 2d2mod" \
+"${PROG} [a-z]*: Updating dir/2mod
+U dir/2mod/file2
+${PROG} [a-z]*: Updating dir/dir2d2/sub2d2
+U dir/dir2d2/sub2d2/file2"
+ dotest cvsadm-1d19b "cat CVS/Repository" \
+"${AREP}\."
+ # the usual for the dir level
+ dotest cvsadm-1d19d "cat dir/CVS/Repository" \
+"${AREP}\."
+ # the usual for 2mod
+ dotest cvsadm-1d19f "cat dir/2mod/CVS/Repository" \
+"${AREP}mod2/sub2"
+ # the usual for 2d2mod
+ dotest cvsadm-1d19h "cat dir/dir2d2/CVS/Repository" \
+"${AREP}CVSROOT/Emptydir"
+ dotest cvsadm-1d19j "cat dir/dir2d2/sub2d2/CVS/Repository" \
+"${AREP}mod2/sub2"
+ rm -rf CVS dir
+
+
+ ### 1d1mod
+
+ dotest cvsadm-1d20 "${testcvs} co -d dir 1d1mod 1d1mod-2" \
+"${PROG} [a-z]*: Updating dir/dir1d1
+U dir/dir1d1/file1
+${PROG} [a-z]*: Updating dir/dir1d1-2
+U dir/dir1d1-2/file1-2"
+ dotest cvsadm-1d20b "cat CVS/Repository" \
+"${AREP}\."
+ # the usual for the dir level
+ dotest cvsadm-1d20d "cat dir/CVS/Repository" \
+"${AREP}\."
+ # the usual for 1d1mod
+ dotest cvsadm-1d20f "cat dir/dir1d1/CVS/Repository" \
+"${AREP}mod1"
+ # the usual for 1d1mod copy
+ dotest cvsadm-1d20h "cat dir/dir1d1-2/CVS/Repository" \
+"${AREP}mod1-2"
+ rm -rf CVS dir
+
+ dotest cvsadm-1d21 "${testcvs} co -d dir 1d1mod 1d2mod" \
+"${PROG} [a-z]*: Updating dir/dir1d1
+U dir/dir1d1/file1
+${PROG} [a-z]*: Updating dir/dir1d2
+U dir/dir1d2/file2"
+ dotest cvsadm-1d21b "cat CVS/Repository" \
+"${AREP}\."
+ # the usual for the dir level
+ dotest cvsadm-1d21d "cat dir/CVS/Repository" \
+"${AREP}\."
+ # the usual for 1d1mod
+ dotest cvsadm-1d21f "cat dir/dir1d1/CVS/Repository" \
+"${AREP}mod1"
+ # the usual for 1d2mod
+ dotest cvsadm-1d21h "cat dir/dir1d2/CVS/Repository" \
+"${AREP}mod2/sub2"
+ rm -rf CVS dir
+
+ dotest cvsadm-1d22 "${testcvs} co -d dir 1d1mod 2d1mod" \
+"${PROG} [a-z]*: Updating dir/dir1d1
+U dir/dir1d1/file1
+${PROG} [a-z]*: Updating dir/dir2d1/sub2d1
+U dir/dir2d1/sub2d1/file1"
+ dotest cvsadm-1d22b "cat CVS/Repository" \
+"${AREP}\."
+ # the usual for the dir level
+ dotest cvsadm-1d22d "cat dir/CVS/Repository" \
+"${AREP}\."
+ # the usual for 1d1mod
+ dotest cvsadm-1d22f "cat dir/dir1d1/CVS/Repository" \
+"${AREP}mod1"
+ # the usual for 2d1mod
+ dotest cvsadm-1d22h "cat dir/dir2d1/CVS/Repository" \
+"${AREP}CVSROOT/Emptydir"
+ dotest cvsadm-1d22j "cat dir/dir2d1/sub2d1/CVS/Repository" \
+"${AREP}mod1"
+ rm -rf CVS dir
+
+ dotest cvsadm-1d23 "${testcvs} co -d dir 1d1mod 2d2mod" \
+"${PROG} [a-z]*: Updating dir/dir1d1
+U dir/dir1d1/file1
+${PROG} [a-z]*: Updating dir/dir2d2/sub2d2
+U dir/dir2d2/sub2d2/file2"
+ dotest cvsadm-1d23b "cat CVS/Repository" \
+"${AREP}\."
+ # the usual for the dir level
+ dotest cvsadm-1d23d "cat dir/CVS/Repository" \
+"${AREP}\."
+ # the usual for 1d1mod
+ dotest cvsadm-1d23f "cat dir/dir1d1/CVS/Repository" \
+"${AREP}mod1"
+ # the usual for 2d2mod
+ dotest cvsadm-1d23h "cat dir/dir2d2/CVS/Repository" \
+"${AREP}CVSROOT/Emptydir"
+ dotest cvsadm-1d23j "cat dir/dir2d2/sub2d2/CVS/Repository" \
+"${AREP}mod2/sub2"
+ rm -rf CVS dir
+
+
+ ### 1d2mod
+
+ dotest cvsadm-1d24 "${testcvs} co -d dir 1d2mod 1d2mod-2" \
+"${PROG} [a-z]*: Updating dir/dir1d2
+U dir/dir1d2/file2
+${PROG} [a-z]*: Updating dir/dir1d2-2
+U dir/dir1d2-2/file2-2"
+ dotest cvsadm-1d24b "cat CVS/Repository" \
+"${AREP}\."
+ # the usual for the dir level
+ dotest cvsadm-1d24d "cat dir/CVS/Repository" \
+"${AREP}\."
+ # the usual for 1d2mod
+ dotest cvsadm-1d24f "cat dir/dir1d2/CVS/Repository" \
+"${AREP}mod2/sub2"
+ # the usual for 1d2mod copy
+ dotest cvsadm-1d24h "cat dir/dir1d2-2/CVS/Repository" \
+"${AREP}mod2-2/sub2-2"
+ rm -rf CVS dir
+
+ dotest cvsadm-1d25 "${testcvs} co -d dir 1d2mod 2d1mod" \
+"${PROG} [a-z]*: Updating dir/dir1d2
+U dir/dir1d2/file2
+${PROG} [a-z]*: Updating dir/dir2d1/sub2d1
+U dir/dir2d1/sub2d1/file1"
+ dotest cvsadm-1d25b "cat CVS/Repository" \
+"${AREP}\."
+ # the usual for the dir level
+ dotest cvsadm-1d25d "cat dir/CVS/Repository" \
+"${AREP}\."
+ # the usual for 1d2mod
+ dotest cvsadm-1d25f "cat dir/dir1d2/CVS/Repository" \
+"${AREP}mod2/sub2"
+ # the usual for 2d1mod
+ dotest cvsadm-1d25h "cat dir/dir2d1/CVS/Repository" \
+"${AREP}CVSROOT/Emptydir"
+ dotest cvsadm-1d25j "cat dir/dir2d1/sub2d1/CVS/Repository" \
+"${AREP}mod1"
+ rm -rf CVS dir
+
+ dotest cvsadm-1d26 "${testcvs} co -d dir 1d2mod 2d2mod" \
+"${PROG} [a-z]*: Updating dir/dir1d2
+U dir/dir1d2/file2
+${PROG} [a-z]*: Updating dir/dir2d2/sub2d2
+U dir/dir2d2/sub2d2/file2"
+ dotest cvsadm-1d26b "cat CVS/Repository" \
+"${AREP}\."
+ # the usual for the dir level
+ dotest cvsadm-1d26d "cat dir/CVS/Repository" \
+"${AREP}\."
+ # the usual for 1d2mod
+ dotest cvsadm-1d26f "cat dir/dir1d2/CVS/Repository" \
+"${AREP}mod2/sub2"
+ # the usual for 2d2mod
+ dotest cvsadm-1d26h "cat dir/dir2d2/CVS/Repository" \
+"${AREP}CVSROOT/Emptydir"
+ dotest cvsadm-1d26j "cat dir/dir2d2/sub2d2/CVS/Repository" \
+"${AREP}mod2/sub2"
+ rm -rf CVS dir
+
+
+ # 2d1mod
+
+ dotest cvsadm-1d27 "${testcvs} co -d dir 2d1mod 2d1mod-2" \
+"${PROG} [a-z]*: Updating dir/dir2d1/sub2d1
+U dir/dir2d1/sub2d1/file1
+${PROG} [a-z]*: Updating dir/dir2d1-2/sub2d1-2
+U dir/dir2d1-2/sub2d1-2/file1-2"
+ dotest cvsadm-1d27b "cat CVS/Repository" \
+"${AREP}\."
+ # the usual for the dir level
+ dotest cvsadm-1d27d "cat dir/CVS/Repository" \
+"${AREP}\."
+ # the usual for 2d1mod
+ dotest cvsadm-1d27f "cat dir/dir2d1/CVS/Repository" \
+"${AREP}CVSROOT/Emptydir"
+ dotest cvsadm-1d27h "cat dir/dir2d1/sub2d1/CVS/Repository" \
+"${AREP}mod1"
+ # the usual for 2d1mod
+ dotest cvsadm-1d27j "cat dir/dir2d1-2/CVS/Repository" \
+"${AREP}CVSROOT/Emptydir"
+ dotest cvsadm-1d27l "cat dir/dir2d1-2/sub2d1-2/CVS/Repository" \
+"${AREP}mod1-2"
+ rm -rf CVS dir
+
+ dotest cvsadm-1d28 "${testcvs} co -d dir 2d1mod 2d2mod" \
+"${PROG} [a-z]*: Updating dir/dir2d1/sub2d1
+U dir/dir2d1/sub2d1/file1
+${PROG} [a-z]*: Updating dir/dir2d2/sub2d2
+U dir/dir2d2/sub2d2/file2"
+ dotest cvsadm-1d28b "cat CVS/Repository" \
+"${AREP}\."
+ # the usual for the dir level
+ dotest cvsadm-1d28d "cat dir/CVS/Repository" \
+"${AREP}\."
+ # the usual for 2d1mod
+ dotest cvsadm-1d28f "cat dir/dir2d1/CVS/Repository" \
+"${AREP}CVSROOT/Emptydir"
+ dotest cvsadm-1d28h "cat dir/dir2d1/sub2d1/CVS/Repository" \
+"${AREP}mod1"
+ # the usual for 2d2mod
+ dotest cvsadm-1d28j "cat dir/dir2d2/CVS/Repository" \
+"${AREP}CVSROOT/Emptydir"
+ dotest cvsadm-1d28l "cat dir/dir2d2/sub2d2/CVS/Repository" \
+"${AREP}mod2/sub2"
+ rm -rf CVS dir
+
+
+ # 2d2mod
+
+ dotest cvsadm-1d29 "${testcvs} co -d dir 2d2mod 2d2mod-2" \
+"${PROG} [a-z]*: Updating dir/dir2d2/sub2d2
+U dir/dir2d2/sub2d2/file2
+${PROG} [a-z]*: Updating dir/dir2d2-2/sub2d2-2
+U dir/dir2d2-2/sub2d2-2/file2-2"
+ dotest cvsadm-1d29b "cat CVS/Repository" \
+"${AREP}\."
+ # the usual for the dir level
+ dotest cvsadm-1d29d "cat dir/CVS/Repository" \
+"${AREP}\."
+ # the usual for 2d2mod
+ dotest cvsadm-1d29f "cat dir/dir2d2/CVS/Repository" \
+"${AREP}CVSROOT/Emptydir"
+ dotest cvsadm-1d29h "cat dir/dir2d2/sub2d2/CVS/Repository" \
+"${AREP}mod2/sub2"
+ # the usual for 2d2mod
+ dotest cvsadm-1d29j "cat dir/dir2d2-2/CVS/Repository" \
+"${AREP}CVSROOT/Emptydir"
+ dotest cvsadm-1d29l "cat dir/dir2d2-2/sub2d2-2/CVS/Repository" \
+"${AREP}mod2-2/sub2-2"
+ rm -rf CVS dir
+
+ ##################################################
+ ## And now, some of that again using the "-d" flag
+ ## on the command line, but use a longer path.
+ ##################################################
+
+ dotest cvsadm-2d3 "${testcvs} co -d dir/dir2 1mod" \
+"${PROG} [a-z]*: Updating dir/dir2
+U dir/dir2/file1"
+ dotest cvsadm-2d3b "cat CVS/Repository" \
+"${AREP}\."
+ dotest cvsadm-2d3d "cat dir/CVS/Repository" \
+"${AREP}CVSROOT/Emptydir"
+ dotest cvsadm-2d3f "cat dir/dir2/CVS/Repository" \
+"${AREP}mod1"
+ rm -rf CVS dir
+
+ dotest cvsadm-2d4 "${testcvs} co -d dir/dir2 2mod" \
+"${PROG} [a-z]*: Updating dir/dir2
+U dir/dir2/file2"
+ dotest cvsadm-2d4b "cat CVS/Repository" \
+"${AREP}\."
+ dotest cvsadm-2d4d "cat dir/CVS/Repository" \
+"${AREP}CVSROOT/Emptydir"
+ dotest cvsadm-2d4f "cat dir/dir2/CVS/Repository" \
+"${AREP}mod2/sub2"
+ rm -rf CVS dir
+
+ dotest cvsadm-2d5 "${testcvs} co -d dir/dir2 1d1mod" \
+"${PROG} [a-z]*: Updating dir/dir2
+U dir/dir2/file1"
+ dotest cvsadm-2d5b "cat CVS/Repository" \
+"${AREP}\."
+ dotest cvsadm-2d5d "cat dir/CVS/Repository" \
+"${AREP}CVSROOT/Emptydir"
+ dotest cvsadm-2d5f "cat dir/dir2/CVS/Repository" \
+"${AREP}mod1"
+ rm -rf CVS dir
+
+ dotest cvsadm-2d6 "${testcvs} co -d dir/dir2 1d2mod" \
+"${PROG} [a-z]*: Updating dir/dir2
+U dir/dir2/file2"
+ dotest cvsadm-2d6b "cat CVS/Repository" \
+"${AREP}\."
+ dotest cvsadm-2d6d "cat dir/CVS/Repository" \
+"${AREP}CVSROOT/Emptydir"
+ dotest cvsadm-2d6f "cat dir/dir2/CVS/Repository" \
+"${AREP}mod2/sub2"
+ rm -rf CVS dir
+
+ dotest cvsadm-2d7 "${testcvs} co -d dir/dir2 2d1mod" \
+"${PROG} [a-z]*: Updating dir/dir2
+U dir/dir2/file1"
+ dotest cvsadm-2d7b "cat CVS/Repository" \
+"${AREP}\."
+ dotest cvsadm-2d7d "cat dir/CVS/Repository" \
+"${AREP}CVSROOT/Emptydir"
+ dotest cvsadm-2d7f "cat dir/dir2/CVS/Repository" \
+"${AREP}mod1"
+ rm -rf CVS dir
+
+ dotest cvsadm-2d8 "${testcvs} co -d dir/dir2 2d2mod" \
+"${PROG} [a-z]*: Updating dir/dir2
+U dir/dir2/file2"
+ dotest cvsadm-2d8b "cat CVS/Repository" \
+"${AREP}\."
+ dotest cvsadm-2d8d "cat dir/CVS/Repository" \
+"${AREP}CVSROOT/Emptydir"
+ dotest cvsadm-2d8f "cat dir/dir2/CVS/Repository" \
+"${AREP}mod2/sub2"
+ rm -rf CVS dir
+
+ ##################################################
+ ## And now, a few of those tests revisited to
+ ## test the behavior of the -N flag.
+ ##################################################
+
+ dotest cvsadm-N3 "${testcvs} co -N 1mod" \
+"${PROG} [a-z]*: Updating 1mod
+U 1mod/file1"
+ dotest cvsadm-N3b "cat CVS/Repository" \
+"${AREP}\."
+ dotest cvsadm-N3d "cat 1mod/CVS/Repository" \
+"${AREP}mod1"
+ rm -rf CVS 1mod
+
+ dotest cvsadm-N4 "${testcvs} co -N 2mod" \
+"${PROG} [a-z]*: Updating 2mod
+U 2mod/file2"
+ dotest cvsadm-N4b "cat CVS/Repository" \
+"${AREP}\."
+ dotest cvsadm-N4d "cat 2mod/CVS/Repository" \
+"${AREP}mod2/sub2"
+ rm -rf CVS 2mod
+
+ dotest cvsadm-N5 "${testcvs} co -N 1d1mod" \
+"${PROG} [a-z]*: Updating dir1d1
+U dir1d1/file1"
+ dotest cvsadm-N5b "cat CVS/Repository" \
+"${AREP}\."
+ dotest cvsadm-N5d "cat dir1d1/CVS/Repository" \
+"${AREP}mod1"
+ rm -rf CVS dir1d1
+
+ dotest cvsadm-N6 "${testcvs} co -N 1d2mod" \
+"${PROG} [a-z]*: Updating dir1d2
+U dir1d2/file2"
+ dotest cvsadm-N6b "cat CVS/Repository" \
+"${AREP}\."
+ dotest cvsadm-N6d "cat dir1d2/CVS/Repository" \
+"${AREP}mod2/sub2"
+ rm -rf CVS dir1d2
+
+ dotest cvsadm-N7 "${testcvs} co -N 2d1mod" \
+"${PROG} [a-z]*: Updating dir2d1/sub2d1
+U dir2d1/sub2d1/file1"
+ dotest cvsadm-N7b "cat CVS/Repository" \
+"${AREP}\."
+ dotest cvsadm-N7d "cat dir2d1/CVS/Repository" \
+"${AREP}CVSROOT/Emptydir"
+ dotest cvsadm-N7f "cat dir2d1/sub2d1/CVS/Repository" \
+"${AREP}mod1"
+ rm -rf CVS dir2d1
+
+ dotest cvsadm-N8 "${testcvs} co -N 2d2mod" \
+"${PROG} [a-z]*: Updating dir2d2/sub2d2
+U dir2d2/sub2d2/file2"
+ dotest cvsadm-N8b "cat CVS/Repository" \
+"${AREP}\."
+ dotest cvsadm-N8d "cat dir2d2/CVS/Repository" \
+"${AREP}CVSROOT/Emptydir"
+ dotest cvsadm-N8f "cat dir2d2/sub2d2/CVS/Repository" \
+"${AREP}mod2/sub2"
+ rm -rf CVS dir2d2
+
+ ## the ones in one-deep directories
+
+ dotest cvsadm-N1d3 "${testcvs} co -N -d dir 1mod" \
+"${PROG} [a-z]*: Updating dir/1mod
+U dir/1mod/file1"
+ dotest cvsadm-N1d3b "cat CVS/Repository" \
+"${AREP}\."
+ dotest cvsadm-N1d3d "cat dir/CVS/Repository" \
+"${AREP}\."
+ dotest cvsadm-N1d3f "cat dir/1mod/CVS/Repository" \
+"${AREP}mod1"
+ rm -rf CVS dir
+
+ dotest cvsadm-N1d4 "${testcvs} co -N -d dir 2mod" \
+"${PROG} [a-z]*: Updating dir/2mod
+U dir/2mod/file2"
+ dotest cvsadm-N1d4b "cat CVS/Repository" \
+"${AREP}\."
+ dotest cvsadm-N1d4d "cat dir/CVS/Repository" \
+"${AREP}\."
+ dotest cvsadm-N1d4f "cat dir/2mod/CVS/Repository" \
+"${AREP}mod2/sub2"
+ rm -rf CVS dir
+
+ dotest cvsadm-N1d5 "${testcvs} co -N -d dir 1d1mod" \
+"${PROG} [a-z]*: Updating dir/dir1d1
+U dir/dir1d1/file1"
+ dotest cvsadm-N1d5b "cat CVS/Repository" \
+"${AREP}\."
+ dotest cvsadm-N1d5d "cat dir/CVS/Repository" \
+"${AREP}\."
+ dotest cvsadm-N1d5d "cat dir/dir1d1/CVS/Repository" \
+"${AREP}mod1"
+ rm -rf CVS dir
+
+ dotest cvsadm-N1d6 "${testcvs} co -N -d dir 1d2mod" \
+"${PROG} [a-z]*: Updating dir/dir1d2
+U dir/dir1d2/file2"
+ dotest cvsadm-N1d6b "cat CVS/Repository" \
+"${AREP}\."
+ dotest cvsadm-N1d6d "cat dir/CVS/Repository" \
+"${AREP}\."
+ dotest cvsadm-N1d6f "cat dir/dir1d2/CVS/Repository" \
+"${AREP}mod2/sub2"
+ rm -rf CVS dir
+
+ dotest cvsadm-N1d7 "${testcvs} co -N -d dir 2d1mod" \
+"${PROG} [a-z]*: Updating dir/dir2d1/sub2d1
+U dir/dir2d1/sub2d1/file1"
+ dotest cvsadm-N1d7b "cat CVS/Repository" \
+"${AREP}\."
+ dotest cvsadm-N1d7d "cat dir/CVS/Repository" \
+"${AREP}\."
+ dotest cvsadm-N1d7f "cat dir/dir2d1/CVS/Repository" \
+"${AREP}CVSROOT/Emptydir"
+ dotest cvsadm-N1d7h "cat dir/dir2d1/sub2d1/CVS/Repository" \
+"${AREP}mod1"
+ rm -rf CVS dir
+
+ dotest cvsadm-N1d8 "${testcvs} co -N -d dir 2d2mod" \
+"${PROG} [a-z]*: Updating dir/dir2d2/sub2d2
+U dir/dir2d2/sub2d2/file2"
+ dotest cvsadm-N1d8b "cat CVS/Repository" \
+"${AREP}\."
+ dotest cvsadm-N1d8d "cat dir/CVS/Repository" \
+"${AREP}\."
+ dotest cvsadm-N1d8d "cat dir/dir2d2/CVS/Repository" \
+"${AREP}CVSROOT/Emptydir"
+ dotest cvsadm-N1d8d "cat dir/dir2d2/sub2d2/CVS/Repository" \
+"${AREP}mod2/sub2"
+ rm -rf CVS dir
+
+ ## the ones in two-deep directories
+
+ dotest cvsadm-N2d3 "${testcvs} co -N -d dir/dir2 1mod" \
+"${PROG} [a-z]*: Updating dir/dir2/1mod
+U dir/dir2/1mod/file1"
+ dotest cvsadm-N2d3b "cat CVS/Repository" \
+"${AREP}\."
+ dotest cvsadm-N2d3d "cat dir/CVS/Repository" \
+"${AREP}CVSROOT/Emptydir"
+ dotest cvsadm-N2d3f "cat dir/dir2/CVS/Repository" \
+"${AREP}\."
+ dotest cvsadm-N2d3h "cat dir/dir2/1mod/CVS/Repository" \
+"${AREP}mod1"
+ rm -rf CVS dir
+
+ dotest cvsadm-N2d4 "${testcvs} co -N -d dir/dir2 2mod" \
+"${PROG} [a-z]*: Updating dir/dir2/2mod
+U dir/dir2/2mod/file2"
+ dotest cvsadm-N2d4b "cat CVS/Repository" \
+"${AREP}\."
+ dotest cvsadm-N2d4d "cat dir/CVS/Repository" \
+"${AREP}CVSROOT/Emptydir"
+ dotest cvsadm-N2d4f "cat dir/dir2/CVS/Repository" \
+"${AREP}\."
+ dotest cvsadm-N2d4h "cat dir/dir2/2mod/CVS/Repository" \
+"${AREP}mod2/sub2"
+ rm -rf CVS dir
+
+ dotest cvsadm-N2d5 "${testcvs} co -N -d dir/dir2 1d1mod" \
+"${PROG} [a-z]*: Updating dir/dir2/dir1d1
+U dir/dir2/dir1d1/file1"
+ dotest cvsadm-N2d5b "cat CVS/Repository" \
+"${AREP}\."
+ dotest cvsadm-N2d5d "cat dir/CVS/Repository" \
+"${AREP}CVSROOT/Emptydir"
+ dotest cvsadm-N2d5f "cat dir/dir2/CVS/Repository" \
+"${AREP}\."
+ dotest cvsadm-N2d5h "cat dir/dir2/dir1d1/CVS/Repository" \
+"${AREP}mod1"
+ rm -rf CVS dir
+
+ dotest cvsadm-N2d6 "${testcvs} co -N -d dir/dir2 1d2mod" \
+"${PROG} [a-z]*: Updating dir/dir2/dir1d2
+U dir/dir2/dir1d2/file2"
+ dotest cvsadm-N2d6b "cat CVS/Repository" \
+"${AREP}\."
+ dotest cvsadm-N2d6d "cat dir/CVS/Repository" \
+"${AREP}CVSROOT/Emptydir"
+ dotest cvsadm-N2d6f "cat dir/dir2/CVS/Repository" \
+"${AREP}\."
+ dotest cvsadm-N2d6h "cat dir/dir2/dir1d2/CVS/Repository" \
+"${AREP}mod2/sub2"
+ rm -rf CVS dir
+
+ dotest cvsadm-N2d7 "${testcvs} co -N -d dir/dir2 2d1mod" \
+"${PROG} [a-z]*: Updating dir/dir2/dir2d1/sub2d1
+U dir/dir2/dir2d1/sub2d1/file1"
+ dotest cvsadm-N2d7b "cat CVS/Repository" \
+"${AREP}\."
+ dotest cvsadm-N2d7d "cat dir/CVS/Repository" \
+"${AREP}CVSROOT/Emptydir"
+ dotest cvsadm-N2d7f "cat dir/dir2/CVS/Repository" \
+"${AREP}\."
+ dotest cvsadm-N2d7f "cat dir/dir2/dir2d1/CVS/Repository" \
+"${AREP}CVSROOT/Emptydir"
+ dotest cvsadm-N2d7h "cat dir/dir2/dir2d1/sub2d1/CVS/Repository" \
+"${AREP}mod1"
+ rm -rf CVS dir
+
+ dotest cvsadm-N2d8 "${testcvs} co -N -d dir/dir2 2d2mod" \
+"${PROG} [a-z]*: Updating dir/dir2/dir2d2/sub2d2
+U dir/dir2/dir2d2/sub2d2/file2"
+ dotest cvsadm-N2d8b "cat CVS/Repository" \
+"${AREP}\."
+ dotest cvsadm-N2d8d "cat dir/CVS/Repository" \
+"${AREP}CVSROOT/Emptydir"
+ dotest cvsadm-N2d8f "cat dir/dir2/CVS/Repository" \
+"${AREP}\."
+ dotest cvsadm-N2d8h "cat dir/dir2/dir2d2/CVS/Repository" \
+"${AREP}CVSROOT/Emptydir"
+ dotest cvsadm-N2d8j "cat dir/dir2/dir2d2/sub2d2/CVS/Repository" \
+"${AREP}mod2/sub2"
+ rm -rf CVS dir
+
+ ##################################################
+ ## That's enough of that, thank you very much.
+ ##################################################
+
+ # remove our junk
+ cd ..
+ rm -rf 1
+ rm -rf ${CVSROOT_DIRNAME}/1mod
+ rm -rf ${CVSROOT_DIRNAME}/1mod-2
+ rm -rf ${CVSROOT_DIRNAME}/2mod
+ rm -rf ${CVSROOT_DIRNAME}/2mod-2
+ rm -rf ${CVSROOT_DIRNAME}/mod1
+ rm -rf ${CVSROOT_DIRNAME}/mod1-2
+ rm -rf ${CVSROOT_DIRNAME}/mod2
+ rm -rf ${CVSROOT_DIRNAME}/mod2-2
+ ;;
+
+ abspath)
+
+ # These tests test the thituations thin thwitch thoo theck
+ # things thout twith thabsolute thaths. Threally.
+
+ #
+ # CHECKOUTS
+ #
+
+ # Create a few modules to use
+ mkdir ${CVSROOT_DIRNAME}/mod1 ${CVSROOT_DIRNAME}/mod2
+ dotest abspath-1a "${testcvs} co mod1 mod2" \
+"${PROG} [a-z]*: Updating mod1
+${PROG} [a-z]*: Updating mod2"
+
+ # Populate the module
+ echo "file1" > mod1/file1
+ echo "file2" > mod2/file2
+ dotest abspath-1b "${testcvs} add mod1/file1 mod2/file2" \
+"${PROG} [a-z]*: scheduling file .mod1/file1. for addition
+${PROG} [a-z]*: scheduling file .mod2/file2. for addition
+${PROG} [a-z]*: use '${PROG} commit' to add these files permanently"
+
+ dotest abspath-1c "${testcvs} ci -m yup mod1 mod2" \
+"${PROG} [a-z]*: Examining mod1
+${PROG} [a-z]*: Examining mod2
+RCS file: ${CVSROOT_DIRNAME}/mod1/file1,v
+done
+Checking in mod1/file1;
+${CVSROOT_DIRNAME}/mod1/file1,v <-- file1
+initial revision: 1.1
+done
+RCS file: ${CVSROOT_DIRNAME}/mod2/file2,v
+done
+Checking in mod2/file2;
+${CVSROOT_DIRNAME}/mod2/file2,v <-- file2
+initial revision: 1.1
+done"
+ # Finished creating the module -- clean up.
+ rm -rf CVS mod1 mod2
+ # Done.
+
+ # Try checking out the module in a local directory
+ dotest abspath-2a "${testcvs} co -d ${TESTDIR}/1 mod1" \
+"${PROG} [a-z]*: Updating ${TESTDIR}/1
+U ${TESTDIR}/1/file1"
+
+ # Are we relative or absolute in our Repository file?
+ echo "${CVSROOT_DIRNAME}/mod1" > ${TESTDIR}/dotest.abs
+ echo "mod1" > ${TESTDIR}/dotest.rel
+ if cmp ${TESTDIR}/dotest.abs ${TESTDIR}/1/CVS/Repository >/dev/null 2>&1; then
+ AREP="${CVSROOT_DIRNAME}/"
+ elif cmp ${TESTDIR}/dotest.rel ${TESTDIR}/1/CVS/Repository >/dev/null 2>&1; then
+ AREP=""
+ else
+ fail "Cannot figure out if RELATIVE_REPOS is defined."
+ fi
+ rm -f ${TESTDIR}/dotest.rel ${TESTDIR}/dotest.abs
+
+ dotest abspath-2b "cat ${TESTDIR}/1/CVS/Repository" \
+"${AREP}mod1"
+
+ # Done. Clean up.
+ rm -rf ${TESTDIR}/1
+
+
+ # Now try in a subdirectory. We're not covering any more
+ # code here, but we might catch a future error if someone
+ # changes the checkout code.
+ dotest abspath-3a "${testcvs} co -d ${TESTDIR}/1/2 mod1" \
+"${PROG} [a-z]*: Updating ${TESTDIR}/1/2
+U ${TESTDIR}/1/2/file1"
+ dotest abspath-3b "cat ${TESTDIR}/1/2/CVS/Repository" \
+"${AREP}mod1"
+ # Done. Clean up.
+ rm -rf ${TESTDIR}/1
+
+
+ # Now try someplace where we don't have permission.
+ mkdir ${TESTDIR}/barf
+ chmod -w ${TESTDIR}/barf
+ dotest_fail abspath-4 "${testcvs} co -d ${TESTDIR}/barf/sub mod1" \
+"${PROG} \[[a-z]* aborted\]: cannot make directory sub: No such file or directory"
+ chmod +w ${TESTDIR}/barf
+ rmdir ${TESTDIR}/barf
+ # Done. Nothing to clean up.
+
+
+ # Try checking out two modules into the same directory.
+ dotest abspath-5a "${testcvs} co -d ${TESTDIR}/1 mod1 mod2" \
+"${PROG} [a-z]*: Updating ${TESTDIR}/1/mod1
+U ${TESTDIR}/1/mod1/file1
+${PROG} [a-z]*: Updating ${TESTDIR}/1/mod2
+U ${TESTDIR}/1/mod2/file2"
+ dotest abspath-5b "cat ${TESTDIR}/1/CVS/Repository" \
+"${AREP}."
+ dotest abspath-5c "cat ${TESTDIR}/1/mod1/CVS/Repository" \
+"${AREP}mod1"
+ dotest abspath-5d "cat ${TESTDIR}/1/mod2/CVS/Repository" \
+"${AREP}mod2"
+ # Done. Clean up.
+ rm -rf ${TESTDIR}/1
+
+
+ # Try checking out the top-level module.
+ dotest abspath-6a "${testcvs} co -d ${TESTDIR}/1 ." \
+"${PROG} [a-z]*: Updating ${TESTDIR}/1
+${PROG} [a-z]*: Updating ${TESTDIR}/1/CVSROOT
+${DOTSTAR}
+${PROG} [a-z]*: Updating ${TESTDIR}/1/mod1
+U ${TESTDIR}/1/mod1/file1
+${PROG} [a-z]*: Updating ${TESTDIR}/1/mod2
+U ${TESTDIR}/1/mod2/file2"
+ dotest abspath-6b "cat ${TESTDIR}/1/CVS/Repository" \
+"${AREP}."
+ dotest abspath-6c "cat ${TESTDIR}/1/CVSROOT/CVS/Repository" \
+"${AREP}CVSROOT"
+ dotest abspath-6c "cat ${TESTDIR}/1/mod1/CVS/Repository" \
+"${AREP}mod1"
+ dotest abspath-6d "cat ${TESTDIR}/1/mod2/CVS/Repository" \
+"${AREP}mod2"
+ # Done. Clean up.
+ rm -rf ${TESTDIR}/1
+
+ #
+ # FIXME: do other functions here (e.g. update /tmp/foo)
+ #
+
+ # Finished with all tests. Remove the module.
+ rm -rf ${CVSROOT_DIRNAME}/mod1 ${CVSROOT_DIRNAME}/mod1
+
+ # FIXME: the absolute pathname fixes create CVS directories
+ # wherever they can. That means for the standard TESTDIR, a
+ # /tmp/CVS directory will be created as well. It's not safe
+ # to remove it, however.
+
+ ;;
+
+ toplevel)
+ # test the feature that cvs creates a CVS subdir also for
+ # the toplevel directory
+
+ # Some test, somewhere, is creating Emptydir. That test
+ # should, perhaps, clean up for itself, but I don't know which
+ # one it is.
+ rm -rf ${CVSROOT_DIRNAME}/CVSROOT/Emptydir
+
+ mkdir 1; cd 1
+ dotest toplevel-1 "${testcvs} -q co -l ." ''
+ mkdir top-dir second-dir
+ dotest toplevel-2 "${testcvs} add top-dir second-dir" \
+"Directory ${TESTDIR}/cvsroot/top-dir added to the repository
+Directory ${TESTDIR}/cvsroot/second-dir added to the repository"
+ cd top-dir
+
+ touch file1
+ dotest toplevel-3 "${testcvs} add file1" \
+"${PROG} [a-z]*: scheduling file .file1. for addition
+${PROG} [a-z]*: use .${PROG} commit. to add this file permanently"
+ dotest toplevel-4 "${testcvs} -q ci -m add" \
+"RCS file: ${TESTDIR}/cvsroot/top-dir/file1,v
+done
+Checking in file1;
+${TESTDIR}/cvsroot/top-dir/file1,v <-- file1
+initial revision: 1\.1
+done"
+ cd ..
+
+ cd second-dir
+ touch file2
+ dotest toplevel-3s "${testcvs} add file2" \
+"${PROG} [a-z]*: scheduling file .file2. for addition
+${PROG} [a-z]*: use .${PROG} commit. to add this file permanently"
+ dotest toplevel-4s "${testcvs} -q ci -m add" \
+"RCS file: ${TESTDIR}/cvsroot/second-dir/file2,v
+done
+Checking in file2;
+${TESTDIR}/cvsroot/second-dir/file2,v <-- file2
+initial revision: 1\.1
+done"
+
+ cd ../..
+ rm -r 1; mkdir 1; cd 1
+ dotest toplevel-5 "${testcvs} co top-dir" \
+"${PROG} [a-z]*: Updating top-dir
+U top-dir/file1"
+
+ dotest toplevel-6 "${testcvs} update top-dir" \
+"${PROG} [a-z]*: Updating top-dir"
+ dotest toplevel-7 "${testcvs} update" \
+"${PROG} [a-z]*: Updating \.
+${PROG} [a-z]*: Updating top-dir"
+
+ dotest toplevel-8 "${testcvs} update -d top-dir" \
+"${PROG} [a-z]*: Updating top-dir"
+ # There is some sentiment that
+ # "${PROG} [a-z]*: Updating \.
+ # ${PROG} [a-z]*: Updating top-dir"
+ # is correct but it isn't clear why that would be correct instead
+ # of the remote CVS behavior (which also updates CVSROOT).
+ #
+ # The DOTSTAR matches of a bunch of lines like
+ # "U CVSROOT/checkoutlist". Trying to match them more precisely
+ # seemed to cause trouble. For example CVSROOT/cvsignore will
+ # be present or absent depending on whether we ran the "ignore"
+ # test or not.
+ dotest toplevel-9 "${testcvs} update -d" \
+"${PROG} [a-z]*: Updating \.
+${PROG} [a-z]*: Updating CVSROOT
+${DOTSTAR}
+${PROG} [a-z]*: Updating top-dir"
+
+ cd ..
+ rm -r 1; mkdir 1; cd 1
+ dotest toplevel-10 "${testcvs} co top-dir" \
+"${PROG} [a-z]*: Updating top-dir
+U top-dir/file1"
+ # This tests more or less the same thing, in a particularly
+ # "real life" example.
+ dotest toplevel-11 "${testcvs} -q update -d second-dir" \
+"U second-dir/file2"
+
+ # Now remove the CVS directory (people may do this manually,
+ # especially if they formed their habits with CVS
+ # 1.9 and older, which didn't create it. Or perhaps the working
+ # directory itself was created with 1.9 or older).
+ rm -r CVS
+ # Now set the permissions so we can't recreate it.
+ chmod -w ../1
+ # Now see whether CVS has trouble because it can't create CVS.
+ dotest toplevel-12 "${testcvs} co top-dir" \
+"${PROG} [a-z]*: warning: cannot make directory CVS in \.: Permission denied
+${PROG} [a-z]*: Updating top-dir"
+ chmod +w ../1
+
+ cd ..
+ rm -r 1
+ rm -rf ${CVSROOT_DIRNAME}/top-dir ${CVSROOT_DIRNAME}/second-dir
+ ;;
+
mflag)
for message in '' ' ' '
' ' test' ; do
@@ -7867,7 +9891,8 @@ ${PROG} [a-z]*: Rebuilding administrative file database"
echo "ALL echo %{v} >>$TESTDIR/testlog2; cat >/dev/null" >> loginfo
echo "ALL echo %s >>$TESTDIR/testlog2; cat >/dev/null" >> loginfo
echo "ALL echo %{V}AX >>$TESTDIR/testlog2; cat >/dev/null" >> loginfo
- echo "ALL echo %sux >>$TESTDIR/testlog2; cat >/dev/null" >> loginfo
+ echo "first-dir echo %sux >>$TESTDIR/testlog2; cat >/dev/null" \
+ >> loginfo
# Might be nice to move this to crerepos tests; it should
# work to create a loginfo file if you didn't create one
@@ -8965,7 +10990,7 @@ add file1
head 1.5 ;
branch 1.2.6;
access ;
-symbols;
+symbols branch:1.2.6;
locks;
testofanewphrase @without newphrase we'd have trouble extending @@ all@ ;
1.5 date 71.01.01.01.00.00; author joe; state bogus; branches; next 1.4;
@@ -8997,6 +11022,16 @@ EOF
dotest rcs-5 "${testcvs} -q update file2" "U file2"
dotest rcs-6 "cat file2" "branch revision"
+ # Check in a revision on the branch to force CVS to
+ # interpret every revision in the file.
+ dotest rcs-6a "${testcvs} -q update -r branch file2" ""
+ echo "next branch revision" > file2
+ dotest rcs-6b "${testcvs} -q ci -m mod file2" \
+"Checking in file2;
+${TESTDIR}/cvsroot/first-dir/file2,v <-- file2
+new revision: 1\.2\.6\.2; previous revision: 1\.2\.6\.1
+done"
+
# Now get rid of the default branch, it will get in the way.
dotest rcs-7 "${testcvs} admin -b file2" \
"RCS file: ${TESTDIR}/cvsroot/first-dir/file2,v
@@ -9013,7 +11048,8 @@ done"
dotest rcs-8a "cat ${CVSROOT_DIRNAME}/first-dir/file2,v" \
"head 1\.5;
access;
-symbols;
+symbols
+ branch:1.2.6;
locks;
testofanewphrase @without newphrase we'd have trouble extending @@ all@;
@@ -9048,6 +11084,11 @@ newph ;
1\.2\.6\.1
date 71\.01\.01\.08\.00\.05; author joe; state Exp;
branches;
+next 1\.2\.6\.2;
+
+1\.2\.6\.2
+date [0-9.]*; author ${username}; state Exp;
+branches;
next ;
@@ -9106,7 +11147,18 @@ log
text
@d1 1
a1 1
-branch revision@"
+branch revision@
+
+
+1\.2\.6\.2
+log
+@mod
+@
+text
+@d1 1
+a1 1
+next branch revision
+@"
# For remote, the "update -p -D" usage seems not to work.
# I'm not sure what is going on.
@@ -9166,8 +11218,9 @@ branch:
locks:
access list:
symbolic names:
+ branch: 1\.2\.6
keyword substitution: kv
-total revisions: 6; selected revisions: 6
+total revisions: 7; selected revisions: 7
description:
----------------------------
revision 1\.5
@@ -9191,6 +11244,10 @@ revision 1\.1
date: 1970/12/31 11:00:05; author: joe; state: bogus;
\*\*\* empty log message \*\*\*
----------------------------
+revision 1\.2\.6\.2
+date: [0-9/]* [0-9:]*; author: ${username}; state: Exp; lines: ${PLUS}1 -1
+mod
+----------------------------
revision 1\.2\.6\.1
date: 1971/01/01 08:00:05; author: joe; state: Exp; lines: ${PLUS}1 -1
\*\*\* empty log message \*\*\*
@@ -9972,108 +12029,6 @@ xx"
rm -rf ${CVSROOT_DIRNAME}/first-dir
;;
- toplevel)
- # test the feature that cvs creates a CVS subdir also for
- # the toplevel directory
-
- # Some test, somewhere, is creating Emptydir. That test
- # should, perhaps, clean up for itself, but I don't know which
- # one it is.
- rm -rf ${CVSROOT_DIRNAME}/CVSROOT/Emptydir
-
- mkdir 1; cd 1
- dotest toplevel-1 "${testcvs} -q co -l ." ''
- mkdir top-dir second-dir
- dotest toplevel-2 "${testcvs} add top-dir second-dir" \
-"Directory ${TESTDIR}/cvsroot/top-dir added to the repository
-Directory ${TESTDIR}/cvsroot/second-dir added to the repository"
- cd top-dir
-
- touch file1
- dotest toplevel-3 "${testcvs} add file1" \
-"${PROG} [a-z]*: scheduling file .file1. for addition
-${PROG} [a-z]*: use .${PROG} commit. to add this file permanently"
- dotest toplevel-4 "${testcvs} -q ci -m add" \
-"RCS file: ${TESTDIR}/cvsroot/top-dir/file1,v
-done
-Checking in file1;
-${TESTDIR}/cvsroot/top-dir/file1,v <-- file1
-initial revision: 1\.1
-done"
- cd ..
-
- cd second-dir
- touch file2
- dotest toplevel-3s "${testcvs} add file2" \
-"${PROG} [a-z]*: scheduling file .file2. for addition
-${PROG} [a-z]*: use .${PROG} commit. to add this file permanently"
- dotest toplevel-4s "${testcvs} -q ci -m add" \
-"RCS file: ${TESTDIR}/cvsroot/second-dir/file2,v
-done
-Checking in file2;
-${TESTDIR}/cvsroot/second-dir/file2,v <-- file2
-initial revision: 1\.1
-done"
-
- cd ../..
- rm -r 1; mkdir 1; cd 1
- dotest toplevel-5 "${testcvs} co top-dir" \
-"${PROG} [a-z]*: Updating top-dir
-U top-dir/file1"
-
- dotest toplevel-6 "${testcvs} update top-dir" \
-"${PROG} [a-z]*: Updating top-dir"
- dotest toplevel-7 "${testcvs} update" \
-"${PROG} [a-z]*: Updating \.
-${PROG} [a-z]*: Updating top-dir"
-
- dotest toplevel-8 "${testcvs} update -d top-dir" \
-"${PROG} [a-z]*: Updating top-dir"
- # There is some sentiment that
- # "${PROG} [a-z]*: Updating \.
- # ${PROG} [a-z]*: Updating top-dir"
- # is correct but it isn't clear why that would be correct instead
- # of the remote CVS behavior (which also updates CVSROOT).
- #
- # The DOTSTAR matches of a bunch of lines like
- # "U CVSROOT/checkoutlist". Trying to match them more precisely
- # seemed to cause trouble. For example CVSROOT/cvsignore will
- # be present or absent depending on whether we ran the "ignore"
- # test or not.
- dotest toplevel-9 "${testcvs} update -d" \
-"${PROG} [a-z]*: Updating \.
-${PROG} [a-z]*: Updating CVSROOT
-${DOTSTAR}
-${PROG} [a-z]*: Updating top-dir"
-
- cd ..
- rm -r 1; mkdir 1; cd 1
- dotest toplevel-10 "${testcvs} co top-dir" \
-"${PROG} [a-z]*: Updating top-dir
-U top-dir/file1"
- # This tests more or less the same thing, in a particularly
- # "real life" example.
- dotest toplevel-11 "${testcvs} -q update -d second-dir" \
-"U second-dir/file2"
-
- # Now remove the CVS directory (people may do this manually,
- # especially if they formed their habits with CVS
- # 1.9 and older, which didn't create it. Or perhaps the working
- # directory itself was created with 1.9 or older).
- rm -r CVS
- # Now set the permissions so we can't recreate it.
- chmod -w ../1
- # Now see whether CVS has trouble because it can't create CVS.
- dotest toplevel-12 "${testcvs} co top-dir" \
-"${PROG} [a-z]*: warning: cannot make directory ./CVS: Permission denied
-${PROG} [a-z]*: Updating top-dir"
- chmod +w ../1
-
- cd ..
- rm -r 1
- rm -rf ${CVSROOT_DIRNAME}/top-dir
- ;;
-
head)
# Testing handling of the HEAD special tag.
# There are many cases involving added and removed files
@@ -10088,10 +12043,11 @@ ${PROG} [a-z]*: Updating top-dir"
# It may seem like we don't do much with file2, but do note that
# the "cvs diff" invocations do also diff file2 (and come up empty).
echo 'imported contents' >file2
- dotest head-1 "${testcvs} import -m add first-dir tag1 tag2" \
-"N first-dir/file1
-N first-dir/file2
+ dotest_sort head-1 "${testcvs} import -m add first-dir tag1 tag2" \
+"
+N first-dir/file1
+N first-dir/file2
No conflicts created by this import"
cd ..
rm -r imp-dir
@@ -10511,6 +12467,15 @@ ${PROG} \[[a-z]* aborted\]: attempt to specify a numeric revision"
"${PROG} [a-z]*: while processing more than one file:
${PROG} \[[a-z]* aborted\]: attempt to specify a numeric revision"
+ # try a bad symbolic revision
+ dotest_fail admin-10c "${testcvs} -q admin -bBOGUS" \
+"RCS file: ${TESTDIR}/cvsroot/first-dir/file1,v
+${PROG} [a-z]*: ${TESTDIR}/cvsroot/first-dir/file1,v: Symbolic name BOGUS is undefined.
+${PROG} [a-z]*: cannot modify RCS file for .file1.
+RCS file: ${TESTDIR}/cvsroot/first-dir/file2,v
+${PROG} [a-z]*: ${TESTDIR}/cvsroot/first-dir/file2,v: Symbolic name BOGUS is undefined.
+${PROG} [a-z]*: cannot modify RCS file for .file2."
+
# Note that -s option applies to the new default branch, not
# the old one.
# Also note that the implementation of -a via "rcs" requires
@@ -10520,8 +12485,33 @@ ${PROG} \[[a-z]* aborted\]: attempt to specify a numeric revision"
-b1.1.2 -cxx -U -sfoo file1" \
"RCS file: ${TESTDIR}/cvsroot/first-dir/file1,v
done"
-
- dotest admin-12 "${testcvs} log -N file1" "
+ dotest admin-11a "${testcvs} log -N file1" "
+RCS file: ${TESTDIR}/cvsroot/first-dir/file1,v
+Working file: file1
+head: 1\.1
+branch: 1\.1\.2
+locks:
+access list:
+ foo
+ bar
+ baz
+keyword substitution: kv
+total revisions: 2; selected revisions: 2
+description:
+----------------------------
+revision 1\.1
+date: [0-9/]* [0-9:]*; author: ${username}; state: Exp;
+branches: 1\.1\.2;
+add
+----------------------------
+revision 1\.1\.2\.1
+date: [0-9/]* [0-9:]*; author: ${username}; state: foo; lines: ${PLUS}1 -0
+modify-on-branch
+============================================================================="
+ dotest admin-12 "${testcvs} -q admin -bbr file1" \
+"RCS file: ${TESTDIR}/cvsroot/first-dir/file1,v
+done"
+ dotest admin-12a "${testcvs} log -N file1" "
RCS file: ${TESTDIR}/cvsroot/first-dir/file1,v
Working file: file1
head: 1\.1
@@ -11179,1408 +13169,6 @@ ${PROG} [a-z]*: Rebuilding administrative file database"
rm -rf ${CVSROOT_DIRNAME}/first-dir
;;
- cvsadm)
- # These test check the content of CVS' administrative
- # files as they are checked out in various configurations.
- # (As a side note, I'm not using the "-q" flag in any of
- # this code, which should provide some extra checking for
- # those messages which don't seem to be checked thoroughly
- # anywhere else.) To do a thorough test, we need to make
- # a bunch of modules in various configurations.
- #
- # <1mod> is a directory at the top level of cvsroot
- # ``foo bar''
- # <2mod> is a directory at the second level of cvsroot
- # ``foo bar/baz''
- # <1d1mod> is a directory at the top level which is
- # checked out into another directory
- # ``foo -d bar baz''
- # <1d2mod> is a directory at the second level which is
- # checked out into another directory
- # ``foo -d bar baz/quux''
- # <2d1mod> is a directory at the top level which is
- # checked out into a directory that is two deep
- # ``foo -d bar/baz quux''
- # <2d2mod> is a directory at the second level which is
- # checked out into a directory that is two deep
- # ``foo -d bar/baz quux''
- #
- # The tests do each of these types separately and in twos.
- # We also repeat each test -d flag for 1-deep and 2-deep
- # directories.
- #
- # Each test should check the output for the Repository
- # file, since that is the one which varies depending on
- # the directory and how it was checked out.
- #
- # Yes, this is verbose, but at least it's very thorough.
-
- # convenience variables
- REP=${CVSROOT}
-
- # First, check out the modules file and edit it.
- mkdir 1; cd 1
- dotest cvsadm-1 "${testcvs} co CVSROOT/modules" \
-"U CVSROOT/modules"
-
- # Try to determine whether RELATIVE_REPOS is defined
- # so that we can make the following a lot less
- # verbose.
-
- echo "${CVSROOT_DIRNAME}/." > ${TESTDIR}/dotest.abs
- echo "." > ${TESTDIR}/dotest.rel
- if cmp ${TESTDIR}/dotest.abs CVS/Repository >/dev/null 2>&1; then
- AREP="${CVSROOT_DIRNAME}/"
- elif cmp ${TESTDIR}/dotest.rel CVS/Repository >/dev/null 2>&1; then
- AREP=""
- else
- fail "Cannot figure out if RELATIVE_REPOS is defined."
- fi
-
- # Test CVS/Root once. Since there is only one part of
- # the code which writes CVS/Root files (Create_Admin),
- # there is no point in testing this every time.
- dotest cvsadm-1a "cat CVS/Root" ${REP}
- dotest cvsadm-1b "cat CVS/Repository" \
-"${AREP}\."
- dotest cvsadm-1c "cat CVSROOT/CVS/Root" ${REP}
- dotest cvsadm-1d "cat CVSROOT/CVS/Repository" \
-"${AREP}CVSROOT"
- # All of the defined module names begin with a number.
- # All of the top-level directory names begin with "dir".
- # All of the subdirectory names begin with "sub".
- # All of the top-level modules begin with "mod".
- echo "# Module defs for cvsadm tests" > CVSROOT/modules
- echo "1mod mod1" >> CVSROOT/modules
- echo "1mod-2 mod1-2" >> CVSROOT/modules
- echo "2mod mod2/sub2" >> CVSROOT/modules
- echo "2mod-2 mod2-2/sub2-2" >> CVSROOT/modules
- echo "1d1mod -d dir1d1 mod1" >> CVSROOT/modules
- echo "1d1mod-2 -d dir1d1-2 mod1-2" >> CVSROOT/modules
- echo "1d2mod -d dir1d2 mod2/sub2" >> CVSROOT/modules
- echo "1d2mod-2 -d dir1d2-2 mod2-2/sub2-2" >> CVSROOT/modules
- echo "2d1mod -d dir2d1/sub2d1 mod1" >> CVSROOT/modules
- echo "2d1mod-2 -d dir2d1-2/sub2d1-2 mod1-2" >> CVSROOT/modules
- echo "2d2mod -d dir2d2/sub2d2 mod2/sub2" >> CVSROOT/modules
- echo "2d2mod-2 -d dir2d2-2/sub2d2-2 mod2-2/sub2-2" >> CVSROOT/modules
- dotest cvsadm-1e "${testcvs} ci -m add-modules" \
-"${PROG} [a-z]*: Examining .
-${PROG} [a-z]*: Examining CVSROOT
-Checking in CVSROOT/modules;
-${CVSROOT_DIRNAME}/CVSROOT/modules,v <-- modules
-new revision: 1\.[0-9]*; previous revision: 1\.[0-9]*
-done
-${PROG} [a-z]*: Rebuilding administrative file database"
- rm -rf CVS CVSROOT;
-
- # Create the various modules
- mkdir ${CVSROOT_DIRNAME}/mod1
- mkdir ${CVSROOT_DIRNAME}/mod1-2
- mkdir ${CVSROOT_DIRNAME}/mod2
- mkdir ${CVSROOT_DIRNAME}/mod2/sub2
- mkdir ${CVSROOT_DIRNAME}/mod2-2
- mkdir ${CVSROOT_DIRNAME}/mod2-2/sub2-2
- dotest cvsadm-2 "${testcvs} co mod1 mod1-2 mod2 mod2-2" \
-"${PROG} [a-z]*: Updating mod1
-${PROG} [a-z]*: Updating mod1-2
-${PROG} [a-z]*: Updating mod2
-${PROG} [a-z]*: Updating mod2/sub2
-${PROG} [a-z]*: Updating mod2-2
-${PROG} [a-z]*: Updating mod2-2/sub2-2"
-
- # Populate the directories for the halibut
- echo "file1" > mod1/file1
- echo "file1-2" > mod1-2/file1-2
- echo "file2" > mod2/sub2/file2
- echo "file2-2" > mod2-2/sub2-2/file2-2
- dotest cvsadm-2a "${testcvs} add mod1/file1 mod1-2/file1-2 mod2/sub2/file2 mod2-2/sub2-2/file2-2" \
-"${PROG} [a-z]*: scheduling file .mod1/file1. for addition
-${PROG} [a-z]*: scheduling file .mod1-2/file1-2. for addition
-${PROG} [a-z]*: scheduling file .mod2/sub2/file2. for addition
-${PROG} [a-z]*: scheduling file .mod2-2/sub2-2/file2-2. for addition
-${PROG} [a-z]*: use '${PROG} commit' to add these files permanently"
-
- dotest cvsadm-2b "${testcvs} ci -m yup mod1 mod1-2 mod2 mod2-2" \
-"${PROG} [a-z]*: Examining mod1
-${PROG} [a-z]*: Examining mod1-2
-${PROG} [a-z]*: Examining mod2
-${PROG} [a-z]*: Examining mod2/sub2
-${PROG} [a-z]*: Examining mod2-2
-${PROG} [a-z]*: Examining mod2-2/sub2-2
-RCS file: ${CVSROOT_DIRNAME}/mod1/file1,v
-done
-Checking in mod1/file1;
-${CVSROOT_DIRNAME}/mod1/file1,v <-- file1
-initial revision: 1.1
-done
-RCS file: ${CVSROOT_DIRNAME}/mod1-2/file1-2,v
-done
-Checking in mod1-2/file1-2;
-${CVSROOT_DIRNAME}/mod1-2/file1-2,v <-- file1-2
-initial revision: 1.1
-done
-RCS file: ${CVSROOT_DIRNAME}/mod2/sub2/file2,v
-done
-Checking in mod2/sub2/file2;
-${CVSROOT_DIRNAME}/mod2/sub2/file2,v <-- file2
-initial revision: 1.1
-done
-RCS file: ${CVSROOT_DIRNAME}/mod2-2/sub2-2/file2-2,v
-done
-Checking in mod2-2/sub2-2/file2-2;
-${CVSROOT_DIRNAME}/mod2-2/sub2-2/file2-2,v <-- file2-2
-initial revision: 1.1
-done"
- # Finished creating the modules -- clean up.
- rm -rf CVS mod1 mod1-2 mod2 mod2-2
- # Done.
-
- ##################################################
- ## Start the dizzying array of possibilities.
- ## Begin with each module type separately.
- ##################################################
-
- # Pattern -- after each checkout, first check the top-level
- # CVS directory. Then, check the directories in numerical
- # order.
-
- dotest cvsadm-3 "${testcvs} co 1mod" \
-"${PROG} [a-z]*: Updating 1mod
-U 1mod/file1"
- dotest cvsadm-3b "cat CVS/Repository" \
-"${AREP}\."
- dotest cvsadm-3d "cat 1mod/CVS/Repository" \
-"${AREP}mod1"
- rm -rf CVS 1mod
-
- dotest cvsadm-4 "${testcvs} co 2mod" \
-"${PROG} [a-z]*: Updating 2mod
-U 2mod/file2"
- dotest cvsadm-4b "cat CVS/Repository" \
-"${AREP}\."
- dotest cvsadm-4d "cat 2mod/CVS/Repository" \
-"${AREP}mod2/sub2"
- rm -rf CVS 2mod
-
- dotest cvsadm-5 "${testcvs} co 1d1mod" \
-"${PROG} [a-z]*: Updating dir1d1
-U dir1d1/file1"
- dotest cvsadm-5b "cat CVS/Repository" \
-"${AREP}\."
- dotest cvsadm-5d "cat dir1d1/CVS/Repository" \
-"${AREP}mod1"
- rm -rf CVS dir1d1
-
- dotest cvsadm-6 "${testcvs} co 1d2mod" \
-"${PROG} [a-z]*: Updating dir1d2
-U dir1d2/file2"
- dotest cvsadm-6b "cat CVS/Repository" \
-"${AREP}\."
- dotest cvsadm-6d "cat dir1d2/CVS/Repository" \
-"${AREP}mod2/sub2"
- rm -rf CVS dir1d2
-
- dotest cvsadm-7 "${testcvs} co 2d1mod" \
-"${PROG} [a-z]*: Updating dir2d1/sub2d1
-U dir2d1/sub2d1/file1"
- dotest cvsadm-7b "cat CVS/Repository" \
-"${AREP}\."
- dotest cvsadm-7d "cat dir2d1/CVS/Repository" \
-"${AREP}CVSROOT/Emptydir"
- dotest cvsadm-7f "cat dir2d1/sub2d1/CVS/Repository" \
-"${AREP}mod1"
- rm -rf CVS dir2d1
-
- dotest cvsadm-8 "${testcvs} co 2d2mod" \
-"${PROG} [a-z]*: Updating dir2d2/sub2d2
-U dir2d2/sub2d2/file2"
- dotest cvsadm-8b "cat CVS/Repository" \
-"${AREP}\."
- dotest cvsadm-8d "cat dir2d2/CVS/Repository" \
-"${AREP}CVSROOT/Emptydir"
- dotest cvsadm-8f "cat dir2d2/sub2d2/CVS/Repository" \
-"${AREP}mod2/sub2"
- rm -rf CVS dir2d2
-
- ##################################################
- ## You are in a shell script of twisted little
- ## module combination statements, all alike.
- ##################################################
-
- ### 1mod
-
- dotest cvsadm-9 "${testcvs} co 1mod 1mod-2" \
-"${PROG} [a-z]*: Updating 1mod
-U 1mod/file1
-${PROG} [a-z]*: Updating 1mod-2
-U 1mod-2/file1-2"
- # the usual for the top level
- dotest cvsadm-9b "cat CVS/Repository" \
-"${AREP}\."
- # the usual for 1mod
- dotest cvsadm-9d "cat 1mod/CVS/Repository" \
-"${AREP}mod1"
- # the usual for 1mod copy
- dotest cvsadm-9f "cat 1mod-2/CVS/Repository" \
-"${AREP}mod1-2"
- rm -rf CVS 1mod 1mod-2
-
- # 1mod 2mod redmod bluemod
- dotest cvsadm-10 "${testcvs} co 1mod 2mod" \
-"${PROG} [a-z]*: Updating 1mod
-U 1mod/file1
-${PROG} [a-z]*: Updating 2mod
-U 2mod/file2"
- # the usual for the top level
- dotest cvsadm-10b "cat CVS/Repository" \
-"${AREP}\."
- # the usual for 1mod
- dotest cvsadm-10d "cat 1mod/CVS/Repository" \
-"${AREP}mod1"
- # the usual for 2dmod
- dotest cvsadm-10f "cat 2mod/CVS/Repository" \
-"${AREP}mod2/sub2"
- rm -rf CVS 1mod 2mod
-
- dotest cvsadm-11 "${testcvs} co 1mod 1d1mod" \
-"${PROG} [a-z]*: Updating 1mod
-U 1mod/file1
-${PROG} [a-z]*: Updating dir1d1
-U dir1d1/file1"
- # the usual for the top level
- dotest cvsadm-11b "cat CVS/Repository" \
-"${AREP}\."
- # the usual for 1mod
- dotest cvsadm-11d "cat 1mod/CVS/Repository" \
-"${AREP}mod1"
- # the usual for 1d1mod
- dotest cvsadm-11f "cat dir1d1/CVS/Repository" \
-"${AREP}mod1"
- rm -rf CVS 1mod dir1d1
-
- dotest cvsadm-12 "${testcvs} co 1mod 1d2mod" \
-"${PROG} [a-z]*: Updating 1mod
-U 1mod/file1
-${PROG} [a-z]*: Updating dir1d2
-U dir1d2/file2"
- # the usual for the top level
- dotest cvsadm-12b "cat CVS/Repository" \
-"${AREP}\."
- # the usual for 1mod
- dotest cvsadm-12d "cat 1mod/CVS/Repository" \
-"${AREP}mod1"
- # the usual for 1d2mod
- dotest cvsadm-12f "cat dir1d2/CVS/Repository" \
-"${AREP}mod2/sub2"
- rm -rf CVS 1mod dir1d2
-
- dotest cvsadm-13 "${testcvs} co 1mod 2d1mod" \
-"${PROG} [a-z]*: Updating 1mod
-U 1mod/file1
-${PROG} [a-z]*: Updating dir2d1/sub2d1
-U dir2d1/sub2d1/file1"
- # the usual for the top level
- dotest cvsadm-13b "cat CVS/Repository" \
-"${AREP}\."
- # the usual for 1mod
- dotest cvsadm-13d "cat 1mod/CVS/Repository" \
-"${AREP}mod1"
- # the usual for 2d1mod
- dotest cvsadm-13f "cat dir2d1/CVS/Repository" \
-"${AREP}CVSROOT/Emptydir"
- dotest cvsadm-13h "cat dir2d1/sub2d1/CVS/Repository" \
-"${AREP}mod1"
- rm -rf CVS 1mod dir2d1
-
- dotest cvsadm-14 "${testcvs} co 1mod 2d2mod" \
-"${PROG} [a-z]*: Updating 1mod
-U 1mod/file1
-${PROG} [a-z]*: Updating dir2d2/sub2d2
-U dir2d2/sub2d2/file2"
- # the usual for the top level
- dotest cvsadm-14b "cat CVS/Repository" \
-"${AREP}\."
- # the usual for 1mod
- dotest cvsadm-14d "cat 1mod/CVS/Repository" \
-"${AREP}mod1"
- # the usual for 2d2mod
- dotest cvsadm-14f "cat dir2d2/CVS/Repository" \
-"${AREP}CVSROOT/Emptydir"
- dotest cvsadm-14h "cat dir2d2/sub2d2/CVS/Repository" \
-"${AREP}mod2/sub2"
- rm -rf CVS 1mod dir2d2
-
-
- ### 2mod
-
- dotest cvsadm-15 "${testcvs} co 2mod 2mod-2" \
-"${PROG} [a-z]*: Updating 2mod
-U 2mod/file2
-${PROG} [a-z]*: Updating 2mod-2
-U 2mod-2/file2-2"
- # the usual for the top level
- dotest cvsadm-15b "cat CVS/Repository" \
-"${AREP}\."
- # the usual for 2mod
- dotest cvsadm-15d "cat 2mod/CVS/Repository" \
-"${AREP}mod2/sub2"
- # the usual for 2mod copy
- dotest cvsadm-15f "cat 2mod-2/CVS/Repository" \
-"${AREP}mod2-2/sub2-2"
- rm -rf CVS 2mod 2mod-2
-
-
- dotest cvsadm-16 "${testcvs} co 2mod 1d1mod" \
-"${PROG} [a-z]*: Updating 2mod
-U 2mod/file2
-${PROG} [a-z]*: Updating dir1d1
-U dir1d1/file1"
- # the usual for the top level
- dotest cvsadm-16b "cat CVS/Repository" \
-"${AREP}\."
- # the usual for 2mod
- dotest cvsadm-16d "cat 2mod/CVS/Repository" \
-"${AREP}mod2/sub2"
- # the usual for 1d1mod
- dotest cvsadm-16f "cat dir1d1/CVS/Repository" \
-"${AREP}mod1"
- rm -rf CVS 2mod dir1d1
-
- dotest cvsadm-17 "${testcvs} co 2mod 1d2mod" \
-"${PROG} [a-z]*: Updating 2mod
-U 2mod/file2
-${PROG} [a-z]*: Updating dir1d2
-U dir1d2/file2"
- # the usual for the top level
- dotest cvsadm-17b "cat CVS/Repository" \
-"${AREP}\."
- # the usual for 2mod
- dotest cvsadm-17d "cat 2mod/CVS/Repository" \
-"${AREP}mod2/sub2"
- # the usual for 1d2mod
- dotest cvsadm-17f "cat dir1d2/CVS/Repository" \
-"${AREP}mod2/sub2"
- rm -rf CVS 2mod dir1d2
-
- dotest cvsadm-18 "${testcvs} co 2mod 2d1mod" \
-"${PROG} [a-z]*: Updating 2mod
-U 2mod/file2
-${PROG} [a-z]*: Updating dir2d1/sub2d1
-U dir2d1/sub2d1/file1"
- # the usual for the top level
- dotest cvsadm-18b "cat CVS/Repository" \
-"${AREP}\."
- # the usual for 2mod
- dotest cvsadm-18d "cat 2mod/CVS/Repository" \
-"${AREP}mod2/sub2"
- # the usual for 2d1mod
- dotest cvsadm-18f "cat dir2d1/CVS/Repository" \
-"${AREP}CVSROOT/Emptydir"
- dotest cvsadm-18h "cat dir2d1/sub2d1/CVS/Repository" \
-"${AREP}mod1"
- rm -rf CVS 2mod dir2d1
-
- dotest cvsadm-19 "${testcvs} co 2mod 2d2mod" \
-"${PROG} [a-z]*: Updating 2mod
-U 2mod/file2
-${PROG} [a-z]*: Updating dir2d2/sub2d2
-U dir2d2/sub2d2/file2"
- # the usual for the top level
- dotest cvsadm-19b "cat CVS/Repository" \
-"${AREP}\."
- # the usual for 2mod
- dotest cvsadm-19d "cat 2mod/CVS/Repository" \
-"${AREP}mod2/sub2"
- # the usual for 2d2mod
- dotest cvsadm-19f "cat dir2d2/CVS/Repository" \
-"${AREP}CVSROOT/Emptydir"
- dotest cvsadm-19h "cat dir2d2/sub2d2/CVS/Repository" \
-"${AREP}mod2/sub2"
- rm -rf CVS 2mod dir2d2
-
-
- ### 1d1mod
-
- dotest cvsadm-20 "${testcvs} co 1d1mod 1d1mod-2" \
-"${PROG} [a-z]*: Updating dir1d1
-U dir1d1/file1
-${PROG} [a-z]*: Updating dir1d1-2
-U dir1d1-2/file1-2"
- # the usual for the top level
- dotest cvsadm-20b "cat CVS/Repository" \
-"${AREP}\."
- # the usual for 1d1mod
- dotest cvsadm-20d "cat dir1d1/CVS/Repository" \
-"${AREP}mod1"
- # the usual for 1d1mod copy
- dotest cvsadm-20f "cat dir1d1-2/CVS/Repository" \
-"${AREP}mod1-2"
- rm -rf CVS dir1d1 dir1d1-2
-
- dotest cvsadm-21 "${testcvs} co 1d1mod 1d2mod" \
-"${PROG} [a-z]*: Updating dir1d1
-U dir1d1/file1
-${PROG} [a-z]*: Updating dir1d2
-U dir1d2/file2"
- # the usual for the top level
- dotest cvsadm-21b "cat CVS/Repository" \
-"${AREP}\."
- # the usual for 1d1mod
- dotest cvsadm-21d "cat dir1d1/CVS/Repository" \
-"${AREP}mod1"
- # the usual for 1d2mod
- dotest cvsadm-21f "cat dir1d2/CVS/Repository" \
-"${AREP}mod2/sub2"
- rm -rf CVS dir1d1 dir1d2
-
- dotest cvsadm-22 "${testcvs} co 1d1mod 2d1mod" \
-"${PROG} [a-z]*: Updating dir1d1
-U dir1d1/file1
-${PROG} [a-z]*: Updating dir2d1/sub2d1
-U dir2d1/sub2d1/file1"
- # the usual for the top level
- dotest cvsadm-22b "cat CVS/Repository" \
-"${AREP}\."
- # the usual for 1d1mod
- dotest cvsadm-22d "cat dir1d1/CVS/Repository" \
-"${AREP}mod1"
- # the usual for 2d1mod
- dotest cvsadm-22f "cat dir2d1/CVS/Repository" \
-"${AREP}CVSROOT/Emptydir"
- dotest cvsadm-22h "cat dir2d1/sub2d1/CVS/Repository" \
-"${AREP}mod1"
- rm -rf CVS dir1d1 dir2d1
-
- dotest cvsadm-23 "${testcvs} co 1d1mod 2d2mod" \
-"${PROG} [a-z]*: Updating dir1d1
-U dir1d1/file1
-${PROG} [a-z]*: Updating dir2d2/sub2d2
-U dir2d2/sub2d2/file2"
- # the usual for the top level
- dotest cvsadm-23b "cat CVS/Repository" \
-"${AREP}\."
- # the usual for 1d1mod
- dotest cvsadm-23d "cat dir1d1/CVS/Repository" \
-"${AREP}mod1"
- # the usual for 2d2mod
- dotest cvsadm-23f "cat dir2d2/CVS/Repository" \
-"${AREP}CVSROOT/Emptydir"
- dotest cvsadm-23h "cat dir2d2/sub2d2/CVS/Repository" \
-"${AREP}mod2/sub2"
- rm -rf CVS dir1d1 dir2d2
-
-
- ### 1d2mod
-
- dotest cvsadm-24 "${testcvs} co 1d2mod 1d2mod-2" \
-"${PROG} [a-z]*: Updating dir1d2
-U dir1d2/file2
-${PROG} [a-z]*: Updating dir1d2-2
-U dir1d2-2/file2-2"
- # the usual for the top level
- dotest cvsadm-24b "cat CVS/Repository" \
-"${AREP}\."
- # the usual for 1d2mod
- dotest cvsadm-24d "cat dir1d2/CVS/Repository" \
-"${AREP}mod2/sub2"
- # the usual for 1d2mod copy
- dotest cvsadm-24f "cat dir1d2-2/CVS/Repository" \
-"${AREP}mod2-2/sub2-2"
- rm -rf CVS dir1d2 dir1d2-2
-
- dotest cvsadm-25 "${testcvs} co 1d2mod 2d1mod" \
-"${PROG} [a-z]*: Updating dir1d2
-U dir1d2/file2
-${PROG} [a-z]*: Updating dir2d1/sub2d1
-U dir2d1/sub2d1/file1"
- # the usual for the top level
- dotest cvsadm-25b "cat CVS/Repository" \
-"${AREP}\."
- # the usual for 1d2mod
- dotest cvsadm-25d "cat dir1d2/CVS/Repository" \
-"${AREP}mod2/sub2"
- # the usual for 2d1mod
- dotest cvsadm-25f "cat dir2d1/CVS/Repository" \
-"${AREP}CVSROOT/Emptydir"
- dotest cvsadm-25h "cat dir2d1/sub2d1/CVS/Repository" \
-"${AREP}mod1"
- rm -rf CVS dir1d2 dir2d1
-
- dotest cvsadm-26 "${testcvs} co 1d2mod 2d2mod" \
-"${PROG} [a-z]*: Updating dir1d2
-U dir1d2/file2
-${PROG} [a-z]*: Updating dir2d2/sub2d2
-U dir2d2/sub2d2/file2"
- # the usual for the top level
- dotest cvsadm-26b "cat CVS/Repository" \
-"${AREP}\."
- # the usual for 1d2mod
- dotest cvsadm-26d "cat dir1d2/CVS/Repository" \
-"${AREP}mod2/sub2"
- # the usual for 2d2mod
- dotest cvsadm-26f "cat dir2d2/CVS/Repository" \
-"${AREP}CVSROOT/Emptydir"
- dotest cvsadm-26h "cat dir2d2/sub2d2/CVS/Repository" \
-"${AREP}mod2/sub2"
- rm -rf CVS dir1d2 dir2d2
-
-
- # 2d1mod
-
- dotest cvsadm-27 "${testcvs} co 2d1mod 2d1mod-2" \
-"${PROG} [a-z]*: Updating dir2d1/sub2d1
-U dir2d1/sub2d1/file1
-${PROG} [a-z]*: Updating dir2d1-2/sub2d1-2
-U dir2d1-2/sub2d1-2/file1-2"
- # the usual for the top level
- dotest cvsadm-27b "cat CVS/Repository" \
-"${AREP}\."
- # the usual for 2d1mod
- dotest cvsadm-27d "cat dir2d1/CVS/Repository" \
-"${AREP}CVSROOT/Emptydir"
- dotest cvsadm-27f "cat dir2d1/sub2d1/CVS/Repository" \
-"${AREP}mod1"
- # the usual for 2d1mod
- dotest cvsadm-27h "cat dir2d1-2/CVS/Repository" \
-"${AREP}CVSROOT/Emptydir"
- dotest cvsadm-27j "cat dir2d1-2/sub2d1-2/CVS/Repository" \
-"${AREP}mod1-2"
- rm -rf CVS dir2d1 dir2d1-2
-
- dotest cvsadm-28 "${testcvs} co 2d1mod 2d2mod" \
-"${PROG} [a-z]*: Updating dir2d1/sub2d1
-U dir2d1/sub2d1/file1
-${PROG} [a-z]*: Updating dir2d2/sub2d2
-U dir2d2/sub2d2/file2"
- # the usual for the top level
- dotest cvsadm-28b "cat CVS/Repository" \
-"${AREP}\."
- # the usual for 2d1mod
- dotest cvsadm-28d "cat dir2d1/CVS/Repository" \
-"${AREP}CVSROOT/Emptydir"
- dotest cvsadm-28f "cat dir2d1/sub2d1/CVS/Repository" \
-"${AREP}mod1"
- # the usual for 2d2mod
- dotest cvsadm-28h "cat dir2d2/CVS/Repository" \
-"${AREP}CVSROOT/Emptydir"
- dotest cvsadm-28j "cat dir2d2/sub2d2/CVS/Repository" \
-"${AREP}mod2/sub2"
- rm -rf CVS dir2d1 dir2d2
-
-
- # 2d2mod
-
- dotest cvsadm-29 "${testcvs} co 2d2mod 2d2mod-2" \
-"${PROG} [a-z]*: Updating dir2d2/sub2d2
-U dir2d2/sub2d2/file2
-${PROG} [a-z]*: Updating dir2d2-2/sub2d2-2
-U dir2d2-2/sub2d2-2/file2-2"
- # the usual for the top level
- dotest cvsadm-29b "cat CVS/Repository" \
-"${AREP}\."
- # the usual for 2d2mod
- dotest cvsadm-29d "cat dir2d2/CVS/Repository" \
-"${AREP}CVSROOT/Emptydir"
- dotest cvsadm-29f "cat dir2d2/sub2d2/CVS/Repository" \
-"${AREP}mod2/sub2"
- # the usual for 2d2mod
- dotest cvsadm-29h "cat dir2d2-2/CVS/Repository" \
-"${AREP}CVSROOT/Emptydir"
- dotest cvsadm-29j "cat dir2d2-2/sub2d2-2/CVS/Repository" \
-"${AREP}mod2-2/sub2-2"
- rm -rf CVS dir2d2 dir2d2-2
-
- ##################################################
- ## And now, all of that again using the "-d" flag
- ## on the command line.
- ##################################################
-
- dotest cvsadm-1d3 "${testcvs} co -d dir 1mod" \
-"${PROG} [a-z]*: Updating dir
-U dir/file1"
- dotest cvsadm-1d3b "cat CVS/Repository" \
-"${AREP}\."
- dotest cvsadm-1d3d "cat dir/CVS/Repository" \
-"${AREP}mod1"
- rm -rf CVS dir
-
- dotest cvsadm-1d4 "${testcvs} co -d dir 2mod" \
-"${PROG} [a-z]*: Updating dir
-U dir/file2"
- dotest cvsadm-1d4b "cat CVS/Repository" \
-"${AREP}\."
- dotest cvsadm-1d4d "cat dir/CVS/Repository" \
-"${AREP}mod2/sub2"
- rm -rf CVS dir
-
- dotest cvsadm-1d5 "${testcvs} co -d dir 1d1mod" \
-"${PROG} [a-z]*: Updating dir
-U dir/file1"
- dotest cvsadm-1d5b "cat CVS/Repository" \
-"${AREP}\."
- dotest cvsadm-1d5d "cat dir/CVS/Repository" \
-"${AREP}mod1"
- rm -rf CVS dir
-
- dotest cvsadm-1d6 "${testcvs} co -d dir 1d2mod" \
-"${PROG} [a-z]*: Updating dir
-U dir/file2"
- dotest cvsadm-1d6b "cat CVS/Repository" \
-"${AREP}\."
- dotest cvsadm-1d6d "cat dir/CVS/Repository" \
-"${AREP}mod2/sub2"
- rm -rf CVS dir
-
- dotest cvsadm-1d7 "${testcvs} co -d dir 2d1mod" \
-"${PROG} [a-z]*: Updating dir
-U dir/file1"
- dotest cvsadm-1d7b "cat CVS/Repository" \
-"${AREP}\."
- dotest cvsadm-1d7d "cat dir/CVS/Repository" \
-"${AREP}mod1"
- rm -rf CVS dir
-
- dotest cvsadm-1d8 "${testcvs} co -d dir 2d2mod" \
-"${PROG} [a-z]*: Updating dir
-U dir/file2"
- dotest cvsadm-1d8b "cat CVS/Repository" \
-"${AREP}\."
- dotest cvsadm-1d8d "cat dir/CVS/Repository" \
-"${AREP}mod2/sub2"
- rm -rf CVS dir
-
- ##################################################
- ## Los Combonaciones
- ##################################################
-
- ### 1mod
-
- dotest cvsadm-1d9 "${testcvs} co -d dir 1mod 1mod-2" \
-"${PROG} [a-z]*: Updating dir/1mod
-U dir/1mod/file1
-${PROG} [a-z]*: Updating dir/1mod-2
-U dir/1mod-2/file1-2"
- # the usual for the top level
- dotest cvsadm-1d9b "cat CVS/Repository" \
-"${AREP}\."
- # the usual for the dir level
- dotest cvsadm-1d9d "cat dir/CVS/Repository" \
-"${AREP}\."
- # the usual for 1mod
- dotest cvsadm-1d9f "cat dir/1mod/CVS/Repository" \
-"${AREP}mod1"
- # the usual for 1mod copy
- dotest cvsadm-1d9h "cat dir/1mod-2/CVS/Repository" \
-"${AREP}mod1-2"
- rm -rf CVS dir
-
- # 1mod 2mod redmod bluemod
- dotest cvsadm-1d10 "${testcvs} co -d dir 1mod 2mod" \
-"${PROG} [a-z]*: Updating dir/1mod
-U dir/1mod/file1
-${PROG} [a-z]*: Updating dir/2mod
-U dir/2mod/file2"
- dotest cvsadm-1d10b "cat CVS/Repository" \
-"${AREP}\."
- # the usual for the dir level
- dotest cvsadm-1d10d "cat dir/CVS/Repository" \
-"${AREP}\."
- # the usual for 1mod
- dotest cvsadm-1d10f "cat dir/1mod/CVS/Repository" \
-"${AREP}mod1"
- # the usual for 2dmod
- dotest cvsadm-1d10h "cat dir/2mod/CVS/Repository" \
-"${AREP}mod2/sub2"
- rm -rf CVS dir
-
- dotest cvsadm-1d11 "${testcvs} co -d dir 1mod 1d1mod" \
-"${PROG} [a-z]*: Updating dir/1mod
-U dir/1mod/file1
-${PROG} [a-z]*: Updating dir/dir1d1
-U dir/dir1d1/file1"
- dotest cvsadm-1d11b "cat CVS/Repository" \
-"${AREP}\."
- # the usual for the dir level
- dotest cvsadm-1d11d "cat dir/CVS/Repository" \
-"${AREP}\."
- # the usual for 1mod
- dotest cvsadm-1d11f "cat dir/1mod/CVS/Repository" \
-"${AREP}mod1"
- # the usual for 1d1mod
- dotest cvsadm-1d11h "cat dir/dir1d1/CVS/Repository" \
-"${AREP}mod1"
- rm -rf CVS dir
-
- dotest cvsadm-1d12 "${testcvs} co -d dir 1mod 1d2mod" \
-"${PROG} [a-z]*: Updating dir/1mod
-U dir/1mod/file1
-${PROG} [a-z]*: Updating dir/dir1d2
-U dir/dir1d2/file2"
- dotest cvsadm-1d12b "cat CVS/Repository" \
-"${AREP}\."
- # the usual for the dir level
- dotest cvsadm-1d12d "cat dir/CVS/Repository" \
-"${AREP}\."
- # the usual for 1mod
- dotest cvsadm-1d12f "cat dir/1mod/CVS/Repository" \
-"${AREP}mod1"
- # the usual for 1d2mod
- dotest cvsadm-1d12h "cat dir/dir1d2/CVS/Repository" \
-"${AREP}mod2/sub2"
- rm -rf CVS dir
-
- dotest cvsadm-1d13 "${testcvs} co -d dir 1mod 2d1mod" \
-"${PROG} [a-z]*: Updating dir/1mod
-U dir/1mod/file1
-${PROG} [a-z]*: Updating dir/dir2d1/sub2d1
-U dir/dir2d1/sub2d1/file1"
- dotest cvsadm-1d13b "cat CVS/Repository" \
-"${AREP}\."
- # the usual for the dir level
- dotest cvsadm-1d13d "cat dir/CVS/Repository" \
-"${AREP}\."
- # the usual for 1mod
- dotest cvsadm-1d13f "cat dir/1mod/CVS/Repository" \
-"${AREP}mod1"
- # the usual for 2d1mod
- dotest cvsadm-1d13h "cat dir/dir2d1/CVS/Repository" \
-"${AREP}CVSROOT/Emptydir"
- dotest cvsadm-1d13j "cat dir/dir2d1/sub2d1/CVS/Repository" \
-"${AREP}mod1"
- rm -rf CVS dir
-
- dotest cvsadm-1d14 "${testcvs} co -d dir 1mod 2d2mod" \
-"${PROG} [a-z]*: Updating dir/1mod
-U dir/1mod/file1
-${PROG} [a-z]*: Updating dir/dir2d2/sub2d2
-U dir/dir2d2/sub2d2/file2"
- dotest cvsadm-1d14b "cat CVS/Repository" \
-"${AREP}\."
- # the usual for the dir level
- dotest cvsadm-1d14d "cat dir/CVS/Repository" \
-"${AREP}\."
- # the usual for 1mod
- dotest cvsadm-1d14f "cat dir/1mod/CVS/Repository" \
-"${AREP}mod1"
- # the usual for 2d2mod
- dotest cvsadm-1d14h "cat dir/dir2d2/CVS/Repository" \
-"${AREP}CVSROOT/Emptydir"
- dotest cvsadm-1d14j "cat dir/dir2d2/sub2d2/CVS/Repository" \
-"${AREP}mod2/sub2"
- rm -rf CVS dir
-
-
- ### 2mod
-
- dotest cvsadm-1d15 "${testcvs} co -d dir 2mod 2mod-2" \
-"${PROG} [a-z]*: Updating dir/2mod
-U dir/2mod/file2
-${PROG} [a-z]*: Updating dir/2mod-2
-U dir/2mod-2/file2-2"
- dotest cvsadm-1d15b "cat CVS/Repository" \
-"${AREP}\."
- # the usual for the dir level
- dotest cvsadm-1d15d "cat dir/CVS/Repository" \
-"${AREP}\."
- # the usual for 2mod
- dotest cvsadm-1d15f "cat dir/2mod/CVS/Repository" \
-"${AREP}mod2/sub2"
- # the usual for 2mod copy
- dotest cvsadm-1d15h "cat dir/2mod-2/CVS/Repository" \
-"${AREP}mod2-2/sub2-2"
- rm -rf CVS dir
-
- dotest cvsadm-1d16 "${testcvs} co -d dir 2mod 1d1mod" \
-"${PROG} [a-z]*: Updating dir/2mod
-U dir/2mod/file2
-${PROG} [a-z]*: Updating dir/dir1d1
-U dir/dir1d1/file1"
- dotest cvsadm-1d16b "cat CVS/Repository" \
-"${AREP}\."
- # the usual for the dir level
- dotest cvsadm-1d16d "cat dir/CVS/Repository" \
-"${AREP}\."
- # the usual for 2mod
- dotest cvsadm-1d16f "cat dir/2mod/CVS/Repository" \
-"${AREP}mod2/sub2"
- # the usual for 1d1mod
- dotest cvsadm-1d16h "cat dir/dir1d1/CVS/Repository" \
-"${AREP}mod1"
- rm -rf CVS dir
-
- dotest cvsadm-1d17 "${testcvs} co -d dir 2mod 1d2mod" \
-"${PROG} [a-z]*: Updating dir/2mod
-U dir/2mod/file2
-${PROG} [a-z]*: Updating dir/dir1d2
-U dir/dir1d2/file2"
- dotest cvsadm-1d17b "cat CVS/Repository" \
-"${AREP}\."
- # the usual for the dir level
- dotest cvsadm-1d17d "cat dir/CVS/Repository" \
-"${AREP}\."
- # the usual for 2mod
- dotest cvsadm-1d17f "cat dir/2mod/CVS/Repository" \
-"${AREP}mod2/sub2"
- # the usual for 1d2mod
- dotest cvsadm-1d17h "cat dir/dir1d2/CVS/Repository" \
-"${AREP}mod2/sub2"
- rm -rf CVS dir
-
- dotest cvsadm-1d18 "${testcvs} co -d dir 2mod 2d1mod" \
-"${PROG} [a-z]*: Updating dir/2mod
-U dir/2mod/file2
-${PROG} [a-z]*: Updating dir/dir2d1/sub2d1
-U dir/dir2d1/sub2d1/file1"
- dotest cvsadm-1d18b "cat CVS/Repository" \
-"${AREP}\."
- # the usual for the dir level
- dotest cvsadm-1d18d "cat dir/CVS/Repository" \
-"${AREP}\."
- # the usual for 2mod
- dotest cvsadm-1d18f "cat dir/2mod/CVS/Repository" \
-"${AREP}mod2/sub2"
- # the usual for 2d1mod
- dotest cvsadm-1d18h "cat dir/dir2d1/CVS/Repository" \
-"${AREP}CVSROOT/Emptydir"
- dotest cvsadm-1d18j "cat dir/dir2d1/sub2d1/CVS/Repository" \
-"${AREP}mod1"
- rm -rf CVS dir
-
- dotest cvsadm-1d19 "${testcvs} co -d dir 2mod 2d2mod" \
-"${PROG} [a-z]*: Updating dir/2mod
-U dir/2mod/file2
-${PROG} [a-z]*: Updating dir/dir2d2/sub2d2
-U dir/dir2d2/sub2d2/file2"
- dotest cvsadm-1d19b "cat CVS/Repository" \
-"${AREP}\."
- # the usual for the dir level
- dotest cvsadm-1d19d "cat dir/CVS/Repository" \
-"${AREP}\."
- # the usual for 2mod
- dotest cvsadm-1d19f "cat dir/2mod/CVS/Repository" \
-"${AREP}mod2/sub2"
- # the usual for 2d2mod
- dotest cvsadm-1d19h "cat dir/dir2d2/CVS/Repository" \
-"${AREP}CVSROOT/Emptydir"
- dotest cvsadm-1d19j "cat dir/dir2d2/sub2d2/CVS/Repository" \
-"${AREP}mod2/sub2"
- rm -rf CVS dir
-
-
- ### 1d1mod
-
- dotest cvsadm-1d20 "${testcvs} co -d dir 1d1mod 1d1mod-2" \
-"${PROG} [a-z]*: Updating dir/dir1d1
-U dir/dir1d1/file1
-${PROG} [a-z]*: Updating dir/dir1d1-2
-U dir/dir1d1-2/file1-2"
- dotest cvsadm-1d20b "cat CVS/Repository" \
-"${AREP}\."
- # the usual for the dir level
- dotest cvsadm-1d20d "cat dir/CVS/Repository" \
-"${AREP}\."
- # the usual for 1d1mod
- dotest cvsadm-1d20f "cat dir/dir1d1/CVS/Repository" \
-"${AREP}mod1"
- # the usual for 1d1mod copy
- dotest cvsadm-1d20h "cat dir/dir1d1-2/CVS/Repository" \
-"${AREP}mod1-2"
- rm -rf CVS dir
-
- dotest cvsadm-1d21 "${testcvs} co -d dir 1d1mod 1d2mod" \
-"${PROG} [a-z]*: Updating dir/dir1d1
-U dir/dir1d1/file1
-${PROG} [a-z]*: Updating dir/dir1d2
-U dir/dir1d2/file2"
- dotest cvsadm-1d21b "cat CVS/Repository" \
-"${AREP}\."
- # the usual for the dir level
- dotest cvsadm-1d21d "cat dir/CVS/Repository" \
-"${AREP}\."
- # the usual for 1d1mod
- dotest cvsadm-1d21f "cat dir/dir1d1/CVS/Repository" \
-"${AREP}mod1"
- # the usual for 1d2mod
- dotest cvsadm-1d21h "cat dir/dir1d2/CVS/Repository" \
-"${AREP}mod2/sub2"
- rm -rf CVS dir
-
- dotest cvsadm-1d22 "${testcvs} co -d dir 1d1mod 2d1mod" \
-"${PROG} [a-z]*: Updating dir/dir1d1
-U dir/dir1d1/file1
-${PROG} [a-z]*: Updating dir/dir2d1/sub2d1
-U dir/dir2d1/sub2d1/file1"
- dotest cvsadm-1d22b "cat CVS/Repository" \
-"${AREP}\."
- # the usual for the dir level
- dotest cvsadm-1d22d "cat dir/CVS/Repository" \
-"${AREP}\."
- # the usual for 1d1mod
- dotest cvsadm-1d22f "cat dir/dir1d1/CVS/Repository" \
-"${AREP}mod1"
- # the usual for 2d1mod
- dotest cvsadm-1d22h "cat dir/dir2d1/CVS/Repository" \
-"${AREP}CVSROOT/Emptydir"
- dotest cvsadm-1d22j "cat dir/dir2d1/sub2d1/CVS/Repository" \
-"${AREP}mod1"
- rm -rf CVS dir
-
- dotest cvsadm-1d23 "${testcvs} co -d dir 1d1mod 2d2mod" \
-"${PROG} [a-z]*: Updating dir/dir1d1
-U dir/dir1d1/file1
-${PROG} [a-z]*: Updating dir/dir2d2/sub2d2
-U dir/dir2d2/sub2d2/file2"
- dotest cvsadm-1d23b "cat CVS/Repository" \
-"${AREP}\."
- # the usual for the dir level
- dotest cvsadm-1d23d "cat dir/CVS/Repository" \
-"${AREP}\."
- # the usual for 1d1mod
- dotest cvsadm-1d23f "cat dir/dir1d1/CVS/Repository" \
-"${AREP}mod1"
- # the usual for 2d2mod
- dotest cvsadm-1d23h "cat dir/dir2d2/CVS/Repository" \
-"${AREP}CVSROOT/Emptydir"
- dotest cvsadm-1d23j "cat dir/dir2d2/sub2d2/CVS/Repository" \
-"${AREP}mod2/sub2"
- rm -rf CVS dir
-
-
- ### 1d2mod
-
- dotest cvsadm-1d24 "${testcvs} co -d dir 1d2mod 1d2mod-2" \
-"${PROG} [a-z]*: Updating dir/dir1d2
-U dir/dir1d2/file2
-${PROG} [a-z]*: Updating dir/dir1d2-2
-U dir/dir1d2-2/file2-2"
- dotest cvsadm-1d24b "cat CVS/Repository" \
-"${AREP}\."
- # the usual for the dir level
- dotest cvsadm-1d24d "cat dir/CVS/Repository" \
-"${AREP}\."
- # the usual for 1d2mod
- dotest cvsadm-1d24f "cat dir/dir1d2/CVS/Repository" \
-"${AREP}mod2/sub2"
- # the usual for 1d2mod copy
- dotest cvsadm-1d24h "cat dir/dir1d2-2/CVS/Repository" \
-"${AREP}mod2-2/sub2-2"
- rm -rf CVS dir
-
- dotest cvsadm-1d25 "${testcvs} co -d dir 1d2mod 2d1mod" \
-"${PROG} [a-z]*: Updating dir/dir1d2
-U dir/dir1d2/file2
-${PROG} [a-z]*: Updating dir/dir2d1/sub2d1
-U dir/dir2d1/sub2d1/file1"
- dotest cvsadm-1d25b "cat CVS/Repository" \
-"${AREP}\."
- # the usual for the dir level
- dotest cvsadm-1d25d "cat dir/CVS/Repository" \
-"${AREP}\."
- # the usual for 1d2mod
- dotest cvsadm-1d25f "cat dir/dir1d2/CVS/Repository" \
-"${AREP}mod2/sub2"
- # the usual for 2d1mod
- dotest cvsadm-1d25h "cat dir/dir2d1/CVS/Repository" \
-"${AREP}CVSROOT/Emptydir"
- dotest cvsadm-1d25j "cat dir/dir2d1/sub2d1/CVS/Repository" \
-"${AREP}mod1"
- rm -rf CVS dir
-
- dotest cvsadm-1d26 "${testcvs} co -d dir 1d2mod 2d2mod" \
-"${PROG} [a-z]*: Updating dir/dir1d2
-U dir/dir1d2/file2
-${PROG} [a-z]*: Updating dir/dir2d2/sub2d2
-U dir/dir2d2/sub2d2/file2"
- dotest cvsadm-1d26b "cat CVS/Repository" \
-"${AREP}\."
- # the usual for the dir level
- dotest cvsadm-1d26d "cat dir/CVS/Repository" \
-"${AREP}\."
- # the usual for 1d2mod
- dotest cvsadm-1d26f "cat dir/dir1d2/CVS/Repository" \
-"${AREP}mod2/sub2"
- # the usual for 2d2mod
- dotest cvsadm-1d26h "cat dir/dir2d2/CVS/Repository" \
-"${AREP}CVSROOT/Emptydir"
- dotest cvsadm-1d26j "cat dir/dir2d2/sub2d2/CVS/Repository" \
-"${AREP}mod2/sub2"
- rm -rf CVS dir
-
-
- # 2d1mod
-
- dotest cvsadm-1d27 "${testcvs} co -d dir 2d1mod 2d1mod-2" \
-"${PROG} [a-z]*: Updating dir/dir2d1/sub2d1
-U dir/dir2d1/sub2d1/file1
-${PROG} [a-z]*: Updating dir/dir2d1-2/sub2d1-2
-U dir/dir2d1-2/sub2d1-2/file1-2"
- dotest cvsadm-1d27b "cat CVS/Repository" \
-"${AREP}\."
- # the usual for the dir level
- dotest cvsadm-1d27d "cat dir/CVS/Repository" \
-"${AREP}\."
- # the usual for 2d1mod
- dotest cvsadm-1d27f "cat dir/dir2d1/CVS/Repository" \
-"${AREP}CVSROOT/Emptydir"
- dotest cvsadm-1d27h "cat dir/dir2d1/sub2d1/CVS/Repository" \
-"${AREP}mod1"
- # the usual for 2d1mod
- dotest cvsadm-1d27j "cat dir/dir2d1-2/CVS/Repository" \
-"${AREP}CVSROOT/Emptydir"
- dotest cvsadm-1d27l "cat dir/dir2d1-2/sub2d1-2/CVS/Repository" \
-"${AREP}mod1-2"
- rm -rf CVS dir
-
- dotest cvsadm-1d28 "${testcvs} co -d dir 2d1mod 2d2mod" \
-"${PROG} [a-z]*: Updating dir/dir2d1/sub2d1
-U dir/dir2d1/sub2d1/file1
-${PROG} [a-z]*: Updating dir/dir2d2/sub2d2
-U dir/dir2d2/sub2d2/file2"
- dotest cvsadm-1d28b "cat CVS/Repository" \
-"${AREP}\."
- # the usual for the dir level
- dotest cvsadm-1d28d "cat dir/CVS/Repository" \
-"${AREP}\."
- # the usual for 2d1mod
- dotest cvsadm-1d28f "cat dir/dir2d1/CVS/Repository" \
-"${AREP}CVSROOT/Emptydir"
- dotest cvsadm-1d28h "cat dir/dir2d1/sub2d1/CVS/Repository" \
-"${AREP}mod1"
- # the usual for 2d2mod
- dotest cvsadm-1d28j "cat dir/dir2d2/CVS/Repository" \
-"${AREP}CVSROOT/Emptydir"
- dotest cvsadm-1d28l "cat dir/dir2d2/sub2d2/CVS/Repository" \
-"${AREP}mod2/sub2"
- rm -rf CVS dir
-
-
- # 2d2mod
-
- dotest cvsadm-1d29 "${testcvs} co -d dir 2d2mod 2d2mod-2" \
-"${PROG} [a-z]*: Updating dir/dir2d2/sub2d2
-U dir/dir2d2/sub2d2/file2
-${PROG} [a-z]*: Updating dir/dir2d2-2/sub2d2-2
-U dir/dir2d2-2/sub2d2-2/file2-2"
- dotest cvsadm-1d29b "cat CVS/Repository" \
-"${AREP}\."
- # the usual for the dir level
- dotest cvsadm-1d29d "cat dir/CVS/Repository" \
-"${AREP}\."
- # the usual for 2d2mod
- dotest cvsadm-1d29f "cat dir/dir2d2/CVS/Repository" \
-"${AREP}CVSROOT/Emptydir"
- dotest cvsadm-1d29h "cat dir/dir2d2/sub2d2/CVS/Repository" \
-"${AREP}mod2/sub2"
- # the usual for 2d2mod
- dotest cvsadm-1d29j "cat dir/dir2d2-2/CVS/Repository" \
-"${AREP}CVSROOT/Emptydir"
- dotest cvsadm-1d29l "cat dir/dir2d2-2/sub2d2-2/CVS/Repository" \
-"${AREP}mod2-2/sub2-2"
- rm -rf CVS dir
-
- ##################################################
- ## And now, some of that again using the "-d" flag
- ## on the command line, but use a longer path.
- ##################################################
-
- dotest cvsadm-2d3 "${testcvs} co -d dir/dir2 1mod" \
-"${PROG} [a-z]*: Updating dir/dir2
-U dir/dir2/file1"
- dotest cvsadm-2d3b "cat CVS/Repository" \
-"${AREP}\."
- dotest cvsadm-2d3d "cat dir/CVS/Repository" \
-"${AREP}CVSROOT/Emptydir"
- dotest cvsadm-2d3f "cat dir/dir2/CVS/Repository" \
-"${AREP}mod1"
- rm -rf CVS dir
-
- dotest cvsadm-2d4 "${testcvs} co -d dir/dir2 2mod" \
-"${PROG} [a-z]*: Updating dir/dir2
-U dir/dir2/file2"
- dotest cvsadm-2d4b "cat CVS/Repository" \
-"${AREP}\."
- dotest cvsadm-2d4d "cat dir/CVS/Repository" \
-"${AREP}CVSROOT/Emptydir"
- dotest cvsadm-2d4f "cat dir/dir2/CVS/Repository" \
-"${AREP}mod2/sub2"
- rm -rf CVS dir
-
- dotest cvsadm-2d5 "${testcvs} co -d dir/dir2 1d1mod" \
-"${PROG} [a-z]*: Updating dir/dir2
-U dir/dir2/file1"
- dotest cvsadm-2d5b "cat CVS/Repository" \
-"${AREP}\."
- dotest cvsadm-2d5d "cat dir/CVS/Repository" \
-"${AREP}CVSROOT/Emptydir"
- dotest cvsadm-2d5f "cat dir/dir2/CVS/Repository" \
-"${AREP}mod1"
- rm -rf CVS dir
-
- dotest cvsadm-2d6 "${testcvs} co -d dir/dir2 1d2mod" \
-"${PROG} [a-z]*: Updating dir/dir2
-U dir/dir2/file2"
- dotest cvsadm-2d6b "cat CVS/Repository" \
-"${AREP}\."
- dotest cvsadm-2d6d "cat dir/CVS/Repository" \
-"${AREP}CVSROOT/Emptydir"
- dotest cvsadm-2d6f "cat dir/dir2/CVS/Repository" \
-"${AREP}mod2/sub2"
- rm -rf CVS dir
-
- dotest cvsadm-2d7 "${testcvs} co -d dir/dir2 2d1mod" \
-"${PROG} [a-z]*: Updating dir/dir2
-U dir/dir2/file1"
- dotest cvsadm-2d7b "cat CVS/Repository" \
-"${AREP}\."
- dotest cvsadm-2d7d "cat dir/CVS/Repository" \
-"${AREP}CVSROOT/Emptydir"
- dotest cvsadm-2d7f "cat dir/dir2/CVS/Repository" \
-"${AREP}mod1"
- rm -rf CVS dir
-
- dotest cvsadm-2d8 "${testcvs} co -d dir/dir2 2d2mod" \
-"${PROG} [a-z]*: Updating dir/dir2
-U dir/dir2/file2"
- dotest cvsadm-2d8b "cat CVS/Repository" \
-"${AREP}\."
- dotest cvsadm-2d8d "cat dir/CVS/Repository" \
-"${AREP}CVSROOT/Emptydir"
- dotest cvsadm-2d8f "cat dir/dir2/CVS/Repository" \
-"${AREP}mod2/sub2"
- rm -rf CVS dir
-
- ##################################################
- ## And now, a few of those tests revisited to
- ## test the behavior of the -N flag.
- ##################################################
-
- dotest cvsadm-N3 "${testcvs} co -N 1mod" \
-"${PROG} [a-z]*: Updating 1mod
-U 1mod/file1"
- dotest cvsadm-N3b "cat CVS/Repository" \
-"${AREP}\."
- dotest cvsadm-N3d "cat 1mod/CVS/Repository" \
-"${AREP}mod1"
- rm -rf CVS 1mod
-
- dotest cvsadm-N4 "${testcvs} co -N 2mod" \
-"${PROG} [a-z]*: Updating 2mod
-U 2mod/file2"
- dotest cvsadm-N4b "cat CVS/Repository" \
-"${AREP}\."
- dotest cvsadm-N4d "cat 2mod/CVS/Repository" \
-"${AREP}mod2/sub2"
- rm -rf CVS 2mod
-
- dotest cvsadm-N5 "${testcvs} co -N 1d1mod" \
-"${PROG} [a-z]*: Updating dir1d1
-U dir1d1/file1"
- dotest cvsadm-N5b "cat CVS/Repository" \
-"${AREP}\."
- dotest cvsadm-N5d "cat dir1d1/CVS/Repository" \
-"${AREP}mod1"
- rm -rf CVS dir1d1
-
- dotest cvsadm-N6 "${testcvs} co -N 1d2mod" \
-"${PROG} [a-z]*: Updating dir1d2
-U dir1d2/file2"
- dotest cvsadm-N6b "cat CVS/Repository" \
-"${AREP}\."
- dotest cvsadm-N6d "cat dir1d2/CVS/Repository" \
-"${AREP}mod2/sub2"
- rm -rf CVS dir1d2
-
- dotest cvsadm-N7 "${testcvs} co -N 2d1mod" \
-"${PROG} [a-z]*: Updating dir2d1/sub2d1
-U dir2d1/sub2d1/file1"
- dotest cvsadm-N7b "cat CVS/Repository" \
-"${AREP}\."
- dotest cvsadm-N7d "cat dir2d1/CVS/Repository" \
-"${AREP}CVSROOT/Emptydir"
- dotest cvsadm-N7f "cat dir2d1/sub2d1/CVS/Repository" \
-"${AREP}mod1"
- rm -rf CVS dir2d1
-
- dotest cvsadm-N8 "${testcvs} co -N 2d2mod" \
-"${PROG} [a-z]*: Updating dir2d2/sub2d2
-U dir2d2/sub2d2/file2"
- dotest cvsadm-N8b "cat CVS/Repository" \
-"${AREP}\."
- dotest cvsadm-N8d "cat dir2d2/CVS/Repository" \
-"${AREP}CVSROOT/Emptydir"
- dotest cvsadm-N8f "cat dir2d2/sub2d2/CVS/Repository" \
-"${AREP}mod2/sub2"
- rm -rf CVS dir2d2
-
- ## the ones in one-deep directories
-
- dotest cvsadm-N1d3 "${testcvs} co -N -d dir 1mod" \
-"${PROG} [a-z]*: Updating dir/1mod
-U dir/1mod/file1"
- dotest cvsadm-N1d3b "cat CVS/Repository" \
-"${AREP}\."
- dotest cvsadm-N1d3d "cat dir/CVS/Repository" \
-"${AREP}\."
- dotest cvsadm-N1d3f "cat dir/1mod/CVS/Repository" \
-"${AREP}mod1"
- rm -rf CVS dir
-
- dotest cvsadm-N1d4 "${testcvs} co -N -d dir 2mod" \
-"${PROG} [a-z]*: Updating dir/2mod
-U dir/2mod/file2"
- dotest cvsadm-N1d4b "cat CVS/Repository" \
-"${AREP}\."
- dotest cvsadm-N1d4d "cat dir/CVS/Repository" \
-"${AREP}\."
- dotest cvsadm-N1d4f "cat dir/2mod/CVS/Repository" \
-"${AREP}mod2/sub2"
- rm -rf CVS dir
-
- dotest cvsadm-N1d5 "${testcvs} co -N -d dir 1d1mod" \
-"${PROG} [a-z]*: Updating dir/dir1d1
-U dir/dir1d1/file1"
- dotest cvsadm-N1d5b "cat CVS/Repository" \
-"${AREP}\."
- dotest cvsadm-N1d5d "cat dir/CVS/Repository" \
-"${AREP}\."
- dotest cvsadm-N1d5d "cat dir/dir1d1/CVS/Repository" \
-"${AREP}mod1"
- rm -rf CVS dir
-
- dotest cvsadm-N1d6 "${testcvs} co -N -d dir 1d2mod" \
-"${PROG} [a-z]*: Updating dir/dir1d2
-U dir/dir1d2/file2"
- dotest cvsadm-N1d6b "cat CVS/Repository" \
-"${AREP}\."
- dotest cvsadm-N1d6d "cat dir/CVS/Repository" \
-"${AREP}\."
- dotest cvsadm-N1d6f "cat dir/dir1d2/CVS/Repository" \
-"${AREP}mod2/sub2"
- rm -rf CVS dir
-
- dotest cvsadm-N1d7 "${testcvs} co -N -d dir 2d1mod" \
-"${PROG} [a-z]*: Updating dir/dir2d1/sub2d1
-U dir/dir2d1/sub2d1/file1"
- dotest cvsadm-N1d7b "cat CVS/Repository" \
-"${AREP}\."
- dotest cvsadm-N1d7d "cat dir/CVS/Repository" \
-"${AREP}\."
- dotest cvsadm-N1d7f "cat dir/dir2d1/CVS/Repository" \
-"${AREP}CVSROOT/Emptydir"
- dotest cvsadm-N1d7h "cat dir/dir2d1/sub2d1/CVS/Repository" \
-"${AREP}mod1"
- rm -rf CVS dir
-
- dotest cvsadm-N1d8 "${testcvs} co -N -d dir 2d2mod" \
-"${PROG} [a-z]*: Updating dir/dir2d2/sub2d2
-U dir/dir2d2/sub2d2/file2"
- dotest cvsadm-N1d8b "cat CVS/Repository" \
-"${AREP}\."
- dotest cvsadm-N1d8d "cat dir/CVS/Repository" \
-"${AREP}\."
- dotest cvsadm-N1d8d "cat dir/dir2d2/CVS/Repository" \
-"${AREP}CVSROOT/Emptydir"
- dotest cvsadm-N1d8d "cat dir/dir2d2/sub2d2/CVS/Repository" \
-"${AREP}mod2/sub2"
- rm -rf CVS dir
-
- ## the ones in two-deep directories
-
- dotest cvsadm-N2d3 "${testcvs} co -N -d dir/dir2 1mod" \
-"${PROG} [a-z]*: Updating dir/dir2/1mod
-U dir/dir2/1mod/file1"
- dotest cvsadm-N2d3b "cat CVS/Repository" \
-"${AREP}\."
- dotest cvsadm-N2d3d "cat dir/CVS/Repository" \
-"${AREP}CVSROOT/Emptydir"
- dotest cvsadm-N2d3f "cat dir/dir2/CVS/Repository" \
-"${AREP}\."
- dotest cvsadm-N2d3h "cat dir/dir2/1mod/CVS/Repository" \
-"${AREP}mod1"
- rm -rf CVS dir
-
- dotest cvsadm-N2d4 "${testcvs} co -N -d dir/dir2 2mod" \
-"${PROG} [a-z]*: Updating dir/dir2/2mod
-U dir/dir2/2mod/file2"
- dotest cvsadm-N2d4b "cat CVS/Repository" \
-"${AREP}\."
- dotest cvsadm-N2d4d "cat dir/CVS/Repository" \
-"${AREP}CVSROOT/Emptydir"
- dotest cvsadm-N2d4f "cat dir/dir2/CVS/Repository" \
-"${AREP}\."
- dotest cvsadm-N2d4h "cat dir/dir2/2mod/CVS/Repository" \
-"${AREP}mod2/sub2"
- rm -rf CVS dir
-
- dotest cvsadm-N2d5 "${testcvs} co -N -d dir/dir2 1d1mod" \
-"${PROG} [a-z]*: Updating dir/dir2/dir1d1
-U dir/dir2/dir1d1/file1"
- dotest cvsadm-N2d5b "cat CVS/Repository" \
-"${AREP}\."
- dotest cvsadm-N2d5d "cat dir/CVS/Repository" \
-"${AREP}CVSROOT/Emptydir"
- dotest cvsadm-N2d5f "cat dir/dir2/CVS/Repository" \
-"${AREP}\."
- dotest cvsadm-N2d5h "cat dir/dir2/dir1d1/CVS/Repository" \
-"${AREP}mod1"
- rm -rf CVS dir
-
- dotest cvsadm-N2d6 "${testcvs} co -N -d dir/dir2 1d2mod" \
-"${PROG} [a-z]*: Updating dir/dir2/dir1d2
-U dir/dir2/dir1d2/file2"
- dotest cvsadm-N2d6b "cat CVS/Repository" \
-"${AREP}\."
- dotest cvsadm-N2d6d "cat dir/CVS/Repository" \
-"${AREP}CVSROOT/Emptydir"
- dotest cvsadm-N2d6f "cat dir/dir2/CVS/Repository" \
-"${AREP}\."
- dotest cvsadm-N2d6h "cat dir/dir2/dir1d2/CVS/Repository" \
-"${AREP}mod2/sub2"
- rm -rf CVS dir
-
- dotest cvsadm-N2d7 "${testcvs} co -N -d dir/dir2 2d1mod" \
-"${PROG} [a-z]*: Updating dir/dir2/dir2d1/sub2d1
-U dir/dir2/dir2d1/sub2d1/file1"
- dotest cvsadm-N2d7b "cat CVS/Repository" \
-"${AREP}\."
- dotest cvsadm-N2d7d "cat dir/CVS/Repository" \
-"${AREP}CVSROOT/Emptydir"
- dotest cvsadm-N2d7f "cat dir/dir2/CVS/Repository" \
-"${AREP}\."
- dotest cvsadm-N2d7f "cat dir/dir2/dir2d1/CVS/Repository" \
-"${AREP}CVSROOT/Emptydir"
- dotest cvsadm-N2d7h "cat dir/dir2/dir2d1/sub2d1/CVS/Repository" \
-"${AREP}mod1"
- rm -rf CVS dir
-
- dotest cvsadm-N2d8 "${testcvs} co -N -d dir/dir2 2d2mod" \
-"${PROG} [a-z]*: Updating dir/dir2/dir2d2/sub2d2
-U dir/dir2/dir2d2/sub2d2/file2"
- dotest cvsadm-N2d8b "cat CVS/Repository" \
-"${AREP}\."
- dotest cvsadm-N2d8d "cat dir/CVS/Repository" \
-"${AREP}CVSROOT/Emptydir"
- dotest cvsadm-N2d8f "cat dir/dir2/CVS/Repository" \
-"${AREP}\."
- dotest cvsadm-N2d8h "cat dir/dir2/dir2d2/CVS/Repository" \
-"${AREP}CVSROOT/Emptydir"
- dotest cvsadm-N2d8j "cat dir/dir2/dir2d2/sub2d2/CVS/Repository" \
-"${AREP}mod2/sub2"
- rm -rf CVS dir
-
- ##################################################
- ## That's enough of that, thank you very much.
- ##################################################
-
- # remove our junk
- cd ..
- rm -rf 1
- rm -rf ${CVSROOT_DIRNAME}/1mod
- rm -rf ${CVSROOT_DIRNAME}/1mod-2
- rm -rf ${CVSROOT_DIRNAME}/2mod
- rm -rf ${CVSROOT_DIRNAME}/2mod-2
- ;;
-
diffmerge1)
# Make sure CVS can merge correctly in circumstances where it
# used to mess up (due to a bug which existed in diffutils 2.7
@@ -13439,6 +14027,9 @@ d472 12
dotest diffmerge2_diff \
"${testcvs} diff -r Review_V1p3 sgrid.h" ''
+ cd ..
+ rm -rf diffmerge2
+ rm -rf ${CVSROOT_DIRNAME}/diffmerge2
;;
*)
diff --git a/contrib/cvs/src/server.c b/contrib/cvs/src/server.c
index a8334153592f..a005646fb402 100644
--- a/contrib/cvs/src/server.c
+++ b/contrib/cvs/src/server.c
@@ -1194,7 +1194,7 @@ serve_modified (arg)
}
{
- int status = change_mode (arg, mode_text);
+ int status = change_mode (arg, mode_text, 0);
free (mode_text);
if (status)
{
@@ -3355,12 +3355,13 @@ server_modtime (finfo, vers_ts)
/* See server.h for description. */
void
-server_updated (finfo, vers, updated, file_info, checksum)
+server_updated (finfo, vers, updated, mode, checksum, filebuf)
struct file_info *finfo;
Vers_TS *vers;
enum server_updated_arg4 updated;
- struct stat *file_info;
+ mode_t mode;
unsigned char *checksum;
+ struct buffer *filebuf;
{
if (noexec)
{
@@ -3379,25 +3380,43 @@ server_updated (finfo, vers, updated, file_info, checksum)
if (entries_line != NULL && scratched_file == NULL)
{
FILE *f;
- struct stat sb;
struct buffer_data *list, *last;
unsigned long size;
char size_text[80];
- if ( CVS_STAT (finfo->file, &sb) < 0)
+ if (filebuf != NULL)
{
- if (existence_error (errno))
+ size = buf_length (filebuf);
+ if (mode == (mode_t) -1)
+ error (1, 0, "\
+CVS server internal error: no mode in server_updated");
+ }
+ else
+ {
+ struct stat sb;
+
+ if ( CVS_STAT (finfo->file, &sb) < 0)
{
- /*
- * If we have a sticky tag for a branch on which the
- * file is dead, and cvs update the directory, it gets
- * a T_CHECKOUT but no file. So in this case just
- * forget the whole thing. */
- free (entries_line);
- entries_line = NULL;
- goto done;
+ if (existence_error (errno))
+ {
+ /* If we have a sticky tag for a branch on which
+ the file is dead, and cvs update the directory,
+ it gets a T_CHECKOUT but no file. So in this
+ case just forget the whole thing. */
+ free (entries_line);
+ entries_line = NULL;
+ goto done;
+ }
+ error (1, errno, "reading %s", finfo->fullname);
+ }
+ size = sb.st_size;
+ if (mode == (mode_t) -1)
+ {
+ /* FIXME: When we check out files the umask of the
+ server (set in .bashrc if rsh is in use) affects
+ what mode we send, and it shouldn't. */
+ mode = sb.st_mode;
}
- error (1, errno, "reading %s", finfo->fullname);
}
if (checksum != NULL)
@@ -3466,21 +3485,14 @@ server_updated (finfo, vers, updated, file_info, checksum)
{
char *mode_string;
- /* FIXME: When we check out files the umask of the server
- (set in .bashrc if rsh is in use) affects what mode we
- send, and it shouldn't. */
- if (file_info != NULL)
- mode_string = mode_to_string (file_info->st_mode);
- else
- mode_string = mode_to_string (sb.st_mode);
+ mode_string = mode_to_string (mode);
buf_output0 (protocol, mode_string);
buf_output0 (protocol, "\n");
free (mode_string);
}
list = last = NULL;
- size = 0;
- if (sb.st_size > 0)
+ if (size > 0)
{
/* Throughout this section we use binary mode to read the
file we are sending. The client handles any line ending
@@ -3493,11 +3505,19 @@ server_updated (finfo, vers, updated, file_info, checksum)
* might be computable somehow; using 100 here is just
* a first approximation.
*/
- && sb.st_size > 100)
+ && size > 100)
{
int status, fd, gzip_status;
pid_t gzip_pid;
+ /* Callers must avoid passing us a buffer if
+ file_gzip_level is set. We could handle this case,
+ but it's not worth it since this case never arises
+ with a current client and server. */
+ if (filebuf != NULL)
+ error (1, 0, "\
+CVS server internal error: unhandled case in server_updated");
+
fd = CVS_OPEN (finfo->file, O_RDONLY | OPEN_BINARY, 0);
if (fd < 0)
error (1, errno, "reading %s", finfo->fullname);
@@ -3520,15 +3540,14 @@ server_updated (finfo, vers, updated, file_info, checksum)
/* Prepending length with "z" is flag for using gzip here. */
buf_output0 (protocol, "z");
}
- else
+ else if (filebuf == NULL)
{
long status;
- size = sb.st_size;
f = CVS_FOPEN (finfo->file, "rb");
if (f == NULL)
error (1, errno, "reading %s", finfo->fullname);
- status = buf_read_file (f, sb.st_size, &list, &last);
+ status = buf_read_file (f, size, &list, &last);
if (status == -2)
(*protocol->memory_error) (protocol);
else if (status != 0)
@@ -3542,7 +3561,13 @@ server_updated (finfo, vers, updated, file_info, checksum)
sprintf (size_text, "%lu\n", size);
buf_output0 (protocol, size_text);
- buf_append_data (protocol, list, last);
+ 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. */
/*
@@ -3555,6 +3580,7 @@ server_updated (finfo, vers, updated, file_info, checksum)
if ((updated == SERVER_UPDATED
|| updated == SERVER_PATCHED
|| updated == SERVER_RCS_DIFF)
+ && filebuf != NULL
/* But if we are joining, we'll need the file when we call
join_file. */
&& !joining ())
@@ -5608,7 +5634,7 @@ this client does not support writing binary files to stdout");
I assume that what they are talking about can also be helped
by flushing the stream before changing the mode. */
fflush (stdout);
- oldmode = _setmode (_fileno (stdout), _O_BINARY);
+ oldmode = _setmode (_fileno (stdout), OPEN_BINARY);
if (oldmode < 0)
error (0, errno, "failed to setmode on stdout");
#endif
@@ -5623,7 +5649,7 @@ this client does not support writing binary files to stdout");
}
#ifdef USE_SETMODE_STDOUT
fflush (stdout);
- if (_setmode (_fileno (stdout), oldmode) != _O_BINARY)
+ if (_setmode (_fileno (stdout), oldmode) != OPEN_BINARY)
error (0, errno, "failed to setmode on stdout");
#endif
}
diff --git a/contrib/cvs/src/server.h b/contrib/cvs/src/server.h
index e5a3c3f55f47..f94b7aa32a49 100644
--- a/contrib/cvs/src/server.h
+++ b/contrib/cvs/src/server.h
@@ -57,16 +57,19 @@ extern void server_checked_in
extern void server_copy_file
PROTO((char *file, char *update_dir, char *repository, char *newfile));
-/* Send the appropriate responses for a file described by FILE,
- UPDATE_DIR, REPOSITORY, and VERS. FILE_INFO is the result of
- statting the file, or NULL if it hasn't been statted yet. This is
- called after server_register or server_scratch. In the latter case
- the file is to be removed (and vers can be NULL). In the former
- case, vers must be non-NULL, and UPDATED indicates whether the file
- is now up to date (SERVER_UPDATED, yes, SERVER_MERGED, no,
- SERVER_PATCHED, yes, but file is a diff from user version to
- repository version, SERVER_RCS_DIFF, yes, like SERVER_PATCHED but
- with an RCS style diff). */
+/* Send the appropriate responses for a file described by FINFO and
+ VERS. This is called after server_register or server_scratch. In
+ the latter case the file is to be removed (and VERS can be NULL).
+ In the former case, VERS must be non-NULL, and UPDATED indicates
+ whether the file is now up to date (SERVER_UPDATED, yes,
+ SERVER_MERGED, no, SERVER_PATCHED, yes, but file is a diff from
+ user version to repository version, SERVER_RCS_DIFF, yes, like
+ SERVER_PATCHED but with an RCS style diff). MODE is the mode the
+ file should get, or (mode_t) -1 if this should be obtained from the
+ file itself. CHECKSUM is the MD5 checksum of the file, or NULL if
+ this need not be sent. If FILEBUF is not NULL, it holds the
+ contents of the file, in which case the file itself may not exist.
+ If FILEBUF is not NULL, server_updated will free it. */
enum server_updated_arg4
{
SERVER_UPDATED,
@@ -74,10 +77,14 @@ enum server_updated_arg4
SERVER_PATCHED,
SERVER_RCS_DIFF
};
+#ifdef __STDC__
+struct buffer;
+#endif
+
extern void server_updated
PROTO((struct file_info *finfo, Vers_TS *vers,
- enum server_updated_arg4 updated, struct stat *,
- unsigned char *checksum));
+ enum server_updated_arg4 updated, mode_t mode,
+ unsigned char *checksum, struct buffer *filebuf));
/* Whether we should send RCS format patches. */
extern int server_use_rcs_diff PROTO((void));
diff --git a/contrib/cvs/src/subr.c b/contrib/cvs/src/subr.c
index 61b31031fe96..07d516f684cc 100644
--- a/contrib/cvs/src/subr.c
+++ b/contrib/cvs/src/subr.c
@@ -288,18 +288,13 @@ increment_revnum (rev)
/* Return the username by which the caller should be identified in
CVS, in contexts such as the author field of RCS files, various
- logs, etc.
-
- Returns a pointer to storage that we manage; it is good until the
- next call to getcaller () (provided that the caller doesn't call
- getlogin () or some such themself). */
+ logs, etc. */
char *
getcaller ()
{
#ifndef SYSTEM_GETCALLER
- static char uidname[20];
+ static char *cache;
struct passwd *pw;
- char *name;
uid_t uid;
#endif
@@ -316,20 +311,32 @@ getcaller ()
try LOGNAME USER or getlogin(). If getlogin() and getpwuid()
both fail, return the uid as a string. */
+ if (cache != NULL)
+ return cache;
+
uid = getuid ();
if (uid == (uid_t) 0)
{
+ char *name;
+
/* super-user; try getlogin() to distinguish */
if (((name = getlogin ()) || (name = getenv("LOGNAME")) ||
(name = getenv("USER"))) && *name)
- return (name);
+ {
+ cache = xstrdup (name);
+ return cache;
+ }
}
if ((pw = (struct passwd *) getpwuid (uid)) == NULL)
{
+ char uidname[20];
+
(void) sprintf (uidname, "uid%lu", (unsigned long) uid);
- return (uidname);
+ cache = xstrdup (uidname);
+ return cache;
}
- return (pw->pw_name);
+ cache = xstrdup (pw->pw_name);
+ return cache;
#endif
}
@@ -608,8 +615,16 @@ get_file (name, fullname, mode, buf, bufsize, len)
}
else
{
- if (CVS_STAT (name, &s) < 0)
+ if (CVS_LSTAT (name, &s) < 0)
error (1, errno, "can't stat %s", fullname);
+
+ /* Don't attempt to read special files or symlinks. */
+ if (!S_ISREG (s.st_mode))
+ {
+ *len = 0;
+ return;
+ }
+
/* Convert from signed to unsigned. */
filesize = s.st_size;
diff --git a/contrib/cvs/src/update.c b/contrib/cvs/src/update.c
index 48bed3bb8d49..2e7bda39febb 100644
--- a/contrib/cvs/src/update.c
+++ b/contrib/cvs/src/update.c
@@ -42,9 +42,14 @@
#include "fileattr.h"
#include "edit.h"
#include "getline.h"
+#include "buffer.h"
+#include "hardlink.h"
static int checkout_file PROTO ((struct file_info *finfo, Vers_TS *vers_ts,
- int adding));
+ int adding, int merging, int update_server));
+#ifdef SERVER_SUPPORT
+static void checkout_to_buffer PROTO ((void *, const char *, size_t));
+#endif
#ifdef SERVER_SUPPORT
static int patch_file PROTO ((struct file_info *finfo,
Vers_TS *vers_ts,
@@ -64,6 +69,9 @@ static int update_fileproc PROTO ((void *callerdat, struct file_info *));
static int update_filesdone_proc PROTO ((void *callerdat, int err,
char *repository, char *update_dir,
List *entries));
+#ifdef PRESERVE_PERMISSIONS_SUPPORT
+static int get_linkinfo_proc PROTO ((void *callerdat, struct file_info *));
+#endif
static void write_letter PROTO ((struct file_info *finfo, int letter));
static void join_file PROTO ((struct file_info *finfo, Vers_TS *vers_ts));
@@ -79,6 +87,10 @@ static char *date = NULL;
static int rewrite_tag;
static int nonbranch;
+/* If we set the tag or date for a subdirectory, we use this to undo
+ the setting. See update_dirent_proc. */
+static char *tag_update_dir;
+
static char *join_rev1, *date_rev1;
static char *join_rev2, *date_rev2;
static int aflag = 0;
@@ -437,6 +449,32 @@ do_update (argc, argv, xoptions, xtag, xdate, xforce, local, xbuild, xaflag,
else
date_rev2 = (char *) NULL;
+#ifdef PRESERVE_PERMISSIONS_SUPPORT
+ if (preserve_perms)
+ {
+ /* We need to do an extra recursion, bleah. It's to make sure
+ that we know as much as possible about file linkage. */
+ hardlist = getlist();
+ working_dir = xgetwd(); /* save top-level working dir */
+
+ /* FIXME-twp: the arguments to start_recursion make me dizzy. This
+ function call was copied from the update_fileproc call that
+ follows it; someone should make sure that I did it right. */
+ err = start_recursion (get_linkinfo_proc, (FILESDONEPROC) NULL,
+ (DIRENTPROC) NULL, (DIRLEAVEPROC) NULL, NULL,
+ argc, argv, local, which, aflag, 1,
+ preload_update_dir, 1);
+ if (err)
+ return (err);
+
+ /* FIXME-twp: at this point we should walk the hardlist
+ and update the `links' field of each hardlink_info struct
+ to list the files that are linked on dist. That would make
+ it easier & more efficient to compare the disk linkage with
+ the repository linkage (a simple strcmp). */
+ }
+#endif
+
/* call the recursion processor */
err = start_recursion (update_fileproc, update_filesdone_proc,
update_dirent_proc, update_dirleave_proc, NULL,
@@ -456,6 +494,50 @@ do_update (argc, argv, xoptions, xtag, xdate, xforce, local, xbuild, xaflag,
return (err);
}
+#ifdef PRESERVE_PERMISSIONS_SUPPORT
+/*
+ * The get_linkinfo_proc callback adds each file to the hardlist
+ * (see hardlink.c).
+ */
+
+static int
+get_linkinfo_proc (callerdat, finfo)
+ void *callerdat;
+ struct file_info *finfo;
+{
+ char *fullpath;
+ Node *linkp;
+ struct hardlink_info *hlinfo;
+
+ /* Get the full pathname of the current file. */
+ fullpath = xmalloc (strlen(working_dir) +
+ strlen(finfo->fullname) + 2);
+ sprintf (fullpath, "%s/%s", working_dir, finfo->fullname);
+
+ /* To permit recursing into subdirectories, files
+ are keyed on the full pathname and not on the basename. */
+ linkp = lookup_file_by_inode (fullpath);
+ if (linkp == NULL)
+ {
+ /* The file isn't on disk; we are probably restoring
+ a file that was removed. */
+ return 0;
+ }
+
+ /* Create a new, empty hardlink_info node. */
+ hlinfo = (struct hardlink_info *)
+ xmalloc (sizeof (struct hardlink_info));
+
+ hlinfo->status = (Ctype) 0; /* is this dumb? */
+ hlinfo->checked_out = 0;
+ hlinfo->links = NULL;
+
+ linkp->data = (char *) hlinfo;
+
+ return 0;
+}
+#endif
+
/*
* This is the callback proc for update. It is called for each file in each
* directory by the recursion code. The current directory is the local
@@ -526,7 +608,7 @@ update_fileproc (callerdat, finfo)
#ifdef SERVER_SUPPORT
case T_PATCH: /* needs patch */
#endif
- retval = checkout_file (finfo, vers, 0);
+ retval = checkout_file (finfo, vers, 0, 0, 0);
break;
default: /* can't ever happen :-) */
@@ -629,7 +711,8 @@ update_fileproc (callerdat, finfo)
(rcs_diff_patches
? SERVER_RCS_DIFF
: SERVER_PATCHED),
- &file_info, checksum);
+ file_info.st_mode, checksum,
+ (struct buffer *) NULL);
break;
}
}
@@ -639,13 +722,7 @@ update_fileproc (callerdat, finfo)
/* Fall through. */
#endif
case T_CHECKOUT: /* needs checkout */
- retval = checkout_file (finfo, vers, 0);
-#ifdef SERVER_SUPPORT
- if (server_active && retval == 0)
- server_updated (finfo, vers,
- SERVER_UPDATED, (struct stat *) NULL,
- (unsigned char *) NULL);
-#endif
+ retval = checkout_file (finfo, vers, 0, 0, 1);
break;
case T_ADDED: /* added but not committed */
write_letter (finfo, 'A');
@@ -663,8 +740,9 @@ update_fileproc (callerdat, finfo)
if (vers->ts_user == NULL)
server_scratch_entry_only ();
server_updated (finfo, vers,
- SERVER_UPDATED, (struct stat *) NULL,
- (unsigned char *) NULL);
+ SERVER_UPDATED, (mode_t) -1,
+ (unsigned char *) NULL,
+ (struct buffer *) NULL);
}
#endif
break;
@@ -806,6 +884,26 @@ update_dirent_proc (callerdat, dir, repository, update_dir, entries)
else
{
/* otherwise, create the dir and appropriate adm files */
+
+ /* If no tag or date were specified on the command line,
+ and we're not using -A, we want the subdirectory to use
+ the tag and date, if any, of the current directory.
+ That way, update -d will work correctly when working on
+ a branch.
+
+ We use TAG_UPDATE_DIR to undo the tag setting in
+ update_dirleave_proc. If we did not do this, we would
+ not correctly handle a working directory with multiple
+ tags (and maybe we should prohibit such working
+ directories, but they work now and we shouldn't make
+ them stop working without more thought). */
+ if ((tag == NULL && date == NULL) && ! aflag)
+ {
+ ParseTag (&tag, &date, &nonbranch);
+ if (tag != NULL || date != NULL)
+ tag_update_dir = xstrdup (update_dir);
+ }
+
make_directory (dir);
Create_Admin (dir, update_dir, repository, tag, date,
/* This is a guess. We will rewrite it later
@@ -897,6 +995,27 @@ update_dirleave_proc (callerdat, dir, err, update_dir, entries)
{
FILE *fp;
+ /* If we set the tag or date for a new subdirectory in
+ update_dirent_proc, and we're now done with that subdirectory,
+ undo the tag/date setting. Note that we know that the tag and
+ date were both originally NULL in this case. */
+ if (tag_update_dir != NULL && strcmp (update_dir, tag_update_dir) == 0)
+ {
+ if (tag != NULL)
+ {
+ free (tag);
+ tag = NULL;
+ }
+ if (date != NULL)
+ {
+ free (date);
+ date = NULL;
+ }
+ nonbranch = 0;
+ free (tag_update_dir);
+ tag_update_dir = NULL;
+ }
+
/* run the update_prog if there is one */
/* FIXME: should be checking for errors from CVS_FOPEN and printing
them if not existence_error. */
@@ -1016,7 +1135,7 @@ isemptydir (dir, might_not_exist)
if (CVS_CHDIR (dir) < 0)
error (1, errno, "cannot change directory to %s", dir);
- l = Entries_Open (0);
+ l = Entries_Open (0, NULL);
files_removed = walklist (l, isremoved, 0);
Entries_Close (l);
@@ -1063,22 +1182,29 @@ scratch_file (finfo)
* Check out a file.
*/
static int
-checkout_file (finfo, vers_ts, adding)
+checkout_file (finfo, vers_ts, adding, merging, update_server)
struct file_info *finfo;
Vers_TS *vers_ts;
int adding;
+ int merging;
+ int update_server;
{
char *backup;
int set_time, retval = 0;
- int retcode = 0;
int status;
int file_is_dead;
+ struct buffer *revbuf;
- /* Solely to suppress a warning from gcc -Wall. */
backup = NULL;
+ revbuf = NULL;
- /* don't screw with backup files if we're going to stdout */
- if (!pipeout)
+ /* Don't screw with backup files if we're going to stdout, or if
+ we are the server. */
+ if (!pipeout
+#ifdef SERVER_SUPPORT
+ && ! server_active
+#endif
+ )
{
backup = xmalloc (strlen (finfo->file)
+ sizeof (CVSADM)
@@ -1088,6 +1214,7 @@ checkout_file (finfo, vers_ts, adding)
if (isfile (finfo->file))
rename_file (finfo->file, backup);
else
+ {
/* If -f/-t wrappers are being used to wrap up a directory,
then backup might be a directory instead of just a file. */
if (unlink_file_dir (backup) < 0)
@@ -1097,6 +1224,9 @@ checkout_file (finfo, vers_ts, adding)
/* FIXME: should include update_dir in message. */
error (0, errno, "error removing %s", backup);
}
+ free (backup);
+ backup = NULL;
+ }
}
file_is_dead = RCS_isdead (vers_ts->srcfile, vers_ts->vn_rcs);
@@ -1124,22 +1254,64 @@ VERS: ", 0);
}
}
- status = RCS_checkout (vers_ts->srcfile,
- pipeout ? NULL : finfo->file,
- vers_ts->vn_rcs, vers_ts->vn_tag,
- vers_ts->options, RUN_TTY,
- (RCSCHECKOUTPROC) NULL, (void *) NULL);
+#ifdef SERVER_SUPPORT
+ if (update_server
+ && server_active
+ && ! pipeout
+ && ! file_gzip_level
+ && ! joining ()
+ && ! wrap_name_has (finfo->file, WRAP_FROMCVS))
+ {
+ revbuf = buf_nonio_initialize ((BUFMEMERRPROC) NULL);
+ status = RCS_checkout (vers_ts->srcfile, (char *) NULL,
+ vers_ts->vn_rcs, vers_ts->vn_tag,
+ vers_ts->options, RUN_TTY,
+ checkout_to_buffer, revbuf);
+ }
+ else
+#endif
+ status = RCS_checkout (vers_ts->srcfile,
+ pipeout ? NULL : finfo->file,
+ vers_ts->vn_rcs, vers_ts->vn_tag,
+ vers_ts->options, RUN_TTY,
+ (RCSCHECKOUTPROC) NULL, (void *) NULL);
}
if (file_is_dead || status == 0)
{
+ mode_t mode;
+
+ mode = (mode_t) -1;
+
if (!pipeout)
{
Vers_TS *xvers_ts;
+ if (revbuf != NULL)
+ {
+ struct stat sb;
+
+ /* FIXME: We should have RCS_checkout return the mode. */
+ if (stat (vers_ts->srcfile->path, &sb) < 0)
+ error (1, errno, "cannot stat %s",
+ vers_ts->srcfile->path);
+ mode = sb.st_mode &~ (S_IWRITE | S_IWGRP | S_IWOTH);
+ }
+
if (cvswrite
&& !file_is_dead
&& !fileattr_get (finfo->file, "_watched"))
- xchmod (finfo->file, 1);
+ {
+ 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
@@ -1171,6 +1343,27 @@ VERS: ", 0);
if (strcmp (xvers_ts->options, "-V4") == 0)
xvers_ts->options[0] = '\0';
+ if (revbuf != NULL)
+ {
+ /* If we stored the file data into a buffer, then we
+ didn't create a file at all, so xvers_ts->ts_user
+ is wrong. The correct value is to have it be the
+ same as xvers_ts->ts_rcs, meaning that the working
+ file is unchanged from the RCS file.
+
+ FIXME: We should tell Version_TS not to waste time
+ statting the nonexistent file.
+
+ FIXME: Actually, I don't think the ts_user value
+ matters at all here. The only use I know of is
+ that it is printed in a trace message by
+ Server_Register. */
+
+ if (xvers_ts->ts_user != NULL)
+ free (xvers_ts->ts_user);
+ xvers_ts->ts_user = xstrdup (xvers_ts->ts_rcs);
+ }
+
(void) time (&last_register_time);
if (file_is_dead)
@@ -1179,7 +1372,7 @@ VERS: ", 0);
{
error (0, 0,
"warning: %s is not (any longer) pertinent",
- finfo->fullname);
+ finfo->fullname);
}
Scratch_Entry (finfo->entries, finfo->file);
#ifdef SERVER_SUPPORT
@@ -1226,21 +1419,29 @@ VERS: ", 0);
write_letter (finfo, 'U');
}
}
+
+#ifdef SERVER_SUPPORT
+ if (update_server && server_active)
+ server_updated (finfo, vers_ts,
+ merging ? SERVER_MERGED : SERVER_UPDATED,
+ mode, (unsigned char *) NULL, revbuf);
+#endif
}
else
{
- int old_errno = errno; /* save errno value over the rename */
-
- if (!pipeout && isfile (backup))
+ if (backup != NULL)
+ {
rename_file (backup, finfo->file);
+ free (backup);
+ backup = NULL;
+ }
- error (retcode == -1 ? 1 : 0, retcode == -1 ? old_errno : 0,
- "could not check out %s", finfo->fullname);
+ error (0, 0, "could not check out %s", finfo->fullname);
- retval = retcode;
+ retval = status;
}
- if (!pipeout)
+ if (backup != NULL)
{
/* If -f/-t wrappers are being used to wrap up a directory,
then backup might be a directory instead of just a file. */
@@ -1259,6 +1460,24 @@ VERS: ", 0);
#ifdef SERVER_SUPPORT
+/* This function is used to write data from a file being checked out
+ into a buffer. */
+
+static void
+checkout_to_buffer (callerdat, data, len)
+ void *callerdat;
+ const char *data;
+ size_t len;
+{
+ struct buffer *buf = (struct buffer *) callerdat;
+
+ buf_output (buf, data, len);
+}
+
+#endif /* SERVER_SUPPORT */
+
+#ifdef SERVER_SUPPORT
+
/* This structure is used to pass information between patch_file and
patch_file_write. */
@@ -1334,6 +1553,14 @@ patch_file (finfo, vers_ts, docheckout, file_info, checksum)
free (rev);
}
+ /* If the revision is dead, let checkout_file handle it rather
+ than duplicating the processing here. */
+ if (RCS_isdead (vers_ts->srcfile, vers_ts->vn_rcs))
+ {
+ *docheckout = 1;
+ return 0;
+ }
+
backup = xmalloc (strlen (finfo->file)
+ sizeof (CVSADM)
+ sizeof (CVSPREFIX)
@@ -1638,25 +1865,32 @@ merge_file (finfo, vers)
xchmod (finfo->file, 1);
if (strcmp (vers->options, "-kb") == 0
- || wrap_merge_is_copy (finfo->file))
+ || wrap_merge_is_copy (finfo->file)
+ || special_file_mismatch (finfo, NULL, vers->vn_rcs))
{
- /* For binary files, a merge is always a conflict. We give the
+ /* For binary files, a merge is always a conflict. Same for
+ files whose permissions or linkage do not match. We give the
user the two files, and let them resolve it. It is possible
that we should require a "touch foo" or similar step before
we allow a checkin. */
- status = checkout_file (finfo, vers, 0);
+
+ /* TODO: it may not always be necessary to regard a permission
+ mismatch as a conflict. The working file and the RCS file
+ have a common ancestor `A'; if the working file's permissions
+ match A's, then it's probably safe to overwrite them with the
+ RCS permissions. Only if the working file, the RCS file, and
+ A all disagree should this be considered a conflict. But more
+ thought needs to go into this, and in the meantime it is safe
+ to treat any such mismatch as an automatic conflict. -twp */
+
#ifdef SERVER_SUPPORT
- /* Send the new contents of the file before the message. If we
- wanted to be totally correct, we would have the client write
- the message only after the file has safely been written. */
if (server_active)
- {
server_copy_file (finfo->file, finfo->update_dir,
finfo->repository, backup);
- server_updated (finfo, vers, SERVER_MERGED,
- (struct stat *) NULL, (unsigned char *) NULL);
- }
#endif
+
+ status = checkout_file (finfo, vers, 0, 1, 1);
+
/* Is there a better term than "nonmergeable file"? What we
really mean is, not something that CVS cannot or does not
want to merge (there might be an external manual or
@@ -1717,7 +1951,8 @@ merge_file (finfo, vers)
server_copy_file (finfo->file, finfo->update_dir, finfo->repository,
backup);
server_updated (finfo, vers, SERVER_MERGED,
- (struct stat *) NULL, (unsigned char *) NULL);
+ (mode_t) -1, (unsigned char *) NULL,
+ (struct buffer *) NULL);
}
#endif
@@ -1951,8 +2186,8 @@ join_file (finfo, vers)
if (server_active)
{
server_scratch (finfo->file);
- server_updated (finfo, vers, SERVER_UPDATED, (struct stat *) NULL,
- (unsigned char *) NULL);
+ server_updated (finfo, vers, SERVER_UPDATED, (mode_t) -1,
+ (unsigned char *) NULL, (struct buffer *) NULL);
}
#endif
mrev = xmalloc (strlen (vers->vn_user) + 2);
@@ -2005,14 +2240,7 @@ join_file (finfo, vers)
/* FIXME: If checkout_file fails, we should arrange to
return a non-zero exit status. */
- status = checkout_file (finfo, xvers, 1);
-
-#ifdef SERVER_SUPPORT
- if (server_active && status == 0)
- server_updated (finfo, xvers,
- SERVER_UPDATED, (struct stat *) NULL,
- (unsigned char *) NULL);
-#endif
+ status = checkout_file (finfo, xvers, 1, 0, 1);
freevers_ts (&xvers);
@@ -2074,7 +2302,7 @@ join_file (finfo, vers)
(char *) NULL, RUN_TTY,
(RCSCHECKOUTPROC) NULL, (void *) NULL);
if (retcode != 0)
- error (1, retcode == -1 ? errno : 0,
+ error (1, 0,
"failed to check out %s file", finfo->fullname);
}
#endif
@@ -2147,9 +2375,11 @@ join_file (finfo, vers)
write_letter (finfo, 'U');
}
else if (strcmp (options, "-kb") == 0
- || wrap_merge_is_copy (finfo->file))
+ || wrap_merge_is_copy (finfo->file)
+ || special_file_mismatch (finfo, rev1, rev2))
{
- /* We are dealing with binary files, but real merging would
+ /* We are dealing with binary files, or files with a
+ permission/linkage mismatch, and real merging would
need to take place. This is a conflict. We give the user
the two files, and let them resolve it. It is possible
that we should require a "touch foo" or similar step before
@@ -2227,12 +2457,318 @@ join_file (finfo, vers)
server_copy_file (finfo->file, finfo->update_dir, finfo->repository,
backup);
server_updated (finfo, vers, SERVER_MERGED,
- (struct stat *) NULL, (unsigned char *) NULL);
+ (mode_t) -1, (unsigned char *) NULL,
+ (struct buffer *) NULL);
}
#endif
free (backup);
}
+/*
+ * Report whether revisions REV1 and REV2 of FINFO agree on:
+ * . file ownership
+ * . permissions
+ * . major and minor device numbers
+ * . symbolic links
+ * . hard links
+ *
+ * If either REV1 or REV2 is NULL, the working copy is used instead.
+ *
+ * Return 1 if the files differ on these data.
+ */
+
+int
+special_file_mismatch (finfo, rev1, rev2)
+ struct file_info *finfo;
+ char *rev1;
+ char *rev2;
+{
+#ifdef PRESERVE_PERMISSIONS_SUPPORT
+ struct stat sb;
+ RCSVers *vp;
+ Node *n;
+ uid_t rev1_uid, rev2_uid;
+ gid_t rev1_gid, rev2_gid;
+ mode_t rev1_mode, rev2_mode;
+ unsigned long dev_long;
+ dev_t rev1_dev, rev2_dev;
+ char *rev1_symlink = NULL;
+ char *rev2_symlink = NULL;
+ char *rev1_hardlinks = NULL;
+ char *rev2_hardlinks = NULL;
+ int check_uids, check_gids, check_modes;
+ int result;
+
+ /* If we don't care about special file info, then
+ don't report a mismatch in any case. */
+ if (!preserve_perms)
+ return 0;
+
+ /* When special_file_mismatch is called from No_Difference, the
+ RCS file has been only partially parsed. We must read the
+ delta tree in order to compare special file info recorded in
+ the delta nodes. (I think this is safe. -twp) */
+ if (finfo->rcs->flags & PARTIAL)
+ RCS_reparsercsfile (finfo->rcs, NULL, NULL);
+
+ check_uids = check_gids = check_modes = 1;
+
+ /* Obtain file information for REV1. If this is null, then stat
+ finfo->file and use that info. */
+ /* If a revision does not know anything about its status,
+ then presumably it doesn't matter, and indicates no conflict. */
+
+ if (rev1 == NULL)
+ {
+ if (islink (finfo->file))
+ rev1_symlink = xreadlink (finfo->file);
+ else
+ {
+ if (CVS_LSTAT (finfo->file, &sb) < 0)
+ error (1, errno, "could not get file information for %s",
+ finfo->file);
+ rev1_uid = sb.st_uid;
+ rev1_gid = sb.st_gid;
+ rev1_mode = sb.st_mode;
+ if (S_ISBLK (rev1_mode) || S_ISCHR (rev1_mode))
+ rev1_dev = sb.st_rdev;
+ }
+ rev1_hardlinks = list_files_linked_to (finfo->file);
+ }
+ else
+ {
+ n = findnode (finfo->rcs->versions, rev1);
+ vp = (RCSVers *) n->data;
+
+ n = findnode (vp->other_delta, "symlink");
+ if (n != NULL)
+ rev1_symlink = xstrdup (n->data);
+ else
+ {
+ n = findnode (vp->other_delta, "owner");
+ if (n == NULL)
+ check_uids = 0; /* don't care */
+ else
+ rev1_uid = strtoul (n->data, NULL, 10);
+
+ n = findnode (vp->other_delta, "group");
+ if (n == NULL)
+ check_gids = 0; /* don't care */
+ else
+ rev1_gid = strtoul (n->data, NULL, 10);
+
+ n = findnode (vp->other_delta, "permissions");
+ if (n == NULL)
+ check_modes = 0; /* don't care */
+ else
+ rev1_mode = strtoul (n->data, NULL, 8);
+
+ n = findnode (vp->other_delta, "special");
+ if (n == NULL)
+ rev1_mode |= S_IFREG;
+ else
+ {
+ /* If the size of `ftype' changes, fix the sscanf call also */
+ char ftype[16];
+ if (sscanf (n->data, "%16s %lu", ftype,
+ &dev_long) < 2)
+ error (1, 0, "%s:%s has bad `special' newphrase %s",
+ finfo->file, rev1, n->data);
+ rev1_dev = dev_long;
+ if (strcmp (ftype, "character") == 0)
+ rev1_mode |= S_IFCHR;
+ else if (strcmp (ftype, "block") == 0)
+ rev1_mode |= S_IFBLK;
+ else
+ error (0, 0, "%s:%s unknown file type `%s'",
+ finfo->file, rev1, ftype);
+ }
+
+ n = findnode (vp->other_delta, "hardlinks");
+ if (n == NULL)
+ rev1_hardlinks = xstrdup ("");
+ else
+ rev1_hardlinks = xstrdup (n->data);
+ }
+ }
+
+ /* Obtain file information for REV2. */
+ if (rev2 == NULL)
+ {
+ if (islink (finfo->file))
+ rev2_symlink = xreadlink (finfo->file);
+ else
+ {
+ if (CVS_LSTAT (finfo->file, &sb) < 0)
+ error (1, errno, "could not get file information for %s",
+ finfo->file);
+ rev2_uid = sb.st_uid;
+ rev2_gid = sb.st_gid;
+ rev2_mode = sb.st_mode;
+ if (S_ISBLK (rev2_mode) || S_ISCHR (rev2_mode))
+ rev2_dev = sb.st_rdev;
+ }
+ rev2_hardlinks = list_files_linked_to (finfo->file);
+ }
+ else
+ {
+ n = findnode (finfo->rcs->versions, rev2);
+ vp = (RCSVers *) n->data;
+
+ n = findnode (vp->other_delta, "symlink");
+ if (n != NULL)
+ rev2_symlink = xstrdup (n->data);
+ else
+ {
+ n = findnode (vp->other_delta, "owner");
+ if (n == NULL)
+ check_uids = 0; /* don't care */
+ else
+ rev2_uid = strtoul (n->data, NULL, 10);
+
+ n = findnode (vp->other_delta, "group");
+ if (n == NULL)
+ check_gids = 0; /* don't care */
+ else
+ rev2_gid = strtoul (n->data, NULL, 10);
+
+ n = findnode (vp->other_delta, "permissions");
+ if (n == NULL)
+ check_modes = 0; /* don't care */
+ else
+ rev2_mode = strtoul (n->data, NULL, 8);
+
+ n = findnode (vp->other_delta, "special");
+ if (n == NULL)
+ rev2_mode |= S_IFREG;
+ else
+ {
+ /* If the size of `ftype' changes, fix the sscanf call also */
+ char ftype[16];
+ if (sscanf (n->data, "%16s %lu", ftype,
+ &dev_long) < 2)
+ error (1, 0, "%s:%s has bad `special' newphrase %s",
+ finfo->file, rev2, n->data);
+ rev2_dev = dev_long;
+ if (strcmp (ftype, "character") == 0)
+ rev2_mode |= S_IFCHR;
+ else if (strcmp (ftype, "block") == 0)
+ rev2_mode |= S_IFBLK;
+ else
+ error (0, 0, "%s:%s unknown file type `%s'",
+ finfo->file, rev2, ftype);
+ }
+
+ n = findnode (vp->other_delta, "hardlinks");
+ if (n == NULL)
+ rev2_hardlinks = xstrdup ("");
+ else
+ rev2_hardlinks = xstrdup (n->data);
+ }
+ }
+
+ /* Check the user/group ownerships and file permissions, printing
+ an error for each mismatch found. Return 0 if all characteristics
+ matched, and 1 otherwise. */
+
+ result = 0;
+
+ /* Compare symlinks first, since symlinks are simpler (don't have
+ any other characteristics). */
+ if (rev1_symlink != NULL && rev2_symlink == NULL)
+ {
+ error (0, 0, "%s is a symbolic link",
+ (rev1 == NULL ? "working file" : rev1));
+ result = 1;
+ }
+ else if (rev1_symlink == NULL && rev2_symlink != NULL)
+ {
+ error (0, 0, "%s is a symbolic link",
+ (rev2 == NULL ? "working file" : rev2));
+ result = 1;
+ }
+ else if (rev1_symlink != NULL)
+ result = (strcmp (rev1_symlink, rev2_symlink) == 0);
+ else
+ {
+ /* Compare user ownership. */
+ if (check_uids && rev1_uid != rev2_uid)
+ {
+ error (0, 0, "%s: owner mismatch between %s and %s",
+ finfo->file,
+ (rev1 == NULL ? "working file" : rev1),
+ (rev2 == NULL ? "working file" : rev2));
+ result = 1;
+ }
+
+ /* Compare group ownership. */
+ if (check_gids && rev1_gid != rev2_gid)
+ {
+ error (0, 0, "%s: group mismatch between %s and %s",
+ finfo->file,
+ (rev1 == NULL ? "working file" : rev1),
+ (rev2 == NULL ? "working file" : rev2));
+ result = 1;
+ }
+
+ /* Compare permissions. */
+ if (check_modes &&
+ (rev1_mode & 07777) != (rev2_mode & 07777))
+ {
+ error (0, 0, "%s: permission mismatch between %s and %s",
+ finfo->file,
+ (rev1 == NULL ? "working file" : rev1),
+ (rev2 == NULL ? "working file" : rev2));
+ result = 1;
+ }
+
+ /* Compare device file characteristics. */
+ if ((rev1_mode & S_IFMT) != (rev2_mode & S_IFMT))
+ {
+ error (0, 0, "%s: %s and %s are different file types",
+ finfo->file,
+ (rev1 == NULL ? "working file" : rev1),
+ (rev2 == NULL ? "working file" : rev2));
+ result = 1;
+ }
+ else if (S_ISBLK (rev1_mode))
+ {
+ if (rev1_dev != rev2_dev)
+ {
+ error (0, 0, "%s: device numbers of %s and %s do not match",
+ finfo->file,
+ (rev1 == NULL ? "working file" : rev1),
+ (rev2 == NULL ? "working file" : rev2));
+ result = 1;
+ }
+ }
+
+ /* Compare hard links. */
+ if (strcmp (rev1_hardlinks, rev2_hardlinks) != 0)
+ {
+ error (0, 0, "%s: hard linkage of %s and %s do not match",
+ finfo->file,
+ (rev1 == NULL ? "working file" : rev1),
+ (rev2 == NULL ? "working file" : rev2));
+ result = 1;
+ }
+ }
+
+ if (rev1_symlink != NULL)
+ free (rev1_symlink);
+ if (rev2_symlink != NULL)
+ free (rev2_symlink);
+ if (rev1_hardlinks != NULL)
+ free (rev1_hardlinks);
+ if (rev2_hardlinks != NULL)
+ free (rev2_hardlinks);
+
+ return result;
+#else
+ return 0;
+#endif
+}
+
int
joining ()
{
diff --git a/contrib/cvs/src/vers_ts.c b/contrib/cvs/src/vers_ts.c
index bae3a43c661f..be0f588b419f 100644
--- a/contrib/cvs/src/vers_ts.c
+++ b/contrib/cvs/src/vers_ts.c
@@ -251,7 +251,7 @@ time_stamp_server (file, vers_ts, entdata)
struct stat sb;
char *cp;
- if ( CVS_STAT (file, &sb) < 0)
+ if (CVS_LSTAT (file, &sb) < 0)
{
if (! existence_error (errno))
error (1, errno, "cannot stat temp file");
@@ -322,7 +322,7 @@ time_stamp (file)
char *cp;
char *ts;
- if ( CVS_STAT (file, &sb) < 0)
+ if (CVS_LSTAT (file, &sb) < 0)
{
ts = NULL;
}
diff --git a/contrib/cvs/src/version.c b/contrib/cvs/src/version.c
index 1f77f585e90a..f386895559b5 100644
--- a/contrib/cvs/src/version.c
+++ b/contrib/cvs/src/version.c
@@ -12,7 +12,7 @@
#include "cvs.h"
-char *version_string = "\nConcurrent Versions System (CVS) 1.9.24";
+char *version_string = "\nConcurrent Versions System (CVS) 1.9.26";
#ifdef CLIENT_SUPPORT
#ifdef SERVER_SUPPORT
diff --git a/contrib/cvs/tools/ChangeLog b/contrib/cvs/tools/ChangeLog
index 407c053d6b2b..8cd177e81e06 100644
--- a/contrib/cvs/tools/ChangeLog
+++ b/contrib/cvs/tools/ChangeLog
@@ -1,3 +1,7 @@
+Sat Feb 21 22:02:12 1998 Ian Lance Taylor <ian@cygnus.com>
+
+ * Makefile.in (clean): Change "/bin/rm" to "rm".
+
Wed Jan 8 14:50:47 1997 Jim Kingdon <kingdon@harvey.cyclic.com>
* Makefile.in: Remove CVSid; we decided to get rid
diff --git a/contrib/cvs/tools/Makefile.in b/contrib/cvs/tools/Makefile.in
index 3a39bc310d99..578b210d053f 100644
--- a/contrib/cvs/tools/Makefile.in
+++ b/contrib/cvs/tools/Makefile.in
@@ -50,7 +50,7 @@ ls:
.PHONY: ls
clean:
- /bin/rm -f *.o core
+ rm -f *.o core
.PHONY: clean
distclean: clean