diff options
author | peter <peter@FreeBSD.org> | 1996-08-20 23:46:10 +0000 |
---|---|---|
committer | peter <peter@FreeBSD.org> | 1996-08-20 23:46:10 +0000 |
commit | 8982e501c77217c860f79bba431f46a62b607a21 (patch) | |
tree | 70187fdf5be4cbefd0baf46bddac7e5e32c13c24 | |
parent | 01ee40fd6a76f6ff7ef247fc1b2cf6e337f216c5 (diff) | |
download | FreeBSD-src-8982e501c77217c860f79bba431f46a62b607a21.zip FreeBSD-src-8982e501c77217c860f79bba431f46a62b607a21.tar.gz |
Import of slightly trimmed cvs-1.8 distribution. Generated files
and non-unix code has been left out.
177 files changed, 93736 insertions, 0 deletions
diff --git a/contrib/cvs/BUGS b/contrib/cvs/BUGS new file mode 100644 index 0000000..fc6af32 --- /dev/null +++ b/contrib/cvs/BUGS @@ -0,0 +1,262 @@ +To report bugs send mail to bug-cvs@prep.ai.mit.edu, or run the "cvsbug" +program and fill out the template: + + $ cvsbug + +The "cvsbug" program is installed in the same location as the "cvs" +program. If your installation failed, you may need to run "cvsbug" +directly out of the "src" directory as "src/cvsbug.sh". This is also +the procedure for submitting suggested changes to CVS--note that all +submitted changes may be distributed under the terms of the GNU Public +License, so if you don't like this, don't submit them. + + + +* `cvs checkout -d nested/dir/path <module>' just doesn't work. The + simpler version -- `cvs checkout -d single-dir <module>' works, + however. + + +* CVS leaves .#mumble files around when a conflict occurs. (Note: + this is intentional and is documented in doc/cvs.texinfo. Of course + whether it is a good idea is a separate question). + + +* pcl-cvs doesn't like it when you try to check in a file which isn't + up-to-date. The messages produced by the server perhaps don't match + what pcl-cvs is looking for. + + +* From: Roland McGrath <roland@gnu.ai.mit.edu> + To: Cyclic CVS Hackers <info-cvs@prep.ai.mit.edu> + Subject: weird bug + Date: Sat, 25 Mar 1995 16:41:41 -0500 + X-Windows: Even your dog won't like it. + + I just noticed some droppings on my disk from what must be a pretty weird + bug in remote CVS. + + In my home directory on a repository machine I use, I find: + + drwxr-xr-x 4 roland staff 512 Mar 7 14:08 cvs-serv28962 + drwxr-xr-x 4 roland staff 512 Mar 7 14:11 cvs-serv28978 + drwxr-xr-x 4 roland staff 512 Mar 7 15:13 cvs-serv29141 + + OK, so these are leftover cruft from some cvs run that got aborted. + Well, it should clean up after itself, but so what. + + The last one is pretty dull; the real weirdness is the contents of the + first two directories. + + duality 77 # ls -RF cvs-serv28978/ + CVS/ cvs-serv28978/ + + cvs-serv28978/CVS: + Entries Repository + + cvs-serv28978/cvs-serv28978: + arpa/ + + cvs-serv28978/cvs-serv28978/arpa: + CVS/ cvs-serv28978/ + + cvs-serv28978/cvs-serv28978/arpa/CVS: + Entries Repository + + cvs-serv28978/cvs-serv28978/arpa/cvs-serv28978: + assert/ + + cvs-serv28978/cvs-serv28978/arpa/cvs-serv28978/assert: + CVS/ cvs-serv28978/ + + cvs-serv28978/cvs-serv28978/arpa/cvs-serv28978/assert/CVS: + Entries Repository + + cvs-serv28978/cvs-serv28978/arpa/cvs-serv28978/assert/cvs-serv28978: + bare/ + + cvs-serv28978/cvs-serv28978/arpa/cvs-serv28978/assert/cvs-serv28978/bare: + CVS/ cvs-serv28978/ + + cvs-serv28978/cvs-serv28978/arpa/cvs-serv28978/assert/cvs-serv28978/bare/CVS: + Entries Repository + + cvs-serv28978/cvs-serv28978/arpa/cvs-serv28978/assert/cvs-serv28978/bare/cvs-serv28978: + conf/ + + cvs-serv28978/cvs-serv28978/arpa/cvs-serv28978/assert/cvs-serv28978/bare/cvs-serv28978/conf: + CVS/ cvs-serv28978/ + + cvs-serv28978/cvs-serv28978/arpa/cvs-serv28978/assert/cvs-serv28978/bare/cvs-serv28978/conf/CVS: + Entries Repository + + cvs-serv28978/cvs-serv28978/arpa/cvs-serv28978/assert/cvs-serv28978/bare/cvs-serv28978/conf/cvs-serv28978: + crypt/ + + cvs-serv28978/cvs-serv28978/arpa/cvs-serv28978/assert/cvs-serv28978/bare/cvs-serv28978/conf/cvs-serv28978/crypt: + CVS/ cvs-serv28978/ + + cvs-serv28978/cvs-serv28978/arpa/cvs-serv28978/assert/cvs-serv28978/bare/cvs-serv28978/conf/cvs-serv28978/crypt/CVS: + Entries Repository + + cvs-serv28978/cvs-serv28978/arpa/cvs-serv28978/assert/cvs-serv28978/bare/cvs-serv28978/conf/cvs-serv28978/crypt/cvs-serv28978: + csu/ + + cvs-serv28978/cvs-serv28978/arpa/cvs-serv28978/assert/cvs-serv28978/bare/cvs-serv28978/conf/cvs-serv28978/crypt/cvs-serv28978/csu: + CVS/ cvs-serv28978/ + + cvs-serv28978/cvs-serv28978/arpa/cvs-serv28978/assert/cvs-serv28978/bare/cvs-serv28978/conf/cvs-serv28978/crypt/cvs-serv28978/csu/CVS: + Entries Repository + + cvs-serv28978/cvs-serv28978/arpa/cvs-serv28978/assert/cvs-serv28978/bare/cvs-serv28978/conf/cvs-serv28978/crypt/cvs-serv28978/csu/cvs-serv28978: + ctype/ + + cvs-serv28978/cvs-serv28978/arpa/cvs-serv28978/assert/cvs-serv28978/bare/cvs-serv28978/conf/cvs-serv28978/crypt/cvs-serv28978/csu/cvs-serv28978/ctype: + CVS/ cvs-serv28978/ + + [...] + + ls: cvs-serv28978/cvs-serv28978/arpa/cvs-serv28978/assert/cvs-serv28978/bare/cvs-serv28978/conf/cvs-serv28978/crypt/cvs-serv28978/csu/cvs-serv28978/ctype/cvs-serv28978/dirent/cvs-serv28978/elf/cvs-serv28978/gnu/cvs-serv28978/gnulib/cvs-serv28978/grp/cvs-serv28978/hurd/cvs-serv28978/hurd/hurd/cvs-serv28978/inet/cvs-serv28978/inet/arpa/cvs-serv28978/inet/netinet[...]/cvs-serv28978/posix/cvs-serv28978/posix/glob/cvs-serv28978/posix/gnu/cvs-serv28978/posix/sys/cvs-serv28978/protocols/cvs-serv28978/pwd/cvs-serv28978/resolv/cvs-serv28978/resolv/arpa/cvs-serv28978/resolv/sys/cvs-serv28978/resource/cvs-serv28978/resource/sys/cvs-serv28978/rpc/cvs-serv28978/setjmp/cvs-serv28978/signal/cvs-serv28978/signal/sys/cvs-serv28978/socket/cvs-serv28978/socket: File name too long + cvs-serv28978/cvs-serv28978/arpa/cvs-serv28978/assert/cvs-serv28978/bare/cvs-serv28978/conf/cvs-serv28978/crypt/cvs-serv28978/csu/cvs-serv28978/ctype/cvs-serv28978/dirent/cvs-serv28978/elf/cvs-serv28978/gnu/cvs-serv28978/gnulib/cvs-serv28978/grp/cvs-serv28978/hurd/cvs-serv28978/hurd/hurd/cvs-serv28978/inet/cvs-serv28978/inet/arpa/cvs-serv28978/inet/netinet[...]/cvs-serv28978/posix/glob/cvs-serv28978/posix/gnu/cvs-serv28978/posix/sys/cvs-serv28978/protocols/cvs-serv28978/pwd/cvs-serv28978/resolv/cvs-serv28978/resolv/arpa/cvs-serv28978/resolv/sys/cvs-serv28978/resource/cvs-serv28978/resource/sys/cvs-serv28978/rpc/cvs-serv28978/setjmp/cvs-serv28978/signal/cvs-serv28978/signal/sys/cvs-serv28978/socket/cvs-serv28978: + + +* From: billr@mpd.tandem.com (Bill Robertson) + Subject: Problem with rtag and the -D option + Date: Fri, 17 Mar 1995 10:53:29 -0600 (CST) + + I have been trying to use the -D option to specify a date for tagging, but + rtag does not recognize the -D option. It is documented to do so and I've + tested the use of -D with cvs update and cvs diff and it works fine there. + + +* We need some version numbers really badly. Are there some + (and Charles Hannum is just not including them in his reports), or do + we simply have no reliable way to distinguish between the various + versions of rCVS people on the list are running? + + Now that I think of it, version numbers present a problem when + people can update their sources anytime and rebuild. I think the + solution is to increment a minor version number *every* time a bug is + fixed, so we can identify uniquely what someone is running when they + submit a report. This implies recording the version increments in the + ChangeLog; that way we can just look to see where a particular version + lies in relation to the flow of changing code. + + Should we be doing same with Guppy? I guess not -- it's only + important when you have people who are updating directly from your + development tree, which is the case with the remote-cvs folks. + + Thoughts? + + +* (Charles Hannum <mycroft@ai.mit.edu>) has these bugs: + + I just tossed remote CVS at a fairly large source tree that I already + had, and noticed a few problems: + + 1) server.c assumes that /usr/tmp is a valid default for the place to + store files uploaded from the client. There are a number of systems + that now use /var/tmp. These should probably be detected by autoconf. + + 2) The server deals rather ungracefully with the tmp directory + becoming full. + + 3) There's some oddness with relative paths in Repository files that + causes the directory prefix to be added twice; e.g. if I have CVSROOT + set to `machine:/this/dir', and I try to update in a directory whose + Repository file says `src/bin', the server looks in + `/this/dir/machine:/this/dir/src/bin'. + +* From: "Charles M. Hannum" <mycroft@ai.mit.edu> + To: jimb@duality.gnu.ai.mit.edu, roland@duality.gnu.ai.mit.edu + Subject: Serious flaw in remote CVS + Date: Wed, 22 Feb 1995 20:54:36 -0500 + + I just found a major flaw in the current implementation. Because the + sockets are not changed to non-blocking mode, write(2)s can hang. In + some cases, this happens on both sides at the same time, with the + socket buffers full in both directions. This causes a deadlock, + because both processes are stuck in I/O wait and thus never drain + their input queues. + + Until this is fixed, I can't use it. I'll look at the problem myself + at some point, but I don't know when. + + + From: "Charles M. Hannum" <mycroft@ai.mit.edu> + To: info-cvs@prep.ai.mit.edu + Cc: jimb@totoro.bio.indiana.edu + Subject: Re: forwarded message from Charles M. Hannum + Date: Wed, 22 Feb 1995 22:07:07 -0500 + + FYI, this happened because the tmp directory on the server became + full. Somehow the server started interpreting the files the client + was sending as commands, and started spewing tons of errors. + Apparently the errors are sent with blocking I/O, or something, and + thus allowed the deadlock to happen. + + +* From: "Charles M. Hannum" <mycroft@ai.mit.edu> + To: info-cvs@prep.ai.mit.edu + Subject: Regarding that relative path problem + Date: Thu, 23 Feb 1995 02:41:51 -0500 + + This is actually more serious. If you have `bar.com:/foo' as your CVS + root directory, then: + + 1) When you check things out, the Repository files will contain + `/foo/...' (i.e. without the machine name), which makes little sense. + + 2) If you instead have a relative path, when the Repository file is + read, `bar.com:/foo' is prepended. This is sent to the server, but + confuses it, because it's not expecting the machine name to be + prepended. + + A slightly klugy fix would be to have the client prepend the machine + name when writing a new Repository file, and strip it off before + sending one to the server. This would be backward-compatible with the + current arrangement. + + +* From: "Charles M. Hannum" <mycroft@ai.mit.edu> + To: info-cvs@prep.ai.mit.edu + Subject: Still one more bug + Date: Sat, 25 Feb 1995 17:01:15 -0500 + + mycroft@duality [1]; cd /usr/src/lib/libc + mycroft@duality [1]; cvs diff -c2 '-D1 day ago' -Dnow + cvs server: Diffing . + cvs server: Diffing DB + cvs [server aborted]: could not chdir to DB: No such file or directory + mycroft@duality [1]; + + `DB' is an old directory, which no longer has files in it, and is + removed automatically when I use the `-P' option to checkout. + + This error doesn't occur when run locally. + + P.S. Is anyone working on fixing these bugs? + + +* From: Roland McGrath <roland@gnu.ai.mit.edu> + To: Cyclic CVS Hackers <info-cvs@prep.ai.mit.edu> + Subject: bizarre failure mode + Date: Tue, 7 Mar 95 14:17:28 -0500 + + This is pretty weird: + + CVS_SERVER='TMPDIR=. /usr/local/bin/cvs' ../cvs-build/src/cvs update -q + cvs [server aborted]: could not get working directory: Result too large + [Exit 1] + asylum 29 % grep 'Result too large' /usr/include/sys/errno.h + #define ERANGE 34 /* Result too large */ + + Now, getcwd fails with ERANGE when the buffer is too small. But I don't + know why that would be the case; I don't think there are exceptionally long + directory names involved. It would be robust to notice ERANGE and use a + bigger buffer. But I suspect something weirder is going on. + + The repository in question in duality.gnu.ai.mit.edu:/gd4/gnu/cvsroot/libc. + + Send me a PGP-signed message if you want the password to use the machine + where the problem showed up. diff --git a/contrib/cvs/COPYING b/contrib/cvs/COPYING new file mode 100644 index 0000000..9a17037 --- /dev/null +++ b/contrib/cvs/COPYING @@ -0,0 +1,249 @@ + + GNU GENERAL PUBLIC LICENSE + Version 1, February 1989 + + Copyright (C) 1989 Free Software Foundation, Inc. + 675 Mass Ave, Cambridge, MA 02139, USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The license agreements of most software companies try to keep users +at the mercy of those companies. By contrast, our General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. The +General Public License applies to the Free Software Foundation's +software and to any other program whose authors commit to using it. +You can use it for your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Specifically, the General Public License is designed to make +sure that you have the freedom to give away or sell copies of free +software, that you receive source code or can get it if you want it, +that you can change the software or use pieces of it in new free +programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of a such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must tell them their rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any program or other work which +contains a notice placed by the copyright holder saying it may be +distributed under the terms of this General Public License. The +"Program", below, refers to any such program or work, and a "work based +on the Program" means either the Program or any work containing the +Program or a portion of it, either verbatim or with modifications. Each +licensee is addressed as "you". + + 1. You may copy and distribute verbatim copies of the Program's source +code as you receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice and +disclaimer of warranty; keep intact all the notices that refer to this +General Public License and to the absence of any warranty; and give any +other recipients of the Program a copy of this General Public License +along with the Program. You may charge a fee for the physical act of +transferring a copy. + + 2. You may modify your copy or copies of the Program or any portion of +it, and copy and distribute such modifications under the terms of Paragraph +1 above, provided that you also do the following: + + a) cause the modified files to carry prominent notices stating that + you changed the files and the date of any change; and + + b) cause the whole of any work that you distribute or publish, that + in whole or in part contains the Program or any part thereof, either + with or without modifications, to be licensed at no charge to all + third parties under the terms of this General Public License (except + that you may choose to grant warranty protection to some or all + third parties, at your option). + + c) If the modified program normally reads commands interactively when + run, you must cause it, when started running for such interactive use + in the simplest and most usual way, to print or display an + announcement including an appropriate copyright notice and a notice + that there is no warranty (or else, saying that you provide a + warranty) and that users may redistribute the program under these + conditions, and telling the user how to view a copy of this General + Public License. + + d) You may charge a fee for the physical act of transferring a + copy, and you may at your option offer warranty protection in + exchange for a fee. + +Mere aggregation of another independent work with the Program (or its +derivative) on a volume of a storage or distribution medium does not bring +the other work under the scope of these terms. + + 3. You may copy and distribute the Program (or a portion or derivative of +it, under Paragraph 2) in object code or executable form under the terms of +Paragraphs 1 and 2 above provided that you also do one of the following: + + a) accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of + Paragraphs 1 and 2 above; or, + + b) accompany it with a written offer, valid for at least three + years, to give any third party free (except for a nominal charge + for the cost of distribution) a complete machine-readable copy of the + corresponding source code, to be distributed under the terms of + Paragraphs 1 and 2 above; or, + + c) accompany it with the information you received as to where the + corresponding source code may be obtained. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form alone.) + +Source code for a work means the preferred form of the work for making +modifications to it. For an executable file, complete source code means +all the source code for all modules it contains; but, as a special +exception, it need not include source code for modules which are standard +libraries that accompany the operating system on which the executable +file runs, or for standard header files or definitions files that +accompany that operating system. + + 4. You may not copy, modify, sublicense, distribute or transfer the +Program except as expressly provided under this General Public License. +Any attempt otherwise to copy, modify, sublicense, distribute or transfer +the Program is void, and will automatically terminate your rights to use +the Program under this License. However, parties who have received +copies, or rights to use copies, from you under this General Public +License will not have their licenses terminated so long as such parties +remain in full compliance. + + 5. By copying, distributing or modifying the Program (or any work based +on the Program) you indicate your acceptance of this license to do so, +and all its terms and conditions. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the original +licensor to copy, distribute or modify the Program subject to these +terms and conditions. You may not impose any further restrictions on the +recipients' exercise of the rights granted herein. + + 7. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of the license which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +the license, you may choose any version ever published by the Free Software +Foundation. + + 8. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 9. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 10. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + Appendix: How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to humanity, the best way to achieve this is to make it +free software which everyone can redistribute and change under these +terms. + + To do so, attach the following notices to the program. It is safest to +attach them to the start of each source file to most effectively convey +the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + <one line to give the program's name and a brief idea of what it does.> + Copyright (C) 19yy <name of author> + + 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 1, 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. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) 19xx name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the +appropriate parts of the General Public License. Of course, the +commands you use may be called something other than `show w' and `show +c'; they could even be mouse-clicks or menu items--whatever suits your +program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + program `Gnomovision' (a program to direct compilers to make passes + at assemblers) written by James Hacker. + + <signature of Ty Coon>, 1 April 1989 + Ty Coon, President of Vice + +That's all there is to it! diff --git a/contrib/cvs/COPYING.LIB b/contrib/cvs/COPYING.LIB new file mode 100644 index 0000000..eb685a5 --- /dev/null +++ b/contrib/cvs/COPYING.LIB @@ -0,0 +1,481 @@ + GNU LIBRARY GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1991 Free Software Foundation, Inc. + 675 Mass Ave, Cambridge, MA 02139, USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the library GPL. It is + numbered 2 because it goes with version 2 of the ordinary GPL.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Library General Public License, applies to some +specially designated Free Software Foundation software, and to any +other libraries whose authors decide to use it. You can use it for +your libraries, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if +you distribute copies of the library, or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link a program with the library, you must provide +complete object files to the recipients so that they can relink them +with the library, after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + Our method of protecting your rights has two steps: (1) copyright +the library, and (2) offer you this license which gives you legal +permission to copy, distribute and/or modify the library. + + Also, for each distributor's protection, we want to make certain +that everyone understands that there is no warranty for this free +library. If the library is modified by someone else and passed on, we +want its recipients to know that what they have is not the original +version, so that any problems introduced by others will not reflect on +the original authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that companies distributing free +software will individually obtain patent licenses, thus in effect +transforming the program into proprietary software. To prevent this, +we have made it clear that any patent must be licensed for everyone's +free use or not licensed at all. + + Most GNU software, including some libraries, is covered by the ordinary +GNU General Public License, which was designed for utility programs. This +license, the GNU Library General Public License, applies to certain +designated libraries. This license is quite different from the ordinary +one; be sure to read it in full, and don't assume that anything in it is +the same as in the ordinary license. + + The reason we have a separate public license for some libraries is that +they blur the distinction we usually make between modifying or adding to a +program and simply using it. Linking a program with a library, without +changing the library, is in some sense simply using the library, and is +analogous to running a utility program or application program. However, in +a textual and legal sense, the linked executable is a combined work, a +derivative of the original library, and the ordinary General Public License +treats it as such. + + Because of this blurred distinction, using the ordinary General +Public License for libraries did not effectively promote software +sharing, because most developers did not use the libraries. We +concluded that weaker conditions might promote sharing better. + + However, unrestricted linking of non-free programs would deprive the +users of those programs of all benefit from the free status of the +libraries themselves. This Library General Public License is intended to +permit developers of non-free programs to use free libraries, while +preserving your freedom as a user of such programs to change the free +libraries that are incorporated in them. (We have not seen how to achieve +this as regards changes in header files, but we have achieved it as regards +changes in the actual functions of the Library.) The hope is that this +will lead to faster development of free libraries. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, while the latter only +works together with the library. + + Note that it is possible for a library to be covered by the ordinary +General Public License rather than by this special one. + + GNU LIBRARY GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library which +contains a notice placed by the copyright holder or other authorized +party saying it may be distributed under the terms of this Library +General Public License (also called "this License"). Each licensee is +addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also compile or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + c) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + d) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the source code distributed need not include anything that is normally +distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Library General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + Appendix: How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + + To apply these terms, attach the following notices to the library. It is +safest to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + <one line to give the library's name and a brief idea of what it does.> + Copyright (C) <year> <name of author> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random Hacker. + + <signature of Ty Coon>, 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! diff --git a/contrib/cvs/ChangeLog b/contrib/cvs/ChangeLog new file mode 100644 index 0000000..ad187ad --- /dev/null +++ b/contrib/cvs/ChangeLog @@ -0,0 +1,1162 @@ +Sun May 5 17:38:21 1996 Benjamin J. Lee <benjamin@cyclic.com> + + Integrated changes submitted by Ian Taylor <ian@cygnus.com> + + * update.c (update_dirent_proc): cvs co -p doesn't print + anything when run from an empty directory. + + * import.c (import_descend_dir): Check for a file in the + repository which will be checked out to the same name as the + directory. + +Sun May 5 15:49:00 1996 Benjamin J. Lee <benjamin@cyclic.com> + + * configure.in: autoconf 2.9 handles AC_CHECK_LIB in a + way that it can not be used to check for main(). Check + for printf() instead. (Reported by ian@cygnus.com) + + * configure: Regenerated. + +Thu May 2 13:34:37 1996 Benjamin J. Lee <benjamin@cyclic.com> + + * Version 1.7.88 + +Thu May 2 10:42:13 1996 Jim Kingdon <kingdon@harvey.cyclic.com> + + * NEWS: Clarify what happened to examples directory. + +Thu May 2 02:06:49 1996 Benjamin J. Lee <benjamin@cyclic.com> + + * INSTALL: Updated for NeXTSTEP 3.3 (1.7) + +Thu May 2 01:40:55 1996 Benjamin J. Lee <benjamin@cyclic.com> + + * Compatibility fixes affecting QNX, NetBSD, and SCO + + * configure.in (AC_CHECK_FUNCS): Added check for initgroups(), + (ac_cv_func_crypt) Added check for crypt() in -lcrypt; + define AUTH_SERVER_SUPPORT only if crypt() is found. + + * configure: Regenerated. + + * src/server.c (HAVE_INITGROUPS): Use initgroups() only if + located by configure. + +Wed May 1 15:38:56 1996 Jim Kingdon <kingdon@harvey.cyclic.com> + + * NEWS: Remove item about reserving all-uppercase tag names. + +Fri Apr 19 11:22:35 1996 Benjamin J. Lee <benjamin@cyclic.com> + + * Version 1.7.86 + +Sun Apr 14 11:06:44 1996 Karl Fogel <kfogel@floss.red-bean.com> + + * configure.in (AC_OUTPUT): generate contrib/elib/Makefile, + tools/Makefile, and tools/pcl-cvs/Makefile. Do not any longer + generate contrib/pcl-cvs/Makefile. + + * Makefile.in: deal w/ above changes. + + * configure: regenerated. + + * Added `tools' subdir (pcl-cvs will live there, as will other + things maintained along with the CVS distribution). + +Wed Apr 10 17:15:25 1996 Jim Kingdon <kingdon@harvey.cyclic.com> + + * README: Mention documentation and A4 paper in particular. + +Thu Mar 28 12:31:38 1996 Jim Kingdon <kingdon@harvey.cyclic.com> + + * NEWS: Add "cvs annotate". + +Tue Mar 26 10:46:59 1996 Jim Kingdon <kingdon@harvey.cyclic.com> + + * INSTALL: In example, change tag name to avoid using a tag name + reserved to CVS. + + * NEWS: Document reservation of some tag names. + +Fri Mar 22 10:45:23 1996 Jim Kingdon <kingdon@harvey.cyclic.com> + + * INSTALL: Clarify that RCS is only for server or local. + +Mon Mar 18 10:15:18 1996 Jim Kingdon <kingdon@harvey.cyclic.com> + + * README: Mention info@cyclic.com where we mention support + contracts, not at the end where people might be tempted to view it + as a generic help line. + +Thu Mar 14 16:34:26 1996 Jim Kingdon <kingdon@harvey.cyclic.com> + + * Makefile.in (stamp-h): Don't run ./config.status --recheck. + +Thu Mar 14 1996 Jim Kingdon <kingdon@cyclic.com> + + * cvsnt.mak: Regenerate dependencies. + +Thu Mar 14 13:45:11 1996 Jim Blandy <jimb@totoro.cyclic.com> + + * configure.in (AC_OUTPUT): Don't create examples/Makefile; we're + not using the examples directory any more. + +Wed Mar 13 17:02:00 1996 Jim Kingdon <kingdon@harvey.cyclic.com> + + * INSTALL: Refer to cvs.texinfo rather than out-of-date cvsinit + instructions. Instead of telling everyone to update modules + whenever adding directories (which is optional), refer to the + manual regarding all administrative files. Revise "make check" + instructions to be even less encouraging about submitting bug + reports. + + * examples/*: Removed. + * Makefile.in (SUBDIRS): Remove examples. + * cvsinit.sh: Removed. + * Makefile.in: Remove all cvsinit and PROGS stuff. + * NEWS: Mention cvsinit -> cvs init change. + +Mon Mar 11 13:12:35 1996 Samuel Tardieu <sam@inf.enst.fr> + + * BUGS: removed previous description from Greg Woods (3/6/96) + since the bug seems to be corrected + +Wed Mar 6 10:35:32 1996 Greg A. Woods <woods@most.weird.com> + + * BUGS: describe a weird core-dump with 'cvs co -c'. Now I can't + even get a stack backtrace again -- dbx dumps core! + +Fri Mar 1 09:21:56 1996 Jim Kingdon <kingdon@harvey.cyclic.com> + + * README.VMS: Remove distribution information (since it is no + longer different for VMS). Various wording fixes to reflect the + fact that using rsh is just one of several ways to connect to a + cvs server, not "the official" one. Say that the unsuitable rsh + is the UCX one. Clarify what rsh uses privileged ports for. + +Fri Mar 1 01:26:28 1996 Benjamin J. Lee <benjamin@cyclic.com> + + * README.VMS, build.com: Added for VMS. + +Thu Feb 29 10:04:20 1996 Jim Kingdon <kingdon@harvey.cyclic.com> + + * NEWS: Mention change to default ignore list. + +Thu Feb 29 00:28:08 1996 Peter Wemm <peter@jhome.DIALix.COM> + + * configure.in: correctly spell FNM_PATHNAME in fnmatch() test, + the supplied test fails on proposed POSIX.2, lib/fnmatch.*, Linux, + FreeBSD, etc. + * configure: Regenerated. + +Tue Feb 27 10:43:14 1996 Jim Kingdon <kingdon@harvey.cyclic.com> + + * INSTALL: Change submission address to bug-cvs from info-cvs. + Encourage submissions to be in the form of diffs to INSTALL. + +Sun Feb 25 15:23:31 1996 Jim Kingdon <kingdon@harvey.cyclic.com> + + * HACKING: Fix typo. + +Fri Feb 23 1996 Jim Kingdon <kingdon@cyclic.com> + + * cvsnt.mak: Add login.c and scramble.c. + +Fri Feb 23 16:36:11 1996 Jim Kingdon <kingdon@harvey.cyclic.com> + + * README: Mention comp.software.config-mgmt. Don't mention old + cyclic-cvs mailing list. + + * acconfig.h: Add AUTH_SERVER_SUPPORT. Remove DIFF and GREP (no + longer used). + * configure.in: Define AUTH_SERVER_SUPPORT. + * config.h.in, configure: Regenerated. + +Thu Feb 22 22:32:09 1996 Jim Kingdon <kingdon@harvey.cyclic.com> + + * configure.in: Remove AC_FUNC_ALLOCA. + * configure: Regenerated. + +Mon Feb 19 09:39:21 1996 Jim Kingdon <kingdon@harvey.cyclic.com> + + * HACKING: Add comments about portability and assert(). + +Thu Feb 15 16:40:13 1996 Jim Kingdon <kingdon@harvey.cyclic.com> + + * NEWS: Mention $USER internal variable. + +Thu Feb 15 14:00:00 1996 Gary Oberbrunner <garyo@avs.com> + and Jim Kingdon <kingdon@cyclic.com> + + * cvsnt.mak: Add vasprintf.c and mkmodules.c + +Tue Feb 13 20:05:47 1996 Jim Kingdon <kingdon@harvey.cyclic.com> + + * configure.in (AC_REPLACE_FUNCS): Add strtoul. + * configure: Regenerated. + +Mon Feb 12 10:06:27 1996 Jim Kingdon <kingdon@harvey.cyclic.com> + + * TODO: Remove mkmodules stuff. + * NEWS: Add item concerning mkmodules. + + * configure.in (AC_REPLACE_FUNCS): Add vasprintf. + * configure: Regenerated. + +Sun Feb 11 16:43:38 1996 Karl Fogel <kfogel@floss.red-bean.com> + + * Makefile.in (DISTFILES): added HACKING. + +Sun Feb 11 12:38:51 1996 Jim Kingdon <kingdon@harvey.cyclic.com> + + * NEWS: Revise *info files feature (now user vars, not env vars). + +Fri Feb 9 23:51:39 1996 Jim Kingdon <kingdon@harvey.cyclic.com> + + * NEWS: Mention env var in *info files feature. + +Fri Feb 9 02:41:50 1996 Jim Blandy <jimb@totoro.cyclic.com> + + * Makefile.in (DISTFILES): Remove config.sub and config.guess from + the list; they're not distributed any more. + +Thu Feb 1 19:47:46 1996 Jim Kingdon <kingdon@harvey.cyclic.com> + + * INSTALL: Remove RM; no longer used. + +Thu Feb 1 14:38:04 1996 Karl Fogel <kfogel@floss.red-bean.com> + + * configure: re-ran autoconf. + + * Makefile.in (USOURCE_SUBDIRS, SUBDIRS): abstract unix source + subdirs to new var USOURCE_SUBDIRS, for lint's sake and possibly + etags's someday. + (lint): run in USOURCE_SUBDIRS only. + +Thu Feb 1 13:06:47 1996 Roland McGrath <roland@baalperazim.frob.com> + + * configure.in (WITH_KRB4): Escape $ in help text. + +Wed Jan 31 19:03:37 1996 Jim Kingdon <kingdon@harvey.cyclic.com> + + * HACKING: Add info about NEWS file and release process. + +Tue Jan 30 16:00:00 1996 Jim Kingdon <kingdon@peary.cyclic.com> + + * cvsnt.mak: Change save-cwd.c to savecwd.c and regenerate + dependencies to take care of save-cwd.h. + * windows-NT/README: Update information about Visual C++ 4.0. + +Tue Jan 30 16:09:53 1996 Jim Kingdon <kingdon@harvey.cyclic.com> + + * Rename lib/save-cwd.c to lib/savecwd.c. Avoiding a hyphen + seems to be the only way to get Visual C++ 2.1 to generate a + cvsnt.mak which Visual C++ 4.0 will accept. + * Rename lib/save-cwd.h to lib/savecwd.h for consistency. + * os2/Makefile.in, lib/Makefile.in, lib/savecwd.c, src/add.c, + src/import.c, src/modules.c, src/recurse.c, src/tag.c: Update + accordingly. + + * INSTALL, os2/options.h, windows-NT/options.h, + macintosh/options.h, src/options.h.in: Remove SORT; it is no + longer used. + +Mon Jan 29 15:16:39 1996 Jim Kingdon <kingdon@harvey.cyclic.com> + + * INSTALL: Mention -b. Don't talk about RCS 5.6.[5-7] beta + releases; this will be an issue for few if any people. Remove + stuff about diff and --with-diffutils which is no longer true. + + * README: Refer to HACKING file. Refer to cvs.texinfo not + manpage. Rewrite section about compatibility between CVS versions. + * HACKING: New file. + * INSTALL: Move -Wall section to HACKING; refer to HACKING. + +Wed Jan 24 20:26:55 1996 Jim Kingdon <kingdon@harvey.cyclic.com> + + * configure.in: Remove diff stuff. Also remove AC_CANONICAL_HOST + and bindir crud as that was the only place they were used. + * config.h.in, configure: Regenerated. + * config.sub, config.guess: Removed. + * src/options.h.in (DIFF): Change to "diff" and change comment to tell + people not to use -a. + * src/sanity.sh: New test binfiles tests for above-fixed bug (see + comments in patch_file in update.c--passing -a to diff generates a + patch which patch cannot apply). + + * NEWS: Adjust to reflect existence of 1.7. + +Tue Jan 23 14:20:39 1996 Jim Blandy <jimb@totoro.cyclic.com> + + * devel-cvs: New file, not to be included in the distribution. + +Thu Jan 18 21:46:56 1996 Jim Blandy <jimb@totoro.cyclic.com> + + * BUGS: Remove all mention of the outdated cyclic-cvs@cyclic.com + and remote-cvs@cyclic.com addresses. It turns out that people see + these addresses and use them. Mention the proper way to report + bugs. + +Wed Jan 17 16:40:01 1996 Jim Kingdon <kingdon@harvey.cyclic.com> + + * README: Fix typo (info-cvs-requests -> info-cvs-request). + +Fri Jan 12 13:38:12 1996 Jim Kingdon <kingdon@harvey.cyclic.com> + + * configure.in, configure: Revert "Checking user's gender" change. + Sure, you only live once, but I want mine to be a *long* life, not + one interrupted by a CVS user who is not amused coming after me + with an axe. + +Fri Jan 12 12:46:23 1996 Karl Fogel <kfogel@floss.red-bean.com> + + * configure: regenerated. + + * configure.in: print "Checking user's gender... ok". I mean, + what the heck, you only live once. + +Thu Jan 11 14:00:00 1996 Jim Kingdon <peary.cyclic.com> + + * cvsnt.mak: Update dependencies. + +Thu Jan 11 12:03:10 1996 Norbert Kiesel <nk@col.sw-ley.de> + + * NEWS: document loss of CVS_NOADMIN. Also, mention the + possibility to use "cvs" in .cvsrc. + +Wed Jan 10 20:40:23 1996 Karl Fogel <kfogel@floss.red-bean.com> + + * configure: regenerated. + + * configure.in (AC_OUTPUT): added `macintosh/Makefile'. + + * Makefile.in (SUBDIRS): added `macintosh'. + +Wed Jan 10 01:17:18 1996 Jim Kingdon <kingdon@harvey.cyclic.com> + + * README: Remove URL of obsolete David Zuhn web page. + + * FAQ: Replace entire file with short paragraph explaining the FAQ + is dead. + + * configure.in: Don't set exec_prefix. Set bindir from prefix if + exec_prefix isn't set. + * configure: Regenerated. + + * INSTALL: Update list of machines for 1.6.85 (further changes to + the list of machines will not receive ChangeLog entries). + +Tue Jan 9 09:02:05 1996 Jim Kingdon <kingdon@harvey.cyclic.com> + + * NEWS: Mention changes in default ignore list. + + * INSTALL: check.log is not in /tmp/cvs-sanity. Mention + submitting bug reports as a possibility, not a request from us. + Separate out "make check" a bit to make clear it is optional. + +Mon Jan 8 11:42:40 1996 Jim Kingdon <kingdon@harvey.cyclic.com> + + * INSTALL: Remove grep stuff; no longer necessary. + Don't say that patch must understand unidiffs; no longer true. + Suggest configuring with -Wall (here until we have a "how to hack + CVS document"). + +Wed Jan 3 19:00:00 1996 Jim Kingdon <kingdon@peary.cyclic.com> + + * .cvsignore: Add cvsnt.vcp. + +Mon Jan 1 22:45:50 1996 Jim Kingdon <kingdon@harvey.cyclic.com> + + * os2/Makefile.in (Makefile), windows-NT/Makefile.in (Makefile): + New rules. + +Sun Dec 31 16:52:49 1995 Karl Fogel <kfogel@floss.cyclic.com> + + * NEWS: add a blurb about password authentication. + +Sun Dec 31 16:16:38 1995 Jim Kingdon <kingdon@harvey.cyclic.com> + + * README: Add "submissions will be distributed under the GPL" + language (like the newspapers have for letters to the editor). + +Thu Dec 21 16:00:00 1995 Jim Kingdon <kingdon@peary.cyclic.com> + + * cvsnt.mak: Revert to an old version, then add in recent changes + to lists of files (using Visual C++; not by hand editing--this way + it can be used as an internal project not just an external one). + +Tue Dec 19 17:13:14 1995 Jim Kingdon <kingdon@harvey.cyclic.com> + + * NEWS: Mention -kb (strictly speaking a bugfix, not a new + feature, I guess, but it seems worth mentioning anyway). + +Tue Dec 19 17:00:00 1995 Jim Kingdon <kingdon@peary.cyclic.com> + + * TODO: Remove "regular TODO list:" line which accidentally got + checked in. + +Mon Dec 18 18:59:30 1995 Jim Kingdon <kingdon@harvey.cyclic.com> + + * Makefile.in (TAR_VERBOSE): Default to empty, not "v". I don't + want that whole long list of files any more than jimb's daily + update script does. + +Sun Dec 17 23:59:11 1995 Jim Kingdon <kingdon@harvey.cyclic.com> + + * configure.in (AC_REPLACE_FUNCS): Remove vasprintf. + * configure: Regenerated. + +Sat Dec 16 17:19:45 1995 Jim Kingdon <kingdon@harvey.cyclic.com> + + * configure.in (AC_REPLACE_FUNCS): Add vasprintf. + * configure: Regenerated. + +Mon Nov 20 14:19:47 1995 Jim Kingdon <kingdon@harvey.cyclic.com> + + * TODO: Remove items about developer communications; they are done. + * NEWS: Mention developer communication features. + * cvsinit.sh: Also add notify file. + +Mon Dec 11 22:44:58 1995 Karl Fogel <kfogel@totoro.cyclic.com> + + * New subdir "macintosh", for Mike Ladwig's + <mike@twinpeaks.prc.com> port-in-progress. + +Thu Dec 7 14:32:49 1995 Jim Meyering (meyering@comco.com) + + * Makefile.in (check): Make sure library is built before running + make in src. + (remotecheck): Likewise. + (installcheck): Likewise. + +Wed Dec 6 11:40:37 1995 J.T. Conklin <jtc@slave.cygnus.com> + + * configure.in: Remove leading -l from first argument of + AC_CHECK_LIB for -lkrb and -ldes checks. + +Mon Dec 4 08:06:31 1995 Jim Kingdon <kingdon@harvey.cyclic.com> + + * config.h.in: Regenerated. + +Sun Dec 3 20:05:10 1995 Jim Kingdon <kingdon@harvey.cyclic.com> + + * configure.in: Remove grep stuff. + * configure: Regenerated. + +Fri Dec 1 11:16:18 1995 Norbert Kiesel <nk@col.sw-ley.de> + + * configure, config.h.in: re-ran autoconf + + * configure.in (AC_CHECK_HEADERS): add sys/resource.h to list of + tested headers + + * Makefile.in (DISTFILES): add config.sub and config.guess + +Thu Nov 23 09:01:53 1995 Jim Kingdon <kingdon@harvey.cyclic.com> + + * TODO: Remove item about doc describing undoing a change; it + already does. + +Sun Nov 19 18:12:36 1995 Jim Blandy <jimb@totoro.cyclic.com> + + * Makefile.in (dist): Pull out the 'v' in the tar command to a + variable, so I can disable it in my daily update script. + +Tue Nov 14 18:31:36 1995 Greg A. Woods <woods@most.weird.com> + + * cvsinit.sh: + - new rcs id + - new opening comment + - read only one "word" for CVSROOT + - add checkoutlist, cvswrappers, taginfo, wrap, & unwrap to + examples install loop, special handling for latter.... + - don't do any special stuff for loginfo -- always comment out + everything in the newly installed examples + - add a wee message to suggest editing newly installed examples + - tweak some more comments, esp. regarding install of contrib + scripts.... + - make $CVSROOT/CVROOT/history group writable if it didn't exist + as it's not very useful otherwise + +Tue Nov 14 15:22:25 1995 Greg A. Woods <woods@most.weird.com> + + * cvsinit.sh: woops! wasn't installing contrib/log! + +Tue Nov 14 12:09:11 1995 Greg A. Woods <woods@most.weird.com> + + * INSTALL: oops, missed a couple of things about "configure" + + * configure: re-ran autoconf + +Tue Nov 14 11:06:25 1995 Greg A. Woods <woods@most.weird.com> + + * config.guess, config.sub: first time in (from autoconf-2.4) + + * configure.in: + - updated to work with autoconf-2.4 + - call AC_CANONICAL_HOST to get host OS type right (needs + config.sub and config.guess) + - added full support for --with-diffutils and --with-gnugrep + - fixed the diff search to work almost like the one for RCS-5.7 + - fixed some quoting problems + + * README: mention optional 'make check' step + + * INSTALL: + - updated notes about working SunOS versions + - re-wrote notes about RCS, diffutils, etc. + - added notes about configuring with GNU diffutils and GNU grep + - added notes about using 'make check' + - changed bug reporiting instructions to mention cvsbug + - re-wrote notes about setting CVSROOT in shell startups + +Fri Nov 3 11:11:16 1995 Jim Kingdon <kingdon@harvey.cyclic.com> + + * README: Fix typo in URL of molli's web site. + +Tue Oct 31 19:28:16 1995 Karl Fogel <kfogel@totoro.cyclic.com> + + * testing something, please ignore. + +Mon Oct 23 18:37:27 1995 Karl Fogel <kfogel@floss.cyclic.com> + + * configure: re-ran autoconf. + + * configure.in (AC_OUTPUT): os2/Makefile. + + * Makefile.in (SUBDIRS): added os2 subdir. + +Mon Oct 23 12:02:51 1995 Norbert Kiesel <nk@col.sw-ley.de> + + * cvsnt.mak: added lib/getline.c + +Fri Oct 20 17:04:55 1995 Norbert Kiesel <nk@col.sw-ley.de> + + * cvsnt.mak: added src/expand_path.c, error.[ch] now in src + +Thu Oct 19 16:26:32 1995 Jim Kingdon <kingdon@harvey.cyclic.com> + + * INSTALL: Remove note about RCS 5.7 and log messages + consisting only of whitespace; fixed in CVS on 11 Jul 95. + +Tue Oct 17 17:57:23 1995 Warren Jones <wjones@tc.fluke.com> + + * man/cvs.5, examples/modules: Document -e. + +Tue Oct 10 16:34:25 1995 Thorsten Lockert <tholo@sigmasoft.com> + + * configure.in: More crud looking for kerberos, this time for 4.4BSD. + * configure: Regenerated. + +Sun Oct 8 12:22:19 1995 Peter Wemm <peter@haywire.DIALix.COM> + + * configure.in: check for POSIX and BSD style reliable signals + * configure: regenerated by autoconf + * config.h.in: regenerated by autoheader + +Fri Oct 6 21:50:48 1995 Jim Kingdon <kingdon@harvey.cyclic.com> + + There is little point in trying to share a file as trivial as + lib/error.c between programs. So just admit it is CVS specific: + * lib/error.c: Move from here... + * src/error.c: ...to here, and remove CVS_SUPPORT ifdefs. + * lib/error.h: Move from here... + * src/error.h: ...to here. Remove CVS_SUPPORT + ifdefs; remove unused variable error_message_count. + * src/Makefile.in (OBJECTS): Add error.o. + (SOURCES): Add error.c. + (HEADERS): Add error.h. + * lib/Makefile.in (OBJECTS): Remove error.o. + (SOURCES): Remove error.c. + (HEADERS): Remove error.h. + * acconfig.h, configure.in: Remove CVS_SUPPORT. + * configure, config.h.in: Rebuilt using autoconf and autoheader. + * windows-NT/config.h: Remove CVS_SUPPORT. + +Thu Oct 5 17:26:38 1995 Jim Kingdon <kingdon@harvey.cyclic.com> + + * INSTALL: Mention Siemens-Nixdorf RM600. + +Tue Oct 3 09:32:19 1995 Jim Kingdon <kingdon@harvey.cyclic.com> + + * NEWS: Remove item about -f global option; it is old news already + mentioned elsewhere in the file. + +Mon Oct 2 18:12:15 1995 Jim Blandy <jimb@totoro.cyclic.com> + + * FAQ: Updated for CVS 1.5. And now 1.6 is almost out. The FAQ + always lags the package, sigh... + +Mon Oct 2 18:10:35 1995 Larry Jones <larry.jones@sdrc.com> + + * configure, config.h.in: Rebuilt using autoconf and autoheader. + + * configure.in: check for <sys/bsdtypes.h>; used by src/server.c. + (ISC keeps all the stuff that BSD has in <sys/types.h> here, so + we need it for the FD_SET stuff for select().) + Moved check for gethostname() after check for connect() since if + connect() is not found, we may add librariesd and gethostname() + may well be in one of those libraries. + If connect() isn't found, look in -linet (ISC) in addition to + -lsocket and -lnsl. Also, ignore the cache since we need to + update LIBS reguardless of whether it was found before or not and + the answer may well be different afterwards. + Define CLIENT_SUPPORT and SERVER_SUPPORT only if connect() is + found. + + * INSTALL: update info for ISC 4.0.1; renumber footnotes. + +Mon Oct 2 17:01:07 1995 Jim Kingdon <kingdon@harvey.cyclic.com> + + * INSTALL: Indicate CVS version tested with Solaris 2.4. + +Mon Oct 2 10:42:37 1995 Karl Fogel <kfogel@totoro.cyclic.com> + + * (configure): Re-ran autoconf. + +Mon Oct 2 10:33:58 1995 Michael Finken <finken@conware.de> + + * configure.in: AC_REPLACE `strstr'. + +Sun Oct 1 23:22:28 1995 Bryan O'Sullivan <bos@serpentine.com> + + * (INSTALL): noted that CVS works fine on Solaris 2.4 with both + gcc and SPARCworks cc. + +Sun Oct 1 18:48:19 1995 Karl Fogel <kfogel@totoro.cyclic.com> + + * (configure): re-ran autoconf following Peter Wemm's change + below. + +Sun Oct 1 22:24:56 1995 Peter Wemm <peter@haywire.dialix.com> + + * configure.in: more extensive searching for -lsocket and -lnsl + as done in Taylor-UUCP 1.06 + +Sun Oct 1 15:32:01 1995 Karl Fogel <kfogel@totoro.cyclic.com> + + * (configure): re-ran autoconf. + +Sun Oct 1 11:35:17 1995 Jim Kingdon <kingdon@harvey.cyclic.com> + + * TODO: Remove item about setting comment leader automatically; + RCS 5.7 does this. + +Wed Sep 27 15:34:04 1995 Peter Wemm <peter@haywire.dialix.com> + + * configure.in: correct detection of GNU diff's -a option for + src/options.h + * configure: regenerate with autoconf + +Fri Sep 22 14:29:31 1995 Jim Kingdon <kingdon@harvey.cyclic.com> + + * TODO: Remove item about reindenting on the way in and out. + wrappers provide this functionality. + +Wed Sep 20 14:27:28 1995 Jim Blandy <jimb@totoro.cyclic.com> + + * configure.in: #define the symbols DIFF and GREP to be the paths + to the DIFF and GREP programs; their values will be edited into + src/options.h (and config.h, coincidentally). + * acconfig.h (DIFF, GREP): Add these. + * configure, config.h.in: Rebuilt using autoconf and autoheader. + +Sun Sep 10 21:38:05 1995 Jim Kingdon <kingdon@harvey.cyclic.com> + + * TODO: CVS can already undo a change, suggest documenting how. + Expand slightly on mode stuff. + Remove item about not letting people check out into repository (it + is done). + Redo item about expanding env vars in *info to reflect current + thinking. + Remove item about making it hard to accidentally move tags; it is + done. + Add client/server note to suggestion regarding interactive merging. + +Fri Sep 1 12:07:02 1995 Jim Kingdon <kingdon@harvey.cyclic.com> + + * BUGS: Remove items about refetching unpatchable files and options.h. + +Fri Sep 1 09:20:09 1995 Jim Blandy <jimb@totoro.cyclic.com> + + * Makefile.in (DISTFILES): Remove cvsnt.vcp; it's been deleted. + +Thu Aug 31 13:47:35 1995 Jim Kingdon <kingdon@harvey.cyclic.com> + + * Makefile.in (stamp-h): Rebuild config.status before trying to + use it to build config.h. + + * Makefile.in: Change "cd foo; make" to "cd foo && make"; + otherwise we get into an infinite loop if an objdir doesn't exist. + +Thu Aug 31 11:07:06 1995 Jim Blandy <jimb@totoro.cyclic.com> + + * configure.in: Arrange not to touch options.h if we haven't + modified it. AC_CONFIG_HEADER checks if the file is unmodified, + whereas AC_OUTPUT doesn't, and they're otherwise identical, so... + (AC_CONFIG_HEADER): ... mention src/options.h here... + (AC_OUTPUT): ... not here. + Copy src/options.h to src/options.h-SAVED, don't move it. + Otherwise, configure will create it again every time. + Remove the code to compare the new src/options.h with + src/options.h-SAVED and move it back if it's unchanged; autoconf + writes that for us now. + +Wed Aug 30 18:45:28 1995 Jim Blandy <jimb@totoro.cyclic.com> + + * .cvsignore: Ignore WinDebug and WinRel directories, used by + Microsoft Visual C++ to store object files and executables. + + * acconfig.h (CVS_SUPPORT, CLIENT_SUPPORT, SERVER_SUPPORT): New + symbols, which autoheader will use to build config.h.in from + configure.in. + * configure.in (SERVER_SUPPORT, CLIENT_SUPPORT): Remove spaces + between AC_DEFINEs and opening parens of argument lists. Oops. + * configure: Rebuild using autoconf. + * config.h.in: Rebuild using autoheader. + + * Makefile.in (SUBDIRS): Uncomment windows-NT. + + * INSTALL: Added Windows NT to list of supported platforms. + Added Windows NT installation instructions. + +Tue Aug 29 16:08:01 1995 Jim Blandy <jimb@totoro.cyclic.com> + + * cvsnt.mak: Completed Windows NT port. + + * configure.in (SERVER_SUPPORT, CLIENT_SUPPORT): Arrange for these + to get #defined. In the config.h file for the Windows NT port, we + only #define CLIENT_SUPPORT. + * config.h.in (SERVER_SUPPORT, CLIENT_SUPPORT): Add #undefs for + these. + + * configure.in (AC_OUTPUT): Build the Makefile for the windows-NT + subdirectory too. + + * cvsnt.vcp: Removed. This doesn't store any information needed + to compile CVS; it seems to be mostly programmer preference stuff. + There's no need to distribute it. + + * INSTALL: Added info about Harris Nighthawk from Steve Allen --- + thanks! + +Mon Aug 21 16:08:37 1995 Jim Blandy <jimb@totoro.cyclic.com> + + Bring the saga to a close: + * configure.in: Use AC_PROG_MAKE_SET here, to decide whether we + need to set the MAKE variable in Makefile. + * Makefile.in: Use @SET_MAKE@ here, to set MAKE when appropriate. + +Mon Aug 21 15:26:29 1995 Jim Kingdon <kingdon@harvey.cyclic.com> + + * Makefile.in: Add comment regarding AC_SET_MAKE. + +Sat Aug 19 21:57:51 1995 Jim Blandy <jimb@totoro.cyclic.com> + + * configure.in: Define CVS_SUPPORT, to tell certain library + functions that they're part of CVS. + * config.h.in: Add #undef for CVS_SUPPORT, for configure to chew + on. + +Fri Aug 18 22:35:34 1995 Jim Kingdon <kingdon@harvey.cyclic.com> + + * Makefile.in: Don't set MAKE; apparently all makes set it and GNU + make, at least, will set it to what make was invoked as (perhaps gmake + or some such), not just "make" (which might not support VPATH, for + example). + +Sun Aug 13 23:35:47 1995 Jim Kingdon <kingdon@harvey.cyclic.com> + + * INSTALL: Convert Data General entry to same format as other entries. + +Sun Aug 13 13:11:36 1995 Jim Blandy <jimb@totoro.cyclic.com> + + * cvs-format.el: Add note about set-c-style. + +Thu Aug 3 16:13:29 1995 Jim Blandy <jimb@totoro.cyclic.com> + + * INSTALL: Fixed mail address for updates. + + * INSTALL: Noted that 1.5 runs on SunOS 4.1.1 -- 4.1.3. + +Sun Jul 30 20:12:26 1995 James Kingdon <kingdon@harvey.cyclic.com> + + * cvsinit.sh: Unify code for modules and loginfo with code for + other files which have checked-out and ,v files in CVSROOT. + Don't add "#" to start of lines in rcstemplate. + +Sat Jul 29 16:48:05 1995 James Kingdon <kingdon@harvey.cyclic.com> + + * cvsinit.sh: If arguments are given, give version number and + usage message. Make printed messages much more concise. + + * cvsinit.sh: Rename log.pl to log. Don't install log twice. + + * Makefile.in (install-local), contrib/Makefile.in (install): + Remove "reminder" to run cvsinit; running cvsinit is not required. + +Fri Jul 28 16:46:10 1995 James Kingdon <kingdon@harvey.cyclic.com> + + * Makefile.in (SUBDIRS): Comment out windows-NT. + +Fri Jul 28 02:27:54 1995 Jim Blandy <jimb@totoro.cyclic.com> + + * Makefile.in (DISTFILES): Add cvsnt.mak and cvsnt.vcp. + (SUBDIRS): Add windows-NT. + + * config.h.in: Regenerated from configure.in by autoheader. + +Wed Jul 19 18:00:00 1995 Jim Blandy <jimb@cyclic.com> + + * configure.in (AC_CHECK_HEADERS): Check for <io.h> and <direct.h>. + +Tue Jul 18 21:18:00 1995 Jim Blandy <jimb@cyclic.com> + + * configure.in (AC_CHECK_HEADERS): Check for sys/param.h; Windows NT + doesn't have it. + + * configure.in (AC_CHECK_HEADERS): Check for sys/time.h. If you're + using AC_HEADER_TIME, it's best to check for this too. + + * cvsnt.mak: New file --- makefile equivalent for Microsoft Visual C++. + Choose this as your project when working with CVS under MSVC++. + * cvsnt.vcp: New file --- configuration info for Microsoft Visual C++. + * windows-NT: New subdirectory, containing files to be used to + build under Microsoft Windows NT. + +Wed Jul 12 23:26:24 1995 James Kingdon <kingdon@harvey.cyclic.com> + + * Makefile.in: Remove duplicate install-info rule. + +Wed Jul 12 16:00:27 1995 Karl Fogel <kfogel@floss.cyclic.com> + + * Makefile.in (install-local): added rule for install-info, made + `install' depend on it. + + * README: correct mailing list addresses. + * INSTALL: same. + +Wed Jul 12 09:15:02 1995 Jim Meyering (meyering@comco.com) + + * configure.in (gdiff_path): Remove gdiff from the list of programs. + SGI's Irix includes a program named gdiff that is an X-based GUI to + diff. + + * configure.in: Add check for working fnmatch functions so that + systems providing it don't incur the space overhead of linking + with the version in lib. Cross compiling builds always use the + version in lib. + +Tue Jul 11 15:47:20 1995 Greg A. Woods <woods@most.weird.com> + + * configure.in: add some FIXME comments + - add a hack to restore src/options.h if AC_OUTPUT() didn't modify + it. Note that this does *not* work for config.status, thus one + FIXME comment. + - add test for #! (to warn about possible failure of perl scripts + - add test for diff and grep paths (for src/options.h.in) + - fix up handling of src/options.h.in + - add checks for PERL_PATH and CSH_PATH (from previous local changes) + +Tue Jul 11 14:31:18 1995 Michael Shields <shields@tembel.org> + + * Makefile.in (LDFLAGS): Pick up from configure. + +Sun Jul 9 19:03:00 1995 Greg A. Woods <woods@most.weird.com> + + * configure: re-ran autoconf-2.4 + + * cvsinit.sh: make use of xVERSIONx from the Makefile + - get rid of stuff duplicated in examples/* and use that instead + + * Makefile.in: $(VERSION) for cvsinit.sh wasn't set, so get it + from src/version.c instead. + + * cvsinit.sh: install two more example CVSROOT control/config + files: rcstemplate checkoutlist + - install useful scripts from $CVSLIB/contrib too... + (from previous local changes) + + * Makefile.in: add another reminder to run 'cvsinit' to update + repository(ies) (from previous local changes) + +Thu Jul 6 17:53:55 1995 Paul Eggert <eggert@twinsun.com> + + * Makefile.in (mostlyclean-local): Remove $(PROGS). + +Sat Jul 1 13:11:41 1995 James Kingdon <kingdon@harvey.cyclic.com> + + * Version 1.5.1. + +Thu Jun 29 01:02:09 1995 James Kingdon <kingdon@harvey.cyclic.com> + + * configure.in, configure: cross_compiling gets set to "no", not + empty--change test accordingly. + + * Version 1.4.93. + +Wed Jun 28 22:33:54 1995 James Kingdon <kingdon@harvey.cyclic.com> + + * lib/Makefile.in, man/Makefile.in, doc/Makefile.in: Comment out + rules for configure and config.status, just like in Makefile.in or + src/Makefile.in. + +Tue Jun 27 19:53:05 1995 James Kingdon <kingdon@harvey.cyclic.com> + + * configure.in (AC_REPLACE_FUNCS), configure: Remove fnmatch. + * lib/Makefile.in (OBJECTS): Add fnmatch. + Avoids buggy Solaris 2.4 libc fnmatch. + + * FAQ: Updated with new version from ftp.odi.com. + +Mon Jun 26 15:17:46 1995 James Kingdon <kingdon@harvey.cyclic.com> + + * Version 1.4.92. + +Thu Jun 22 12:45:24 1995 James Kingdon <kingdon@harvey.cyclic.com> + + * Version 1.4.91. + +Wed Jun 21 16:33:04 1995 James Kingdon <kingdon@harvey.cyclic.com> + + * PROJECTS: New file. + * Makefile.in (DISTFILES): Add it. + +Wed Jun 21 16:12:14 1995 James Kingdon <kingdon@harvey.cyclic.com> + + * Makefile.in (FLAGS_TO_PASS): Don't pass INSTALL to sub-makes. + The reason for passing it is gone now that we are using autoconf + 2.x which will set INSTALL in the sub-makefiles correctly. + +Tue Jun 20 18:14:54 1995 James Kingdon <kingdon@harvey.cyclic.com> + + * configure.in, configure: Make sure src directory exists before + trying to copy options.h to it. + +Mon Jun 19 13:47:20 1995 Jim Blandy <jimb@totoro.cyclic.com> + + * Makefile.in: Add a "remotecheck" target here, for consistency; + people shouldn't have to switch to src before running the tests. + +Mon Jun 19 10:08:03 1995 Jim Kingdon (kingdon@lioth.cygnus.com) + + * INSTALL: Update list of machines tested. Remove note about + systems missing opendir--this is an autoconf issue, not something + installers should have to worry about. Refer to NEWS instead of + ChangeLog. No longer "strongly recommend" putting diff -a in + options.h. + +Fri Jun 16 22:30:03 1995 Jim Kingdon (kingdon@cyclic.com) + + * Version 1.4.90. + + * configure, configure.in (AC_OUTPUT): Add config/pcl-cvs/Makefile. + + * Makefile.in (dist): Rename dist from ccvs-<version> to cvs-<version>. + + * Makefile.in (dist, dist-dir), src/Makefile.in, doc/Makefile.in, + examples/Makefile.in, contrib/Makefile.in, + contrib/pcl-cvs/Makefile, man/Makefile.in, lib/Makefile.in + (dist-dir): Use srcdir where appropriate. + +Thu Jun 15 14:33:37 1995 Jim Kingdon (kingdon@cyclic.com) + + * CYCLIC-CVS-FAQ: Removed. + * Rename ChangeLog.fsf to NEWS. Add information about changes + since 1.4A2. + * Makefile.in (DISTFILES): Adjust accordingly. + * README: Revise to reflect current status of releases. + +Thu Jun 15 12:22:42 1995 Jim Kingdon (kingdon@cyclic.com) + + * TODO: Remove various items already fixed. Revise others. + +Thu Jun 15 12:24:45 1995 J.T. Conklin <jtc@rtl.cygnus.com> + + * configure.in: Use AC_C_INLINE to handle inline. + Reorganized to put compiler and OS checks first so that any + special defines they might provide are used in subsequent tests. + + * configure, config.h.in: regenerated with autoconf and + autoheader version 2.3. + +Thu Jun 8 16:33:51 1995 Jim Kingdon (kingdon@lioth.cygnus.com) + + * INSTALL (Installation): Disrecommend RCS 5.6.[5-7]. + +Tue May 30 00:07:15 1995 Jim Meyering (meyering@comco.com) + + * Makefile.in (distclean-local): Don't delete config.status here. + (distclean): Delete config.status here instead, but only after + recursive make invocations. Otherwise, the new dependencies + in */Makefile.in on ../config.status led to failure in each sub-make + because there is no rule there to make ../config.status. + Reported by Jeff Johnson <jbj@brewster.jbj.org>. + (realclean): Likewise. + +Mon May 29 22:24:28 1995 J.T. Conklin <jtc@rtl.cygnus.com> + + * configure.in: Use AC_HEADER_DIRENT instead of AC_DIR_HEADER. + Use AC_HEADER_STAT to determine if S_FOO() macros work. + Use AC_HEADER_TIME to determine if both <sys/time.h> and <time.h> + can be included as recommend by autoconf manual. + Remove AC_STRUCT_TM test, as above test is better. + + * configure, config.h.in: regenerated with autoconf and + autoheader version 2.3. + +Fri Apr 28 14:36:49 1995 Ken Raeburn (raeburn@kr-pc.cygnus.com) + + * Makefile.in: Set "all" as default target instead of ".PHONY". + Some versions of make will otherwise try building all of the phony + targets, in order. + +Mon May 1 14:02:42 1995 Jim Blandy <jimb@totoro.bio.indiana.edu> + + * configure.in: Set up src/options.h for the user. Its defaults are + usually right. + * README, INSTALL: Adjust installation instructions appropriately. + +Fri Apr 28 22:31:26 1995 Jim Blandy <jimb@totoro.bio.indiana.edu> + + * Makefile.in (DISTFILES): Brought up-to-date. + (dist): Rewritten to use dist-dir targets, passing DISTDIR variable. + (GZIP, GZIP_EXT): New variables. + (dist-dir): New target. + + We don't want to include a file the user has to edit in the + distribution. + * src/options.h: No longer distributed. + * src/options.h.in: Distribute this instead. + * INSTALL, README: Installation instructions updated. + +Sat Apr 8 19:02:21 1995 Roland McGrath <roland@baalperazim.frob.com> + + * configure.in: Check for fchdir. + (connect check): Use AC_CHECK_LIB instead of (obsolete) + AC_HAVE_LIBRARY. + +Sat Apr 8 14:52:46 1995 Jim Blandy <jimb@totoro.bio.indiana.edu> + + * Makefile.in (CFLAGS): Let configure set the default for CFLAGS. + Under GCC, we want -g -O. + +Wed Feb 8 06:49:49 1995 Roland McGrath <roland@churchy.gnu.ai.mit.edu> + + * Makefile.in (stamp-h): Pass CONFIG_FILES=$@ to config.status so + the target is created. + * configure.in: Applied `autoupdate' from Autoconf 2.1 to + modernize macro usage. + (AC_RSH): Call removed. It was obsolete and not doing anything useful. + (AC_OUTPUT): Write stamp-h as the Makefile rules expect we will. + (AC_TYPE_PID_T): Add this check. + +Tue Nov 8 06:26:54 1994 Jim Kingdon (kingdon@lioth.cygnus.com) + + * Add stamp-h.in. Remove it from .cvsignore. + +Fri Oct 28 11:50:51 1994 Jim Kingdon (kingdon@lioth.cygnus.com) + + * Makefile.in: Comment out autoconf and autoheader rules. + +Tue Oct 25 17:44:13 1994 Ken Raeburn <raeburn@cujo.cygnus.com> + + * Makefile.in (all, install, uninstall): Fail if make in + subdirectory fails. + +Tue Oct 18 13:26:15 1994 Jim Kingdon (kingdon@lioth.cygnus.com) + + * Makefile.in (FLAGS_TO_PASS): Pass INSTALL*. Add comment about + why we need to. + +Tue Sep 27 08:27:06 1994 Jim Kingdon (kingdon@lioth.cygnus.com) + + * Makefile.in (SUBDIRS): Reinstate "contrib". + * configure.in (AC_OUTPUT): Add contrib/Makefile. + * configure: Regenerated. + +Tue Sep 27 01:03:59 1994 John Gilmore (gnu@cygnus.com) + + * Makefile.in (SUBDIRS): Comment out "contrib". Since we don't + bother to configure it, we shouldn't make it either. + +Wed Aug 10 14:52:57 1994 Ken Raeburn (raeburn@cujo.cygnus.com) + + * Makefile.in (FLAGS_TO_PASS): Don't include LIBS or CFLAGS twice. + + * configure.in: Include waitpid and memmove in AC_REPLACE_FUNCS + list. Don't check for memmove separately. + * configure: Regenerated. + * config.h.in: Regenerated for Mark's change. + +Wed Aug 10 14:32:24 1994 Mark Eichin (eichin@cygnus.com) + + * configure.in (KRB4): recognize --with-krb4=path. Also test for + krb_get_err_text so src/main.c and src/client.c can deal + appropriately. + +Tue Aug 9 15:49:07 1994 Ken Raeburn (raeburn@cujo.cygnus.com) + + * configure.in: Check sizes of `long' and `int', needed for md5 + code. + * acconfig.h: New file. Mention HAVE_KERBEROS, to keep autoheader + happy. + * configure, config.h.in: Regenerated. + +Tue Jul 19 11:23:21 1994 Ian Lance Taylor (ian@sanguine.cygnus.com) + + * configure.in: Check not only that krb.h exists, but that it will + actually compile correctly. + * configure: Regenerated. + +Mon Jul 11 07:04:36 1994 Jim Kingdon (kingdon@lioth.cygnus.com) + + * configure.in: Add comment re autoheader. + +Tue Jun 28 22:09:23 1994 Ian Lance Taylor (ian@tweedledumb.cygnus.com) + + * configure.in: Only look for -lsocket and -lnsl if we don't + already have connect. + * configure: Regenerated. + +Mon Jun 27 17:21:48 1994 Ian Lance Taylor (ian@sanguine.cygnus.com) + + * configure.in: Correct "krb_libdir" to "${krb_libdir}". + * configure: Regenerated. + +Fri Jun 3 10:15:24 1994 Ian Lance Taylor (ian@tweedledumb.cygnus.com) + + * configure.in: Check for -lsocket and -lnsl. + * configure: Regenerated. + +Fri May 27 18:12:43 1994 Ian Lance Taylor (ian@tweedledumb.cygnus.com) + + * configure.in: Add valloc to AC_REPLACE_FUNCS. Add getpagesize + to AC_HAVE_FUNCS. Check for krb.h and -lkrb. If not found, look + in /usr/kerberos if native. If found somewhere, define + HAVE_KERBEROS and also look for -ldes. Substitute includeopt. + * configure: Regenerated. + +Fri Mar 11 13:11:51 1994 Ian Lance Taylor (ian@tweedledumb.cygnus.com) + + * configure.in: Check for <sys/select.h>; used by src/server.c. + * configure: Regenerated. + +Sun Jan 9 12:04:15 1994 Ken Raeburn (raeburn@kr-pc.cygnus.com) + + * configure.in: Check for timezone function, for NetBSD support. + * configure: Regenerated. + +Wed Dec 15 18:05:21 1993 david d `zoo' zuhn (zoo@andros.cygnus.com) + + * Makefile.in: add MAKEINFO to MDEFINES, pass down MDEFINES on all + recursive make invocations that require it; define + INSTALL_PROGRAM and use it; reorganize MDEFINES; set infodir and + add to MDEFINES; use YACC instead of BISON + + +Mon Dec 6 17:02:18 1993 K. Richard Pixley (rich@sendai.cygnus.com) + + * src/diff.c (diff_fileproc): add support for "cvs diff -N" which + allows for adding or removing files via patches. + diff --git a/contrib/cvs/ChangeLog.zoo b/contrib/cvs/ChangeLog.zoo new file mode 100644 index 0000000..a1e1d0e --- /dev/null +++ b/contrib/cvs/ChangeLog.zoo @@ -0,0 +1,700 @@ +Thu Sep 15 14:19:21 1994 david d `zoo' zuhn <zoo@monad.armadillo.com> + + * Makefile.in: define TEXI2DVI, add it to FLAGS_TO_PASS; remove + old comments about parameters for DEFS + +Wed Jul 13 21:54:46 1994 david d `zoo' zuhn (zoo@monad.armadillo.com) + + * contrib/rcs-to-cvs: rewritten for Bourne shell (thanks to David + MacKenzie <djm@cygnus.com>) + +Wed Jul 13 21:48:38 1994 Ken Raeburn (raeburn@cujo.cygnus.com) + + * Makefile.in: Deleted line consisting of only whitespace; it + confuses some versions of make. + +Mon Jan 24 12:26:47 1994 david d zuhn (zoo@monad.armadillo.com) + + * configure.in: check for <sys/select.h> and <ndbm.h> + + * Makefile.in: define YACC and not BISON + +Sat Dec 18 00:52:04 1993 david d zuhn (zoo@monad.armadillo.com) + + * config.h.in: handle HAVE_SYS_WAIT_H, HAVE_ERRNO_H + + * configure.in: check for memmove, <errno.h> + + * Makefile.in (VPATH): don't use $(srcdir), but @srcdir@ instead + + * configure.in (AC_HAVE_HEADERS): check for <sys/wait.h> + +Mon Nov 29 15:05:43 1993 K. Richard Pixley (rich@sendai.cygnus.com) + + * lib/Makefile.in, src/Makefile.in (CFLAGS): default to -g. + + * src/log.c (log_fileproc): if a file has been added, but not + committed, then say so rather than reporting that nothing is + known. + + * src/sanity.el: update for emacs-19. + + * src/RCS-patches, src/README-rm-add: update for rcs-5.6.6. + + * src/Makefile.in: removed some gratuitous diffs from cvs-1.3. + + * src/cvsrc.c: strdup -> xstrdup, malloc -> xmalloc, comment about + fgets lossage. + + * configure, configure.in, Makefile.in: support man and doc + directories and info and dvi targets. + + * doc/cvs.texinfo: comment out include of gpl.texinfo. + + * doc/Makefile.in: added dvi & info targets. + + * doc/cvsclient.texi: added @setfilename. + + * lib/Makefile.in: remove some extraneous diffs against the + patched cvs-1.3. + + * doc/Makefile.in, man/Makefile.in: update for autoconf. + +Fri Nov 19 12:56:34 1993 K. Richard Pixley (rich@sendai.cygnus.com) + + * Many files: added configure.in, updated configure based on + autoconf. + +Tue Jun 1 17:02:41 1993 david d `zoo' zuhn (zoo at cirdan.cygnus.com) + + * configure: add support for alloca and sys/select.h + +Wed May 19 19:34:48 1993 Jim Kingdon (kingdon@lioth.cygnus.com) + + * cvs-format.el: Don't set c-tab-always-indent. + +Mon Mar 22 23:25:33 1993 david d `zoo' zuhn (zoo at cirdan.cygnus.com) + + * Makefile.in: installcheck: recurse into src directory to run tests + +Mon Jan 18 17:21:16 1993 K. Richard Pixley (rich@rtl.cygnus.com) + + * Makefile.in (check): recur into src directory in order to pick + up the sanity check. + +Thu Dec 17 19:41:22 1992 david d `zoo' zuhn (zoo at cirdan.cygnus.com) + + * Makefile.in: added blank 'dvi' target + +Tue Apr 7 15:55:25 1992 Brian Berliner (berliner at sun.com) + + * Changes between CVS 1.3 Beta-3 and official CVS 1.3! + + * A new shell script is provided, "./cvsinit", which can be run at + install time to help setup your $CVSROOT area. This can greatly + ease your entry into CVS usage. + + * The INSTALL file has been updated to include the machines on + which CVS has compiled successfully. I think CVS 1.3 is finally + portable. Thanks to all the Beta testers! + + * Support for the "editinfo" file was contributed. This file + (located in $CVSROOT/CVSROOT) can be used to specify a special + "editor" to run on a per-directory basis within the repository, + instead of the usual user's editor. As such, it can verify that + the log message entered by the user is of the appropriate form + (contains a bugid and test validation, for example). + + * The manual pages cvs(1) and cvs(5) have been updated. + + * The "mkmodules" command now informs you when your modules file + has duplicate entries. + + * The "add" command now preserves any per-directory sticky tag when + you add a new directory to your checked-out sources. + + * The "admin" command is now a fully recursive interface to the + "rcs" program which operates on your checked-out sources. It no + longer requires you to specify the full path to the RCS file. + + * The per-file sticky tags can now be effectively removed with + "cvs update -A file", even if you had checked out the whole + directory with a per-directory sticky tag. This allows a great + deal of flexibility in managing the revisions that your checked-out + sources are based upon (both per-directory and per-file sticky + tags). + + * The "cvs -n commit" command now works, to show which files are + out-of-date and will cause the real commit to fail, or which files + will fail any pre-commit checks. Also, the "cvs -n import ..." + command will now show you what it would've done without actually + doing it. + + * Doing "cvs commit modules" to checkin the modules file will no + properly run the "mkmodules" program (assuming you have setup your + $CVSROOT/CVSROOT/modules file to do so). + + * The -t option in the modules file (which specifies a program to + run when you do a "cvs rtag" operation on a module) now gets the + symbolic tag as the second argument when invoked. + + * When the source repository is locked by another user, that user's + login name will be displayed as the holder of the lock. + + * Doing "cvs checkout module/file.c" now works even if + module/file.c is in the Attic (has been removed from main-line + development). + + * Doing "cvs commit */Makefile" now works as one would expect. + Rather than trying to commit everything recursively, it will now + commit just the files specified. + + * The "cvs remove" command is now fully recursive. To schedule a + file for removal, all you have to do is "rm file" and "cvs rm". + With no arguments, "cvs rm" will schedule all files that have been + physically removed for removal from the source repository at the + next "cvs commit". + + * The "cvs tag" command now prints "T file" for each file that was + tagged by this invocation and "D file" for each file that had the + tag removed (as with "cvs tag -d"). + + * The -a option has been added to "cvs rtag" to force it to clean + up any old, matching tags for files that have been removed (in the + Attic) that may not have been touched by this tag operation. This + can help keep a consistent view with your tag, even if you re-use + it frequently. + +Sat Feb 29 16:02:05 1992 Brian Berliner (berliner at sun.com) + + * Changes between CVS 1.3 Beta-2 and CVS 1.3 Beta-3 + + * Many portability fixes, thanks to all the Beta testers! With any + luck, this Beta release will compile correctly on most anything. + Hey, what are we without our dreams. + + * CVS finally has support for doing isolated development on a + branch off the current (or previous!) revisions. This is also + extremely nice for generating patches for previously released + software while development is progressing on the next release. + Here's an example of creating a branch to fix a patch with the 2.0 + version of the "foo" module, even though we are already well into + the 3.0 release. Do: + + % cvs rtag -b -rFOO_2_0 FOO_2_0_Patch foo + % cvs checkout -rFOO_2_0_Patch foo + % cd foo + [[ hack away ]] + % cvs commit + + A physical branch will be created in the RCS file only when you + actually commit the change. As such, forking development at some + random point in time is extremely light-weight -- requiring just a + symbolic tag in each file until a commit is done. To fork + development at the currently checked out sources, do: + + % cvs tag -b Personal_Hack + % cvs update -rPersonal_Hack + [[ hack away ]] + % cvs commit + + Now, if you decide you want the changes made in the Personal_Hack + branch to be merged in with other changes made in the main-line + development, you could do: + + % cvs commit # to make Personal_Hack complete + % cvs update -A # to update sources to main-line + % cvs update -jPersonal_Hack # to merge Personal_Hack + + to update your checked-out sources, or: + + % cvs checkout -jPersonal_Hack module + + to checkout a fresh copy. + + To support this notion of forked development, CVS reserves + all even-numbered branches for its own use. In addition, CVS + reserves the ".0" and ".1" branches. So, if you intend to do your + own branches by hand with RCS, you should use odd-numbered branches + starting with ".3", as in "1.1.3", "1.1.5", 1.2.9", .... + + * The "cvs commit" command now supports a fully functional -r + option, allowing you to commit your changes to a specific numeric + revision or symbolic tag with full consistency checks. Numeric + tags are useful for bringing your sources all up to some revision + level: + + % cvs commit -r2.0 + + For symbolic tags, you can only commit to a tag that references a + branch in the RCS file. One created by "cvs rtag -b" or from + "cvs tag -b" is appropriate (see below). + + * Roland Pesch <pesch@cygnus.com> and K. Richard Pixley + <rich@cygnus.com> were kind enough to contribute two new manual + pages for CVS: cvs(1) and cvs(5). Most of the new CVS 1.3 features + are now documented, with the exception of the new branch support + added to commit/rtag/tag/checkout/update. + + * The -j options of checkout/update have been added. The "cvs join" + command has been removed. + + With one -j option, CVS will merge the changes made between the + resulting revision and the revision that it is based on (e.g., if + the tag refers to a branch, CVS will merge all changes made in + that branch into your working file). + + With two -j options, CVS will merge in the changes between the two + respective revisions. This can be used to "remove" a certain delta + from your working file. E.g., If the file foo.c is based on + revision 1.6 and I want to remove the changes made between 1.3 and + 1.5, I might do: + + % cvs update -j1.5 -j1.3 foo.c # note the order... + + In addition, each -j option can contain on optional date + specification which, when used with branches, can limit the chosen + revision to one within a specific date. An optional date is + specified by adding a colon (:) to the tag, as in: + + -jSymbolic_Tag:Date_Specifier + + An example might be what "cvs import" tells you to do when you have + just imported sources that have conflicts with local changes: + + % cvs checkout -jTAG:yesterday -jTAG module + + which tells CVS to merge in the changes made to the branch + specified by TAG in the last 24 hours. If this is not what is + intended, substitute "yesterday" for whatever format of date that + is appropriate, like: + + % cvs checkout -jTAG:'1 week ago' -jTAG module + + * "cvs diff" now supports the special tags "BASE" and "HEAD". So, + the command: + + % cvs diff -u -rBASE -rHEAD + + will effectively show the changes made by others (in unidiff + format) that will be merged into your working sources with your + next "cvs update" command. "-rBASE" resolves to the revision that + your working file is based on. "-rHEAD" resolves to the current + head of the branch or trunk that you are working on. + + * The -P option of "cvs checkout" now means to Prune empty + directories, as with "update". The default is to not remove empty + directories. However, if you do "checkout" with any -r options, -P + will be implied. I.e., checking out with a tag will cause empty + directories to be pruned automatically. + + * The new file INSTALL describes how to install CVS, including + detailed descriptions of interfaces to "configure". + + * The example loginfo file in examples/loginfo has been updated to + use the perl script included in contrib/log.pl. The nice thing + about this log program is that it records the revision numbers of + your change in the log message. + + Example files for commitinfo and rcsinfo are now included in the + examples directory. + + * All "#if defined(__STDC__) && __STDC__ == 1" lines have been + changed to be "#if __STDC__" to fix some problems with the former. + + * The lib/regex.[ch] files have been updated to the 1.3 release of + the GNU regex package. + + * The ndbm emulation routines included with CVS 1.3 Beta-2 in the + src/ndbm.[ch] files has been moved into the src/myndbm.[ch] files + to avoid any conflict with the system <ndbm.h> header file. If + you had a previous CVS 1.3 Beta release, you will want to "cvs + remove ndbm.[ch]" form your copy of CVS as well. + + * "cvs add" and "cvs remove" are a bit more verbose, telling you + what to do to add/remove your file permanently. + + * We no longer mess with /dev/tty in "commit" and "add". + + * More things are quiet with the -Q option set. + + * New src/config.h option: If CVS_BADROOT is set, CVS will not + allow people really logged in as "root" to commit changes. + + * "cvs diff" exits with a status of 0 if there were no diffs, 1 if + there were diffs, and 2 if there were errors. + + * "cvs -n diff" is now supported so that you can still run diffs + even while in the middle of committing files. + + * Handling of the CVS/Entries file is now much more robust. + + * The default file ignore list now includes "*.so". + + * "cvs import" did not expand '@' in the log message correctly. It + does now. Also, import now uses the ignore file facility + correctly. + + Import will now tell you whether there were conflicts that need to + be resolved, and how to resolve them. + + * "cvs log" has been changed so that you can "log" things that are + not a part of the current release (in the Attic). + + * If you don't change the editor message on commit, CVS now prompts + you with the choice: + + !)reuse this message unchanged for remaining dirs + + which allows you to tell CVS that you have no intention of changing + the log message for the remainder of the commit. + + * It is no longer necessary to have CVSROOT set if you are using + the -H option to get Usage information on the commands. + + * Command argument changes: + checkout: -P handling changed as described above. + New -j option (up to 2 can be specified) + for doing rcsmerge kind of things on + checkout. + commit: -r option now supports committing to a + numeric or symbolic tags, with some + restrictions. Full consistency checks will + be done. + Added "-f logfile" option, which tells + commit to glean the log message from the + specified file, rather than invoking the + editor. + rtag: Added -b option to create a branch tag, + useful for creating a patch for a previous + release, or for forking development. + tag: Added -b option to create a branch tag, + useful for creating a patch for a previous + release, or for forking development. + update: New -j option (up to 2 can be specified) + for doing rcsmerge kind of things on + update. + +Thu Jan 9 10:51:35 MST 1992 Jeff Polk (polk at BSDI.COM) + + * Changes between CVS 1.3 Beta-1 and CVS 1.3 Beta-2 + + * Thanks to K. Richard Pixley at Cygnus we now have function + prototypes in all the files + + * Some small changes to configure for portability. There have + been other portability problems submitted that have not been fixed + (Brian will be working on those). Additionally all __STDC__ + tests have been modified to check __STDC__ against the constant 1 + (this is what the Second edition of K&R says must be true). + + * Lots of additional error checking for forked processes (run_exec) + (thanks again to K. Richard Pixley) + + * Lots of miscellaneous bug fixes - including but certainly not + limited to: + various commit core dumps + various update core dumps + bogus results from status with numeric sticky tags + commitprog used freed memory + Entries file corruption caused by No_Difference + commit to revision broken (now works if branch exists) + ignore file processing broken for * and ! + ignore processing didn't handle memory reasonably + miscellaneous bugs in the recursion processor + file descriptor leak in ParseInfo + CVSROOT.adm->CVSROOT rename bug + lots of lint fixes + + * Reformatted all the code in src (with GNU indent) and then + went back and fixed prototypes, etc since indent gets confused. The + rationale is that it is better to do it sooner than later and now + everything is consistent and will hopefully stay that way. + The basic options to indent were: "-bad -bbb -bap -cdb -d0 -bl -bli0 + -nce -pcs -cs -cli4 -di1 -nbc -psl -lp -i4 -ip4 -c41" and then + miscellaneous formatting fixes were applied. Note also that the + "-nfc1" or "-nfca" may be appropriate in files where comments have + been carefully formatted (e.g, modules.c). + +Sat Dec 14 20:35:22 1991 Brian Berliner (berliner at sun.com) + + * Changes between CVS 1.2 and CVS 1.3 Beta are described here. + + * Lots of portability work. CVS now uses the GNU "configure" + script to dynamically determine the features provided by your + system. It probably is not foolproof, but it is better than + nothing. Please let me know of any portability problems. Some + file names were changed to fit within 14-characters. + + * CVS has a new RCS parser that is much more flexible and + extensible. It should read all known RCS ",v" format files. + + * Most of the commands now are fully recursive, rather than just + operating on the current directory alone. This includes "commit", + which makes it real easy to do an "atomic" commit of all the + changes made to a CVS hierarchy of sources. Most of the commands + also correctly handle file names that are in directories other than + ".", including absolute path names. Commands now accept the "-R" + option to force recursion on (though it is always the default now) + and the "-l" option to force recursion off, doing just "." and not + any sub-directories. + + * CVS supports many of the features provided with the RCS 5.x + distribution - including the new "-k" keyword expansion options. I + recommend using RCS 5.x (5.6 is the current official RCS version) + and GNU diff 1.15 (or later) distributions with CVS. + + * Checking out files with symbolic tags/dates is now "sticky", in + that CVS remembers the tag/date used for each file (and directory) + and will use that tag/date automatically on the next "update" call. + This stickyness also holds for files checked out with the the new + RCS 5.x "-k" options. + + * The "cvs diff" command now recognizes all of the rcsdiff 5.x + options. Unidiff format is available by installing the GNU + diff 1.15 distribution. + + * The old "CVS.adm" directories created on checkout are now called + "CVS" directories, to look more like "RCS" and "SCCS". Old CVS.adm + directories are automagically converted to CVS directories. The + old "CVSROOT.adm" directory within the source repository is + automagically changed into a "CVSROOT" directory as well. + + * Symbolic links in the source repository are fully supported ONLY + if you use RCS 5.6 or later and (of course) your system supports + symlinks. + + * A history database has been contributed which maintains the + history of certain CVS operations, as well as providing a wide array + of querying options. + + * The "cvs" program has a "-n" option which can be used with the + "update" command to show what would be updated without actually + doing the update, like: "cvs -n update". All usage statements + have been cleaned up and made more verbose. + + * The module database parsing has been rewritten. The new format + is compatible with the old format, but with much more + functionality. It allows modules to be created that grab pieces or + whole directories from various different parts of your source + repository. Module-relative specifications are also correctly + recognized now, like "cvs checkout module/file.c". + + * A configurable template can be specified such that on a "commit", + certain directories can supply a template that the user must fill + before completing the commit operation. + + * A configurable pre-commit checking program can be specified which + will run to verify that a "commit" can happen. This feature can be + used to restrict certain users from changing certain pieces of the + source repository, or denying commits to the entire source + repository. + + * The new "cvs export" command is much like "checkout", but + establishes defaults suitable for exporting code to others (expands + out keywords, forces the use of a symbolic tag, and does not create + "CVS" directories within the checked out sources. + + * The new "cvs import" command replaces the deprecated "checkin" + shell script and is used to import sources into CVS control. It is + also much faster for the first-time import. Some algorithmic + improvements have also been made to reduce the number of + conflicting files on next-time imports. + + * The new "cvs admin" command is basically an interface to the + "rcs" program. (Not yet implemented very well). + + * Signal handling (on systems with BSD or POSIX signals) is much + improved. Interrupting CVS now works with a single interrupt! + + * CVS now invokes RCS commands by direct fork/exec rather than + calling system(3). This improves performance by removing a call to + the shell to parse the arguments. + + * Support for the .cvsignore file has been contributed. CVS will + now show "unknown" files as "? filename" as the result of an "update" + command. The .cvsignore file can be used to add files to the + current list of ignored files so that they won't show up as unknown. + + * Command argument changes: + cvs: Added -l to turn off history logging. + Added -n to show what would be done without actually + doing anything. + Added -q/-Q for quiet and really quiet settings. + Added -t to show debugging trace. + add: Added -k to allow RCS 5.x -k options to be specified. + admin: New command; an interface to rcs(1). + checkout: Added -A to reset sticky tags/date/options. + Added -N to not shorten module paths. + Added -R option to force recursion. + Changed -p (prune empty directories) to -P option. + Changed -f option; forcing tags match is now default. + Added -p option to checkout module to standard output. + Added -s option to cat the modules db with status. + Added -d option to checkout in the specified directory. + Added -k option to use RCS 5.x -k support. + commit: Removed -a option; use -l instead. + Removed -f option. + Added -l option to disable recursion. + Added -R option to force recursion. + If no files specified, commit is recursive. + diff: Now recognizes all RCS 5.x rcsdiff options. + Added -l option to disable recursion. + Added -R option to force recursion. + history: New command; displays info about CVS usage. + import: Replaces "checkin" shell script; imports sources + under CVS control. Ignores files on the ignore + list (see -I option or .cvsignore description above). + export: New command; like "checkout", but w/special options + turned on by default to facilitate exporting sources. + join: Added -B option to join from base of the branch; + join now defaults to only joining with the top two + revisions on the branch. + Added -k option for RCS 5.x -k support. + log: Supports all RCS 5.x options. + Added -l option to disable recursion. + Added -R option to force recursion. + patch: Changed -f option; forcing tags match is now default. + Added -c option to force context-style diffs. + Added -u option to support unidiff-style diffs. + Added -V option to support RCS specific-version + keyword expansion formats. + Added -R option to force recursion. + remove: No option changes. It's a bit more verbose. + rtag: Equivalent to the old "cvs tag" command. + No option changes. It's a lot faster for re-tag. + status: New output formats with more information. + Added -l option to disable recursion. + Added -R option to force recursion. + Added -v option to show symbolic tags for files. + tag: Functionality changed to tag checked out files + rather than modules; use "rtag" command to get the + old "cvs tag" behaviour. + update: Added -A to reset sticky tags/date/options. + Changed -p (prune empty directories) to -P option. + Changed -f option; forcing tags match is now default. + Added -p option to checkout module to standard output. + Added -I option to add files to the ignore list. + Added -R option to force recursion. + + Major Contributors: + + * Jeff Polk <polk@bsdi.com> rewrote most of the grody code of CVS + 1.2. He made just about everything dynamic (by using malloc), + added a generic hashed list manager, re-wrote the modules database + parsing in a compatible - but extended way, generalized directory + hierarchy recursion for virtually all the commands (including + commit!), generalized the loginfo file to be used for pre-commit + checks and commit templates, wrote a new and flexible RCS parser, + fixed an uncountable number of bugs, and helped in the design of + future CVS features. If there's anything gross left in CVS, it's + probably my fault! + + * David G. Grubbs <dgg@ksr.com> contributed the CVS "history" and + "release" commands. As well as the ever-so-useful "-n" option of + CVS which tells CVS to show what it would do, without actually + doing it. He also contributed support for the .cvsignore file. + + * Paul Sander, HaL Computer Systems, Inc. <paul@hal.com> wrote and + contributed the code in lib/sighandle.c. I added support for + POSIX, BSD, and non-POSIX/non-BSD systems. + + * Free Software Foundation contributed the "configure" script and + other compatibility support in the "lib" directory, which will help + make CVS much more portable. + + * Many others have contributed bug reports and enhancement requests. + Some have even submitted actual code which I have not had time yet + to integrate into CVS. Maybe for the next release. + + * Thanks to you all! + +Wed Feb 6 10:10:58 1991 Brian Berliner (berliner at sun.com) + + * Changes from CVS 1.0 Patchlevel 1 to CVS 1.0 Patchlevel 2; also + known as "Changes from CVS 1.1 to CVS 1.2". + + * Major new support with this release is the ability to use the + recently-posted RCS 5.5 distribution with CVS 1.2. See below for + other assorted bug-fixes that have been thrown in. + + * ChangeLog (new): Added Emacs-style change-log file to CVS 1.2 + release. Chronological description of changes between release. + + * README: Small fixes to installation instructions. My email + address is now "berliner@sun.com". + + * src/Makefile: Removed "rcstime.h". Removed "depend" rule. + + * src/partime.c: Updated to RCS 5.5 version with hooks for CVS. + * src/maketime.c: Updated to RCS 5.5 version with hooks for CVS. + * src/rcstime.h: Removed from the CVS 1.2 distribution. + Thanks to Paul Eggert <eggert@twinsun.com> for these changes. + + * src/checkin.csh: Support for RCS 5.5 parsing. + Thanks to Paul Eggert <eggert@twinsun.com> for this change. + + * src/collect_sets.c (Collect_Sets): Be quieter if "-f" option is + specified. When checking out files on-top-of other files that CVS + doesn't know about, run a diff in the hopes that they are really + the same file before aborting. + + * src/commit.c (branch_number): Fix for RCS 5.5 parsing. + Thanks to Paul Eggert <eggert@twinsun.com> for this change. + + * src/commit.c (do_editor): Bug fix - fprintf missing argument + which sometimes caused core dumps. + + * src/modules.c (process_module): Properly NULL-terminate + update_dir[] in all cases. + + * src/no_difference.c (No_Difference): The wrong RCS revision was + being registered in certain (strange) cases. + + * src/patch.c (get_rcsdate): New algorithm. No need to call + maketime() any longer. + Thanks to Paul Eggert <eggert@twinsun.com> for this change. + + * src/patchlevel.h: Increased patch level to "2". + + * src/subr.c (isdir, islink): Changed to compare stat mode bits + correctly. + + * src/tag.c (tag_file): Added support for following symbolic links + that are in the master source repository when tagging. Made tag + somewhat quieter in certain cases. + + * src/update.c (update_process_lists): Unlink the user's file if it + was put on the Wlist, meaning that the user's file is not modified + and its RCS file has been removed by someone else. + + * src/update.c (update): Support for "cvs update dir" to correctly + just update the argument directory "dir". + + * src/cvs.h: Fixes for RCS 5.5 parsing. + * src/version_number.c (Version_Number): Fixes for parsing RCS 5.5 + and older RCS-format files. + Thanks to Paul Eggert <eggert@twinsun.com> for these changes. + + * src/version_number.c (Version_Number): Bug fixes for "-f" option. + Bug fixes for parsing with certain branch numbers. RCS + revision/symbol parsing is much more solid now. + +Wed Feb 14 10:01:33 1990 Brian Berliner (berliner at sun.com) + + * Changes from CVS 1.0 Patchlevel 0 to CVS 1.0 Patchlevel 1; also + known as "Changes from CVS 1.0 to CVS 1.1". + + * src/patch.c (get_rcsdate): Portability fix. Replaced call to + timelocal() with call to maketime(). + +Mon Nov 19 23:15:11 1990 Brian Berliner (berliner at prisma.com) + + * Sent CVS 1.0 release to comp.sources.unix moderator and FSF. + + * Special thanks to Dick Grune <dick@cs.vu.nl> for his work on the + 1986 version of CVS and making it available to the world. Dick's + version is available on uunet.uu.net in the + comp.sources.unix/volume6/cvs directory. + +@(#)ChangeLog 1.17 92/04/10 diff --git a/contrib/cvs/FAQ b/contrib/cvs/FAQ new file mode 100644 index 0000000..450fa32 --- /dev/null +++ b/contrib/cvs/FAQ @@ -0,0 +1,14 @@ +This file formerly contained a CVS FAQ, maintained by David Grubbs. +However, it has been out of date for a long time, he officially gave up +maintaining it in the fall of 1995, and noone else has taken it over. +Most of the information which used to be in it can be found in the CVS +manual, doc/cvs.texinfo in this distribution. For information on +questions like "what is the latest version of CVS?" and "what about GUIs +for CVS?", see the CVS web site, +http://www.loria.fr/~molli/cvs-index.html. There are many web sites on +Configuration Management packages (of which CVS is an example); for +example see + +http://www.yahoo.com/Computers_and_Internet/Software/Software_Engineering/Configuration_Management/index.html + +or http://www.iac.honeywell.com/Pub/Tech/CM/index.html. diff --git a/contrib/cvs/HACKING b/contrib/cvs/HACKING new file mode 100644 index 0000000..ac4cdd1 --- /dev/null +++ b/contrib/cvs/HACKING @@ -0,0 +1,102 @@ +How to write code for CVS + +* Compiler options + +If you are using GCC, you'll want to configure with -Wall, which can +detect many programming errors. This is not the default because it +might cause spurious warnings, but at least on some machines, there +should be no spurious warnings. For example: + + $ CFLAGS="-g -Wall" ./configure + +Configure is not very good at remembering this setting; it will get +wiped out whenever you do a ./config.status --recheck, so you'll need +to use: + + $ CFLAGS="-g -Wall" ./config.status --recheck + +* Indentation style + +CVS mostly uses a consistent indentation style which looks like this: + +void +foo (arg) + char *arg; +{ + if (arg != NULL) + { + bar (arg); + baz (arg); + } +} + +The file cvs-format.el contains settings for emacs and the NEWS file +contains a set of options for the indent program which I haven't tried +but which are correct as far as I know. You will find some code which +does not conform to this indentation style; the plan is to reindent it +as those sections of the code are changed (one function at a time, +perhaps). + +In a submitted patch it is acceptable to refrain from changing the +indentation of large blocks of code to minimize the size of the patch; +the person checking in such a patch should reindent it. + +* Portability + +If it is in ANSI C and it is in SunOS4 (using /bin/cc), generally it +is OK to use it without ifdefs (for example, assert() and void * as +long as you add more casts to and from void * than ANSI requires. But +not function prototypes). Such constructs are generally portable +enough, including to NT, OS/2, VMS, etc. + +* Run-time behaviors + +Use assert() to check "can't happen" conditions internal to CVS. We +realize that there are functions in CVS which instead return NULL or +some such value (thus confusing the meaning of such a returned value), +but we want to fix that code. Of course, bad input data, a corrupt +repository, bad options, etc., should always print a real error +message instead. + +* Coding standards in general + +Generally speaking the GNU coding standards are mostly used by CVS +(but see the exceptions mentioned above, such as indentation style, +and perhaps an exception or two we haven't mentioned). This is the +file standards.text at the GNU FTP sites. + +* Submitting patches + +Please include a ChangeLog entry (see the GNU coding standards for +information on writing one) with patches. Include a description of +what the patch does (sometimes the ChangeLog entry and/or comments in +the code are appropriate for this, but not always)--patches should not +be checked in unless there is some reason for them, and the +description may be helpful if there is a better way to solve the +problem. In addition to the ChangeLog entry, there should be a change +to the NEWS file in the case of a new feature. + +If you solve several unrelated problems, submit a separate +patch for each one. Patches should be tested before submission. Use +context diffs or unidiffs for patches. + +Note that all submitted changes may be distributed under the terms of +the GNU Public License, so if you don't like this, don't submit them. +Submit changes to bug-cvs@prep.ai.mit.edu. + +Generally speaking if you follow the guidelines in this file you can +expect a yes or no answer about whether your patch is accepted. But +even in this case there is no guarantee because wading through a bunch +of submissions can be time consuming, and noone has volunteered to +offer any such guarantee. If you don't receive an answer one way or +another within a month, feel free to ask what the status is. You can, +if you wish, distribute your patch on mailing lists or newsgroups, if +you want to make it available before it gets merged. + +* What is the schedule for the next release? + +There isn't one. That is, upcoming releases are not announced (or +even hinted at, really) until the feature freeze which is +approximately 2 weeks before the final release (at this time test +releases start appearing and are announced on info-cvs). This is +intentional, to avoid a last minute rush to get new features in. diff --git a/contrib/cvs/INSTALL b/contrib/cvs/INSTALL new file mode 100644 index 0000000..4907134 --- /dev/null +++ b/contrib/cvs/INSTALL @@ -0,0 +1,344 @@ +#ident "$CVSid$" + +First, read the README file. If you're still happy... + +CVS has been tested on the following platforms. The most recent +version of CVS reported to have been tested is indicated, but more +recent versions of CVS probably will work too. Please send updates to +this list to bug-cvs@prep.ai.mit.edu (doing so in the form of a diff +to this file is encouraged). + +Alpha: + DEC Alpha running OSF/1 version 1.3 using cc (about 1.4A2) + DEC Alpha running OSF/1 version 2.0 (1.4.90) + DEC Alpha running OSF/1 version 2.1 (about 1.4A2) + DEC Alpha running OSF/1 version 3.0 (1.5.95) (footnote 7) + DEC Alpha running OSF/1 version 3.2 (1.7+obvious patch) +HPPA: + HP 9000/710 running HP-UX 8.07A using gcc (about 1.4A2) + HP 9000/715 running HP-UX 9.01 (1.6) + HPPA running HP-UX 10.01 (1.7) + HPPA 1.1 running HP-UX A.09.03 (1.5.95) (footnote 8) + HPPA 1.1 running HP-UX A.09.04 (1.7.1) + NextSTEP 3.3 (1.6.86) +i386 family: + Solaris 2.4 using gcc (about 1.4A2) + UnixWare v1.1.1 using gcc (about 1.4A2) + ISC 4.0.1 (1.5.94) + Linux (kernel 1.2.x) (1.7.1) + BSDI 2.0 (1.4.93) (footnote 5) + FreeBSD 2.0.5, i486, gcc (1.5.95) + NextSTEP 3.3 (1.6.86) + NeXTSTEP 3.3 (1.7), (footnote 10) + SCO Unix 3.2.4.2 (1.4.93) (footnote 4) + SCO OpenServer 5.0.0, "CC='cc -b elf' configure" + Lynx 2.3.0 080695 (1.6.86) (footnote 9) + Windows NT 3.51 (1.7.87 client-only) + QNX 4 (1.7 + obvious patches) + OS/2 Version 3 using IBM C/C++ Tools 2.01 (1.7.86 with patches) +m68k: + Sun 3 running SunOS 4.1.1_U1 w/ bundled K&R /usr/5bin/cc (1.6) + NextSTEP 3.3 (1.6.86) + NeXTSTEP 3.3 (1.7), (footnote 10) + Lynx 2.3.0 062695 (1.6.86) (footnote 9) +m88k: + Data General AViiON running dgux 5.4R2.10 (1.5) + Data General AViiON running dgux 5.4R3.10 (1.7.1) + Harris Nighthawk 5800 running CX/UX 7.1 (1.5) (footnote 6) +MIPS: + DECstation running Ultrix 4.2a (1.4.90) + DECstation running Ultrix 4.3 (1.6.86) + SGI running Irix 4.0.5H using gcc and cc (about 1.4A2) (footnote 2) + SGI running Irix 5.3 (1.7) + SGI running Irix-6 (about 1.4.90) (footnote 3) + Siemens-Nixdorf RM600 running SINIX-Y (1.6) +PowerPC or RS/6000: + IBM RS/6000 running AIX 3.1 using gcc and cc (1.6.86) + IBM RS/6000 running AIX 3.2.5 (1.7.87) + IBM RS/6000 running AIX 4.1 using gcc and cc (about 1.4A2) (footnote 1) + Lynx 2.3.1 120495 (1.6.86) (footnote 9) +SPARC: + Sun SPARC running SunOS 4.1.x (1.6.86) + Sun SPARCstation 10 running Solaris 2.3 using gcc and cc (about 1.4A2) + Sun SPARCstation running Solaris 2.4 using gcc and cc (about 1.5.91) + Sun SPARC running Solaris 2.5 (2.5 beta?) (1.6.4) + NextSTEP 3.3 (1.6.86) + NeXTSTEP 3.3 (1.7), (footnote 10) + +(footnote 1) + AIX 4.1 systems fail to run "configure" due to bugs in their + "/bin/sh" implementation. You might want to try feeding the + configure script to "bash" ported to AIX 4.1. (about 1.4A2). + +(footnote 2) + Some Irix 4.0 systems may core dump in malloc while running + CVS. We believe this is a bug in the Irix malloc. You can + workaround this bug by linking with "-lmalloc" if necessary. + (about 1.4A2). + +(footnote 3) + There are some warnings about pointer casts which can safely be + ignored. (about 1.4.90). + +(footnote 4) Comment out the include of sys/time.h in src/server.c. (1.4.93) + You also may have to make sure TIME_WITH_SYS_TIME is undef'ed. + +(footnote 5) Change /usr/tmp to /var/tmp in src/server.c (2 places) (1.4.93). + +(footnote 6) Build in ucb universe with COFF compiler tools. Put + /usr/local/bin first in PATH while doing a configure, make + and install of GNU diffutils-2.7, rcs-5.7, then cvs-1.5. + +(footnote 7) Manoj Srivastava <srivasta@pilgrim.umass.edu> reports + success with this configure command: + CC=cc CFLAGS='-O2 -Olimit 2000 -std1' ./configure --verbose alpha-dec-osf + +(footnote 8) Manoj Srivastava <srivasta@pilgrim.umass.edu> reports + success with this configure command: + CC=cc CFLAGS='+O2 -Aa -D_HPUX_SOURCE' ./configure --verbose hppa1.1-hp-hpux + +(footnote 9) + Had to configure with ./configure --host=<arch>-lynx. + + In src/cvs.h, protected the waitpid prototype with ifdef _POSIX_SOURCE. + (I might try building with gcc -mposix -D_POSIX_SOURCE.) + + LynxOS has <dirent.h>, but you don't want to use it. + You want to use <sys/dir.h> instead. + So after running configure I had to undef HAVE_DIRENT_H and + define HAVE_SYS_DIR_H. + +(footnote 10) Ralf E. Stranzenbach <ralf@reswi.ruhr.de> + I've made some modifications to "filesubr.c" to deal with NFS + mounted directories (and those funny .nfs* files). This patch + should be used whenever the programmers "sandbox" is located on + a NFS mounted device --- at least on NeXTSTEP. + +------------------------------------------------------------------------------- + +Installation under Unix: + +1) Run "configure": + + $ ./configure + + You can specify an alternate destination to override the default with + the --prefix option: + + $ ./configure --prefix=/usr/local/gnu + + or some path that is more appropriate for your site. The default prefix + value is "/usr/local", with binaries in sub-directory "bin", manual + pages in sub-directory "man", and libraries in sub-directory "lib". + + If you are using server or local CVS, RCS needs to be installed in + the user's PATH (or a path you have configured in src/options.h, + or a path specified with the -b option). If you don't have RCS, + you will need to get it from GNU as well. It is best to get the + version 5.7 (or later) version of RCS, available from + prep.ai.mit.edu in the file pub/gnu/rcs-5.7.tar.gz. + + If you want version control of files with binary data, make sure + that the RCS configure script finds GNU diff 1.15 or later and + notices that diff supports the -a option. CVS itself is much less + picky about which version of diff it uses, and you shouldn't need + to worry about that. + + NOTE: The configure program will cache the results of the previous + configure execution. If you need to re-run configure from scratch, you + may need to run "make distclean" first to remove the cached + configuration information. + + If you are using gcc and are planning to modify CVS, you may want to + configure with -Wall; see the file HACKING for details. + + Try './configure --help' for further information on its usage. + + NOTE ON CVS's USE OF NDBM: + + By default, CVS uses some built-in ndbm emulation code to allow + CVS to work in a heterogeneous environment. However, if you have + a very large modules database, this may not work well. You will + need to edit src/options.h to turn off the MY_NDBM #define and + re-run configure. If you do this, the following comments apply. + If not, you may safely skip these comments. + + If you configure CVS to use the real ndbm(3) libraries and + you do not have them installed in a "normal" place, you will + probably want to get the GNU version of ndbm (gdbm) and install + that before running the CVS configure script. Be aware that the + GDBM 1.5 release does NOT install the <ndbm.h> header file included + with the release automatically. You may have to install it by hand. + + If you configure CVS to use the ndbm(3) libraries, you cannot + compile CVS with GNU cc (gcc) on Sun-4 SPARC systems. However, gcc + 2.0 may have fixed this limitation if -fpcc-struct-return is + defined. When using gcc on other systems to compile CVS, you *may* + need to specify the -fpcc-struct-return option to gcc (you will + *know* you have to if "cvs checkout" core dumps in some ndbm + function). You can do this as follows: + + $ CC='gcc -fpcc-struct-return' ./configure + + for sh, bash, and ksh users and: + + % setenv CC 'gcc -fpcc-struct-return' + % ./configure + + for csh and tcsh users. + + END OF NOTE FOR NDBM GUNK. + +2) Edit src/options.h. Appropriate things to look at may be the + invocation locations of programs like DIFF and GREP. + Also glance at the default values for the environment variables + that CVS uses, in particular, the RCSBIN variable, which holds the + path to where the RCS programs live on your system. + +3) Try to build it: + + $ make + + This will (hopefully) make the needed CVS binaries within the + "src" directory. If something fails for your system, and you want + to submit a bug report, you may wish to include your + "config.status" file, your host type, operating system and + compiler information, make output, and anything else you think + will be helpful. + +3a) Run the regression tests (optional). + + You may also wish to validate the correctness of the new binary by + running the regression tests: + + $ make check + + Note that if your /bin/sh doesn't support shell functions, you'll + have to try something like this, where "/bin/sh5" is replaced by the + pathname of a shell which handles normal shell functions: + + $ make SHELL=/bin/sh5 check + + WARNING: This test can take quite a while to run, esp. if your + disks are slow or over-loaded. + + If you receive any un-expected output from the regression tests, + it may indicate a bug in CVS (or might just indicate a problem + running the tests). If you choose to submit a bug report, + be aware that, as with all bug reports, you may or may not get a + response, and your odds might be better if you include enough information + to reproduce the bug, an analysis of what is going wrong (if you have + the time and ability to provide one), etc. The check.log file is the + first place to look. + +4) Install the binaries/documentation: + + $ make install + + Depending on your installation's configuration, you may need to be + root to do this. + +5) Take a look at the CVS documentation. + + $ man cvs + + and + + $ info cvs + + See what it can do for you, and if it fits your environment (or can + possibly be made to fit your environment). If things look good, + continue on... + +6) Set up the master source repository. See the "Setting up the repository" + section of cvs.texinfo for details; the one-line summary is (if you + are putting the repository in /src/master): + $ cvs -d /src/master init + +7) Have all users of the CVS system set the CVSROOT environment + variable appropriately to reflect the placement of your source + repository. If the above example is used, the following commands + can be placed in user's ~/.profile, ~/.bash_profile file; or in the + site-wide /etc/profile: + + CVSROOT=/src/master; export CVSROOT + + for sh/bash/ksh users, or place the following commands in the user's + ~/.cshrc, ~/.login, or /etc/chsrc file: + + setenv CVSROOT /src/master + + for csh/tcsh users. If these environment variables are not already set + in your current shell, set them now (or source the login script you + just edited). You will need to have the CVSROOT environment variable + set to continue on to the next step. + +8) It might be a good idea to jump right in and put the CVS distribution + directly under CVS control. From within the top-level directory of the + CVS distribution (the one that contains this README file) do the + following commands: + + $ make distclean + $ cvs import -m 'CVS 1.6 distribution' cvs CVS_DIST CVS-1_6 + +9) Having done step 8, one should be able to checkout a fresh copy of the + CVS distribution and hack away at the sources with the following command: + + $ cd + $ cvs checkout cvs + + This will make the directory "cvs" in your current directory and + populate it with the appropriate CVS files and directories. + +10) You may wish to customize the various administrative files, in particular + modules. See cvs.texinfo for details. + +11) Read the NEWS file to see what's new. + +12) Hack away. + +------------------------------------------------------------------------------- + +Detailed information about your interaction with "configure": + +The "configure" script and its interaction with its options and the +environment is described here. For more detailed documentation about +"configure", please refer to the GNU Autoconf documentation. + +Supported options are: + + --srcdir=DIR Useful for compiling on many different + machines sharing one source tree. + --prefix=DIR The root of where to install the + various pieces of CVS (/usr/local). + --exec_prefix=DIR If you want executables in a + host-dependent place and shared + things in a host-independent place. + +The following environment variables override configure's default +behaviour: + + CC If not set, tries to use gcc first, + then cc. Also tries to use "-g -O" + as options, backing down to -g + alone if that doesn't work. + INSTALL If not set, tries to use "install", then + "./install-sh" as a final choice. + RANLIB If not set, tries to determine if "ranlib" + is available, choosing "echo" if it doesn't + appear to be. + YACC If not set, tries to determine if "bison" + is available, choosing "yacc" if it doesn't + appear to be. + +------------------------------------------------------------------------------- +Installation under Windows NT: + +You may find interesting information in windows-NT/README. + +1) Using Microsoft Visual C++ version 2.1, open the project `cvsnt.mak', + in the top directory of the CVS distribution. +2) Choose "Build cvs.exe" from the "Project" menu. +3) MSVC will place the executable file cvs.exe in WinDebug, or whatever + your target directory is. +------------------------------------------------------------------------------- diff --git a/contrib/cvs/MINOR-BUGS b/contrib/cvs/MINOR-BUGS new file mode 100644 index 0000000..7b85719 --- /dev/null +++ b/contrib/cvs/MINOR-BUGS @@ -0,0 +1,60 @@ +Low-priority bugs go here. We don't have many yet -- everything is +high-priority at the moment. :-) + + +* From: Jeff Johnson <jbj@brewster.JBJ.ORG> + To: cyclic-cvs@cyclic.com + Subject: Named_Root assumes . on server + Date: Wed, 17 May 1995 11:04:53 -0400 (EDT) + + Problem: + On server, Name_Root() attempts (aggressively) to set CVSADM_Root. + If ~/CVS/Root exists (wrto rsh login), then CVSADM_Root will be + initialized from that file. The sanity check between the root + repository and the invocation will fail if the two values are not + coincidentally the same. + + Workaround: + There's a zillion ways to fix this bugture/featurelet. My current + workaround is to remove ~/CVS/Root on the server. I shall attempt + a better fix as soon as I can determine what appears politically + correct. IMHO, the CVS/Root stuff (and getenv("CVSROOT") also) is + a bit fragile and tedious in an rcmd() driven CCVS environment. + + +* (Jeff Johnson <jbj@jbj.org>) + I tried a "cvs status -v" and received the following: + + ? CVS + ? programs/CVS + ? tests/CVS + cvs server: Examining . + =================================================================== + File: Install.dec Status: Up-to-date + ... + + I claim that CVS dirs should be ignored. + + +* I sometimes get this message: + + Could not look up address for your host. Permission denied. + cvs [update aborted]: premature end of file from server + + The client's response should be cleaned up. + +* In the gb-grep module, update-ChangeLog (and therefore, I assume, + rcs2log) truncates file names --- I get entries for things called + ring/lenstring.h instead of lenstring/lenstring.h. + +* On remote checkout, files don't have the right time/date stamps in + the CVS/Entries files. Doesn't look like the C/S protocol has any + way to send this information along (according to cvsclient.texi). + Perhaps we can spiff it up a bit by using the conflict field for the + stamp on the checkout/update command. Please note that this really + doesn't do very much for us even if we get it done. + +* Does the function that lists the available modules in the repository + belong under the "checkout" function? Perhaps it is more logically + grouped with the "history" function or we should create a new "info" + function? diff --git a/contrib/cvs/Makefile.in b/contrib/cvs/Makefile.in new file mode 100644 index 0000000..636a0d7 --- /dev/null +++ b/contrib/cvs/Makefile.in @@ -0,0 +1,264 @@ +# Master Makefile for the GNU Concurrent Versions System. +# Copyright (C) 1986, 1988-1992, 1994 Free Software Foundation, Inc. + +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2, or (at your option) +# any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +# $CVSid: @(#)Makefile.in 1.30 94/10/22 $ + +SHELL = /bin/sh + +#### Start of system configuration section. #### + +srcdir = @srcdir@ +top_srcdir = @top_srcdir@ +VPATH = @srcdir@ + +# If you use gcc, you should either run the fixincludes script that +# comes with it or else use gcc with the -traditional option. Otherwise +# ioctl calls will be compiled incorrectly on some systems. +CC = @CC@ +AR = ar + +@SET_MAKE@ + +# Set RANLIB = echo if your system doesn't have or need ranlib. +RANLIB = @RANLIB@ +# Set YACC = bison or yacc, depending on which you have on your system +YACC = @YACC@ +# Use cp if you don't have install. +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ + +DEFS = @DEFS@ +LIBS = @LIBS@ + +INCLUDES = -I. -I../lib @includeopt@ +CFLAGS = @CFLAGS@ +LDFLAGS = @LDFLAGS@ + +MAKEINFO = makeinfo +TEXI2DVI = texi2dvi + +prefix = @prefix@ +exec_prefix = @exec_prefix@ + +# Where to install the executables. +bindir = $(exec_prefix)/bin + +# Where to put the system-wide .cvsrc file +libdir = $(prefix)/lib + +# Where to put the Info files +infodir = $(prefix)/info + +# Where to put the manual pages. +mandir = $(prefix)/man + +#### End of system configuration section. #### + +FLAGS_TO_PASS = \ + AR='$(AR)' \ + CC='$(CC)' \ + CFLAGS='$(CFLAGS)' \ + LDFLAGS='$(LDFLAGS)' \ + LIBPROGS='$(LIBPROGS)' \ + LIBS='$(LIBS)' \ + MAKE='$(MAKE)' \ + MAKEINFO='$(MAKEINFO)' \ + RANLIB='$(RANLIB)' \ + TEXI2DVI='$(TEXI2DVI)' \ + YACC='$(YACC)' \ + bindir='$(bindir)' \ + infodir='$(infodir)' \ + libdir='$(libdir)' \ + mandir='$(mandir)' \ + prefix='$(prefix)' \ + exec_prefix='$(exec_prefix)' + +DISTFILES = \ + COPYING COPYING.LIB INSTALL README TODO PROJECTS \ + BUGS MINOR-BUGS FAQ HACKING \ + ChangeLog NEWS ChangeLog.zoo \ + configure configure.in stamp-h.in config.h.in Makefile.in acconfig.h \ + cvs-format.el mkinstalldirs install-sh \ + cvsnt.mak \ + .cvsignore + +### Subdirectories to run make in for the primary targets. +# Unix source subdirs, where we'll want to run lint and etags: +USOURCE_SUBDIRS = lib src +# All other subdirs: +SUBDIRS = ${USOURCE_SUBDIRS} man doc contrib tools windows-NT os2 macintosh +# Only make TAGS/tags files in these directories, in this order +# [Why in this order? If we didn't have to stick to this order, we +# could make "TSUBDIRS = ${USOURCE_SUBDIRS}". -Karl] +TSUBDIRS= src lib + +# Set default target. +all: + +.PHONY: all install uninstall +all install uninstall: config.h Makefile all-local + @for subdir in $(SUBDIRS); do \ + echo "making $@ in $$subdir"; \ + ( cd $$subdir && $(MAKE) $(FLAGS_TO_PASS) $@ ) || exit 1; \ + done + +install: all install-local install-info + +.PHONY: all-local +all-local: + +.PHONY: info dvi clean-info install-info +info dvi clean-info install-info: + cd doc && $(MAKE) $(FLAGS_TO_PASS) $@ || exit 1 + +.PHONY: install-local +install-local: all-local + @: nothing to do locally + +.PHONY: tags +tags: + @for dir in $(TSUBDIRS); do echo making $@ in $$dir; cd $$dir && $(MAKE) $(FLAGS_TO_PASS) $@ || exit 1; cd ..; done + @echo making $@ in . + @ctags `for i in \`$(MAKE) SUBDIRS="$(TSUBDIRS)" ls\` ; do echo $(srcdir)/$$i ; done` + +.PHONY: TAGS +TAGS: + @for dir in $(TSUBDIRS); do echo making $@ in $$dir; cd $$dir && $(MAKE) $(FLAGS_TO_PASS) $@ || exit 1; cd ..; done + @echo making $@ in . + @etags `for i in \`$(MAKE) SUBDIRS="$(TSUBDIRS)" ls | grep -v 'make\[[0-9]\]'\` ; do echo $(srcdir)/$$i ; done` + +.PHONY: ls +ls: + @echo $(DISTFILES) + @for dir in $(SUBDIRS); do \ + for i in `cd $$dir && $(MAKE) $(FLAGS_TO_PASS) $@ | grep -v 'make\[[0-9]\]'` ; do \ + echo $$dir/$$i ; \ + done ; \ + done + +.PHONY: clean +clean: clean-local + @for dir in $(SUBDIRS); do echo making $@ in $$dir; cd $$dir && $(MAKE) $(FLAGS_TO_PASS) $@ || exit 1; cd ..; done + +.PHONY: distclean +distclean: distclean-local + @for dir in $(SUBDIRS); do echo making $@ in $$dir; cd $$dir && $(MAKE) $(FLAGS_TO_PASS) $@ || exit 1; cd ..; done + rm -f config.status + +.PHONY: realclean +realclean: realclean-local + @for dir in $(SUBDIRS); do echo making $@ in $$dir; cd $$dir && $(MAKE) $(FLAGS_TO_PASS) $@ || exit 1; cd ..; done + rm -f config.status + +.PHONY: mostlyclean-local +mostlyclean-local: + rm -f *~ + +.PHONY: clean-local +clean-local: mostlyclean-local + +.PHONY: distclean-local +distclean-local: clean-local + rm -f Makefile config.cache config.h config.log stamp-h + rm -f tags TAGS + +.PHONY: realclean-local +realclean-local: distclean-local + +.PHONY: saber +saber: + @for dir in $(SUBDIRS); do cd $$dir && $(MAKE) $(FLAGS_TO_PASS) $@ || exit 1; cd ..; done + +.PHONY: check +check: + cd lib ; $(MAKE) $(FLAGS_TO_PASS) + cd src ; $(MAKE) $(FLAGS_TO_PASS) check + +.PHONY: remotecheck +remotecheck: + cd lib ; $(MAKE) $(FLAGS_TO_PASS) + cd src ; $(MAKE) $(FLAGS_TO_PASS) remotecheck + +.PHONY: installcheck +installcheck: + cd lib ; $(MAKE) $(FLAGS_TO_PASS) + cd src ; $(MAKE) $(FLAGS_TO_PASS) installcheck + +.PHONY: lint +lint: + @for dir in $(USOURCE_SUBDIRS); do cd $$dir && $(MAKE) $(FLAGS_TO_PASS) xlint || exit 1; cd ..; done + +.PHONY: dist +GZIP=gzip --best +GZIP_EXT=.gz +TAR_VERBOSE= +dist: + echo > .fname \ + cvs-`sed < $(srcdir)/src/version.c \ + -e '/version_string/!d' \ + -e 's/[^0-9.]*\([0-9.]*\).*/\1/' \ + -e q` + rm -rf `cat .fname` + ${MAKE} dist-dir DISTDIR="`cat .fname`" + for dir in ${SUBDIRS}; do \ + ( DISTDIR="../`cat .fname`/$${dir}"; \ + cd $${dir} && \ + ${MAKE} dist-dir DISTDIR="$${DISTDIR}" \ + ); \ + done + tar chf${TAR_VERBOSE} - `cat .fname` | ${GZIP} > "`cat .fname`.tar${GZIP_EXT}" + rm -rf `cat .fname` .fname + +.PHONY: dist-dir +dist-dir: + mkdir ${DISTDIR} + for i in ${DISTFILES}; do \ + ln $(srcdir)/$${i} ${DISTDIR}; \ + done + +# For the justification of the following Makefile rules, see node +# `Automatic Remaking' in GNU Autoconf documentation. +Makefile: Makefile.in config.status + CONFIG_FILES=$@ CONFIG_HEADERS= ./config.status +config.status: configure + ./config.status --recheck +# The rules to run autoconf and autoheader are commented out. This is because +# when the user unpacks a tarfile, configure.in might end up newer than +# configure, but the user might not have (and does not need to have) autoconf +# installed. +#configure: configure.in #aclocal.m4 +# cd $(srcdir); autoconf + +config.h: stamp-h + +# This used to do a ./config.status --recheck, to update config.status with +# any new #defines from config.h.in. The problem was that the rule itself +# depends on config.status, so that the --recheck would get run several +# times, which is bad if the user was trying to specify CFLAGS manually. +# It was a big pain in the neck. +stamp-h: config.h.in config.status + CONFIG_FILES=$@ CONFIG_HEADERS=config.h ./config.status + +#config.h.in: stamp-h.in +#stamp-h.in: configure.in #aclocal.m4 acconfig.h +# cd $(srcdir); autoheader +# date > $(srcdir)/stamp-h.in + +# Tell versions [3.59,3.63) of GNU make not to export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/contrib/cvs/NEWS b/contrib/cvs/NEWS new file mode 100644 index 0000000..81d82ac --- /dev/null +++ b/contrib/cvs/NEWS @@ -0,0 +1,907 @@ +Changes since 1.7: + +* New "cvs annotate" command to display the last modification for each +line of a file, with the revision number, user checking in the +modification, and date of the modification. For more information see +the `annotate' node in cvs.texinfo. + +* The cvsinit shell script has been replaced by a cvs init command. +The cvs init command creates some example administrative files which +are similar to the files found in the examples directory (and copied +by cvsinit) in previous releases. + +* Added the patterns *.olb *.exe _$* *$ to default ignore list. + +* There is now a $USER internal variable for *info files. + +* There is no longer a separate `mkmodules' program; the functionality +is now built into `cvs'. If upgrading an old repository, it is OK to +leave in the lines in the modules file which run mkmodules (the +mkmodules actions will get done twice, but that is harmless); you will +probably want to remove them once you are no longer using the old CVS. + +* One can now specify user variables in *info files via the +${=varname} syntax; there is a -s global option to set them. See the +Variables node in cvs.texinfo for details. + +Changes from 1.6 to 1.7: + +* The default ignore list has changed slightly: *.obj has been added +and CVS* has been changed to CVS CVS.adm. + +* CVS now supports password authentication when accessing remote +repositories; this is useful for sites that can't use rsh (because of +a firewall, for example), and also don't have kerberos. See node +"Password authenticated" (in "Remote repositories", in +doc/cvs.texinfo) for more details. Note: This feature requires both +the client and server to be upgraded. + +* Using the -kb option to specify binary files now works--most cases +did not work before. See the "Binary files" section of +doc/cvs.texinfo for details. + +* New developer communication features. See the "Watches" section of +doc/cvs.texinfo for details. + +* RCS keyword "Name" supported for "cvs update -r <tag>" and "cvs +checkout -r <tag>". + +* If there is a group whose name matches a compiled in value which +defaults to "cvsadmin", only members of that group can use "cvs +admin". This replaces the CVS_NOADMIN option. + +* CVS now sets the modes of files in the repository based on the +CVSUMASK environment variable or a compiled in value defaulting to +002. This way other developers will be able to access the files in +the repository regardless of the umask of the developer creating them. + +* The command names in .cvsrc now match the official name of the +command, not the one (possibly an alias) by which it was invoked. If +you had previously relied on "cvs di" and "cvs diff" using different +options, instead use a shell function or alias (for example "alias +cvsdi='cvs diff -u'"). You also can specify global CVS options (like +"-z") using the command name "cvs". + +Changes from 1.5 to 1.6: + +* Del updated the man page to include all of the new features +of CVS 1.6. + +* "cvs tag" now supports a "-r | -D" option for tagging an already +tagged revision / specific revision of a file. + +* There is a "taginfo" file in CVSROOT that supports filtering and +recording of tag operations. + +* Long options support added, including --help and --version options. + +* "cvs release" no longer cares whether or not the directory being +released has an entry in the `modules' file. + +* The modules file now takes a -e option which is used instead of -o +for "cvs export". If your modules file has a -o option which you want +to be used for "cvs export", change it to specify -e as well as -o. + +* "cvs export" now takes a -k option to set RCS keyword expansion. +This way you can export binary files. If you want the old behavior, +you need to specify -kv. + +* "cvs update", "cvs rdiff", "cvs checkout", "cvs import", "cvs +release", "cvs rtag", and "cvs tag" used to take -q and -Q options +after the command name (e.g. "cvs update -q"). This was confusing +because other commands, such as "cvs ci", did not. So the options +after the command name have been removed and you must now specify, for +example, "cvs -q update", which has been supported since CVS 1.3. + +* New "wrappers" feature. This allows you to set a hook which +transforms files on their way in and out of cvs (apparently on the +NeXT there is some particular usefulness in tarring things up in the +repository). It also allows you to declare files as merge-by-copy +which means that instead of trying to merge the file, CVS will merely +copy the new version. There is a CVSROOT/cvswrappers file and an +optionsl ~/.cvswrappers file to support this feature. + +* You can set CVSROOT to user@host:dir, not just host:dir, if your +username on the server host is different than on the client host. + +* VISUAL is accepted as well as EDITOR. + +* $CVSROOT is expanded in *info files. + +Changes from 1.4A2 to 1.5: + +* Remote implementation. This is very helpful when collaborating on a +project with someone across a wide-area network. This release can +also be used locally, like other CVS versions, if you have no need for +remote access. + +Here are some of the features of the remote implementation: +- It uses reliable transport protocols (TCP/IP) for remote repository + access, not NFS. NFS is unusable over long distances (and sometimes + over short distances) +- It transfers only those files that have changed in the repository or + the working directory. To save transmission time, it will transfer + patches when appropriate, and can compress data for transmission. +- The server never holds CVS locks while waiting for a reply from the client; + this makes the system robust when used over flaky networks. + +The remote features are documented in doc/cvsclient.texi in the CVS +distribution, but the main doc file, cvs.texinfo, has not yet been +updated to include the remote features. + +* Death support. See src/README-rm-add for more information on this. + +* Many speedups, especially from jtc@cygnus.com. + +* CVS 1.2 compatibility code has been removed as a speedup. If you +have working directories checked out by CVS 1.2, CVS 1.3 or 1.4A2 will +try to convert them, but CVS 1.5 and later will not (if the working +directory is up to date and contains no extraneous files, you can just +remove it, and then check out a new working directory). Likewise if +your repository contains a CVSROOT.adm directory instead of a CVSROOT +directory, you need to rename it. + +Fri Oct 21 20:58:54 1994 Brian Berliner <berliner@sun.com> + + * Changes between CVS 1.3 and CVS 1.4 Alpha-2 + + * A new program, "cvsbug", is provided to let you send bug reports + directly to the CVS maintainers. Please use it instead of sending + mail to the info-cvs mailing list. If your build fails, you may + have to invoke "cvsbug" directly from the "src" directory as + "src/cvsbug.sh". + + * A new User's Guide and Tutorial, written by Per Cederqvist + <ceder@signum.se> of Signum Support. See the "doc" directory. A + PostScript version is included as "doc/cvs.ps". + + * The Frequesntly Asked Questions file, FAQ, has been added to the + release. Unfortunately, its contents are likely out-of-date. + + * The "cvsinit" shell script is now installed in the $prefix/bin + directory like the other programs. You can now create new + CVS repositories with great ease. + + * Index: lines are now printed on output from 'diff' and 'rdiff', + in order to facilitate application of patches to multiple subdirs. + + * Support for a ~/.cvsrc file, which allows you to specify options + that are always supposed to be given to a specific command. This + feature shows the non-orthogonality of the option set, since while + there may be an option to turn something on, the option to turn + that same thing off may not exist. + + * You can now list subdirectories that you wish to ignore in a + modules listing, such as: + + gcc -a gnu/gcc, !gnu/gcc/testsuites + + which will check out everything underneath gnu/gcc, except + everything underneath gnu/gcc/testsuites. + + * It is now much harder to accidentally overwrite an existing tag + name, since attempting to move a tag name will result in a error, + unless the -F (force) flag is given to the tag subcommands. + + * Better error checking on matching of the repository used to + check code out from against the repository the current cvs + commnands would use. (Thanks to Mark Baushke <mdb@cisco.com>) + + * Better support for sites with multiple CVSROOT repositories has + been contributed. The file "CVS/Root" in your working directory + is created to hold the full path to the CVS repository and a + simple check is made against your current CVSROOT setting. + + * You can now specify an RCS keyword substitution value when you + import files into the repository. + + * Uses a much newer version of Autoconf, and conforms to the GNU + coding standards much more closely. No, it still doesn't have + long option names. + + * Code cleanup. Many passes through gcc -Wall helped to identify + a number of questionable constructs. Most arbitrary length limits + were removed. + + * Profiling to determine bottlenecks helped to identify the best + places to spend time speeding up the code, which was then done. A + number of performance enhancements in filename matching have sped + up checkouts. + + * Many more contributions have been added to the "contrib" + directory. See the README file in that directory for more + information. + + * "cvs commit" will try harder to not change the file's + modification time after the commit. If the file does not change + as a result of the commit operation, CVS will preserve the + original modification time, thus speeding up future make-type + builds. + + * "cvs commit" now includes any removed files in the (optional) + pre-commit checking program that may be invoked. Previously, only + added and modified files were included. + + * It is now possible to commit a file directly onto the trunk at a + specific revision level by doing "cvs commit -r3.0 file.c", where + "3.0" specifies the revision you wish to create. The file must be + up-to-date with the current head of the trunk for this to succeed. + + * "cvs commit" will now function with a pre-commit program that + has arguments specified in the "commitinfo" file. + + * The "mkmodules" program will now look within the + $CVSROOT/CVSROOT/checkoutlist" file for any additional files that + should be automatically checked out within CVSROOT; mkmodules also + tries harder to preserve any execute bits the files may have + originally had. + + * "cvs diff" is much more accurate about its exit status now. It + now returns the maximum exit status of any invoked diff. + + * The "-I !" option is now supported for the import and update + commands correctly. It will properly clear the ignore list now. + + * Some problems with "cvs import" handling of .cvsignore have been + fixed; as well, some rampant recursion problems with import have + also been fixed. + + * "cvs rdiff" (aka "cvs patch") now tries to set the modify time + of any temporary files it uses to match those specified for the + particular revision. This allows a more accurate patch image to + be created. + + * "cvs status" has improved revision descriptions. "Working + revision" is used for the revision of the working file that you + edit directly; "Repository revision" is the revision of the file + with the $CVSROOT source repository. Also, the output is clearer + with regard to sticky and branch revisions. + + * CVS no longer dumps core when given a mixture of directories and + files in sub-directories (as in "cvs ci file1 dir1/file2"). + Instead, arguments are now clumped into their respective directory + and operated on in chunks, together. + + * If the CVSEDITOR environment variable is set, that editor is + used for log messages instead of the EDITOR environment variable. + This makes it easy to substitute intelligent programs to make more + elaborate log messages. Contributed by Mark D Baushke + (mdb@cisco.com). + + * Command argument changes: + cvs: The "-f" option has been added to ignore + the ~/.cvsrc file. + commit: Renamed the "-f logfile" option to the + "-F logfile" option. Added the "-f" + option to force a commit of the specified + files (this disables recursion). + history: Added "-t timezone" option to force any + date-specific output into the specified + timezone. + import: Added "-d" option to use the file's + modification time as the time of the + import. Added "-k sub" option to set the + default RCS keyword substitution mode for + newly-created files. + remove: Added "-f" option to force the file's + automatic removal if it still exists in + the working directory (use with caution). + rtag: Added "-F" option to move the tag if it + already exists -- new default is to NOT + move tags automatically. + tag: Added "-F" option to move the tag if it + already exists -- new default is to NOT + move tags automatically. + +Tue Apr 7 15:55:25 1992 Brian Berliner (berliner at sun.com) + + * Changes between CVS 1.3 Beta-3 and official CVS 1.3! + + * A new shell script is provided, "./cvsinit", which can be run at + install time to help setup your $CVSROOT area. This can greatly + ease your entry into CVS usage. + + * The INSTALL file has been updated to include the machines on + which CVS has compiled successfully. I think CVS 1.3 is finally + portable. Thanks to all the Beta testers! + + * Support for the "editinfo" file was contributed. This file + (located in $CVSROOT/CVSROOT) can be used to specify a special + "editor" to run on a per-directory basis within the repository, + instead of the usual user's editor. As such, it can verify that + the log message entered by the user is of the appropriate form + (contains a bugid and test validation, for example). + + * The manual pages cvs(1) and cvs(5) have been updated. + + * The "mkmodules" command now informs you when your modules file + has duplicate entries. + + * The "add" command now preserves any per-directory sticky tag when + you add a new directory to your checked-out sources. + + * The "admin" command is now a fully recursive interface to the + "rcs" program which operates on your checked-out sources. It no + longer requires you to specify the full path to the RCS file. + + * The per-file sticky tags can now be effectively removed with + "cvs update -A file", even if you had checked out the whole + directory with a per-directory sticky tag. This allows a great + deal of flexibility in managing the revisions that your checked-out + sources are based upon (both per-directory and per-file sticky + tags). + + * The "cvs -n commit" command now works, to show which files are + out-of-date and will cause the real commit to fail, or which files + will fail any pre-commit checks. Also, the "cvs -n import ..." + command will now show you what it would've done without actually + doing it. + + * Doing "cvs commit modules" to checkin the modules file will no + properly run the "mkmodules" program (assuming you have setup your + $CVSROOT/CVSROOT/modules file to do so). + + * The -t option in the modules file (which specifies a program to + run when you do a "cvs rtag" operation on a module) now gets the + symbolic tag as the second argument when invoked. + + * When the source repository is locked by another user, that user's + login name will be displayed as the holder of the lock. + + * Doing "cvs checkout module/file.c" now works even if + module/file.c is in the Attic (has been removed from main-line + development). + + * Doing "cvs commit */Makefile" now works as one would expect. + Rather than trying to commit everything recursively, it will now + commit just the files specified. + + * The "cvs remove" command is now fully recursive. To schedule a + file for removal, all you have to do is "rm file" and "cvs rm". + With no arguments, "cvs rm" will schedule all files that have been + physically removed for removal from the source repository at the + next "cvs commit". + + * The "cvs tag" command now prints "T file" for each file that was + tagged by this invocation and "D file" for each file that had the + tag removed (as with "cvs tag -d"). + + * The -a option has been added to "cvs rtag" to force it to clean + up any old, matching tags for files that have been removed (in the + Attic) that may not have been touched by this tag operation. This + can help keep a consistent view with your tag, even if you re-use + it frequently. + +Sat Feb 29 16:02:05 1992 Brian Berliner (berliner at sun.com) + + * Changes between CVS 1.3 Beta-2 and CVS 1.3 Beta-3 + + * Many portability fixes, thanks to all the Beta testers! With any + luck, this Beta release will compile correctly on most anything. + Hey, what are we without our dreams. + + * CVS finally has support for doing isolated development on a + branch off the current (or previous!) revisions. This is also + extremely nice for generating patches for previously released + software while development is progressing on the next release. + Here's an example of creating a branch to fix a patch with the 2.0 + version of the "foo" module, even though we are already well into + the 3.0 release. Do: + + % cvs rtag -b -rFOO_2_0 FOO_2_0_Patch foo + % cvs checkout -rFOO_2_0_Patch foo + % cd foo + [[ hack away ]] + % cvs commit + + A physical branch will be created in the RCS file only when you + actually commit the change. As such, forking development at some + random point in time is extremely light-weight -- requiring just a + symbolic tag in each file until a commit is done. To fork + development at the currently checked out sources, do: + + % cvs tag -b Personal_Hack + % cvs update -rPersonal_Hack + [[ hack away ]] + % cvs commit + + Now, if you decide you want the changes made in the Personal_Hack + branch to be merged in with other changes made in the main-line + development, you could do: + + % cvs commit # to make Personal_Hack complete + % cvs update -A # to update sources to main-line + % cvs update -jPersonal_Hack # to merge Personal_Hack + + to update your checked-out sources, or: + + % cvs checkout -jPersonal_Hack module + + to checkout a fresh copy. + + To support this notion of forked development, CVS reserves + all even-numbered branches for its own use. In addition, CVS + reserves the ".0" and ".1" branches. So, if you intend to do your + own branches by hand with RCS, you should use odd-numbered branches + starting with ".3", as in "1.1.3", "1.1.5", 1.2.9", .... + + * The "cvs commit" command now supports a fully functional -r + option, allowing you to commit your changes to a specific numeric + revision or symbolic tag with full consistency checks. Numeric + tags are useful for bringing your sources all up to some revision + level: + + % cvs commit -r2.0 + + For symbolic tags, you can only commit to a tag that references a + branch in the RCS file. One created by "cvs rtag -b" or from + "cvs tag -b" is appropriate (see below). + + * Roland Pesch <pesch@cygnus.com> and K. Richard Pixley + <rich@cygnus.com> were kind enough to contribute two new manual + pages for CVS: cvs(1) and cvs(5). Most of the new CVS 1.3 features + are now documented, with the exception of the new branch support + added to commit/rtag/tag/checkout/update. + + * The -j options of checkout/update have been added. The "cvs join" + command has been removed. + + With one -j option, CVS will merge the changes made between the + resulting revision and the revision that it is based on (e.g., if + the tag refers to a branch, CVS will merge all changes made in + that branch into your working file). + + With two -j options, CVS will merge in the changes between the two + respective revisions. This can be used to "remove" a certain delta + from your working file. E.g., If the file foo.c is based on + revision 1.6 and I want to remove the changes made between 1.3 and + 1.5, I might do: + + % cvs update -j1.5 -j1.3 foo.c # note the order... + + In addition, each -j option can contain on optional date + specification which, when used with branches, can limit the chosen + revision to one within a specific date. An optional date is + specified by adding a colon (:) to the tag, as in: + + -jSymbolic_Tag:Date_Specifier + + An example might be what "cvs import" tells you to do when you have + just imported sources that have conflicts with local changes: + + % cvs checkout -jTAG:yesterday -jTAG module + + which tells CVS to merge in the changes made to the branch + specified by TAG in the last 24 hours. If this is not what is + intended, substitute "yesterday" for whatever format of date that + is appropriate, like: + + % cvs checkout -jTAG:'1 week ago' -jTAG module + + * "cvs diff" now supports the special tags "BASE" and "HEAD". So, + the command: + + % cvs diff -u -rBASE -rHEAD + + will effectively show the changes made by others (in unidiff + format) that will be merged into your working sources with your + next "cvs update" command. "-rBASE" resolves to the revision that + your working file is based on. "-rHEAD" resolves to the current + head of the branch or trunk that you are working on. + + * The -P option of "cvs checkout" now means to Prune empty + directories, as with "update". The default is to not remove empty + directories. However, if you do "checkout" with any -r options, -P + will be implied. I.e., checking out with a tag will cause empty + directories to be pruned automatically. + + * The new file INSTALL describes how to install CVS, including + detailed descriptions of interfaces to "configure". + + * The example loginfo file in examples/loginfo has been updated to + use the perl script included in contrib/log.pl. The nice thing + about this log program is that it records the revision numbers of + your change in the log message. + + Example files for commitinfo and rcsinfo are now included in the + examples directory. + + * All "#if defined(__STDC__) && __STDC__ == 1" lines have been + changed to be "#if __STDC__" to fix some problems with the former. + + * The lib/regex.[ch] files have been updated to the 1.3 release of + the GNU regex package. + + * The ndbm emulation routines included with CVS 1.3 Beta-2 in the + src/ndbm.[ch] files has been moved into the src/myndbm.[ch] files + to avoid any conflict with the system <ndbm.h> header file. If + you had a previous CVS 1.3 Beta release, you will want to "cvs + remove ndbm.[ch]" form your copy of CVS as well. + + * "cvs add" and "cvs remove" are a bit more verbose, telling you + what to do to add/remove your file permanently. + + * We no longer mess with /dev/tty in "commit" and "add". + + * More things are quiet with the -Q option set. + + * New src/config.h option: If CVS_BADROOT is set, CVS will not + allow people really logged in as "root" to commit changes. + + * "cvs diff" exits with a status of 0 if there were no diffs, 1 if + there were diffs, and 2 if there were errors. + + * "cvs -n diff" is now supported so that you can still run diffs + even while in the middle of committing files. + + * Handling of the CVS/Entries file is now much more robust. + + * The default file ignore list now includes "*.so". + + * "cvs import" did not expand '@' in the log message correctly. It + does now. Also, import now uses the ignore file facility + correctly. + + Import will now tell you whether there were conflicts that need to + be resolved, and how to resolve them. + + * "cvs log" has been changed so that you can "log" things that are + not a part of the current release (in the Attic). + + * If you don't change the editor message on commit, CVS now prompts + you with the choice: + + !)reuse this message unchanged for remaining dirs + + which allows you to tell CVS that you have no intention of changing + the log message for the remainder of the commit. + + * It is no longer necessary to have CVSROOT set if you are using + the -H option to get Usage information on the commands. + + * Command argument changes: + checkout: -P handling changed as described above. + New -j option (up to 2 can be specified) + for doing rcsmerge kind of things on + checkout. + commit: -r option now supports committing to a + numeric or symbolic tags, with some + restrictions. Full consistency checks will + be done. + Added "-f logfile" option, which tells + commit to glean the log message from the + specified file, rather than invoking the + editor. + rtag: Added -b option to create a branch tag, + useful for creating a patch for a previous + release, or for forking development. + tag: Added -b option to create a branch tag, + useful for creating a patch for a previous + release, or for forking development. + update: New -j option (up to 2 can be specified) + for doing rcsmerge kind of things on + update. + +Thu Jan 9 10:51:35 MST 1992 Jeff Polk (polk at BSDI.COM) + + * Changes between CVS 1.3 Beta-1 and CVS 1.3 Beta-2 + + * Thanks to K. Richard Pixley at Cygnus we now have function + prototypes in all the files + + * Some small changes to configure for portability. There have + been other portability problems submitted that have not been fixed + (Brian will be working on those). Additionally all __STDC__ + tests have been modified to check __STDC__ against the constant 1 + (this is what the Second edition of K&R says must be true). + + * Lots of additional error checking for forked processes (run_exec) + (thanks again to K. Richard Pixley) + + * Lots of miscellaneous bug fixes - including but certainly not + limited to: + various commit core dumps + various update core dumps + bogus results from status with numeric sticky tags + commitprog used freed memory + Entries file corruption caused by No_Difference + commit to revision broken (now works if branch exists) + ignore file processing broken for * and ! + ignore processing didn't handle memory reasonably + miscellaneous bugs in the recursion processor + file descriptor leak in ParseInfo + CVSROOT.adm->CVSROOT rename bug + lots of lint fixes + + * Reformatted all the code in src (with GNU indent) and then + went back and fixed prototypes, etc since indent gets confused. The + rationale is that it is better to do it sooner than later and now + everything is consistent and will hopefully stay that way. + The basic options to indent were: "-bad -bbb -bap -cdb -d0 -bl -bli0 + -nce -pcs -cs -cli4 -di1 -nbc -psl -lp -i4 -ip4 -c41" and then + miscellaneous formatting fixes were applied. Note also that the + "-nfc1" or "-nfca" may be appropriate in files where comments have + been carefully formatted (e.g, modules.c). + +Sat Dec 14 20:35:22 1991 Brian Berliner (berliner at sun.com) + + * Changes between CVS 1.2 and CVS 1.3 Beta are described here. + + * Lots of portability work. CVS now uses the GNU "configure" + script to dynamically determine the features provided by your + system. It probably is not foolproof, but it is better than + nothing. Please let me know of any portability problems. Some + file names were changed to fit within 14-characters. + + * CVS has a new RCS parser that is much more flexible and + extensible. It should read all known RCS ",v" format files. + + * Most of the commands now are fully recursive, rather than just + operating on the current directory alone. This includes "commit", + which makes it real easy to do an "atomic" commit of all the + changes made to a CVS hierarchy of sources. Most of the commands + also correctly handle file names that are in directories other than + ".", including absolute path names. Commands now accept the "-R" + option to force recursion on (though it is always the default now) + and the "-l" option to force recursion off, doing just "." and not + any sub-directories. + + * CVS supports many of the features provided with the RCS 5.x + distribution - including the new "-k" keyword expansion options. I + recommend using RCS 5.x (5.6 is the current official RCS version) + and GNU diff 1.15 (or later) distributions with CVS. + + * Checking out files with symbolic tags/dates is now "sticky", in + that CVS remembers the tag/date used for each file (and directory) + and will use that tag/date automatically on the next "update" call. + This stickyness also holds for files checked out with the the new + RCS 5.x "-k" options. + + * The "cvs diff" command now recognizes all of the rcsdiff 5.x + options. Unidiff format is available by installing the GNU + diff 1.15 distribution. + + * The old "CVS.adm" directories created on checkout are now called + "CVS" directories, to look more like "RCS" and "SCCS". Old CVS.adm + directories are automagically converted to CVS directories. The + old "CVSROOT.adm" directory within the source repository is + automagically changed into a "CVSROOT" directory as well. + + * Symbolic links in the source repository are fully supported ONLY + if you use RCS 5.6 or later and (of course) your system supports + symlinks. + + * A history database has been contributed which maintains the + history of certain CVS operations, as well as providing a wide array + of querying options. + + * The "cvs" program has a "-n" option which can be used with the + "update" command to show what would be updated without actually + doing the update, like: "cvs -n update". All usage statements + have been cleaned up and made more verbose. + + * The module database parsing has been rewritten. The new format + is compatible with the old format, but with much more + functionality. It allows modules to be created that grab pieces or + whole directories from various different parts of your source + repository. Module-relative specifications are also correctly + recognized now, like "cvs checkout module/file.c". + + * A configurable template can be specified such that on a "commit", + certain directories can supply a template that the user must fill + before completing the commit operation. + + * A configurable pre-commit checking program can be specified which + will run to verify that a "commit" can happen. This feature can be + used to restrict certain users from changing certain pieces of the + source repository, or denying commits to the entire source + repository. + + * The new "cvs export" command is much like "checkout", but + establishes defaults suitable for exporting code to others (expands + out keywords, forces the use of a symbolic tag, and does not create + "CVS" directories within the checked out sources. + + * The new "cvs import" command replaces the deprecated "checkin" + shell script and is used to import sources into CVS control. It is + also much faster for the first-time import. Some algorithmic + improvements have also been made to reduce the number of + conflicting files on next-time imports. + + * The new "cvs admin" command is basically an interface to the + "rcs" program. (Not yet implemented very well). + + * Signal handling (on systems with BSD or POSIX signals) is much + improved. Interrupting CVS now works with a single interrupt! + + * CVS now invokes RCS commands by direct fork/exec rather than + calling system(3). This improves performance by removing a call to + the shell to parse the arguments. + + * Support for the .cvsignore file has been contributed. CVS will + now show "unknown" files as "? filename" as the result of an "update" + command. The .cvsignore file can be used to add files to the + current list of ignored files so that they won't show up as unknown. + + * Command argument changes: + cvs: Added -l to turn off history logging. + Added -n to show what would be done without actually + doing anything. + Added -q/-Q for quiet and really quiet settings. + Added -t to show debugging trace. + add: Added -k to allow RCS 5.x -k options to be specified. + admin: New command; an interface to rcs(1). + checkout: Added -A to reset sticky tags/date/options. + Added -N to not shorten module paths. + Added -R option to force recursion. + Changed -p (prune empty directories) to -P option. + Changed -f option; forcing tags match is now default. + Added -p option to checkout module to standard output. + Added -s option to cat the modules db with status. + Added -d option to checkout in the specified directory. + Added -k option to use RCS 5.x -k support. + commit: Removed -a option; use -l instead. + Removed -f option. + Added -l option to disable recursion. + Added -R option to force recursion. + If no files specified, commit is recursive. + diff: Now recognizes all RCS 5.x rcsdiff options. + Added -l option to disable recursion. + Added -R option to force recursion. + history: New command; displays info about CVS usage. + import: Replaces "checkin" shell script; imports sources + under CVS control. Ignores files on the ignore + list (see -I option or .cvsignore description above). + export: New command; like "checkout", but w/special options + turned on by default to facilitate exporting sources. + join: Added -B option to join from base of the branch; + join now defaults to only joining with the top two + revisions on the branch. + Added -k option for RCS 5.x -k support. + log: Supports all RCS 5.x options. + Added -l option to disable recursion. + Added -R option to force recursion. + patch: Changed -f option; forcing tags match is now default. + Added -c option to force context-style diffs. + Added -u option to support unidiff-style diffs. + Added -V option to support RCS specific-version + keyword expansion formats. + Added -R option to force recursion. + remove: No option changes. It's a bit more verbose. + rtag: Equivalent to the old "cvs tag" command. + No option changes. It's a lot faster for re-tag. + status: New output formats with more information. + Added -l option to disable recursion. + Added -R option to force recursion. + Added -v option to show symbolic tags for files. + tag: Functionality changed to tag checked out files + rather than modules; use "rtag" command to get the + old "cvs tag" behaviour. + update: Added -A to reset sticky tags/date/options. + Changed -p (prune empty directories) to -P option. + Changed -f option; forcing tags match is now default. + Added -p option to checkout module to standard output. + Added -I option to add files to the ignore list. + Added -R option to force recursion. + + Major Contributors: + + * Jeff Polk <polk@bsdi.com> rewrote most of the grody code of CVS + 1.2. He made just about everything dynamic (by using malloc), + added a generic hashed list manager, re-wrote the modules database + parsing in a compatible - but extended way, generalized directory + hierarchy recursion for virtually all the commands (including + commit!), generalized the loginfo file to be used for pre-commit + checks and commit templates, wrote a new and flexible RCS parser, + fixed an uncountable number of bugs, and helped in the design of + future CVS features. If there's anything gross left in CVS, it's + probably my fault! + + * David G. Grubbs <dgg@odi.com> contributed the CVS "history" and + "release" commands. As well as the ever-so-useful "-n" option of + CVS which tells CVS to show what it would do, without actually + doing it. He also contributed support for the .cvsignore file. + + * Paul Sander, HaL Computer Systems, Inc. <paul@hal.com> wrote and + contributed the code in lib/sighandle.c. I added support for + POSIX, BSD, and non-POSIX/non-BSD systems. + + * Free Software Foundation contributed the "configure" script and + other compatibility support in the "lib" directory, which will help + make CVS much more portable. + + * Many others have contributed bug reports and enhancement requests. + Some have even submitted actual code which I have not had time yet + to integrate into CVS. Maybe for the next release. + + * Thanks to you all! + +Wed Feb 6 10:10:58 1991 Brian Berliner (berliner at sun.com) + + * Changes from CVS 1.0 Patchlevel 1 to CVS 1.0 Patchlevel 2; also + known as "Changes from CVS 1.1 to CVS 1.2". + + * Major new support with this release is the ability to use the + recently-posted RCS 5.5 distribution with CVS 1.2. See below for + other assorted bug-fixes that have been thrown in. + + * ChangeLog (new): Added Emacs-style change-log file to CVS 1.2 + release. Chronological description of changes between release. + + * README: Small fixes to installation instructions. My email + address is now "berliner@sun.com". + + * src/Makefile: Removed "rcstime.h". Removed "depend" rule. + + * src/partime.c: Updated to RCS 5.5 version with hooks for CVS. + * src/maketime.c: Updated to RCS 5.5 version with hooks for CVS. + * src/rcstime.h: Removed from the CVS 1.2 distribution. + Thanks to Paul Eggert <eggert@twinsun.com> for these changes. + + * src/checkin.csh: Support for RCS 5.5 parsing. + Thanks to Paul Eggert <eggert@twinsun.com> for this change. + + * src/collect_sets.c (Collect_Sets): Be quieter if "-f" option is + specified. When checking out files on-top-of other files that CVS + doesn't know about, run a diff in the hopes that they are really + the same file before aborting. + + * src/commit.c (branch_number): Fix for RCS 5.5 parsing. + Thanks to Paul Eggert <eggert@twinsun.com> for this change. + + * src/commit.c (do_editor): Bug fix - fprintf missing argument + which sometimes caused core dumps. + + * src/modules.c (process_module): Properly NULL-terminate + update_dir[] in all cases. + + * src/no_difference.c (No_Difference): The wrong RCS revision was + being registered in certain (strange) cases. + + * src/patch.c (get_rcsdate): New algorithm. No need to call + maketime() any longer. + Thanks to Paul Eggert <eggert@twinsun.com> for this change. + + * src/patchlevel.h: Increased patch level to "2". + + * src/subr.c (isdir, islink): Changed to compare stat mode bits + correctly. + + * src/tag.c (tag_file): Added support for following symbolic links + that are in the master source repository when tagging. Made tag + somewhat quieter in certain cases. + + * src/update.c (update_process_lists): Unlink the user's file if it + was put on the Wlist, meaning that the user's file is not modified + and its RCS file has been removed by someone else. + + * src/update.c (update): Support for "cvs update dir" to correctly + just update the argument directory "dir". + + * src/cvs.h: Fixes for RCS 5.5 parsing. + * src/version_number.c (Version_Number): Fixes for parsing RCS 5.5 + and older RCS-format files. + Thanks to Paul Eggert <eggert@twinsun.com> for these changes. + + * src/version_number.c (Version_Number): Bug fixes for "-f" option. + Bug fixes for parsing with certain branch numbers. RCS + revision/symbol parsing is much more solid now. + +Wed Feb 14 10:01:33 1990 Brian Berliner (berliner at sun.com) + + * Changes from CVS 1.0 Patchlevel 0 to CVS 1.0 Patchlevel 1; also + known as "Changes from CVS 1.0 to CVS 1.1". + + * src/patch.c (get_rcsdate): Portability fix. Replaced call to + timelocal() with call to maketime(). + +Mon Nov 19 23:15:11 1990 Brian Berliner (berliner at prisma.com) + + * Sent CVS 1.0 release to comp.sources.unix moderator and FSF. + + * Special thanks to Dick Grune <dick@cs.vu.nl> for his work on the + 1986 version of CVS and making it available to the world. Dick's + version is available on uunet.uu.net in the + comp.sources.unix/volume6/cvs directory. + +$CVSid: @(#)ChangeLog 1.35 94/10/22 $ diff --git a/contrib/cvs/PROJECTS b/contrib/cvs/PROJECTS new file mode 100644 index 0000000..de76576 --- /dev/null +++ b/contrib/cvs/PROJECTS @@ -0,0 +1,59 @@ +This is a list of projects for CVS. In general, unlike the things in +the TODO file, these need more analysis to determine if and how +worthwhile each task is. + +I haven't gone through TODO, but it's likely that it has entries that +are actually more appropriate for this list. + +0. Improved Efficency + +* CVS uses a single doubly linked list/hash table data structure for + all of its lists. Since the back links are only used for deleting + list nodes it might be beneficial to use singly linked lists or a + tree structure. Most likely, a single list implementation will not + be appropriate for all uses. + + One easy change would be to remove the "type" field out of the list + and node structures. I have found it to be of very little use when + debugging, and each instance eats up a word of memory. This can add + up and be a problem on memory-starved machines. + + Profiles have shown that on fast machines like the Alpha, fsortcmp() + is one of the hot spots. + +* Dynamically allocated character strings are created, copied, and + destroyed throughout CVS. The overhead of malloc()/strcpy()/free() + needs to be measured. If significant, it could be minimized by using a + reference counted string "class". + +* File modification time is stored as a character string. It might be + worthwile to use a time_t internally if the time to convert a time_t + (from struct stat) to a string is greater that the time to convert a + ctime style string (from the entries file) to a time_t. time_t is + an machine-dependant type (although it's pretty standard on UN*X + systems), so we would have to have different conversion routines. + Profiles show that both operations are called about the same number + of times. + +* stat() is one of the largest performance bottlenecks on systems + without the 4.4BSD filesystem. By spliting information out of + the filesystem (perhaps the "rename database") we should be + able to improve performance. + +* Parsing RCS files is very expensive. This might be unnecessary if + RCS files are only used as containers for revisions, and tag, + revision, and date information was available in easy to read + (and modify) indexes. This becomes very apparent with files + with several hundred revisions. + +* A RCS "library", so CVS could operate on RCS files directly. + + CVS parses RCS files in order to determine if work needs to be done, + and then RCS parses the files again when it is performing the work. + This would be much faster if CVS could do whatever is necessary + by itself. + +1. Improved testsuite/sanity check script + +* Need to use a code coverage tool to determine how much the sanity + script tests, and fill in the holes. diff --git a/contrib/cvs/README b/contrib/cvs/README new file mode 100644 index 0000000..5c4c9b6 --- /dev/null +++ b/contrib/cvs/README @@ -0,0 +1,222 @@ +$CVSid: @(#)README 1.32 94/10/22 $ + + CVS Kit + + Copyright (c) 1993-1994 Brian Berliner + Copyright (c) 1992 Brian Berliner and Jeff Polk + Copyright (c) 1989-1992, Brian Berliner + All Rights Reserved + + 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 1, 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. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +------------------------------------------------------------------------------- + +Welcome to CVS! + +Bug reports are accepted, however note that someone may or may not +feel like taking care of your bug report. Support contracts are +available from Cyclic Software (http://www.cyclic.com or +info@cyclic.com). + +To report bugs send mail to bug-cvs@prep.ai.mit.edu, or run the "cvsbug" +program and fill out the template: + + $ cvsbug + +The "cvsbug" program is installed in the same location as the "cvs" +program. If your installation failed, you may need to run "cvsbug" +directly out of the "src" directory as "src/cvsbug.sh". This is also +the procedure for submitting suggested changes to CVS (see the file +HACKING for more details). Note that all submitted changes may be +distributed under the terms of the GNU Public License, so if you don't +like this, don't submit them. + +Please consult the INSTALL file for information on tested +configurations. If you have a comment about an already tested +configuration, or have tried CVS on a new configuration, please write +to the above address and let us know! Free software only works if we +all help out. + +Finally, we cannot guarantee that this release will not completely wipe out +all of your work from your system. We do some simple testing before each +release, but you are completely on your own. We recommend testing this +release on a source repository that is not critical to your work. THIS +SOFTWARE IS SUPPLIED COMPLETELY "AS IS". NO WARRANTY.... + +Thanks for your support! + + -The CVS Team + +------------------------------------------------------------------------------- + +CVS is a freely available collection of programs that provide for software +release and revision control functions in a UNIX environment. It is +designed to work on top of the RCS distribution, V4 and later. CVS does +understand how to parse older RCS formats, but cannot do any of the fancier +features (like vendor branch support) without RCS branch support. + +Short blurb from the manual page (larger blurb is included there): + cvs is a front end to the rcs(1) revision control system + which extends the notion of revision control from a collec- + tion of files in a single directory to a hierarchical col- + lection of directories consisting of revision controlled + files. These directories and files can be combined together + to form a software release. cvs provides the functions + necessary to manage these software releases and to control + the concurrent editing of source files among multiple + software developers. + +And a whole lot more. See the doc/cvs.texinfo file for more information. + +------------------------------------------------------------------------------- + +Notes to people upgrading from a previous release of CVS: + +See the NEWS file for a description of features new in this version. + +The repository format is compatible going back to CVS 1.3. But see +the "Watches compatibility" section of doc/cvs.texinfo if you have +copies of CVS 1.6 or older and you want to use the optional developer +communication features. + +The working directory format is compatible going back to CVS 1.5. It +did change between CVS 1.3 and CVS 1.5. If you run CVS 1.5 or newer +on a working directory checked out with CVS 1.3, CVS will convert it, +but to go back to CVS 1.3 you need to check out a new working +directory with CVS 1.3. + +The remote protocol is interoperable going back to CVS 1.5. Using a +client or server older than 1.5 is deprecated and may fail to work at +some point in the future (1.5 was the first official release with the +remote protocol, but some older versions might still be floating +around). In many cases you need to upgrade both the client and the +server to take advantage of new features and bugfixes, however. + +------------------------------------------------------------------------------- + +Installation: + +Please read the INSTALL file for installation instructions. Brief summary: + + $ ./configure + $ make + $ make check # optional, long-running, step + $ make install + $ cvsinit + +The documentation is in the doc subdirectory. cvs.texinfo is the main +manual; cvs.info* and cvs.ps are the info and postscript versions, +respectively, generated from cvs.texinfo. The postscript version is +for A4 paper; if you want US letter size, you need to remove the line +@afourpaper from cvs.texinfo and re-generate cvs.ps using TeX. + +------------------------------------------------------------------------------- + +* How do I get up-to-date information and information about other +versions of CVS? + +On the web, http://www.loria.fr/~molli/cvs-index.html. + +The mailing list for CVS is info-cvs@prep.ai.mit.edu. Send +subscription and removal requests for that list to +info-cvs-request@prep.ai.mit.edu. + +The newsgroup for CVS (and other configuration management systems) is +comp.software.config-mgmt. There is not yet a CVS-specific newsgroup, +but perhaps if comp.software.config-mgmt gets enough CVS discussion, +then it will be possible to create one. + +------------------------------------------------------------------------------- + +Credits: + +The conflict-resolution algorithms and much of the administrative file +definitions of CVS were based on the original package written by Dick Grune +at Vrije Universiteit in Amsterdam <dick@cs.vu.nl>, and posted to +comp.sources.unix in the volume 6 release sometime in 1986. This original +version was a collection of shell scripts. I am thankful that Dick made +his work available. + +Brian Berliner from Prisma, Inc. (now at Sun Microsystems, Inc.) +<berliner@sun.com> converted the original CVS shell scripts into reasonably +fast C and added many, many features to support software release control +functions. See the manual page in the "man" directory. A copy of the +USENIX article presented at the Winter 1990 USENIX Conference, Washington +D.C., is included in the "doc" directory. + +Jeff Polk from BSDI <polk@bsdi.com> converted the CVS 1.2 +sources into much more readable and maintainable C code. He also added a +whole lot of functionality and modularity to the code in the process. +See the ChangeLog file. + +david d `zoo' zuhn <zoo@armadillo.com> contributed the working base code +for CVS 1.4 Alpha. His work carries on from work done by K. Richard Pixley +and others at Cygnus Support. The CVS 1.4 upgrade is due in large part to +Zoo's efforts. + +David G. Grubbs <dgg@odi.com> contributed the CVS "history" and "release" +commands. As well as the ever-so-useful "-n" option of CVS which tells CVS +to show what it would do, without actually doing it. He also contributed +support for the .cvsignore file. + +The Free Software Foundation (GNU) contributed most of the portability +framework that CVS now uses. This can be found in the "configure" script, +the Makefile's, and basically most of the "lib" directory. + +K. Richard Pixley, Cygnus Support <rich@cygnus.com> contributed many bug +fixes/enhancement as well as completing early reviews of the CVS 1.3 manual +pages. + +Roland Pesch, then of Cygnus Support <roland@wrs.com> contributed brand new +cvs(1) and cvs(5) manual pages. We should all thank him for saving us from +my poor use of our language! + +Paul Sander, HaL Computer Systems, Inc. <paul@hal.com> wrote and +contributed the code in lib/sighandle.c. I added support for POSIX, BSD, +and non-POSIX/non-BSD systems. + +Jim Kingdon and others at Cygnus Support <info@cygnus.com> wrote the +remote repository access code. + +In addition to the above contributors, the following Beta testers deserve +special mention for their support. If I have left off your name, I +apologize. Just write to me and let me know! + + Mark D. Baushke <mdb@cisco.com> + Per Cederqvist <ceder@signum.se> + J.T. Conklin (jtc@cygnus.com> + Vince DeMarco <vdemarco@fdcsrvr.cs.mci.com> + Paul Eggert <eggert@twinsun.com> + Lal George <george@research.att.com> + Dean E. Hardi <Dean.E.Hardi@ccmail.jpl.nasa.gov> + Mike Heath <mike@pencom.com> + Jim Kingdon <kingdon@cygnus.com> + Bernd Leibing <bernd.leibing@rz.uni-ulm.de> + Benedict Lofstedt <benedict@tusc.com.au> + Dave Love <d.love@dl.ac.uk> + Robert Lupton the Good <rhl@astro.princeton.edu> + Tom McAliney <tom@hilco.com> + Eberhard Mattes <mattes@azu.informatik.uni-stuttgart.de> + Jim Meyering <meyering@comco.com> + Thomas Mohr <mohr@lts.sel.alcatel.de> + Thomas Nilsson <thoni@softlab.se> + Raye Raskin <raye.raskin@lia.com> + Harlan Stenn <harlan@landmark.com> + Gunnar Tornblom <gunnar.tornblom@senet.abb.se> + Greg A. Woods <woods@planix.com> + +Many contributors have added code to the "contrib" directory. See the +README file there for a list of what is available. There is also a +contributed GNU Emacs CVS-mode in contrib/pcl-cvs. diff --git a/contrib/cvs/TODO b/contrib/cvs/TODO new file mode 100644 index 0000000..ef9306c --- /dev/null +++ b/contrib/cvs/TODO @@ -0,0 +1,423 @@ +$CVSid: @(#)TODO 1.26 94/09/21 $ + +14. Pathname stripper, for checkout, as well as for writing the + Repository file. + [[ I have a simple one, but need to make sure to call it at all the + appropriate points ]] + (I'm not sure what this means -kingdon, Jun 1995). + +22. Catch signals for cleanup when "add"ing files. + +24. Insist on a log message. + (This should be configurable via commitinfo or some new config file + -kingdon, Jun 1995). + +30. Add "patch" program option to the modules database. + +31. Think hard about ^C recovery. + +35. Add "admin" command as an interface to "rcs". + [[ a cheesy version is there, but it should be re-done ]] + +38. Think hard about using RCS state information to allow one to checkin + a new vendor release without having it be accessed until it has been + integrated into the local changes. + +39. Think about allowing parallel source trees that can easily track + each other. + [[ sort of solved with the automagic branch support, but I want more ]] + +45. Consider enhancing the "patch" and "tag" command support in the module + database -- they seem hard to use since these commands deal directly + with the RCS ,v files. + +46. Perhaps checkout/checkin/tag/patch commands should be imbedded in the + file system directly, using special known command names? + +49. cvs xxx commands should be able to deal with files in other + directories. I want to do a cvs add foo/bar.c. + [[ most commands now use the generic recursion processor, but not all; + this note is left here to remind me to fix the others ]] + +52. SCCS has a feature that I would *love* to see in CVS, as it is very + useful. One may make a private copy of SCCS suid to a particular user, + so other users in the authentication list may check files in and out of + a project directory without mucking about with groups. Is there any + plan to provide a similar functionality to CVS? Our site (and, I'd + imagine, many other sites with large user bases) has decided against + having the user-groups feature of unix available to the users, due to + perceived administrative, technical and performance headaches. A tool + such as CVS with features that provide group-like functionality would + be a huge help. + +62. Consider using revision controlled files and directories to handle the + new module format -- consider a cvs command front-end to + add/delete/modify module contents, maybe. + +63. The "import" and vendor support commands (co -j) need to be documented + better. + +64. Need to greatly increase the performance of an initial checkout. + [[ it got better, then we added functionality, making it worse again ]] + +66. Length of the CVS temporary files must be limited to 14 characters for + System-V stupid support. As well as the length on the CVS.adm files. + +67. cvs import should populate the vendor sources with CVS.adm files so + that one could use the vendor sources directly without having the check + them out. + +69. Consider enhacing import to add a module automatically to the module + database. Perhaps with a new option, or perhaps with an editor. + +72. Consider re-design of the module -o, -i, -t options to use the file + system more intuitively. + +73. Consider an option (in .cvsrc?) to automatically add files that are new + and specified to commit. + +76. Consider adding a layer of abstraction so that CVS can work with both + RCS and SCCS files. Larry says this should be #ifdef'ed. + +79. Might be nice to have some sort of interface to TFS and tagged + revisions. + +82. Maybe the import stuff should allow an arbitrary revision to be + specified. + +84. Improve the documentation about administration of the repository and + how to add/remove files and the use of symbolic links. + +85. Add revision controlled symbolic links to CVS using one of the tag + fields in the RCS file. + +91. Better document the format of the source repository and how one might + convert their current SCCS or RCS files into CVS format. + +92. Look into this: + After a bit of soul searching via dbx, I realized my sin was that I'd + specified "echo" as the program to call from loginfo. The commit + procedure worked fine till it hit my echo, then silently aborted + leaving the lockfiles intact. Since I needn't use the loginfo + facility, I simply removed those commands and it all works. + +93. Need to think hard about release and development environments. Think + about execsets as well. + +98. If diff3 bombs out (too many differences) cvs then thinks that the file + has been updated and is OK to be commited even though the file + has not yet been merged. + +100. Checked out files should have revision control support. Maybe. + +102. Perhaps directory modes should be propagated on all import check-ins. + Not necessarily uid/gid changes. + +103. setuid/setgid on files is suspect. + +104. cvs should recover nicely on unreadable files/directories. + +105. cvs should have administrative tools to allow for changing permissions + and modes and what not. In particular, this would make cvs a + more attractive alternative to rdist. + +107. It should be possible to specify a list of symbolic revisions to + checkout such that the list is processed in reverse order looking for + matches within the RCS file for the symbolic revision. If there is + not a match, the next symbolic rev on the list is checked, and so on, + until all symbolic revs are exhausted. This would allow one to, say, + checkout "4.0" + "4.0.3" + "4.0.3Patch1" + "4.0.3Patch2" to get the + most recent 4.x stuff. This is usually handled by just specifying the + right release_tag, but most people forget to do this. + +108. If someone creates a whole new directory (i.e. adds it to the cvs + repository) and you happen to have a directory in your source farm by + the same name, when you do your cvs update -d it SILENTLY does + *nothing* to that directory. At least, I think it was silent; + certainly, it did *not* abort my cvs update, as it would have if the + same thing had happened with a file instead of a directory. + +109. I had gotten pieces of the sys directory in the past but not a + complete tree. I just did something like: + + cvs get * + + Where sys was in * and got the message + + cvs get: Executing 'sys/tools/make_links sys' + sh: sys/tools/make_links: not found + + I suspect this is because I didn't have the file in question, + but I do not understand how I could fool it into getting an + error. I think a later cvs get sys seemed to work so perhaps + something is amiss in handling multiple arguments to cvs get? + +113. The "cvs update" command should tee its output to a log file in ".". + (why? What is wrong with piping stdout to "tee"? -kingdon, Jun 1995) + +119. Consider an option to have import checkout the RCS or SCCS files + if necessary. + +122. If Name_Repository fails, it currently causes CVS to die completely. It + should instead return NULL and have the caller do something reasonable. + +123. Add a flag to import to not build vendor branches for local code. + +124. Anyway, I thought you might want to add something like the following + to the cvs man pages: + + BUGS + The sum of the sizes of a module key and its contents are + limited. See ndbm(3). + +126. Do an analysis to see if CVS is forgetting to close file descriptors. + Especially when committing many files (more than the open file limit + for the particular UNIX). + +127. Look at *info files; they should all be quiet if the files are not + there. Should be able to point at a RCS directory and go. + +128. When I tag a file, the message tells me that I'm tagging a directory. + +129. Something strange seems to have happened here. When I check this out, + the update lines (U CFTS/...) seem to report a bogus leading CFTS + (e.g. U CFTS/Medusa_TS/...) when the later files are checked out. + + The directory structure doesn't seem to be botched, just the + messages. I don't recall seeing this before. + +130. cvs diff with no -r arguments does not need to look up the current RCS + version number since it only cares about what's in the Entries file. + This should make it much faster. + + It should ParseEntries itself and access the entries list much like + Version_TS does (sticky tags and sticky options may need to be + supported here as well). Then it should only diff the things that + have the wrong time stamp (the ones that look modified). + +134. Make a statement about using hard NFS mounts to your source + repository. Look into checking NULL fgets() returns with ferror() to + see if an error had occurred. + +135. The email CVS sends with each checkin, should include the version + number of each file it is checking in. + [[ Sort of solved by contrib/log.pl, which does a good job of this ]] + +137. Some sites might want CVS to fsync() the RCS ,v file to protect + against nasty hardware errors. There is a slight performance hit with + doing so, though, so it should be configurable in the .cvsrc file. + Also, along with this, we should look at the places where CVS itself + could be a little more synchronous so as not to lose data. + [[ I've done some of this, but it could use much more ]] + +138. Some people have suggested that CVS use a VPATH-like environment + variable to limit the amount of sources that need to be duplicated for + sites with giant source trees and no disk space. + +141. Import should accept modules as its directory argument. + +143. Update the documentation to show that the source repository is + something far away from the files that you work on. + +144. Have cvs checkout look for the environment variable CVSPREFIX + (or CVSMODPREFIX or some such). If it's set, then when looking + up an alias in the modules database, first look it up with the + value of CVSPREFIX attached, and then look for the alias itself. + This would be useful when you have several projects in a single + repository. You could have aliases abc_src and xyz_src and + tell people working on project abc to put "setenv CVSPREFIX abc_" + in their .cshrc file (or equivalent for other shells). + Then they could do "cvs co src" to get a copy of their src + directory, not xyz's. (This should create a directory called + src, not abc_src.) + +145. After you create revision 1.1.1.1 in the previous scenario, if + you do "cvs update -r1 filename" you get revision 1.1, not + 1.1.1.1. It would be nice to get the later revision. Again, + this restriction comes from RCS and is probably hard to + change in CVS. Sigh. + + |"cvs update -r1 filename" does not tell RCS to follow any branches. CVS + |tries to be consistent with RCS in this fashion, so I would not change + |this. Within CVS we do have the flexibility of extending things, like + |making a revision of the form "-r1HEAD" find the most recent revision + |(branch or not) with a "1." prefix in the RCS file. This would get what + |you want maybe. + + This would be very useful. Though I would prefer an option + such as "-v1" rather than "-r1HEAD". This option might be + used quite often. + +146. The merging of files should be controlled via a hook so that programs + other than "rcsmerge" can be used, like Sun's filemerge or emacs's + emerge.el. (but be careful in making this work client/server--it means + doing the interactive merging at the end after the server is done). + +149. On Sun, 2 Feb 92 22:01:38 EST, rouilj@dl5000.bc.edu (John P. Rouillard) + said: + Maybe there should be an option to cvs admin that allows a user to + change the Repository file with some degree of error checking? + Something like "cvs admin reposmv /old/path /new/pretty/path". Before + it does the replace it check to see that the files + /new/pretty/path/<dir>/<files> exist. + +150. I have a customer request for a way to specify log message per + file, non-interactively before the commit, such that a single, fully + recursive commit prompts for one commit message, and concatenates the + per file messages for each file. In short, one commit, one editor + session, log messages allowed to vary across files within the commit. + Also, the per file messages should be allowed to be written when the + files are changed, which may predate the commit considerably. + + A new command seems appropriate for this. The state can be saved in the + CVS directory. I.e., + + % cvs msg foo.c + Enter log message for foo.c + >> fixed an uninitialized variable + >> ^D + + The text is saved as CVS/foo.c,m (or some such name) and commit is + modified to append (prepend?) the text (if found) to the log message + specified at commit time. Easy enough. + +151. Also, is there a flag I am missing that allows replacing Ulrtx_Build + by Ultrix_build? I.E. I would like a tag replacement to be a one step + operation rather than a two step "cvs rtag -r Ulrtx_Build Ultrix_Build" + followed by "cvs trag -d Ulrtx_Build" + +152. The "cvs -n" option does not work as one would expect for all the + commands. In particular, for "commit" and "import", where one would + also like to see what it would do, without actually doing anything. + +153. There should be some command (maybe I just haven't figured + out which one...) to import a source directory which is already + RCS-administered without losing all prior RCS gathered data. Thus, it + would have to examine the RCS files and choose a starting version and + branch higher than previous ones used. + +154. When committing the modules file, a pre-commit check should be done to + verify the validity of the new modules file before allowing it to be + committed. + +155. The options for "cvs history" are mutually exclusive, even though + useful queries can be done if they are not, as in specifying both a + module and a tag. A workaround is to specify the module, then run the + output through grep to only display lines that begin with T, which are + tag lines. + +156. Also, how hard would it be to allow continuation lines in the + {commit,rcs,log}info files? It would probably be useful with all of + the various flags that are now available, or if somebody has a lot of + files to put into a module. + +157. The "cvs release" command does not understand about module names with + the same flexibility that the "checkout" and "rdiff" commands do. + It should, though, since it's confusing right now. + +158. If I do a recursive commit and find that the same RCS file is checked + out (and modified!) in two different places within my checked-out + files (but within the realm of a single "commit"), CVS will commit the + first change, then overwrite that change with the second change. We + should catch this (typically unusual) case and issue an appropriate + diagnostic and die. + +159. On "update", when a merge is done, CVS should remember that your file + was merged into and should keep reminding you of this fact until you + actually look at the file (change its access time). Once you do this, + it should go back to being a normal, unmodified file. This way, after + a big update, you can run update again to see which files just got + merged and may need attention. + +160. The checks that the commit command does should be extended to make + sure that the revision that we will lock is not already locked by + someone else. Maybe it should also lock the new revision if the old + revision was already locked by the user as well, thus moving the lock + forward after the commit. + +161. The date parser included with CVS (lib/getdate.y) does not support + such RCS-supported dates as "1992/03/07". It probably should. + +163. The rtag/tag commands should have an option that removes the specified + tag from any file that is in the attic. This allows one to re-use a + tag (like "Mon", "Tue", ...) all the time and still have it tag the + real main-line code. + +164. The *info files should allow multiple ocurrences of $CVSROOT and/or + other cvs variables. They probably should *not* expand environment + variables, as their behavior probably should not depend on who is + running CVS. + +165. The "import" command will create RCS files automatically, but will + screw-up when trying to create long file names on short file name + file systems. Perhaps import should be a bit more cautious. + +166. There really needs to be a "Getting Started" document which describes + some of the new CVS philosophies. Folks coming straight from SCCS or + RCS might be confused by "cvs import". Also need to explain: + - How one might setup their $CVSROOT + - What all the tags mean in an "import" command + - Tags are important; revision numbers are not + +167. "cvs log" doesn't understand about CVS magic branch numbers. As such, + the command: + + cvs log -r1.63.2 + cvs log -rC2 + + where "C2" is a magic branch that resolves to 1.63.2 do not print the + same things. Sigh. + +169. We are using CVS as the configuration control for a software reuse library. + What we do is do system calls passing the needed arguments. In the next + release, it would be nice to see an option to put cvs .o files into a + archive library with an API. This enhancement would go nicely with the + notion of being able to integrate tools into a large software engineering + environment. + +170. Is there an "info" file that can be invoked when a file is checked out, or + updated ? What I want to do is to advise users, particularly novices, of + the state of their working source whenever they check something out, as + a sanity check. + + For example, I've written a perl script which tells you what branch you're + on, if any. Hopefully this will help guard against mistaken checkins to + the trunk, or to the wrong branch. I suppose I can do this in + "commitinfo", but it'd be nice to advise people before they edit their + files. + + It would also be nice if there was some sort of "verboseness" switch to + the checkout and update commands that could turn this invocation of the + script off, for mature users. + +173. We have a tagged branch in CVS. How do we get the version of that branch + (for an entire directory) that corresponds to the files on that branch on a + certain day? I'd like to specify BOTH -r and -D to 'cvs checkout', but I + can't. It looks like I can only specify the date for the main line (as + opposed to any branches). True? Any workarounds to get what I need? + +174. I would like to see "cvs release" modified so that it only removes files + which are known to CVS - all the files in the repository, plus those which + are listed in .cvsignore. This way, if you do leave something valuable in + a source tree you can "cvs release -d" the tree and your non-CVS goodies + are still there. If a user is going to leave non-CVS files in their source + trees, they really should have to clean them up by hand. + +175. And, in the feature request department, I'd dearly love a command-line + interface to adding a new module to the CVSROOT/modules file. + +176. If you use the -i flag in the modules file, you can control access + to source code; this is a Good Thing under certain circumstances. I + just had a nasty thought, and on experiment discovered that the + filter specified by -i is _not_ run before a cvs admin command; as + this allows a user to go behind cvs's back and delete information + (cvs admin -o1.4 file) this seems like a serious problem. + +177. We've got some external vendor source that sits under a source code + hierarchy, and when we do a cvs update, it gets wiped out because + its tag is different from the "main" distribution. I've tried to + use "-I" to ignore the directory, as well as .cvsignore, but this + doesn't work. + +179. "cvs admin" does not log its actions with loginfo, nor does it check + whether the action is allowed with commitinfo. It should. diff --git a/contrib/cvs/acconfig.h b/contrib/cvs/acconfig.h new file mode 100644 index 0000000..551a8aa --- /dev/null +++ b/contrib/cvs/acconfig.h @@ -0,0 +1,12 @@ +/* Define if you have MIT Kerberos version 4 available. */ +#undef HAVE_KERBEROS + +/* Define if you want CVS to be able to be a remote repository client. */ +#undef CLIENT_SUPPORT + +/* Define if you want CVS to be able to serve repositories to remote + clients. */ +#undef SERVER_SUPPORT + +/* Define if you want to use the password authenticated server. */ +#undef AUTH_SERVER_SUPPORT diff --git a/contrib/cvs/config.h.in b/contrib/cvs/config.h.in new file mode 100644 index 0000000..9181936 --- /dev/null +++ b/contrib/cvs/config.h.in @@ -0,0 +1,210 @@ +/* config.h.in. Generated automatically from configure.in by autoheader. */ + +/* Define if on AIX 3. + System headers sometimes define this. + We just want to avoid a redefinition error message. */ +#ifndef _ALL_SOURCE +#undef _ALL_SOURCE +#endif + +/* Define if type char is unsigned and you are not using gcc. */ +#ifndef __CHAR_UNSIGNED__ +#undef __CHAR_UNSIGNED__ +#endif + +/* Define to empty if the keyword does not work. */ +#undef const + +/* Define to `int' if <sys/types.h> doesn't define. */ +#undef gid_t + +/* Define if you support file names longer than 14 characters. */ +#undef HAVE_LONG_FILE_NAMES + +/* Define if you have <sys/wait.h> that is POSIX.1 compatible. */ +#undef HAVE_SYS_WAIT_H + +/* Define if utime(file, NULL) sets file's timestamp to the present. */ +#undef HAVE_UTIME_NULL + +/* Define as __inline if that's what the C compiler calls it. */ +#undef inline + +/* Define if on MINIX. */ +#undef _MINIX + +/* Define to `int' if <sys/types.h> doesn't define. */ +#undef mode_t + +/* Define to `int' if <sys/types.h> doesn't define. */ +#undef pid_t + +/* Define if the system does not provide POSIX.1 features except + with this defined. */ +#undef _POSIX_1_SOURCE + +/* Define if you need to in order for stat and other things to work. */ +#undef _POSIX_SOURCE + +/* Define as the return type of signal handlers (int or void). */ +#undef RETSIGTYPE + +/* Define to `unsigned' if <sys/types.h> doesn't define. */ +#undef size_t + +/* Define if the `S_IS*' macros in <sys/stat.h> do not work properly. */ +#undef STAT_MACROS_BROKEN + +/* Define if you have the ANSI C header files. */ +#undef STDC_HEADERS + +/* Define if you can safely include both <sys/time.h> and <time.h>. */ +#undef TIME_WITH_SYS_TIME + +/* Define to `int' if <sys/types.h> doesn't define. */ +#undef uid_t + +/* Define if you have MIT Kerberos version 4 available. */ +#undef HAVE_KERBEROS + +/* Define if you want CVS to be able to be a remote repository client. */ +#undef CLIENT_SUPPORT + +/* Define if you want CVS to be able to serve repositories to remote + clients. */ +#undef SERVER_SUPPORT + +/* Define if you want to use the password authenticated server. */ +#undef AUTH_SERVER_SUPPORT + +/* The number of bytes in a int. */ +#undef SIZEOF_INT + +/* The number of bytes in a long. */ +#undef SIZEOF_LONG + +/* Define if you have the connect function. */ +#undef HAVE_CONNECT + +/* Define if you have the fchdir function. */ +#undef HAVE_FCHDIR + +/* Define if you have the fchmod function. */ +#undef HAVE_FCHMOD + +/* Define if you have the fsync function. */ +#undef HAVE_FSYNC + +/* Define if you have the ftime function. */ +#undef HAVE_FTIME + +/* Define if you have the ftruncate function. */ +#undef HAVE_FTRUNCATE + +/* Define if you have the getpagesize function. */ +#undef HAVE_GETPAGESIZE + +/* Define if you have the krb_get_err_text function. */ +#undef HAVE_KRB_GET_ERR_TEXT + +/* Define if you have the mkfifo function. */ +#undef HAVE_MKFIFO + +/* Define if you have the putenv function. */ +#undef HAVE_PUTENV + +/* Define if you have the setvbuf function. */ +#undef HAVE_SETVBUF + +/* Define if you have the sigaction function. */ +#undef HAVE_SIGACTION + +/* Define if you have the sigblock function. */ +#undef HAVE_SIGBLOCK + +/* Define if you have the sigprocmask function. */ +#undef HAVE_SIGPROCMASK + +/* Define if you have the sigsetmask function. */ +#undef HAVE_SIGSETMASK + +/* Define if you have the sigvec function. */ +#undef HAVE_SIGVEC + +/* Define if you have the timezone function. */ +#undef HAVE_TIMEZONE + +/* Define if you have the vfork function. */ +#undef HAVE_VFORK + +/* Define if you have the vprintf function. */ +#undef HAVE_VPRINTF + +/* Define if you have the <direct.h> header file. */ +#undef HAVE_DIRECT_H + +/* Define if you have the <dirent.h> header file. */ +#undef HAVE_DIRENT_H + +/* Define if you have the <errno.h> header file. */ +#undef HAVE_ERRNO_H + +/* Define if you have the <fcntl.h> header file. */ +#undef HAVE_FCNTL_H + +/* Define if you have the <io.h> header file. */ +#undef HAVE_IO_H + +/* Define if you have the <memory.h> header file. */ +#undef HAVE_MEMORY_H + +/* Define if you have the <ndbm.h> header file. */ +#undef HAVE_NDBM_H + +/* Define if you have the <ndir.h> header file. */ +#undef HAVE_NDIR_H + +/* Define if you have the <string.h> header file. */ +#undef HAVE_STRING_H + +/* Define if you have the <sys/bsdtypes.h> header file. */ +#undef HAVE_SYS_BSDTYPES_H + +/* Define if you have the <sys/dir.h> header file. */ +#undef HAVE_SYS_DIR_H + +/* Define if you have the <sys/ndir.h> header file. */ +#undef HAVE_SYS_NDIR_H + +/* Define if you have the <sys/param.h> header file. */ +#undef HAVE_SYS_PARAM_H + +/* Define if you have the <sys/resource.h> header file. */ +#undef HAVE_SYS_RESOURCE_H + +/* Define if you have the <sys/select.h> header file. */ +#undef HAVE_SYS_SELECT_H + +/* Define if you have the <sys/time.h> header file. */ +#undef HAVE_SYS_TIME_H + +/* Define if you have the <sys/timeb.h> header file. */ +#undef HAVE_SYS_TIMEB_H + +/* Define if you have the <unistd.h> header file. */ +#undef HAVE_UNISTD_H + +/* Define if you have the <utime.h> header file. */ +#undef HAVE_UTIME_H + +/* Define if you have the inet library (-linet). */ +#undef HAVE_LIBINET + +/* Define if you have the nsl library (-lnsl). */ +#undef HAVE_LIBNSL + +/* Define if you have the nsl_s library (-lnsl_s). */ +#undef HAVE_LIBNSL_S + +/* Define if you have the socket library (-lsocket). */ +#undef HAVE_LIBSOCKET diff --git a/contrib/cvs/configure b/contrib/cvs/configure new file mode 100755 index 0000000..3025eef --- /dev/null +++ b/contrib/cvs/configure @@ -0,0 +1,3186 @@ +#! /bin/sh + +# Guess values for system-dependent variables and create Makefiles. +# Generated automatically using autoconf version 2.9 +# Copyright (C) 1992, 93, 94, 95, 96 Free Software Foundation, Inc. +# +# This configure script is free software; the Free Software Foundation +# gives unlimited permission to copy, distribute and modify it. + +# Defaults: +ac_help= +ac_default_prefix=/usr/local +# Any additions from configure.in: +ac_help="$ac_help + --with-krb4=value set default \$(KRB4) from value" + +# Initialize some variables set by options. +# The variables have the same names as the options, with +# dashes changed to underlines. +build=NONE +cache_file=./config.cache +exec_prefix=NONE +host=NONE +no_create= +nonopt=NONE +no_recursion= +prefix=NONE +program_prefix=NONE +program_suffix=NONE +program_transform_name=s,x,x, +silent= +site= +srcdir= +target=NONE +verbose= +x_includes=NONE +x_libraries=NONE +bindir='${exec_prefix}/bin' +sbindir='${exec_prefix}/sbin' +libexecdir='${exec_prefix}/libexec' +datadir='${prefix}/share' +sysconfdir='${prefix}/etc' +sharedstatedir='${prefix}/com' +localstatedir='${prefix}/var' +libdir='${exec_prefix}/lib' +includedir='${prefix}/include' +oldincludedir='/usr/include' +infodir='${prefix}/info' +mandir='${prefix}/man' + +# Initialize some other variables. +subdirs= +MFLAGS= MAKEFLAGS= + +ac_prev= +for ac_option +do + + # If the previous option needs an argument, assign it. + if test -n "$ac_prev"; then + eval "$ac_prev=\$ac_option" + ac_prev= + continue + fi + + case "$ac_option" in + -*=*) ac_optarg=`echo "$ac_option" | sed 's/[-_a-zA-Z0-9]*=//'` ;; + *) ac_optarg= ;; + esac + + # Accept the important Cygnus configure options, so we can diagnose typos. + + case "$ac_option" in + + -bindir | --bindir | --bindi | --bind | --bin | --bi) + ac_prev=bindir ;; + -bindir=* | --bindir=* | --bindi=* | --bind=* | --bin=* | --bi=*) + bindir="$ac_optarg" ;; + + -build | --build | --buil | --bui | --bu) + ac_prev=build ;; + -build=* | --build=* | --buil=* | --bui=* | --bu=*) + build="$ac_optarg" ;; + + -cache-file | --cache-file | --cache-fil | --cache-fi \ + | --cache-f | --cache- | --cache | --cach | --cac | --ca | --c) + ac_prev=cache_file ;; + -cache-file=* | --cache-file=* | --cache-fil=* | --cache-fi=* \ + | --cache-f=* | --cache-=* | --cache=* | --cach=* | --cac=* | --ca=* | --c=*) + cache_file="$ac_optarg" ;; + + -datadir | --datadir | --datadi | --datad | --data | --dat | --da) + ac_prev=datadir ;; + -datadir=* | --datadir=* | --datadi=* | --datad=* | --data=* | --dat=* \ + | --da=*) + datadir="$ac_optarg" ;; + + -disable-* | --disable-*) + ac_feature=`echo $ac_option|sed -e 's/-*disable-//'` + # Reject names that are not valid shell variable names. + if test -n "`echo $ac_feature| sed 's/[-a-zA-Z0-9_]//g'`"; then + { echo "configure: error: $ac_feature: invalid feature name" 1>&2; exit 1; } + fi + ac_feature=`echo $ac_feature| sed 's/-/_/g'` + eval "enable_${ac_feature}=no" ;; + + -enable-* | --enable-*) + ac_feature=`echo $ac_option|sed -e 's/-*enable-//' -e 's/=.*//'` + # Reject names that are not valid shell variable names. + if test -n "`echo $ac_feature| sed 's/[-_a-zA-Z0-9]//g'`"; then + { echo "configure: error: $ac_feature: invalid feature name" 1>&2; exit 1; } + fi + ac_feature=`echo $ac_feature| sed 's/-/_/g'` + case "$ac_option" in + *=*) ;; + *) ac_optarg=yes ;; + esac + eval "enable_${ac_feature}='$ac_optarg'" ;; + + -exec-prefix | --exec_prefix | --exec-prefix | --exec-prefi \ + | --exec-pref | --exec-pre | --exec-pr | --exec-p | --exec- \ + | --exec | --exe | --ex) + ac_prev=exec_prefix ;; + -exec-prefix=* | --exec_prefix=* | --exec-prefix=* | --exec-prefi=* \ + | --exec-pref=* | --exec-pre=* | --exec-pr=* | --exec-p=* | --exec-=* \ + | --exec=* | --exe=* | --ex=*) + exec_prefix="$ac_optarg" ;; + + -gas | --gas | --ga | --g) + # Obsolete; use --with-gas. + with_gas=yes ;; + + -help | --help | --hel | --he) + # Omit some internal or obsolete options to make the list less imposing. + # This message is too long to be a string in the A/UX 3.1 sh. + cat << EOF +Usage: configure [options] [host] +Options: [defaults in brackets after descriptions] +Configuration: + --cache-file=FILE cache test results in FILE + --help print this message + --no-create do not create output files + --quiet, --silent do not print \`checking...' messages + --version print the version of autoconf that created configure +Directory and file names: + --prefix=PREFIX install architecture-independent files in PREFIX + [$ac_default_prefix] + --exec-prefix=EPREFIX install architecture-dependent files in EPREFIX + [same as prefix] + --bindir=DIR user executables in DIR [EPREFIX/bin] + --sbindir=DIR system admin executables in DIR [EPREFIX/sbin] + --libexecdir=DIR program executables in DIR [EPREFIX/libexec] + --datadir=DIR read-only architecture-independent data in DIR + [PREFIX/share] + --sysconfdir=DIR read-only single-machine data in DIR [PREFIX/etc] + --sharedstatedir=DIR modifiable architecture-independent data in DIR + [PREFIX/com] + --localstatedir=DIR modifiable single-machine data in DIR [PREFIX/var] + --libdir=DIR object code libraries in DIR [EPREFIX/lib] + --includedir=DIR C header files in DIR [PREFIX/include] + --oldincludedir=DIR C header files for non-gcc in DIR [/usr/include] + --infodir=DIR info documentation in DIR [PREFIX/info] + --mandir=DIR man documentation in DIR [PREFIX/man] + --srcdir=DIR find the sources in DIR [configure dir or ..] + --program-prefix=PREFIX prepend PREFIX to installed program names + --program-suffix=SUFFIX append SUFFIX to installed program names + --program-transform-name=PROGRAM + run sed PROGRAM on installed program names +EOF + cat << EOF +Host type: + --build=BUILD configure for building on BUILD [BUILD=HOST] + --host=HOST configure for HOST [guessed] + --target=TARGET configure for TARGET [TARGET=HOST] +Features and packages: + --disable-FEATURE do not include FEATURE (same as --enable-FEATURE=no) + --enable-FEATURE[=ARG] include FEATURE [ARG=yes] + --with-PACKAGE[=ARG] use PACKAGE [ARG=yes] + --without-PACKAGE do not use PACKAGE (same as --with-PACKAGE=no) + --x-includes=DIR X include files are in DIR + --x-libraries=DIR X library files are in DIR +EOF + if test -n "$ac_help"; then + echo "--enable and --with options recognized:$ac_help" + fi + exit 0 ;; + + -host | --host | --hos | --ho) + ac_prev=host ;; + -host=* | --host=* | --hos=* | --ho=*) + host="$ac_optarg" ;; + + -includedir | --includedir | --includedi | --included | --include \ + | --includ | --inclu | --incl | --inc) + ac_prev=includedir ;; + -includedir=* | --includedir=* | --includedi=* | --included=* | --include=* \ + | --includ=* | --inclu=* | --incl=* | --inc=*) + includedir="$ac_optarg" ;; + + -infodir | --infodir | --infodi | --infod | --info | --inf) + ac_prev=infodir ;; + -infodir=* | --infodir=* | --infodi=* | --infod=* | --info=* | --inf=*) + infodir="$ac_optarg" ;; + + -libdir | --libdir | --libdi | --libd) + ac_prev=libdir ;; + -libdir=* | --libdir=* | --libdi=* | --libd=*) + libdir="$ac_optarg" ;; + + -libexecdir | --libexecdir | --libexecdi | --libexecd | --libexec \ + | --libexe | --libex | --libe) + ac_prev=libexecdir ;; + -libexecdir=* | --libexecdir=* | --libexecdi=* | --libexecd=* | --libexec=* \ + | --libexe=* | --libex=* | --libe=*) + libexecdir="$ac_optarg" ;; + + -localstatedir | --localstatedir | --localstatedi | --localstated \ + | --localstate | --localstat | --localsta | --localst \ + | --locals | --local | --loca | --loc | --lo) + ac_prev=localstatedir ;; + -localstatedir=* | --localstatedir=* | --localstatedi=* | --localstated=* \ + | --localstate=* | --localstat=* | --localsta=* | --localst=* \ + | --locals=* | --local=* | --loca=* | --loc=* | --lo=*) + localstatedir="$ac_optarg" ;; + + -mandir | --mandir | --mandi | --mand | --man | --ma | --m) + ac_prev=mandir ;; + -mandir=* | --mandir=* | --mandi=* | --mand=* | --man=* | --ma=* | --m=*) + mandir="$ac_optarg" ;; + + -nfp | --nfp | --nf) + # Obsolete; use --without-fp. + with_fp=no ;; + + -no-create | --no-create | --no-creat | --no-crea | --no-cre \ + | --no-cr | --no-c) + no_create=yes ;; + + -no-recursion | --no-recursion | --no-recursio | --no-recursi \ + | --no-recurs | --no-recur | --no-recu | --no-rec | --no-re | --no-r) + no_recursion=yes ;; + + -oldincludedir | --oldincludedir | --oldincludedi | --oldincluded \ + | --oldinclude | --oldinclud | --oldinclu | --oldincl | --oldinc \ + | --oldin | --oldi | --old | --ol | --o) + ac_prev=oldincludedir ;; + -oldincludedir=* | --oldincludedir=* | --oldincludedi=* | --oldincluded=* \ + | --oldinclude=* | --oldinclud=* | --oldinclu=* | --oldincl=* | --oldinc=* \ + | --oldin=* | --oldi=* | --old=* | --ol=* | --o=*) + oldincludedir="$ac_optarg" ;; + + -prefix | --prefix | --prefi | --pref | --pre | --pr | --p) + ac_prev=prefix ;; + -prefix=* | --prefix=* | --prefi=* | --pref=* | --pre=* | --pr=* | --p=*) + prefix="$ac_optarg" ;; + + -program-prefix | --program-prefix | --program-prefi | --program-pref \ + | --program-pre | --program-pr | --program-p) + ac_prev=program_prefix ;; + -program-prefix=* | --program-prefix=* | --program-prefi=* \ + | --program-pref=* | --program-pre=* | --program-pr=* | --program-p=*) + program_prefix="$ac_optarg" ;; + + -program-suffix | --program-suffix | --program-suffi | --program-suff \ + | --program-suf | --program-su | --program-s) + ac_prev=program_suffix ;; + -program-suffix=* | --program-suffix=* | --program-suffi=* \ + | --program-suff=* | --program-suf=* | --program-su=* | --program-s=*) + program_suffix="$ac_optarg" ;; + + -program-transform-name | --program-transform-name \ + | --program-transform-nam | --program-transform-na \ + | --program-transform-n | --program-transform- \ + | --program-transform | --program-transfor \ + | --program-transfo | --program-transf \ + | --program-trans | --program-tran \ + | --progr-tra | --program-tr | --program-t) + ac_prev=program_transform_name ;; + -program-transform-name=* | --program-transform-name=* \ + | --program-transform-nam=* | --program-transform-na=* \ + | --program-transform-n=* | --program-transform-=* \ + | --program-transform=* | --program-transfor=* \ + | --program-transfo=* | --program-transf=* \ + | --program-trans=* | --program-tran=* \ + | --progr-tra=* | --program-tr=* | --program-t=*) + program_transform_name="$ac_optarg" ;; + + -q | -quiet | --quiet | --quie | --qui | --qu | --q \ + | -silent | --silent | --silen | --sile | --sil) + silent=yes ;; + + -sbindir | --sbindir | --sbindi | --sbind | --sbin | --sbi | --sb) + ac_prev=sbindir ;; + -sbindir=* | --sbindir=* | --sbindi=* | --sbind=* | --sbin=* \ + | --sbi=* | --sb=*) + sbindir="$ac_optarg" ;; + + -sharedstatedir | --sharedstatedir | --sharedstatedi \ + | --sharedstated | --sharedstate | --sharedstat | --sharedsta \ + | --sharedst | --shareds | --shared | --share | --shar \ + | --sha | --sh) + ac_prev=sharedstatedir ;; + -sharedstatedir=* | --sharedstatedir=* | --sharedstatedi=* \ + | --sharedstated=* | --sharedstate=* | --sharedstat=* | --sharedsta=* \ + | --sharedst=* | --shareds=* | --shared=* | --share=* | --shar=* \ + | --sha=* | --sh=*) + sharedstatedir="$ac_optarg" ;; + + -site | --site | --sit) + ac_prev=site ;; + -site=* | --site=* | --sit=*) + site="$ac_optarg" ;; + + -srcdir | --srcdir | --srcdi | --srcd | --src | --sr) + ac_prev=srcdir ;; + -srcdir=* | --srcdir=* | --srcdi=* | --srcd=* | --src=* | --sr=*) + srcdir="$ac_optarg" ;; + + -sysconfdir | --sysconfdir | --sysconfdi | --sysconfd | --sysconf \ + | --syscon | --sysco | --sysc | --sys | --sy) + ac_prev=sysconfdir ;; + -sysconfdir=* | --sysconfdir=* | --sysconfdi=* | --sysconfd=* | --sysconf=* \ + | --syscon=* | --sysco=* | --sysc=* | --sys=* | --sy=*) + sysconfdir="$ac_optarg" ;; + + -target | --target | --targe | --targ | --tar | --ta | --t) + ac_prev=target ;; + -target=* | --target=* | --targe=* | --targ=* | --tar=* | --ta=* | --t=*) + target="$ac_optarg" ;; + + -v | -verbose | --verbose | --verbos | --verbo | --verb) + verbose=yes ;; + + -version | --version | --versio | --versi | --vers) + echo "configure generated by autoconf version 2.9" + exit 0 ;; + + -with-* | --with-*) + ac_package=`echo $ac_option|sed -e 's/-*with-//' -e 's/=.*//'` + # Reject names that are not valid shell variable names. + if test -n "`echo $ac_package| sed 's/[-_a-zA-Z0-9]//g'`"; then + { echo "configure: error: $ac_package: invalid package name" 1>&2; exit 1; } + fi + ac_package=`echo $ac_package| sed 's/-/_/g'` + case "$ac_option" in + *=*) ;; + *) ac_optarg=yes ;; + esac + eval "with_${ac_package}='$ac_optarg'" ;; + + -without-* | --without-*) + ac_package=`echo $ac_option|sed -e 's/-*without-//'` + # Reject names that are not valid shell variable names. + if test -n "`echo $ac_package| sed 's/[-a-zA-Z0-9_]//g'`"; then + { echo "configure: error: $ac_package: invalid package name" 1>&2; exit 1; } + fi + ac_package=`echo $ac_package| sed 's/-/_/g'` + eval "with_${ac_package}=no" ;; + + --x) + # Obsolete; use --with-x. + with_x=yes ;; + + -x-includes | --x-includes | --x-include | --x-includ | --x-inclu \ + | --x-incl | --x-inc | --x-in | --x-i) + ac_prev=x_includes ;; + -x-includes=* | --x-includes=* | --x-include=* | --x-includ=* | --x-inclu=* \ + | --x-incl=* | --x-inc=* | --x-in=* | --x-i=*) + x_includes="$ac_optarg" ;; + + -x-libraries | --x-libraries | --x-librarie | --x-librari \ + | --x-librar | --x-libra | --x-libr | --x-lib | --x-li | --x-l) + ac_prev=x_libraries ;; + -x-libraries=* | --x-libraries=* | --x-librarie=* | --x-librari=* \ + | --x-librar=* | --x-libra=* | --x-libr=* | --x-lib=* | --x-li=* | --x-l=*) + x_libraries="$ac_optarg" ;; + + -*) { echo "configure: error: $ac_option: invalid option; use --help to show usage" 1>&2; exit 1; } + ;; + + *) + if test -n "`echo $ac_option| sed 's/[-a-z0-9.]//g'`"; then + echo "configure: warning: $ac_option: invalid host type" 1>&2 + fi + if test "x$nonopt" != xNONE; then + { echo "configure: error: can only configure for one host and one target at a time" 1>&2; exit 1; } + fi + nonopt="$ac_option" + ;; + + esac +done + +if test -n "$ac_prev"; then + { echo "configure: error: missing argument to --`echo $ac_prev | sed 's/_/-/g'`" 1>&2; exit 1; } +fi + +trap 'rm -fr conftest* confdefs* core core.* *.core $ac_clean_files; exit 1' 1 2 15 + +# File descriptor usage: +# 0 standard input +# 1 file creation +# 2 errors and warnings +# 3 some systems may open it to /dev/tty +# 4 used on the Kubota Titan +# 6 checking for... messages and results +# 5 compiler messages saved in config.log +if test "$silent" = yes; then + exec 6>/dev/null +else + exec 6>&1 +fi +exec 5>./config.log + +echo "\ +This file contains any messages produced by compilers while +running configure, to aid debugging if configure makes a mistake. +" 1>&5 + +# Strip out --no-create and --no-recursion so they do not pile up. +# Also quote any args containing shell metacharacters. +ac_configure_args= +for ac_arg +do + case "$ac_arg" in + -no-create | --no-create | --no-creat | --no-crea | --no-cre \ + | --no-cr | --no-c) ;; + -no-recursion | --no-recursion | --no-recursio | --no-recursi \ + | --no-recurs | --no-recur | --no-recu | --no-rec | --no-re | --no-r) ;; + *" "*|*" "*|*[\[\]\~\#\$\^\&\*\(\)\{\}\\\|\;\<\>\?]*) + ac_configure_args="$ac_configure_args '$ac_arg'" ;; + *) ac_configure_args="$ac_configure_args $ac_arg" ;; + esac +done + +# NLS nuisances. +# Only set LANG and LC_ALL to C if already set. +# These must not be set unconditionally because not all systems understand +# e.g. LANG=C (notably SCO). +if test "${LC_ALL+set}" = set; then LC_ALL=C; export LC_ALL; fi +if test "${LANG+set}" = set; then LANG=C; export LANG; fi + +# confdefs.h avoids OS command line length limits that DEFS can exceed. +rm -rf conftest* confdefs.h +# AIX cpp loses on an empty file, so make sure it contains at least a newline. +echo > confdefs.h + +# A filename unique to this package, relative to the directory that +# configure is in, which we can look for to find out if srcdir is correct. +ac_unique_file=src/cvs.h + +# Find the source files, if location was not specified. +if test -z "$srcdir"; then + ac_srcdir_defaulted=yes + # Try the directory containing this script, then its parent. + ac_prog=$0 + ac_confdir=`echo $ac_prog|sed 's%/[^/][^/]*$%%'` + test "x$ac_confdir" = "x$ac_prog" && ac_confdir=. + srcdir=$ac_confdir + if test ! -r $srcdir/$ac_unique_file; then + srcdir=.. + fi +else + ac_srcdir_defaulted=no +fi +if test ! -r $srcdir/$ac_unique_file; then + if test "$ac_srcdir_defaulted" = yes; then + { echo "configure: error: can not find sources in $ac_confdir or .." 1>&2; exit 1; } + else + { echo "configure: error: can not find sources in $srcdir" 1>&2; exit 1; } + fi +fi +srcdir=`echo "${srcdir}" | sed 's%\([^/]\)/*$%\1%'` + +# Prefer explicitly selected file to automatically selected ones. +if test -z "$CONFIG_SITE"; then + if test "x$prefix" != xNONE; then + CONFIG_SITE="$prefix/share/config.site $prefix/etc/config.site" + else + CONFIG_SITE="$ac_default_prefix/share/config.site $ac_default_prefix/etc/config.site" + fi +fi +for ac_site_file in $CONFIG_SITE; do + if test -r "$ac_site_file"; then + echo "loading site script $ac_site_file" + . "$ac_site_file" + fi +done + +if test -r "$cache_file"; then + echo "loading cache $cache_file" + . $cache_file +else + echo "creating cache $cache_file" + > $cache_file +fi + +ac_ext=c +# CFLAGS is not in ac_cpp because -g, -O, etc. are not valid cpp options. +ac_cpp='$CPP $CPPFLAGS' +ac_compile='${CC-cc} -c $CFLAGS $CPPFLAGS conftest.$ac_ext 1>&5' +ac_link='${CC-cc} -o conftest $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS 1>&5' + +if (echo "testing\c"; echo 1,2,3) | grep c >/dev/null; then + # Stardent Vistra SVR4 grep lacks -e, says ghazi@caip.rutgers.edu. + if (echo -n testing; echo 1,2,3) | sed s/-n/xn/ | grep xn >/dev/null; then + ac_n= ac_c=' +' ac_t=' ' + else + ac_n=-n ac_c= ac_t= + fi +else + ac_n= ac_c='\c' ac_t= +fi + + + + +# Extract the first word of "gcc", so it can be a program name with args. +set dummy gcc; ac_word=$2 +echo $ac_n "checking for $ac_word""... $ac_c" 1>&6 +if eval "test \"`echo '$''{'ac_cv_prog_CC'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + if test -n "$CC"; then + ac_cv_prog_CC="$CC" # Let the user override the test. +else + IFS="${IFS= }"; ac_save_ifs="$IFS"; IFS="${IFS}:" + for ac_dir in $PATH; do + test -z "$ac_dir" && ac_dir=. + if test -f $ac_dir/$ac_word; then + ac_cv_prog_CC="gcc" + break + fi + done + IFS="$ac_save_ifs" +fi +fi +CC="$ac_cv_prog_CC" +if test -n "$CC"; then + echo "$ac_t""$CC" 1>&6 +else + echo "$ac_t""no" 1>&6 +fi + +if test -z "$CC"; then + # Extract the first word of "cc", so it can be a program name with args. +set dummy cc; ac_word=$2 +echo $ac_n "checking for $ac_word""... $ac_c" 1>&6 +if eval "test \"`echo '$''{'ac_cv_prog_CC'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + if test -n "$CC"; then + ac_cv_prog_CC="$CC" # Let the user override the test. +else + IFS="${IFS= }"; ac_save_ifs="$IFS"; IFS="${IFS}:" + ac_prog_rejected=no + for ac_dir in $PATH; do + test -z "$ac_dir" && ac_dir=. + if test -f $ac_dir/$ac_word; then + if test "$ac_dir/$ac_word" = "/usr/ucb/cc"; then + ac_prog_rejected=yes + continue + fi + ac_cv_prog_CC="cc" + break + fi + done + IFS="$ac_save_ifs" +if test $ac_prog_rejected = yes; then + # We found a bogon in the path, so make sure we never use it. + set dummy $ac_cv_prog_CC + shift + if test $# -gt 0; then + # We chose a different compiler from the bogus one. + # However, it has the same basename, so the bogon will be chosen + # first if we set CC to just the basename; use the full file name. + shift + set dummy "$ac_dir/$ac_word" "$@" + shift + ac_cv_prog_CC="$@" + fi +fi +fi +fi +CC="$ac_cv_prog_CC" +if test -n "$CC"; then + echo "$ac_t""$CC" 1>&6 +else + echo "$ac_t""no" 1>&6 +fi + + test -z "$CC" && { echo "configure: error: no acceptable cc found in \$PATH" 1>&2; exit 1; } +fi + +echo $ac_n "checking whether we are using GNU C""... $ac_c" 1>&6 +if eval "test \"`echo '$''{'ac_cv_prog_gcc'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + cat > conftest.c <<EOF +#ifdef __GNUC__ + yes; +#endif +EOF +if { ac_try='${CC-cc} -E conftest.c'; { (eval echo configure:606: \"$ac_try\") 1>&5; (eval $ac_try) 2>&5; }; } | egrep yes >/dev/null 2>&1; then + ac_cv_prog_gcc=yes +else + ac_cv_prog_gcc=no +fi +fi + +echo "$ac_t""$ac_cv_prog_gcc" 1>&6 +if test $ac_cv_prog_gcc = yes; then + GCC=yes + if test "${CFLAGS+set}" != set; then + echo $ac_n "checking whether ${CC-cc} accepts -g""... $ac_c" 1>&6 +if eval "test \"`echo '$''{'ac_cv_prog_gcc_g'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + echo 'void f(){}' > conftest.c +if test -z "`${CC-cc} -g -c conftest.c 2>&1`"; then + ac_cv_prog_gcc_g=yes +else + ac_cv_prog_gcc_g=no +fi +rm -f conftest* + +fi + +echo "$ac_t""$ac_cv_prog_gcc_g" 1>&6 + if test $ac_cv_prog_gcc_g = yes; then + CFLAGS="-g -O" + else + CFLAGS="-O" + fi + fi +else + GCC= + test "${CFLAGS+set}" = set || CFLAGS="-g" +fi + + +echo $ac_n "checking how to run the C preprocessor""... $ac_c" 1>&6 +# On Suns, sometimes $CPP names a directory. +if test -n "$CPP" && test -d "$CPP"; then + CPP= +fi +if test -z "$CPP"; then +if eval "test \"`echo '$''{'ac_cv_prog_CPP'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + # This must be in double quotes, not single quotes, because CPP may get + # substituted into the Makefile and "${CC-cc}" will confuse make. + CPP="${CC-cc} -E" + # On the NeXT, cc -E runs the code through the compiler's parser, + # not just through cpp. + cat > conftest.$ac_ext <<EOF +#line 659 "configure" +#include "confdefs.h" +#include <assert.h> +Syntax Error +EOF +ac_try="$ac_cpp conftest.$ac_ext >/dev/null 2>conftest.out" +{ (eval echo configure:665: \"$ac_try\") 1>&5; (eval $ac_try) 2>&5; } +ac_err=`grep -v '^ *+' conftest.out` +if test -z "$ac_err"; then + : +else + echo "$ac_err" >&5 + rm -rf conftest* + CPP="${CC-cc} -E -traditional-cpp" + cat > conftest.$ac_ext <<EOF +#line 674 "configure" +#include "confdefs.h" +#include <assert.h> +Syntax Error +EOF +ac_try="$ac_cpp conftest.$ac_ext >/dev/null 2>conftest.out" +{ (eval echo configure:680: \"$ac_try\") 1>&5; (eval $ac_try) 2>&5; } +ac_err=`grep -v '^ *+' conftest.out` +if test -z "$ac_err"; then + : +else + echo "$ac_err" >&5 + rm -rf conftest* + CPP=/lib/cpp +fi +rm -f conftest* +fi +rm -f conftest* + ac_cv_prog_CPP="$CPP" +fi + CPP="$ac_cv_prog_CPP" +else + ac_cv_prog_CPP="$CPP" +fi +echo "$ac_t""$CPP" 1>&6 + +echo $ac_n "checking for AIX""... $ac_c" 1>&6 +cat > conftest.$ac_ext <<EOF +#line 702 "configure" +#include "confdefs.h" +#ifdef _AIX + yes +#endif + +EOF +if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | + egrep "yes" >/dev/null 2>&1; then + rm -rf conftest* + echo "$ac_t""yes" 1>&6; cat >> confdefs.h <<\EOF +#define _ALL_SOURCE 1 +EOF + +else + rm -rf conftest* + echo "$ac_t""no" 1>&6 +fi +rm -f conftest* + + +ac_safe=`echo "minix/config.h" | tr './\055' '___'` +echo $ac_n "checking for minix/config.h""... $ac_c" 1>&6 +if eval "test \"`echo '$''{'ac_cv_header_$ac_safe'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + cat > conftest.$ac_ext <<EOF +#line 729 "configure" +#include "confdefs.h" +#include <minix/config.h> +EOF +ac_try="$ac_cpp conftest.$ac_ext >/dev/null 2>conftest.out" +{ (eval echo configure:734: \"$ac_try\") 1>&5; (eval $ac_try) 2>&5; } +ac_err=`grep -v '^ *+' conftest.out` +if test -z "$ac_err"; then + rm -rf conftest* + eval "ac_cv_header_$ac_safe=yes" +else + echo "$ac_err" >&5 + rm -rf conftest* + eval "ac_cv_header_$ac_safe=no" +fi +rm -f conftest* +fi +if eval "test \"`echo '$ac_cv_header_'$ac_safe`\" = yes"; then + echo "$ac_t""yes" 1>&6 + MINIX=yes +else + echo "$ac_t""no" 1>&6 +MINIX= +fi + +if test "$MINIX" = yes; then + cat >> confdefs.h <<\EOF +#define _POSIX_SOURCE 1 +EOF + + cat >> confdefs.h <<\EOF +#define _POSIX_1_SOURCE 2 +EOF + + cat >> confdefs.h <<\EOF +#define _MINIX 1 +EOF + +fi + +echo $ac_n "checking for POSIXized ISC""... $ac_c" 1>&6 +if test -d /etc/conf/kconfig.d && + grep _POSIX_VERSION /usr/include/sys/unistd.h >/dev/null 2>&1 +then + echo "$ac_t""yes" 1>&6 + ISC=yes # If later tests want to check for ISC. + cat >> confdefs.h <<\EOF +#define _POSIX_SOURCE 1 +EOF + + if test "$GCC" = yes; then + CC="$CC -posix" + else + CC="$CC -Xp" + fi +else + echo "$ac_t""no" 1>&6 + ISC= +fi + +if test "$ISC" = yes; then +CFLAGS="$CFLAGS -D_SYSV3" +fi + +if test "x$prefix" = xNONE; then +echo $ac_n "checking for prefix by ""... $ac_c" 1>&6 +# Extract the first word of "cvs", so it can be a program name with args. +set dummy cvs; ac_word=$2 +echo $ac_n "checking for $ac_word""... $ac_c" 1>&6 +if eval "test \"`echo '$''{'ac_cv_path_CVS'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + case "$CVS" in + /*) + ac_cv_path_CVS="$CVS" # Let the user override the test with a path. + ;; + *) + IFS="${IFS= }"; ac_save_ifs="$IFS"; IFS="${IFS}:" + for ac_dir in $PATH; do + test -z "$ac_dir" && ac_dir=. + if test -f $ac_dir/$ac_word; then + ac_cv_path_CVS="$ac_dir/$ac_word" + break + fi + done + IFS="$ac_save_ifs" + ;; +esac +fi +CVS="$ac_cv_path_CVS" +if test -n "$CVS"; then + echo "$ac_t""$CVS" 1>&6 +else + echo "$ac_t""no" 1>&6 +fi + + if test -n "$ac_cv_path_CVS"; then + prefix=`echo $ac_cv_path_CVS|sed 's%/[^/][^/]*//*[^/][^/]*$%%'` + fi +fi + + +# If we cannot run a trivial program, we must be cross compiling. +echo $ac_n "checking whether cross-compiling""... $ac_c" 1>&6 +if eval "test \"`echo '$''{'ac_cv_c_cross'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + if test "$cross_compiling" = yes; then + ac_cv_c_cross=yes +else +cat > conftest.$ac_ext <<EOF +#line 840 "configure" +#include "confdefs.h" +main(){return(0);} +EOF +{ (eval echo configure:844: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } +if test -s conftest && (./conftest; exit) 2>/dev/null; then + ac_cv_c_cross=no +else + ac_cv_c_cross=yes +fi +fi +rm -fr conftest* +fi + +echo "$ac_t""$ac_cv_c_cross" 1>&6 +cross_compiling=$ac_cv_c_cross + + +echo $ac_n "checking for working const""... $ac_c" 1>&6 +if eval "test \"`echo '$''{'ac_cv_c_const'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + cat > conftest.$ac_ext <<EOF +#line 863 "configure" +#include "confdefs.h" + +int main() { return 0; } +int t() { + +/* Ultrix mips cc rejects this. */ +typedef int charset[2]; const charset x; +/* SunOS 4.1.1 cc rejects this. */ +char const *const *ccp; +char **p; +/* NEC SVR4.0.2 mips cc rejects this. */ +struct point {int x, y;}; +static struct point const zero = {0,0}; +/* AIX XL C 1.02.0.0 rejects this. + It does not let you subtract one const X* pointer from another in an arm + of an if-expression whose if-part is not a constant expression */ +const char *g = "string"; +ccp = &g + (g ? g-g : 0); +/* HPUX 7.0 cc rejects these. */ +++ccp; +p = (char**) ccp; +ccp = (char const *const *) p; +{ /* SCO 3.2v4 cc rejects this. */ + char *t; + char const *s = 0 ? (char *) 0 : (char const *) 0; + + *t++ = 0; +} +{ /* Someone thinks the Sun supposedly-ANSI compiler will reject this. */ + int x[] = {25, 17}; + const int *foo = &x[0]; + ++foo; +} +{ /* Sun SC1.0 ANSI compiler rejects this -- but not the above. */ + typedef const int *iptr; + iptr p = 0; + ++p; +} +{ /* AIX XL C 1.02.0.0 rejects this saying + "k.c", line 2.27: 1506-025 (S) Operand must be a modifiable lvalue. */ + struct s { int j; const int *ap[3]; }; + struct s *b; b->j = 5; +} +{ /* ULTRIX-32 V3.1 (Rev 9) vcc rejects this */ + const int foo = 10; +} + +; return 0; } +EOF +if { (eval echo configure:913: \"$ac_compile\") 1>&5; (eval $ac_compile) 2>&5; }; then + rm -rf conftest* + ac_cv_c_const=yes +else + rm -rf conftest* + ac_cv_c_const=no +fi +rm -f conftest* + +fi + +echo "$ac_t""$ac_cv_c_const" 1>&6 +if test $ac_cv_c_const = no; then + cat >> confdefs.h <<\EOF +#define const +EOF + +fi + +echo $ac_n "checking whether char is unsigned""... $ac_c" 1>&6 +if eval "test \"`echo '$''{'ac_cv_c_char_unsigned'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + if test "$GCC" = yes; then + # GCC predefines this symbol on systems where it applies. +cat > conftest.$ac_ext <<EOF +#line 939 "configure" +#include "confdefs.h" +#ifdef __CHAR_UNSIGNED__ + yes +#endif + +EOF +if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | + egrep "yes" >/dev/null 2>&1; then + rm -rf conftest* + ac_cv_c_char_unsigned=yes +else + rm -rf conftest* + ac_cv_c_char_unsigned=no +fi +rm -f conftest* + +else +if test "$cross_compiling" = yes; then + { echo "configure: error: can not run test program while cross compiling" 1>&2; exit 1; } +else +cat > conftest.$ac_ext <<EOF +#line 961 "configure" +#include "confdefs.h" +/* volatile prevents gcc2 from optimizing the test away on sparcs. */ +#if !defined(__STDC__) || __STDC__ != 1 +#define volatile +#endif +main() { + volatile char c = 255; exit(c < 0); +} +EOF +{ (eval echo configure:971: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } +if test -s conftest && (./conftest; exit) 2>/dev/null; then + ac_cv_c_char_unsigned=yes +else + ac_cv_c_char_unsigned=no +fi +fi +rm -fr conftest* +fi +fi + +echo "$ac_t""$ac_cv_c_char_unsigned" 1>&6 +if test $ac_cv_c_char_unsigned = yes && test "$GCC" != yes; then + cat >> confdefs.h <<\EOF +#define __CHAR_UNSIGNED__ 1 +EOF + +fi + +echo $ac_n "checking for inline""... $ac_c" 1>&6 +if eval "test \"`echo '$''{'ac_cv_c_inline'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + ac_cv_c_inline=no +for ac_kw in inline __inline__ __inline; do + cat > conftest.$ac_ext <<EOF +#line 997 "configure" +#include "confdefs.h" + +int main() { return 0; } +int t() { +} $ac_kw foo() { +; return 0; } +EOF +if { (eval echo configure:1005: \"$ac_compile\") 1>&5; (eval $ac_compile) 2>&5; }; then + rm -rf conftest* + ac_cv_c_inline=$ac_kw; break +fi +rm -f conftest* + +done + +fi + +echo "$ac_t""$ac_cv_c_inline" 1>&6 +case "$ac_cv_c_inline" in + inline | yes) ;; + no) cat >> confdefs.h <<\EOF +#define inline +EOF + ;; + *) cat >> confdefs.h <<EOF +#define inline $ac_cv_c_inline +EOF + ;; +esac + + +ac_aux_dir= +for ac_dir in $srcdir $srcdir/.. $srcdir/../..; do + if test -f $ac_dir/install-sh; then + ac_aux_dir=$ac_dir + ac_install_sh="$ac_aux_dir/install-sh -c" + break + elif test -f $ac_dir/install.sh; then + ac_aux_dir=$ac_dir + ac_install_sh="$ac_aux_dir/install.sh -c" + break + fi +done +if test -z "$ac_aux_dir"; then + { echo "configure: error: can not find install-sh or install.sh in $srcdir $srcdir/.. $srcdir/../.." 1>&2; exit 1; } +fi +ac_config_guess=$ac_aux_dir/config.guess +ac_config_sub=$ac_aux_dir/config.sub +ac_configure=$ac_aux_dir/configure # This should be Cygnus configure. + +# Find a good install program. We prefer a C program (faster), +# so one script is as good as another. But avoid the broken or +# incompatible versions: +# SysV /etc/install, /usr/sbin/install +# SunOS /usr/etc/install +# IRIX /sbin/install +# AIX /bin/install +# AFS /usr/afsws/bin/install, which mishandles nonexistent args +# SVR4 /usr/ucb/install, which tries to use the nonexistent group "staff" +# ./install, which can be erroneously created by make from ./install.sh. +echo $ac_n "checking for a BSD compatible install""... $ac_c" 1>&6 +if test -z "$INSTALL"; then +if eval "test \"`echo '$''{'ac_cv_path_install'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + IFS="${IFS= }"; ac_save_ifs="$IFS"; IFS="${IFS}:" + for ac_dir in $PATH; do + # Account for people who put trailing slashes in PATH elements. + case "$ac_dir/" in + /|./|.//|/etc/*|/usr/sbin/*|/usr/etc/*|/sbin/*|/usr/afsws/bin/*|/usr/ucb/*) ;; + *) + # OSF1 and SCO ODT 3.0 have their own names for install. + for ac_prog in ginstall installbsd scoinst install; do + if test -f $ac_dir/$ac_prog; then + if test $ac_prog = install && + grep dspmsg $ac_dir/$ac_prog >/dev/null 2>&1; then + # AIX install. It has an incompatible calling convention. + # OSF/1 installbsd also uses dspmsg, but is usable. + : + else + ac_cv_path_install="$ac_dir/$ac_prog -c" + break 2 + fi + fi + done + ;; + esac + done + IFS="$ac_save_ifs" + +fi + if test "${ac_cv_path_install+set}" = set; then + INSTALL="$ac_cv_path_install" + else + # As a last resort, use the slow shell script. We don't cache a + # path for INSTALL within a source directory, because that will + # break other packages using the cache if that directory is + # removed, or if the path is relative. + INSTALL="$ac_install_sh" + fi +fi +echo "$ac_t""$INSTALL" 1>&6 + +# Use test -z because SunOS4 sh mishandles braces in ${var-val}. +# It thinks the first close brace ends the variable substitution. +test -z "$INSTALL_PROGRAM" && INSTALL_PROGRAM='${INSTALL}' + +test -z "$INSTALL_DATA" && INSTALL_DATA='${INSTALL} -m 644' + +# Extract the first word of "ranlib", so it can be a program name with args. +set dummy ranlib; ac_word=$2 +echo $ac_n "checking for $ac_word""... $ac_c" 1>&6 +if eval "test \"`echo '$''{'ac_cv_prog_RANLIB'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + if test -n "$RANLIB"; then + ac_cv_prog_RANLIB="$RANLIB" # Let the user override the test. +else + IFS="${IFS= }"; ac_save_ifs="$IFS"; IFS="${IFS}:" + for ac_dir in $PATH; do + test -z "$ac_dir" && ac_dir=. + if test -f $ac_dir/$ac_word; then + ac_cv_prog_RANLIB="ranlib" + break + fi + done + IFS="$ac_save_ifs" + test -z "$ac_cv_prog_RANLIB" && ac_cv_prog_RANLIB=":" +fi +fi +RANLIB="$ac_cv_prog_RANLIB" +if test -n "$RANLIB"; then + echo "$ac_t""$RANLIB" 1>&6 +else + echo "$ac_t""no" 1>&6 +fi + +for ac_prog in 'bison -y' byacc +do +# Extract the first word of "$ac_prog", so it can be a program name with args. +set dummy $ac_prog; ac_word=$2 +echo $ac_n "checking for $ac_word""... $ac_c" 1>&6 +if eval "test \"`echo '$''{'ac_cv_prog_YACC'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + if test -n "$YACC"; then + ac_cv_prog_YACC="$YACC" # Let the user override the test. +else + IFS="${IFS= }"; ac_save_ifs="$IFS"; IFS="${IFS}:" + for ac_dir in $PATH; do + test -z "$ac_dir" && ac_dir=. + if test -f $ac_dir/$ac_word; then + ac_cv_prog_YACC="$ac_prog" + break + fi + done + IFS="$ac_save_ifs" +fi +fi +YACC="$ac_cv_prog_YACC" +if test -n "$YACC"; then + echo "$ac_t""$YACC" 1>&6 +else + echo "$ac_t""no" 1>&6 +fi + +test -n "$YACC" && break +done +test -n "$YACC" || YACC="yacc" + +echo $ac_n "checking whether ${MAKE-make} sets \${MAKE}""... $ac_c" 1>&6 +set dummy ${MAKE-make}; ac_make=$2 +if eval "test \"`echo '$''{'ac_cv_prog_make_${ac_make}_set'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + cat > conftestmake <<\EOF +all: + @echo 'ac_maketemp="${MAKE}"' +EOF +# GNU make sometimes prints "make[1]: Entering...", which would confuse us. +eval `${MAKE-make} -f conftestmake 2>/dev/null | grep temp=` +if test -n "$ac_maketemp"; then + eval ac_cv_prog_make_${ac_make}_set=yes +else + eval ac_cv_prog_make_${ac_make}_set=no +fi +rm -f conftestmake +fi +if eval "test \"`echo '$ac_cv_prog_make_'${ac_make}_set`\" = yes"; then + echo "$ac_t""yes" 1>&6 + SET_MAKE= +else + echo "$ac_t""no" 1>&6 + SET_MAKE="MAKE=${MAKE-make}" +fi + + +# Extract the first word of "perl", so it can be a program name with args. +set dummy perl; ac_word=$2 +echo $ac_n "checking for $ac_word""... $ac_c" 1>&6 +if eval "test \"`echo '$''{'ac_cv_path_perl_path'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + case "$perl_path" in + /*) + ac_cv_path_perl_path="$perl_path" # Let the user override the test with a path. + ;; + *) + IFS="${IFS= }"; ac_save_ifs="$IFS"; IFS="${IFS}:" + for ac_dir in $PATH; do + test -z "$ac_dir" && ac_dir=. + if test -f $ac_dir/$ac_word; then + ac_cv_path_perl_path="$ac_dir/$ac_word" + break + fi + done + IFS="$ac_save_ifs" + test -z "$ac_cv_path_perl_path" && ac_cv_path_perl_path="no" + ;; +esac +fi +perl_path="$ac_cv_path_perl_path" +if test -n "$perl_path"; then + echo "$ac_t""$perl_path" 1>&6 +else + echo "$ac_t""no" 1>&6 +fi + +# Extract the first word of "csh", so it can be a program name with args. +set dummy csh; ac_word=$2 +echo $ac_n "checking for $ac_word""... $ac_c" 1>&6 +if eval "test \"`echo '$''{'ac_cv_path_csh_path'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + case "$csh_path" in + /*) + ac_cv_path_csh_path="$csh_path" # Let the user override the test with a path. + ;; + *) + IFS="${IFS= }"; ac_save_ifs="$IFS"; IFS="${IFS}:" + for ac_dir in $PATH; do + test -z "$ac_dir" && ac_dir=. + if test -f $ac_dir/$ac_word; then + ac_cv_path_csh_path="$ac_dir/$ac_word" + break + fi + done + IFS="$ac_save_ifs" + test -z "$ac_cv_path_csh_path" && ac_cv_path_csh_path="no" + ;; +esac +fi +csh_path="$ac_cv_path_csh_path" +if test -n "$csh_path"; then + echo "$ac_t""$csh_path" 1>&6 +else + echo "$ac_t""no" 1>&6 +fi + + +# Pull the hash mark out of the macro call to avoid m4 problems. +ac_msg="whether #! works in shell scripts" +echo $ac_n "checking $ac_msg""... $ac_c" 1>&6 +if eval "test \"`echo '$''{'ac_cv_sys_interpreter'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + echo '#! /bin/cat +exit 69 +' > conftest +chmod u+x conftest +(SHELL=/bin/sh; export SHELL; ./conftest >/dev/null) +if test $? -ne 69; then + ac_cv_sys_interpreter=yes +else + ac_cv_sys_interpreter=no +fi +rm -f conftest +fi + +echo "$ac_t""$ac_cv_sys_interpreter" 1>&6 + +if test X"$ac_cv_sys_interpreter" != X"yes" ; then + # silly trick to avoid problems in AC macros... + ac_msg='perl scripts using #! may not be invoked properly' + echo "configure: warning: $ac_msg" 1>&2 +fi + +echo $ac_n "checking for ANSI C header files""... $ac_c" 1>&6 +if eval "test \"`echo '$''{'ac_cv_header_stdc'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + cat > conftest.$ac_ext <<EOF +#line 1290 "configure" +#include "confdefs.h" +#include <stdlib.h> +#include <stdarg.h> +#include <string.h> +#include <float.h> +EOF +ac_try="$ac_cpp conftest.$ac_ext >/dev/null 2>conftest.out" +{ (eval echo configure:1298: \"$ac_try\") 1>&5; (eval $ac_try) 2>&5; } +ac_err=`grep -v '^ *+' conftest.out` +if test -z "$ac_err"; then + rm -rf conftest* + ac_cv_header_stdc=yes +else + echo "$ac_err" >&5 + rm -rf conftest* + ac_cv_header_stdc=no +fi +rm -f conftest* + +if test $ac_cv_header_stdc = yes; then + # SunOS 4.x string.h does not declare mem*, contrary to ANSI. +cat > conftest.$ac_ext <<EOF +#line 1313 "configure" +#include "confdefs.h" +#include <string.h> +EOF +if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | + egrep "memchr" >/dev/null 2>&1; then + : +else + rm -rf conftest* + ac_cv_header_stdc=no +fi +rm -f conftest* + +fi + +if test $ac_cv_header_stdc = yes; then + # ISC 2.0.2 stdlib.h does not declare free, contrary to ANSI. +cat > conftest.$ac_ext <<EOF +#line 1331 "configure" +#include "confdefs.h" +#include <stdlib.h> +EOF +if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | + egrep "free" >/dev/null 2>&1; then + : +else + rm -rf conftest* + ac_cv_header_stdc=no +fi +rm -f conftest* + +fi + +if test $ac_cv_header_stdc = yes; then + # /bin/cc in Irix-4.0.5 gets non-ANSI ctype macros unless using -ansi. +if test "$cross_compiling" = yes; then + : +else +cat > conftest.$ac_ext <<EOF +#line 1352 "configure" +#include "confdefs.h" +#include <ctype.h> +#define ISLOWER(c) ('a' <= (c) && (c) <= 'z') +#define TOUPPER(c) (ISLOWER(c) ? 'A' + ((c) - 'a') : (c)) +#define XOR(e, f) (((e) && !(f)) || (!(e) && (f))) +int main () { int i; for (i = 0; i < 256; i++) +if (XOR (islower (i), ISLOWER (i)) || toupper (i) != TOUPPER (i)) exit(2); +exit (0); } + +EOF +{ (eval echo configure:1363: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } +if test -s conftest && (./conftest; exit) 2>/dev/null; then + : +else + ac_cv_header_stdc=no +fi +fi +rm -fr conftest* +fi +fi + +echo "$ac_t""$ac_cv_header_stdc" 1>&6 +if test $ac_cv_header_stdc = yes; then + cat >> confdefs.h <<\EOF +#define STDC_HEADERS 1 +EOF + +fi + +for ac_hdr in errno.h unistd.h string.h memory.h utime.h fcntl.h ndbm.h \ + sys/param.h sys/select.h sys/time.h sys/timeb.h \ + io.h direct.h sys/bsdtypes.h sys/resource.h +do +ac_safe=`echo "$ac_hdr" | tr './\055' '___'` +echo $ac_n "checking for $ac_hdr""... $ac_c" 1>&6 +if eval "test \"`echo '$''{'ac_cv_header_$ac_safe'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + cat > conftest.$ac_ext <<EOF +#line 1392 "configure" +#include "confdefs.h" +#include <$ac_hdr> +EOF +ac_try="$ac_cpp conftest.$ac_ext >/dev/null 2>conftest.out" +{ (eval echo configure:1397: \"$ac_try\") 1>&5; (eval $ac_try) 2>&5; } +ac_err=`grep -v '^ *+' conftest.out` +if test -z "$ac_err"; then + rm -rf conftest* + eval "ac_cv_header_$ac_safe=yes" +else + echo "$ac_err" >&5 + rm -rf conftest* + eval "ac_cv_header_$ac_safe=no" +fi +rm -f conftest* +fi +if eval "test \"`echo '$ac_cv_header_'$ac_safe`\" = yes"; then + echo "$ac_t""yes" 1>&6 + ac_tr_hdr=HAVE_`echo $ac_hdr | tr 'abcdefghijklmnopqrstuvwxyz./\055' 'ABCDEFGHIJKLMNOPQRSTUVWXYZ___'` + cat >> confdefs.h <<EOF +#define $ac_tr_hdr 1 +EOF + +else + echo "$ac_t""no" 1>&6 +fi +done + +echo $ac_n "checking for sys/wait.h that is POSIX.1 compatible""... $ac_c" 1>&6 +if eval "test \"`echo '$''{'ac_cv_header_sys_wait_h'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + cat > conftest.$ac_ext <<EOF +#line 1426 "configure" +#include "confdefs.h" +#include <sys/types.h> +#include <sys/wait.h> +#ifndef WEXITSTATUS +#define WEXITSTATUS(stat_val) ((unsigned)(stat_val) >> 8) +#endif +#ifndef WIFEXITED +#define WIFEXITED(stat_val) (((stat_val) & 255) == 0) +#endif +int main() { return 0; } +int t() { +int s; +wait (&s); +s = WIFEXITED (s) ? WEXITSTATUS (s) : 1; +; return 0; } +EOF +if { (eval echo configure:1443: \"$ac_compile\") 1>&5; (eval $ac_compile) 2>&5; }; then + rm -rf conftest* + ac_cv_header_sys_wait_h=yes +else + rm -rf conftest* + ac_cv_header_sys_wait_h=no +fi +rm -f conftest* + +fi + +echo "$ac_t""$ac_cv_header_sys_wait_h" 1>&6 +if test $ac_cv_header_sys_wait_h = yes; then + cat >> confdefs.h <<\EOF +#define HAVE_SYS_WAIT_H 1 +EOF + +fi + +echo $ac_n "checking whether stat file-mode macros are broken""... $ac_c" 1>&6 +if eval "test \"`echo '$''{'ac_cv_header_stat_broken'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + cat > conftest.$ac_ext <<EOF +#line 1467 "configure" +#include "confdefs.h" +#include <sys/types.h> +#include <sys/stat.h> + +#if defined(S_ISBLK) && defined(S_IFDIR) +# if S_ISBLK (S_IFDIR) +You lose. +# endif +#endif + +#if defined(S_ISBLK) && defined(S_IFCHR) +# if S_ISBLK (S_IFCHR) +You lose. +# endif +#endif + +#if defined(S_ISLNK) && defined(S_IFREG) +# if S_ISLNK (S_IFREG) +You lose. +# endif +#endif + +#if defined(S_ISSOCK) && defined(S_IFREG) +# if S_ISSOCK (S_IFREG) +You lose. +# endif +#endif + +EOF +if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | + egrep "You lose" >/dev/null 2>&1; then + rm -rf conftest* + ac_cv_header_stat_broken=yes +else + rm -rf conftest* + ac_cv_header_stat_broken=no +fi +rm -f conftest* + +fi + +echo "$ac_t""$ac_cv_header_stat_broken" 1>&6 +if test $ac_cv_header_stat_broken = yes; then + cat >> confdefs.h <<\EOF +#define STAT_MACROS_BROKEN 1 +EOF + +fi + +echo $ac_n "checking whether time.h and sys/time.h may both be included""... $ac_c" 1>&6 +if eval "test \"`echo '$''{'ac_cv_header_time'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + cat > conftest.$ac_ext <<EOF +#line 1522 "configure" +#include "confdefs.h" +#include <sys/types.h> +#include <sys/time.h> +#include <time.h> +int main() { return 0; } +int t() { +struct tm *tp; +; return 0; } +EOF +if { (eval echo configure:1532: \"$ac_compile\") 1>&5; (eval $ac_compile) 2>&5; }; then + rm -rf conftest* + ac_cv_header_time=yes +else + rm -rf conftest* + ac_cv_header_time=no +fi +rm -f conftest* + +fi + +echo "$ac_t""$ac_cv_header_time" 1>&6 +if test $ac_cv_header_time = yes; then + cat >> confdefs.h <<\EOF +#define TIME_WITH_SYS_TIME 1 +EOF + +fi + +ac_header_dirent=no +for ac_hdr in dirent.h sys/ndir.h sys/dir.h ndir.h +do +ac_safe=`echo "$ac_hdr" | tr './\055' '___'` +echo $ac_n "checking for $ac_hdr that defines DIR""... $ac_c" 1>&6 +if eval "test \"`echo '$''{'ac_cv_header_dirent_$ac_safe'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + cat > conftest.$ac_ext <<EOF +#line 1560 "configure" +#include "confdefs.h" +#include <sys/types.h> +#include <$ac_hdr> +int main() { return 0; } +int t() { +DIR *dirp = 0; +; return 0; } +EOF +if { (eval echo configure:1569: \"$ac_compile\") 1>&5; (eval $ac_compile) 2>&5; }; then + rm -rf conftest* + eval "ac_cv_header_dirent_$ac_safe=yes" +else + rm -rf conftest* + eval "ac_cv_header_dirent_$ac_safe=no" +fi +rm -f conftest* + +fi +if eval "test \"`echo '$ac_cv_header_dirent_'$ac_safe`\" = yes"; then + echo "$ac_t""yes" 1>&6 + ac_tr_hdr=HAVE_`echo $ac_hdr | tr 'abcdedfghijklmnopqrstuvwxyz./\055' 'ABCDEDFGHIJKLMNOPQRSTUVWXYZ___'` + cat >> confdefs.h <<EOF +#define $ac_tr_hdr 1 +EOF + ac_header_dirent=$ac_hdr; break +else + echo "$ac_t""no" 1>&6 +fi +done +# Two versions of opendir et al. are in -ldir and -lx on SCO Xenix. +if test $ac_header_dirent = dirent.h; then +echo $ac_n "checking for -ldir""... $ac_c" 1>&6 +ac_lib_var=`echo dir_opendir | tr '.-/+' '___p'` +if eval "test \"`echo '$''{'ac_cv_lib_$ac_lib_var'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + ac_save_LIBS="$LIBS" +LIBS="-ldir $LIBS" +cat > conftest.$ac_ext <<EOF +#line 1600 "configure" +#include "confdefs.h" +/* Override any gcc2 internal prototype to avoid an error. */ +char opendir(); + +int main() { return 0; } +int t() { +opendir() +; return 0; } +EOF +if { (eval echo configure:1610: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; }; then + rm -rf conftest* + eval "ac_cv_lib_$ac_lib_var=yes" +else + rm -rf conftest* + eval "ac_cv_lib_$ac_lib_var=no" +fi +rm -f conftest* +LIBS="$ac_save_LIBS" + +fi +if eval "test \"`echo '$ac_cv_lib_'$ac_lib_var`\" = yes"; then + echo "$ac_t""yes" 1>&6 + LIBS="$LIBS -ldir" +else + echo "$ac_t""no" 1>&6 +fi + +else +echo $ac_n "checking for -lx""... $ac_c" 1>&6 +ac_lib_var=`echo x_opendir | tr '.-/+' '___p'` +if eval "test \"`echo '$''{'ac_cv_lib_$ac_lib_var'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + ac_save_LIBS="$LIBS" +LIBS="-lx $LIBS" +cat > conftest.$ac_ext <<EOF +#line 1637 "configure" +#include "confdefs.h" +/* Override any gcc2 internal prototype to avoid an error. */ +char opendir(); + +int main() { return 0; } +int t() { +opendir() +; return 0; } +EOF +if { (eval echo configure:1647: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; }; then + rm -rf conftest* + eval "ac_cv_lib_$ac_lib_var=yes" +else + rm -rf conftest* + eval "ac_cv_lib_$ac_lib_var=no" +fi +rm -f conftest* +LIBS="$ac_save_LIBS" + +fi +if eval "test \"`echo '$ac_cv_lib_'$ac_lib_var`\" = yes"; then + echo "$ac_t""yes" 1>&6 + LIBS="$LIBS -lx" +else + echo "$ac_t""no" 1>&6 +fi + +fi + +echo $ac_n "checking return type of signal handlers""... $ac_c" 1>&6 +if eval "test \"`echo '$''{'ac_cv_type_signal'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + cat > conftest.$ac_ext <<EOF +#line 1672 "configure" +#include "confdefs.h" +#include <sys/types.h> +#include <signal.h> +#ifdef signal +#undef signal +#endif +#ifdef __cplusplus +extern "C" void (*signal (int, void (*)(int)))(int); +#else +void (*signal ()) (); +#endif + +int main() { return 0; } +int t() { +int i; +; return 0; } +EOF +if { (eval echo configure:1690: \"$ac_compile\") 1>&5; (eval $ac_compile) 2>&5; }; then + rm -rf conftest* + ac_cv_type_signal=void +else + rm -rf conftest* + ac_cv_type_signal=int +fi +rm -f conftest* + +fi + +echo "$ac_t""$ac_cv_type_signal" 1>&6 +cat >> confdefs.h <<EOF +#define RETSIGTYPE $ac_cv_type_signal +EOF + + +echo $ac_n "checking for uid_t in sys/types.h""... $ac_c" 1>&6 +if eval "test \"`echo '$''{'ac_cv_type_uid_t'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + cat > conftest.$ac_ext <<EOF +#line 1712 "configure" +#include "confdefs.h" +#include <sys/types.h> +EOF +if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | + egrep "uid_t" >/dev/null 2>&1; then + rm -rf conftest* + ac_cv_type_uid_t=yes +else + rm -rf conftest* + ac_cv_type_uid_t=no +fi +rm -f conftest* + +fi + +echo "$ac_t""$ac_cv_type_uid_t" 1>&6 +if test $ac_cv_type_uid_t = no; then + cat >> confdefs.h <<\EOF +#define uid_t int +EOF + + cat >> confdefs.h <<\EOF +#define gid_t int +EOF + +fi + +echo $ac_n "checking for mode_t""... $ac_c" 1>&6 +if eval "test \"`echo '$''{'ac_cv_type_mode_t'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + cat > conftest.$ac_ext <<EOF +#line 1745 "configure" +#include "confdefs.h" +#include <sys/types.h> +#if STDC_HEADERS +#include <stdlib.h> +#endif +EOF +if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | + egrep "mode_t" >/dev/null 2>&1; then + rm -rf conftest* + ac_cv_type_mode_t=yes +else + rm -rf conftest* + ac_cv_type_mode_t=no +fi +rm -f conftest* + +fi +echo "$ac_t""$ac_cv_type_mode_t" 1>&6 +if test $ac_cv_type_mode_t = no; then + cat >> confdefs.h <<\EOF +#define mode_t int +EOF + +fi + +echo $ac_n "checking for size_t""... $ac_c" 1>&6 +if eval "test \"`echo '$''{'ac_cv_type_size_t'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + cat > conftest.$ac_ext <<EOF +#line 1776 "configure" +#include "confdefs.h" +#include <sys/types.h> +#if STDC_HEADERS +#include <stdlib.h> +#endif +EOF +if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | + egrep "size_t" >/dev/null 2>&1; then + rm -rf conftest* + ac_cv_type_size_t=yes +else + rm -rf conftest* + ac_cv_type_size_t=no +fi +rm -f conftest* + +fi +echo "$ac_t""$ac_cv_type_size_t" 1>&6 +if test $ac_cv_type_size_t = no; then + cat >> confdefs.h <<\EOF +#define size_t unsigned +EOF + +fi + +echo $ac_n "checking for pid_t""... $ac_c" 1>&6 +if eval "test \"`echo '$''{'ac_cv_type_pid_t'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + cat > conftest.$ac_ext <<EOF +#line 1807 "configure" +#include "confdefs.h" +#include <sys/types.h> +#if STDC_HEADERS +#include <stdlib.h> +#endif +EOF +if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | + egrep "pid_t" >/dev/null 2>&1; then + rm -rf conftest* + ac_cv_type_pid_t=yes +else + rm -rf conftest* + ac_cv_type_pid_t=no +fi +rm -f conftest* + +fi +echo "$ac_t""$ac_cv_type_pid_t" 1>&6 +if test $ac_cv_type_pid_t = no; then + cat >> confdefs.h <<\EOF +#define pid_t int +EOF + +fi + +for ac_func in getwd mkdir rename strdup 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 + echo $ac_n "(cached) $ac_c" 1>&6 +else + cat > conftest.$ac_ext <<EOF +#line 1840 "configure" +#include "confdefs.h" +/* System header to define __stub macros and hopefully few prototypes, + which can conflict with char $ac_func(); below. */ +#include <assert.h> +/* Override any gcc2 internal prototype to avoid an error. */ +char $ac_func(); + +int main() { return 0; } +int t() { + +/* The GNU C library defines this for functions which it implements + to always fail with ENOSYS. Some functions are actually named + something starting with __ and the normal name is an alias. */ +#if defined (__stub_$ac_func) || defined (__stub___$ac_func) +choke me +#else +$ac_func(); +#endif + +; return 0; } +EOF +if { (eval echo configure:1862: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; }; then + rm -rf conftest* + eval "ac_cv_func_$ac_func=yes" +else + rm -rf conftest* + eval "ac_cv_func_$ac_func=no" +fi +rm -f conftest* + +fi +if eval "test \"`echo '$ac_cv_func_'$ac_func`\" = yes"; then + echo "$ac_t""yes" 1>&6 + : +else + echo "$ac_t""no" 1>&6 +LIBOBJS="$LIBOBJS ${ac_func}.o" +fi + +done + +for ac_func in fchmod fsync ftime mkfifo putenv setvbuf vfork vprintf ftruncate timezone getpagesize initgroups fchdir sigaction sigprocmask sigvec sigsetmask sigblock +do +echo $ac_n "checking for $ac_func""... $ac_c" 1>&6 +if eval "test \"`echo '$''{'ac_cv_func_$ac_func'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + cat > conftest.$ac_ext <<EOF +#line 1889 "configure" +#include "confdefs.h" +/* System header to define __stub macros and hopefully few prototypes, + which can conflict with char $ac_func(); below. */ +#include <assert.h> +/* Override any gcc2 internal prototype to avoid an error. */ +char $ac_func(); + +int main() { return 0; } +int t() { + +/* The GNU C library defines this for functions which it implements + to always fail with ENOSYS. Some functions are actually named + something starting with __ and the normal name is an alias. */ +#if defined (__stub_$ac_func) || defined (__stub___$ac_func) +choke me +#else +$ac_func(); +#endif + +; return 0; } +EOF +if { (eval echo configure:1911: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; }; then + rm -rf conftest* + eval "ac_cv_func_$ac_func=yes" +else + rm -rf conftest* + eval "ac_cv_func_$ac_func=no" +fi +rm -f conftest* + +fi +if eval "test \"`echo '$ac_cv_func_'$ac_func`\" = yes"; then + echo "$ac_t""yes" 1>&6 + ac_tr_func=HAVE_`echo $ac_func | tr 'abcdefghijklmnopqrstuvwxyz' 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'` + cat >> confdefs.h <<EOF +#define $ac_tr_func 1 +EOF + +else + echo "$ac_t""no" 1>&6 +fi +done + +echo $ac_n "checking for re_exec""... $ac_c" 1>&6 +if eval "test \"`echo '$''{'ac_cv_func_re_exec'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + cat > conftest.$ac_ext <<EOF +#line 1938 "configure" +#include "confdefs.h" +/* System header to define __stub macros and hopefully few prototypes, + which can conflict with char re_exec(); below. */ +#include <assert.h> +/* Override any gcc2 internal prototype to avoid an error. */ +char re_exec(); + +int main() { return 0; } +int t() { + +/* The GNU C library defines this for functions which it implements + to always fail with ENOSYS. Some functions are actually named + something starting with __ and the normal name is an alias. */ +#if defined (__stub_re_exec) || defined (__stub___re_exec) +choke me +#else +re_exec(); +#endif + +; return 0; } +EOF +if { (eval echo configure:1960: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; }; then + rm -rf conftest* + eval "ac_cv_func_re_exec=yes" +else + rm -rf conftest* + eval "ac_cv_func_re_exec=no" +fi +rm -f conftest* + +fi +if eval "test \"`echo '$ac_cv_func_'re_exec`\" = yes"; then + echo "$ac_t""yes" 1>&6 + : +else + echo "$ac_t""no" 1>&6 +LIBOBJS="$LIBOBJS regex.o" +fi + +echo $ac_n "checking whether utime accepts a null argument""... $ac_c" 1>&6 +if eval "test \"`echo '$''{'ac_cv_func_utime_null'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + rm -f conftestdata; > conftestdata +# Sequent interprets utime(file, 0) to mean use start of epoch. Wrong. +if test "$cross_compiling" = yes; then + ac_cv_func_utime_null=no +else +cat > conftest.$ac_ext <<EOF +#line 1988 "configure" +#include "confdefs.h" +#include <sys/types.h> +#include <sys/stat.h> +main() { +struct stat s, t; +exit(!(stat ("conftestdata", &s) == 0 && utime("conftestdata", (long *)0) == 0 +&& stat("conftestdata", &t) == 0 && t.st_mtime >= s.st_mtime +&& t.st_mtime - s.st_mtime < 120)); +} +EOF +{ (eval echo configure:1999: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } +if test -s conftest && (./conftest; exit) 2>/dev/null; then + ac_cv_func_utime_null=yes +else + ac_cv_func_utime_null=no +fi +fi +rm -fr conftest* +rm -f core core.* *.core +fi + +echo "$ac_t""$ac_cv_func_utime_null" 1>&6 +if test $ac_cv_func_utime_null = yes; then + cat >> confdefs.h <<\EOF +#define HAVE_UTIME_NULL 1 +EOF + +fi + +echo $ac_n "checking for long file names""... $ac_c" 1>&6 +if eval "test \"`echo '$''{'ac_cv_sys_long_file_names'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + ac_cv_sys_long_file_names=yes +# Test for long file names in all the places we know might matter: +# . the current directory, where building will happen +# /tmp where it might want to write temporary files +# /var/tmp likewise +# /usr/tmp likewise +# $prefix/lib where we will be installing things +# $exec_prefix/lib likewise +# eval it to expand exec_prefix. +for ac_dir in `eval echo . /tmp /var/tmp /usr/tmp $prefix/lib $exec_prefix/lib` ; do + test -d $ac_dir || continue + test -w $ac_dir || continue # It is less confusing to not echo anything here. + (echo 1 > $ac_dir/conftest9012345) 2>/dev/null + (echo 2 > $ac_dir/conftest9012346) 2>/dev/null + val=`cat $ac_dir/conftest9012345 2>/dev/null` + if test ! -f $ac_dir/conftest9012345 || test "$val" != 1; then + ac_cv_sys_long_file_names=no + rm -f $ac_dir/conftest9012345 $ac_dir/conftest9012346 2>/dev/null + break + fi + rm -f $ac_dir/conftest9012345 $ac_dir/conftest9012346 2>/dev/null +done +fi + +echo "$ac_t""$ac_cv_sys_long_file_names" 1>&6 +if test $ac_cv_sys_long_file_names = yes; then + cat >> confdefs.h <<\EOF +#define HAVE_LONG_FILE_NAMES 1 +EOF + +fi + + +echo $ac_n "checking for working fnmatch function""... $ac_c" 1>&6 +if eval "test \"`echo '$''{'ccvs_cv_sys_working_fnmatch'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + if test "$cross_compiling" = yes; then + ccvs_cv_sys_working_fnmatch=no +else +cat > conftest.$ac_ext <<EOF +#line 2063 "configure" +#include "confdefs.h" + +#include <fnmatch.h> +int +main () +{ + exit ((fnmatch ("a", "a", FNM_PATHNAME) == 0 + && fnmatch ("a", "b", FNM_PATHNAME) == FNM_NOMATCH) + ? 0 : 1); +} +EOF +{ (eval echo configure:2075: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } +if test -s conftest && (./conftest; exit) 2>/dev/null; then + ccvs_cv_sys_working_fnmatch=yes +else + ccvs_cv_sys_working_fnmatch=no +fi +fi +rm -fr conftest* +fi + +if test $ccvs_cv_sys_working_fnmatch = no; then + LIBOBJS="$LIBOBJS fnmatch.o" +fi +echo "$ac_t""$ccvs_cv_sys_working_fnmatch" 1>&6 + +KRB4=/usr/kerberos + +# Check whether --with-krb4 or --without-krb4 was given. +if test "${with_krb4+set}" = set; then + withval="$with_krb4" + KRB4=$withval +fi +echo "default place for krb4 is $KRB4" + + +echo $ac_n "checking size of long""... $ac_c" 1>&6 +if eval "test \"`echo '$''{'ac_cv_sizeof_long'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + if test "$cross_compiling" = yes; then + { echo "configure: error: can not run test program while cross compiling" 1>&2; exit 1; } +else +cat > conftest.$ac_ext <<EOF +#line 2108 "configure" +#include "confdefs.h" +#include <stdio.h> +main() +{ + FILE *f=fopen("conftestval", "w"); + if (!f) exit(1); + fprintf(f, "%d\n", sizeof(long)); + exit(0); +} +EOF +{ (eval echo configure:2119: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } +if test -s conftest && (./conftest; exit) 2>/dev/null; then + ac_cv_sizeof_long=`cat conftestval` +else + ac_cv_sizeof_long=0 +fi +fi +rm -fr conftest* +fi +echo "$ac_t""$ac_cv_sizeof_long" 1>&6 +cat >> confdefs.h <<EOF +#define SIZEOF_LONG $ac_cv_sizeof_long +EOF + + +echo $ac_n "checking size of int""... $ac_c" 1>&6 +if eval "test \"`echo '$''{'ac_cv_sizeof_int'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + if test "$cross_compiling" = yes; then + { echo "configure: error: can not run test program while cross compiling" 1>&2; exit 1; } +else +cat > conftest.$ac_ext <<EOF +#line 2142 "configure" +#include "confdefs.h" +#include <stdio.h> +main() +{ + FILE *f=fopen("conftestval", "w"); + if (!f) exit(1); + fprintf(f, "%d\n", sizeof(int)); + exit(0); +} +EOF +{ (eval echo configure:2153: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } +if test -s conftest && (./conftest; exit) 2>/dev/null; then + ac_cv_sizeof_int=`cat conftestval` +else + ac_cv_sizeof_int=0 +fi +fi +rm -fr conftest* +fi +echo "$ac_t""$ac_cv_sizeof_int" 1>&6 +cat >> confdefs.h <<EOF +#define SIZEOF_INT $ac_cv_sizeof_int +EOF + + + +krb_h= +echo $ac_n "checking for krb.h""... $ac_c" 1>&6 +cat > conftest.$ac_ext <<EOF +#line 2172 "configure" +#include "confdefs.h" +#include <krb.h> +int main() { return 0; } +int t() { +int i; +; return 0; } +EOF +if { (eval echo configure:2180: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; }; then + rm -rf conftest* + krb_h=yes krb_incdir= +else + rm -rf conftest* + if test "$cross_compiling" != yes && test -r $KRB4/include/krb.h; then + hold_cflags=$CFLAGS + CFLAGS="$CFLAGS -I$KRB4/include" + cat > conftest.$ac_ext <<EOF +#line 2189 "configure" +#include "confdefs.h" +#include <krb.h> +int main() { return 0; } +int t() { +int i; +; return 0; } +EOF +if { (eval echo configure:2197: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; }; then + rm -rf conftest* + krb_h=yes krb_incdir=$KRB4/include +fi +rm -f conftest* + + CFLAGS=$hold_cflags + fi +fi +rm -f conftest* + +if test -z "$krb_h"; then + cat > conftest.$ac_ext <<EOF +#line 2210 "configure" +#include "confdefs.h" +#include <krb.h> +int main() { return 0; } +int t() { +int i; +; return 0; } +EOF +if { (eval echo configure:2218: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; }; then + rm -rf conftest* + krb_h=yes krb_incdir= +else + rm -rf conftest* + if test "$cross_compiling" != yes && test -r $KRB4/include/kerberosIV/krb.h; then + hold_cflags=$CFLAGS + CFLAGS="$CFLAGS -I$KRB4/include/kerberosIV" + cat > conftest.$ac_ext <<EOF +#line 2227 "configure" +#include "confdefs.h" +#include <krb.h> +int main() { return 0; } +int t() { +int i; +; return 0; } +EOF +if { (eval echo configure:2235: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; }; then + rm -rf conftest* + krb_h=yes krb_incdir=$KRB4/include/kerberosIV +fi +rm -f conftest* + + CFLAGS=$hold_cflags + fi +fi +rm -f conftest* + +fi +echo "$ac_t""$krb_h" 1>&6 + +if test -n "$krb_h"; then + krb_lib= + echo $ac_n "checking for -lkrb""... $ac_c" 1>&6 +ac_lib_var=`echo krb_printf | tr '.-/+' '___p'` +if eval "test \"`echo '$''{'ac_cv_lib_$ac_lib_var'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + ac_save_LIBS="$LIBS" +LIBS="-lkrb $LIBS" +cat > conftest.$ac_ext <<EOF +#line 2259 "configure" +#include "confdefs.h" +/* Override any gcc2 internal prototype to avoid an error. */ +char printf(); + +int main() { return 0; } +int t() { +printf() +; return 0; } +EOF +if { (eval echo configure:2269: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; }; then + rm -rf conftest* + eval "ac_cv_lib_$ac_lib_var=yes" +else + rm -rf conftest* + eval "ac_cv_lib_$ac_lib_var=no" +fi +rm -f conftest* +LIBS="$ac_save_LIBS" + +fi +if eval "test \"`echo '$ac_cv_lib_'$ac_lib_var`\" = yes"; then + echo "$ac_t""yes" 1>&6 + krb_lib=yes krb_libdir= +else + echo "$ac_t""no" 1>&6 +if test "$cross_compiling" != yes && test -r $KRB4/lib/libkrb.a; then + krb_lib=yes krb_libdir=$KRB4/lib + fi +fi + + if test -n "$krb_lib"; then + cat >> confdefs.h <<\EOF +#define HAVE_KERBEROS 1 +EOF + + test -n "${krb_libdir}" && LIBS="${LIBS} -L${krb_libdir}" + LIBS="${LIBS} -lkrb" + echo $ac_n "checking for -ldes""... $ac_c" 1>&6 +ac_lib_var=`echo des_printf | tr '.-/+' '___p'` +if eval "test \"`echo '$''{'ac_cv_lib_$ac_lib_var'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + ac_save_LIBS="$LIBS" +LIBS="-ldes $LIBS" +cat > conftest.$ac_ext <<EOF +#line 2305 "configure" +#include "confdefs.h" +/* Override any gcc2 internal prototype to avoid an error. */ +char printf(); + +int main() { return 0; } +int t() { +printf() +; return 0; } +EOF +if { (eval echo configure:2315: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; }; then + rm -rf conftest* + eval "ac_cv_lib_$ac_lib_var=yes" +else + rm -rf conftest* + eval "ac_cv_lib_$ac_lib_var=no" +fi +rm -f conftest* +LIBS="$ac_save_LIBS" + +fi +if eval "test \"`echo '$ac_cv_lib_'$ac_lib_var`\" = yes"; then + echo "$ac_t""yes" 1>&6 + LIBS="${LIBS} -ldes" +else + echo "$ac_t""no" 1>&6 +fi + + if test -n "$krb_incdir"; then + includeopt="${includeopt} -I$krb_incdir" + + fi + fi +fi +for ac_func in krb_get_err_text +do +echo $ac_n "checking for $ac_func""... $ac_c" 1>&6 +if eval "test \"`echo '$''{'ac_cv_func_$ac_func'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + cat > conftest.$ac_ext <<EOF +#line 2346 "configure" +#include "confdefs.h" +/* System header to define __stub macros and hopefully few prototypes, + which can conflict with char $ac_func(); below. */ +#include <assert.h> +/* Override any gcc2 internal prototype to avoid an error. */ +char $ac_func(); + +int main() { return 0; } +int t() { + +/* The GNU C library defines this for functions which it implements + to always fail with ENOSYS. Some functions are actually named + something starting with __ and the normal name is an alias. */ +#if defined (__stub_$ac_func) || defined (__stub___$ac_func) +choke me +#else +$ac_func(); +#endif + +; return 0; } +EOF +if { (eval echo configure:2368: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; }; then + rm -rf conftest* + eval "ac_cv_func_$ac_func=yes" +else + rm -rf conftest* + eval "ac_cv_func_$ac_func=no" +fi +rm -f conftest* + +fi +if eval "test \"`echo '$ac_cv_func_'$ac_func`\" = yes"; then + echo "$ac_t""yes" 1>&6 + ac_tr_func=HAVE_`echo $ac_func | tr 'abcdefghijklmnopqrstuvwxyz' 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'` + cat >> confdefs.h <<EOF +#define $ac_tr_func 1 +EOF + +else + echo "$ac_t""no" 1>&6 +fi +done + +# If we can't find connect, try looking in -lsocket, -lnsl, and -linet. +# The Irix 5 libc.so has connect and gethostbyname, but Irix 5 also has +# libsocket.so which has a bad implementation of gethostbyname (it +# only looks in /etc/hosts), so we only look for -lsocket if we need +# it. +unset ac_cv_func_connect +echo $ac_n "checking for connect""... $ac_c" 1>&6 +if eval "test \"`echo '$''{'ac_cv_func_connect'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + cat > conftest.$ac_ext <<EOF +#line 2401 "configure" +#include "confdefs.h" +/* System header to define __stub macros and hopefully few prototypes, + which can conflict with char connect(); below. */ +#include <assert.h> +/* Override any gcc2 internal prototype to avoid an error. */ +char connect(); + +int main() { return 0; } +int t() { + +/* The GNU C library defines this for functions which it implements + to always fail with ENOSYS. Some functions are actually named + something starting with __ and the normal name is an alias. */ +#if defined (__stub_connect) || defined (__stub___connect) +choke me +#else +connect(); +#endif + +; return 0; } +EOF +if { (eval echo configure:2423: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; }; then + rm -rf conftest* + eval "ac_cv_func_connect=yes" +else + rm -rf conftest* + eval "ac_cv_func_connect=no" +fi +rm -f conftest* + +fi +if eval "test \"`echo '$ac_cv_func_'connect`\" = yes"; then + echo "$ac_t""yes" 1>&6 + : +else + echo "$ac_t""no" 1>&6 +case "$LIBS" in +*-lnsl*) ;; +*) echo $ac_n "checking for -lnsl_s""... $ac_c" 1>&6 +ac_lib_var=`echo nsl_s_printf | tr '.-/+' '___p'` +if eval "test \"`echo '$''{'ac_cv_lib_$ac_lib_var'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + ac_save_LIBS="$LIBS" +LIBS="-lnsl_s $LIBS" +cat > conftest.$ac_ext <<EOF +#line 2448 "configure" +#include "confdefs.h" +/* Override any gcc2 internal prototype to avoid an error. */ +char printf(); + +int main() { return 0; } +int t() { +printf() +; return 0; } +EOF +if { (eval echo configure:2458: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; }; then + rm -rf conftest* + eval "ac_cv_lib_$ac_lib_var=yes" +else + rm -rf conftest* + eval "ac_cv_lib_$ac_lib_var=no" +fi +rm -f conftest* +LIBS="$ac_save_LIBS" + +fi +if eval "test \"`echo '$ac_cv_lib_'$ac_lib_var`\" = yes"; then + echo "$ac_t""yes" 1>&6 + ac_tr_lib=HAVE_LIB`echo nsl_s | tr 'abcdefghijklmnopqrstuvwxyz' 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'` + cat >> confdefs.h <<EOF +#define $ac_tr_lib 1 +EOF + + LIBS="-lnsl_s $LIBS" + +else + echo "$ac_t""no" 1>&6 +fi + ;; +esac +case "$LIBS" in +*-lnsl*) ;; +*) echo $ac_n "checking for -lnsl""... $ac_c" 1>&6 +ac_lib_var=`echo nsl_printf | tr '.-/+' '___p'` +if eval "test \"`echo '$''{'ac_cv_lib_$ac_lib_var'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + ac_save_LIBS="$LIBS" +LIBS="-lnsl $LIBS" +cat > conftest.$ac_ext <<EOF +#line 2493 "configure" +#include "confdefs.h" +/* Override any gcc2 internal prototype to avoid an error. */ +char printf(); + +int main() { return 0; } +int t() { +printf() +; return 0; } +EOF +if { (eval echo configure:2503: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; }; then + rm -rf conftest* + eval "ac_cv_lib_$ac_lib_var=yes" +else + rm -rf conftest* + eval "ac_cv_lib_$ac_lib_var=no" +fi +rm -f conftest* +LIBS="$ac_save_LIBS" + +fi +if eval "test \"`echo '$ac_cv_lib_'$ac_lib_var`\" = yes"; then + echo "$ac_t""yes" 1>&6 + ac_tr_lib=HAVE_LIB`echo nsl | tr 'abcdefghijklmnopqrstuvwxyz' 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'` + cat >> confdefs.h <<EOF +#define $ac_tr_lib 1 +EOF + + LIBS="-lnsl $LIBS" + +else + echo "$ac_t""no" 1>&6 +fi + ;; +esac +case "$LIBS" in +*-lsocket*) ;; +*) echo $ac_n "checking for -lsocket""... $ac_c" 1>&6 +ac_lib_var=`echo socket_connect | tr '.-/+' '___p'` +if eval "test \"`echo '$''{'ac_cv_lib_$ac_lib_var'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + ac_save_LIBS="$LIBS" +LIBS="-lsocket $LIBS" +cat > conftest.$ac_ext <<EOF +#line 2538 "configure" +#include "confdefs.h" +/* Override any gcc2 internal prototype to avoid an error. */ +char connect(); + +int main() { return 0; } +int t() { +connect() +; return 0; } +EOF +if { (eval echo configure:2548: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; }; then + rm -rf conftest* + eval "ac_cv_lib_$ac_lib_var=yes" +else + rm -rf conftest* + eval "ac_cv_lib_$ac_lib_var=no" +fi +rm -f conftest* +LIBS="$ac_save_LIBS" + +fi +if eval "test \"`echo '$ac_cv_lib_'$ac_lib_var`\" = yes"; then + echo "$ac_t""yes" 1>&6 + ac_tr_lib=HAVE_LIB`echo socket | tr 'abcdefghijklmnopqrstuvwxyz' 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'` + cat >> confdefs.h <<EOF +#define $ac_tr_lib 1 +EOF + + LIBS="-lsocket $LIBS" + +else + echo "$ac_t""no" 1>&6 +fi + ;; +esac +case "$LIBS" in +*-linet*) ;; +*) echo $ac_n "checking for -linet""... $ac_c" 1>&6 +ac_lib_var=`echo inet_connect | tr '.-/+' '___p'` +if eval "test \"`echo '$''{'ac_cv_lib_$ac_lib_var'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + ac_save_LIBS="$LIBS" +LIBS="-linet $LIBS" +cat > conftest.$ac_ext <<EOF +#line 2583 "configure" +#include "confdefs.h" +/* Override any gcc2 internal prototype to avoid an error. */ +char connect(); + +int main() { return 0; } +int t() { +connect() +; return 0; } +EOF +if { (eval echo configure:2593: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; }; then + rm -rf conftest* + eval "ac_cv_lib_$ac_lib_var=yes" +else + rm -rf conftest* + eval "ac_cv_lib_$ac_lib_var=no" +fi +rm -f conftest* +LIBS="$ac_save_LIBS" + +fi +if eval "test \"`echo '$ac_cv_lib_'$ac_lib_var`\" = yes"; then + echo "$ac_t""yes" 1>&6 + ac_tr_lib=HAVE_LIB`echo inet | tr 'abcdefghijklmnopqrstuvwxyz' 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'` + cat >> confdefs.h <<EOF +#define $ac_tr_lib 1 +EOF + + LIBS="-linet $LIBS" + +else + echo "$ac_t""no" 1>&6 +fi + ;; +esac +unset ac_cv_func_connect +for ac_func in connect +do +echo $ac_n "checking for $ac_func""... $ac_c" 1>&6 +if eval "test \"`echo '$''{'ac_cv_func_$ac_func'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + cat > conftest.$ac_ext <<EOF +#line 2626 "configure" +#include "confdefs.h" +/* System header to define __stub macros and hopefully few prototypes, + which can conflict with char $ac_func(); below. */ +#include <assert.h> +/* Override any gcc2 internal prototype to avoid an error. */ +char $ac_func(); + +int main() { return 0; } +int t() { + +/* The GNU C library defines this for functions which it implements + to always fail with ENOSYS. Some functions are actually named + something starting with __ and the normal name is an alias. */ +#if defined (__stub_$ac_func) || defined (__stub___$ac_func) +choke me +#else +$ac_func(); +#endif + +; return 0; } +EOF +if { (eval echo configure:2648: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; }; then + rm -rf conftest* + eval "ac_cv_func_$ac_func=yes" +else + rm -rf conftest* + eval "ac_cv_func_$ac_func=no" +fi +rm -f conftest* + +fi +if eval "test \"`echo '$ac_cv_func_'$ac_func`\" = yes"; then + echo "$ac_t""yes" 1>&6 + ac_tr_func=HAVE_`echo $ac_func | tr 'abcdefghijklmnopqrstuvwxyz' 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'` + cat >> confdefs.h <<EOF +#define $ac_tr_func 1 +EOF + +else + echo "$ac_t""no" 1>&6 +fi +done + +fi + + +echo $ac_n "checking for gethostname""... $ac_c" 1>&6 +if eval "test \"`echo '$''{'ac_cv_func_gethostname'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + cat > conftest.$ac_ext <<EOF +#line 2678 "configure" +#include "confdefs.h" +/* System header to define __stub macros and hopefully few prototypes, + which can conflict with char gethostname(); below. */ +#include <assert.h> +/* Override any gcc2 internal prototype to avoid an error. */ +char gethostname(); + +int main() { return 0; } +int t() { + +/* The GNU C library defines this for functions which it implements + to always fail with ENOSYS. Some functions are actually named + something starting with __ and the normal name is an alias. */ +#if defined (__stub_gethostname) || defined (__stub___gethostname) +choke me +#else +gethostname(); +#endif + +; return 0; } +EOF +if { (eval echo configure:2700: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; }; then + rm -rf conftest* + eval "ac_cv_func_gethostname=yes" +else + rm -rf conftest* + eval "ac_cv_func_gethostname=no" +fi +rm -f conftest* + +fi +if eval "test \"`echo '$ac_cv_func_'gethostname`\" = yes"; then + echo "$ac_t""yes" 1>&6 + : +else + echo "$ac_t""no" 1>&6 +LIBOBJS="$LIBOBJS hostname.o" +fi + + +# If we have connect(), we want the full client & server arrangement. +if test "$ac_cv_func_connect" = yes; then +cat >> confdefs.h <<\EOF +#define CLIENT_SUPPORT 1 +EOF + +cat >> confdefs.h <<\EOF +#define SERVER_SUPPORT 1 +EOF + +# Define AUTH_SERVER_SUPPORT only if we could locate the crypt() function +unset ac_cv_func_crypt +echo $ac_n "checking for crypt""... $ac_c" 1>&6 +if eval "test \"`echo '$''{'ac_cv_func_crypt'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + cat > conftest.$ac_ext <<EOF +#line 2736 "configure" +#include "confdefs.h" +/* System header to define __stub macros and hopefully few prototypes, + which can conflict with char crypt(); below. */ +#include <assert.h> +/* Override any gcc2 internal prototype to avoid an error. */ +char crypt(); + +int main() { return 0; } +int t() { + +/* The GNU C library defines this for functions which it implements + to always fail with ENOSYS. Some functions are actually named + something starting with __ and the normal name is an alias. */ +#if defined (__stub_crypt) || defined (__stub___crypt) +choke me +#else +crypt(); +#endif + +; return 0; } +EOF +if { (eval echo configure:2758: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; }; then + rm -rf conftest* + eval "ac_cv_func_crypt=yes" +else + rm -rf conftest* + eval "ac_cv_func_crypt=no" +fi +rm -f conftest* + +fi +if eval "test \"`echo '$ac_cv_func_'crypt`\" = yes"; then + echo "$ac_t""yes" 1>&6 + : +else + echo "$ac_t""no" 1>&6 +case "$LIBS" in +*-lcrypt*) ;; +*) echo $ac_n "checking for -lcrypt""... $ac_c" 1>&6 +ac_lib_var=`echo crypt_crypt | tr '.-/+' '___p'` +if eval "test \"`echo '$''{'ac_cv_lib_$ac_lib_var'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + ac_save_LIBS="$LIBS" +LIBS="-lcrypt $LIBS" +cat > conftest.$ac_ext <<EOF +#line 2783 "configure" +#include "confdefs.h" +/* Override any gcc2 internal prototype to avoid an error. */ +char crypt(); + +int main() { return 0; } +int t() { +crypt() +; return 0; } +EOF +if { (eval echo configure:2793: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; }; then + rm -rf conftest* + eval "ac_cv_lib_$ac_lib_var=yes" +else + rm -rf conftest* + eval "ac_cv_lib_$ac_lib_var=no" +fi +rm -f conftest* +LIBS="$ac_save_LIBS" + +fi +if eval "test \"`echo '$ac_cv_lib_'$ac_lib_var`\" = yes"; then + echo "$ac_t""yes" 1>&6 + ac_tr_lib=HAVE_LIB`echo crypt | tr 'abcdefghijklmnopqrstuvwxyz' 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'` + cat >> confdefs.h <<EOF +#define $ac_tr_lib 1 +EOF + + LIBS="-lcrypt $LIBS" + +else + echo "$ac_t""no" 1>&6 +fi + ;; +esac +unset ac_cv_func_crypt +for ac_func in crypt +do +echo $ac_n "checking for $ac_func""... $ac_c" 1>&6 +if eval "test \"`echo '$''{'ac_cv_func_$ac_func'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + cat > conftest.$ac_ext <<EOF +#line 2826 "configure" +#include "confdefs.h" +/* System header to define __stub macros and hopefully few prototypes, + which can conflict with char $ac_func(); below. */ +#include <assert.h> +/* Override any gcc2 internal prototype to avoid an error. */ +char $ac_func(); + +int main() { return 0; } +int t() { + +/* The GNU C library defines this for functions which it implements + to always fail with ENOSYS. Some functions are actually named + something starting with __ and the normal name is an alias. */ +#if defined (__stub_$ac_func) || defined (__stub___$ac_func) +choke me +#else +$ac_func(); +#endif + +; return 0; } +EOF +if { (eval echo configure:2848: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; }; then + rm -rf conftest* + eval "ac_cv_func_$ac_func=yes" +else + rm -rf conftest* + eval "ac_cv_func_$ac_func=no" +fi +rm -f conftest* + +fi +if eval "test \"`echo '$ac_cv_func_'$ac_func`\" = yes"; then + echo "$ac_t""yes" 1>&6 + ac_tr_func=HAVE_`echo $ac_func | tr 'abcdefghijklmnopqrstuvwxyz' 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'` + cat >> confdefs.h <<EOF +#define $ac_tr_func 1 +EOF + +else + echo "$ac_t""no" 1>&6 +fi +done + +fi + + if test "$ac_cv_func_crypt" = yes; then + cat >> confdefs.h <<\EOF +#define AUTH_SERVER_SUPPORT 1 +EOF + + fi +fi + +test -f src/options.h && ( + echo "configure: warning: saving ./src/options.h in ./src/options.h-SAVED" 1>&2 + echo "configure: warning: You may wish to check that local options have not been lost." 1>&2 + echo "configure: warning: Do not re-run ./configure or ./config.status until you have...." 1>&2 + cp ./src/options.h ./src/options.h-SAVED +) + +trap '' 1 2 15 +cat > confcache <<\EOF +# This file is a shell script that caches the results of configure +# tests run on this system so they can be shared between configure +# scripts and configure runs. It is not useful on other systems. +# If it contains results you don't want to keep, you may remove or edit it. +# +# By default, configure uses ./config.cache as the cache file, +# creating it if it does not exist already. You can give configure +# the --cache-file=FILE option to use a different cache file; that is +# what configure does when it calls configure scripts in +# subdirectories, so they share the cache. +# Giving --cache-file=/dev/null disables caching, for debugging configure. +# config.status only pays attention to the cache file if you give it the +# --recheck option to rerun configure. +# +EOF +# Ultrix sh set writes to stderr and can't be redirected directly, +# and sets the high bit in the cache file unless we assign to the vars. +(set) 2>&1 | + sed -n "s/^\([a-zA-Z0-9_]*_cv_[a-zA-Z0-9_]*\)=\(.*\)/\1=\${\1='\2'}/p" \ + >> confcache +if cmp -s $cache_file confcache; then + : +else + if test -w $cache_file; then + echo "updating cache $cache_file" + cat confcache > $cache_file + else + echo "not updating unwritable cache $cache_file" + fi +fi +rm -f confcache + +trap 'rm -fr conftest* confdefs* core core.* *.core $ac_clean_files; exit 1' 1 2 15 + +test "x$prefix" = xNONE && prefix=$ac_default_prefix +# Let make expand exec_prefix. +test "x$exec_prefix" = xNONE && exec_prefix='${prefix}' + +# Any assignment to VPATH causes Sun make to only execute +# the first set of double-colon rules, so remove it if not needed. +# If there is a colon in the path, we need to keep it. +if test "x$srcdir" = x.; then + ac_vpsub='/^[ ]*VPATH[ ]*=[^:]*$/d' +fi + +trap 'rm -f $CONFIG_STATUS conftest*; exit 1' 1 2 15 + +DEFS=-DHAVE_CONFIG_H + +# Without the "./", some shells look in PATH for config.status. +: ${CONFIG_STATUS=./config.status} + +echo creating $CONFIG_STATUS +rm -f $CONFIG_STATUS +cat > $CONFIG_STATUS <<EOF +#! /bin/sh +# Generated automatically by configure. +# Run this file to recreate the current configuration. +# This directory was configured as follows, +# on host `(hostname || uname -n) 2>/dev/null | sed 1q`: +# +# $0 $ac_configure_args +# +# Compiler output produced by configure, useful for debugging +# configure, is in ./config.log if it exists. + +ac_cs_usage="Usage: $CONFIG_STATUS [--recheck] [--version] [--help]" +for ac_option +do + case "\$ac_option" in + -recheck | --recheck | --rechec | --reche | --rech | --rec | --re | --r) + echo "running \${CONFIG_SHELL-/bin/sh} $0 $ac_configure_args --no-create --no-recursion" + exec \${CONFIG_SHELL-/bin/sh} $0 $ac_configure_args --no-create --no-recursion ;; + -version | --version | --versio | --versi | --vers | --ver | --ve | --v) + echo "$CONFIG_STATUS generated by autoconf version 2.9" + exit 0 ;; + -help | --help | --hel | --he | --h) + echo "\$ac_cs_usage"; exit 0 ;; + *) echo "\$ac_cs_usage"; exit 1 ;; + esac +done + +ac_given_srcdir=$srcdir +ac_given_INSTALL="$INSTALL" + +trap 'rm -fr `echo "Makefile lib/Makefile src/Makefile doc/Makefile \ + man/Makefile tools/Makefile tools/pcl-cvs/Makefile \ + contrib/Makefile contrib/elib/Makefile \ + windows-NT/Makefile os2/Makefile macintosh/Makefile stamp-h config.h src/options.h" | sed "s/:[^ ]*//g"` conftest*; exit 1' 1 2 15 +EOF +cat >> $CONFIG_STATUS <<EOF + +# Protect against being on the right side of a sed subst in config.status. +sed 's/%@/@@/; s/@%/@@/; s/%g\$/@g/; /@g\$/s/[\\\\&%]/\\\\&/g; + s/@@/%@/; s/@@/@%/; s/@g\$/%g/' > conftest.subs <<\\CEOF +$ac_vpsub +$extrasub +s%@CFLAGS@%$CFLAGS%g +s%@CPPFLAGS@%$CPPFLAGS%g +s%@CXXFLAGS@%$CXXFLAGS%g +s%@DEFS@%$DEFS%g +s%@LDFLAGS@%$LDFLAGS%g +s%@LIBS@%$LIBS%g +s%@exec_prefix@%$exec_prefix%g +s%@prefix@%$prefix%g +s%@program_transform_name@%$program_transform_name%g +s%@bindir@%$bindir%g +s%@sbindir@%$sbindir%g +s%@libexecdir@%$libexecdir%g +s%@datadir@%$datadir%g +s%@sysconfdir@%$sysconfdir%g +s%@sharedstatedir@%$sharedstatedir%g +s%@localstatedir@%$localstatedir%g +s%@libdir@%$libdir%g +s%@includedir@%$includedir%g +s%@oldincludedir@%$oldincludedir%g +s%@infodir@%$infodir%g +s%@mandir@%$mandir%g +s%@CC@%$CC%g +s%@CPP@%$CPP%g +s%@CVS@%$CVS%g +s%@INSTALL_PROGRAM@%$INSTALL_PROGRAM%g +s%@INSTALL_DATA@%$INSTALL_DATA%g +s%@RANLIB@%$RANLIB%g +s%@YACC@%$YACC%g +s%@SET_MAKE@%$SET_MAKE%g +s%@perl_path@%$perl_path%g +s%@csh_path@%$csh_path%g +s%@LIBOBJS@%$LIBOBJS%g +s%@KRB4@%$KRB4%g +s%@includeopt@%$includeopt%g + +CEOF +EOF +cat >> $CONFIG_STATUS <<EOF + +CONFIG_FILES=\${CONFIG_FILES-"Makefile lib/Makefile src/Makefile doc/Makefile \ + man/Makefile tools/Makefile tools/pcl-cvs/Makefile \ + contrib/Makefile contrib/elib/Makefile \ + windows-NT/Makefile os2/Makefile macintosh/Makefile stamp-h"} +EOF +cat >> $CONFIG_STATUS <<\EOF +for ac_file in .. $CONFIG_FILES; do if test "x$ac_file" != x..; then + # Support "outfile[:infile]", defaulting infile="outfile.in". + case "$ac_file" in + *:*) ac_file_in=`echo "$ac_file"|sed 's%.*:%%'` + ac_file=`echo "$ac_file"|sed 's%:.*%%'` ;; + *) ac_file_in="${ac_file}.in" ;; + esac + + # Adjust relative srcdir, etc. for subdirectories. + + # Remove last slash and all that follows it. Not all systems have dirname. + ac_dir=`echo $ac_file|sed 's%/[^/][^/]*$%%'` + if test "$ac_dir" != "$ac_file" && test "$ac_dir" != .; then + # The file is in a subdirectory. + test ! -d "$ac_dir" && mkdir "$ac_dir" + ac_dir_suffix="/`echo $ac_dir|sed 's%^\./%%'`" + # A "../" for each directory in $ac_dir_suffix. + ac_dots=`echo $ac_dir_suffix|sed 's%/[^/]*%../%g'` + else + ac_dir_suffix= ac_dots= + fi + + case "$ac_given_srcdir" in + .) srcdir=. + if test -z "$ac_dots"; then top_srcdir=. + else top_srcdir=`echo $ac_dots|sed 's%/$%%'`; fi ;; + /*) srcdir="$ac_given_srcdir$ac_dir_suffix"; top_srcdir="$ac_given_srcdir" ;; + *) # Relative path. + srcdir="$ac_dots$ac_given_srcdir$ac_dir_suffix" + top_srcdir="$ac_dots$ac_given_srcdir" ;; + esac + + case "$ac_given_INSTALL" in + [/$]*) INSTALL="$ac_given_INSTALL" ;; + *) INSTALL="$ac_dots$ac_given_INSTALL" ;; + esac + echo creating "$ac_file" + rm -f "$ac_file" + configure_input="Generated automatically from `echo $ac_file_in|sed 's%.*/%%'` by configure." + case "$ac_file" in + *Makefile*) ac_comsub="1i\\ +# $configure_input" ;; + *) ac_comsub= ;; + esac + sed -e "$ac_comsub +s%@configure_input@%$configure_input%g +s%@srcdir@%$srcdir%g +s%@top_srcdir@%$top_srcdir%g +s%@INSTALL@%$INSTALL%g +" -f conftest.subs $ac_given_srcdir/$ac_file_in > $ac_file +fi; done +rm -f conftest.subs + +# These sed commands are passed to sed as "A NAME B NAME C VALUE D", where +# NAME is the cpp macro being defined and VALUE is the value it is being given. +# +# ac_d sets the value in "#define NAME VALUE" lines. +ac_dA='s%^\([ ]*\)#\([ ]*define[ ][ ]*\)' +ac_dB='\([ ][ ]*\)[^ ]*%\1#\2' +ac_dC='\3' +ac_dD='%g' +# ac_u turns "#undef NAME" with trailing blanks into "#define NAME VALUE". +ac_uA='s%^\([ ]*\)#\([ ]*\)undef\([ ][ ]*\)' +ac_uB='\([ ]\)%\1#\2define\3' +ac_uC=' ' +ac_uD='\4%g' +# ac_e turns "#undef NAME" without trailing blanks into "#define NAME VALUE". +ac_eA='s%^\([ ]*\)#\([ ]*\)undef\([ ][ ]*\)' +ac_eB='$%\1#\2define\3' +ac_eC=' ' +ac_eD='%g' + +CONFIG_HEADERS=${CONFIG_HEADERS-"config.h src/options.h"} +for ac_file in .. $CONFIG_HEADERS; do if test "x$ac_file" != x..; then + # Support "outfile[:infile]", defaulting infile="outfile.in". + case "$ac_file" in + *:*) ac_file_in=`echo "$ac_file"|sed 's%.*:%%'` + ac_file=`echo "$ac_file"|sed 's%:.*%%'` ;; + *) ac_file_in="${ac_file}.in" ;; + esac + + echo creating $ac_file + + rm -f conftest.frag conftest.in conftest.out + cp $ac_given_srcdir/$ac_file_in conftest.in + +EOF + +# Transform confdefs.h into a sed script conftest.vals that substitutes +# the proper values into config.h.in to produce config.h. And first: +# Protect against being on the right side of a sed subst in config.status. +# Protect against being in an unquoted here document in config.status. +rm -f conftest.vals +cat > conftest.hdr <<\EOF +s/[\\&%]/\\&/g +s%[\\$`]%\\&%g +s%#define \([A-Za-z_][A-Za-z0-9_]*\) *\(.*\)%${ac_dA}\1${ac_dB}\1${ac_dC}\2${ac_dD}%gp +s%ac_d%ac_u%gp +s%ac_u%ac_e%gp +EOF +sed -n -f conftest.hdr confdefs.h > conftest.vals +rm -f conftest.hdr + +# This sed command replaces #undef with comments. This is necessary, for +# example, in the case of _POSIX_SOURCE, which is predefined and required +# on some systems where configure will not decide to define it. +cat >> conftest.vals <<\EOF +s%^[ ]*#[ ]*undef[ ][ ]*[a-zA-Z_][a-zA-Z_0-9]*%/* & */% +EOF + +# Break up conftest.vals because some shells have a limit on +# the size of here documents, and old seds have small limits too. +# Maximum number of lines to put in a single here document. +ac_max_here_lines=12 + +rm -f conftest.tail +while : +do + ac_lines=`grep -c . conftest.vals` + # grep -c gives empty output for an empty file on some AIX systems. + if test -z "$ac_lines" || test "$ac_lines" -eq 0; then break; fi + # Write a limited-size here document to conftest.frag. + echo ' cat > conftest.frag <<CEOF' >> $CONFIG_STATUS + sed ${ac_max_here_lines}q conftest.vals >> $CONFIG_STATUS + echo 'CEOF + sed -f conftest.frag conftest.in > conftest.out + rm -f conftest.in + mv conftest.out conftest.in +' >> $CONFIG_STATUS + sed 1,${ac_max_here_lines}d conftest.vals > conftest.tail + rm -f conftest.vals + mv conftest.tail conftest.vals +done +rm -f conftest.vals + +cat >> $CONFIG_STATUS <<\EOF + rm -f conftest.frag conftest.h + echo "/* $ac_file. Generated automatically by configure. */" > conftest.h + cat conftest.in >> conftest.h + rm -f conftest.in + if cmp -s $ac_file conftest.h 2>/dev/null; then + echo "$ac_file is unchanged" + rm -f conftest.h + else + rm -f $ac_file + mv conftest.h $ac_file + fi +fi; done + + + +exit 0 +EOF +chmod +x $CONFIG_STATUS +rm -fr confdefs* $ac_clean_files +test "$no_create" = yes || ${CONFIG_SHELL-/bin/sh} $CONFIG_STATUS || exit 1 + diff --git a/contrib/cvs/configure.in b/contrib/cvs/configure.in new file mode 100644 index 0000000..554a401 --- /dev/null +++ b/contrib/cvs/configure.in @@ -0,0 +1,191 @@ +dnl configure.in for cvs +dnl "$CVSid$" +AC_INIT(src/cvs.h) +AC_PREREQ(2.4)dnl Required Autoconf version. +AC_CONFIG_HEADER(config.h src/options.h) + +AC_PROG_CC + +AC_AIX +AC_MINIX +AC_ISC_POSIX +if test "$ISC" = yes; then +CFLAGS="$CFLAGS -D_SYSV3" +fi + +AC_PREFIX_PROGRAM(cvs) + +AC_C_CROSS + +AC_C_CONST +AC_C_CHAR_UNSIGNED +AC_C_INLINE + +AC_PROG_INSTALL +AC_PROG_RANLIB +AC_PROG_YACC +AC_PROG_MAKE_SET + +AC_PATH_PROG(perl_path, perl, no) +AC_PATH_PROG(csh_path, csh, no) + +AC_SYS_INTERPRETER +if test X"$ac_cv_sys_interpreter" != X"yes" ; then + # silly trick to avoid problems in AC macros... + ac_msg='perl scripts using #! may not be invoked properly' + AC_MSG_WARN($ac_msg) +fi + +AC_HEADER_STDC +AC_CHECK_HEADERS(errno.h unistd.h string.h memory.h utime.h fcntl.h ndbm.h \ + sys/param.h sys/select.h sys/time.h sys/timeb.h \ + io.h direct.h sys/bsdtypes.h sys/resource.h) +AC_HEADER_SYS_WAIT +AC_HEADER_STAT +AC_HEADER_TIME +AC_HEADER_DIRENT +AC_TYPE_SIGNAL +AC_TYPE_UID_T +AC_TYPE_MODE_T +AC_TYPE_SIZE_T +AC_TYPE_PID_T +AC_REPLACE_FUNCS(getwd mkdir rename strdup strstr dup2 strerror valloc waitpid memmove vasprintf strtoul) +AC_CHECK_FUNCS(fchmod fsync ftime mkfifo putenv setvbuf vfork vprintf ftruncate timezone getpagesize initgroups fchdir sigaction sigprocmask sigvec sigsetmask sigblock) +AC_CHECK_FUNC(re_exec, :, LIBOBJS="$LIBOBJS regex.o") +AC_FUNC_UTIME_NULL +AC_SYS_LONG_FILE_NAMES + +AC_MSG_CHECKING([for working fnmatch function]) +AC_CACHE_VAL(ccvs_cv_sys_working_fnmatch, +[AC_TRY_RUN([ +#include <fnmatch.h> +int +main () +{ + exit ((fnmatch ("a", "a", FNM_PATHNAME) == 0 + && fnmatch ("a", "b", FNM_PATHNAME) == FNM_NOMATCH) + ? 0 : 1); +}], +ccvs_cv_sys_working_fnmatch=yes, +ccvs_cv_sys_working_fnmatch=no, +ccvs_cv_sys_working_fnmatch=no)]) +if test $ccvs_cv_sys_working_fnmatch = no; then + LIBOBJS="$LIBOBJS fnmatch.o" +fi +AC_MSG_RESULT($ccvs_cv_sys_working_fnmatch) + +dnl +dnl set $(KRB4) from --with-krb4=value -- WITH_KRB4 +dnl +KRB4=/usr/kerberos +define(WITH_KRB4,[ +AC_ARG_WITH([krb4], + [ --with-krb4=value set default \$(KRB4) from value], + [KRB4=$withval], +)dnl +echo "default place for krb4 is $KRB4" +AC_SUBST(KRB4)])dnl +WITH_KRB4 + +AC_CHECK_SIZEOF(long) +AC_CHECK_SIZEOF(int) + +krb_h= +AC_MSG_CHECKING([for krb.h]) +AC_TRY_LINK([#include <krb.h>],[int i;], + [krb_h=yes krb_incdir=], + [if test "$cross_compiling" != yes && test -r $KRB4/include/krb.h; then + hold_cflags=$CFLAGS + CFLAGS="$CFLAGS -I$KRB4/include" + AC_TRY_LINK([#include <krb.h>],[int i;], + [krb_h=yes krb_incdir=$KRB4/include]) + CFLAGS=$hold_cflags + fi]) +if test -z "$krb_h"; then + AC_TRY_LINK([#include <krb.h>],[int i;], + [krb_h=yes krb_incdir=], + [if test "$cross_compiling" != yes && test -r $KRB4/include/kerberosIV/krb.h; then + hold_cflags=$CFLAGS + CFLAGS="$CFLAGS -I$KRB4/include/kerberosIV" + AC_TRY_LINK([#include <krb.h>],[int i;], + [krb_h=yes krb_incdir=$KRB4/include/kerberosIV]) + CFLAGS=$hold_cflags + fi]) +fi +AC_MSG_RESULT($krb_h) + +if test -n "$krb_h"; then + krb_lib= + AC_CHECK_LIB(krb,printf,[krb_lib=yes krb_libdir=], + [if test "$cross_compiling" != yes && test -r $KRB4/lib/libkrb.a; then + krb_lib=yes krb_libdir=$KRB4/lib + fi]) + if test -n "$krb_lib"; then + AC_DEFINE(HAVE_KERBEROS) + test -n "${krb_libdir}" && LIBS="${LIBS} -L${krb_libdir}" + LIBS="${LIBS} -lkrb" + AC_CHECK_LIB(des,printf,[LIBS="${LIBS} -ldes"]) + if test -n "$krb_incdir"; then + includeopt="${includeopt} -I$krb_incdir" + AC_SUBST(includeopt) + fi + fi +fi +AC_CHECK_FUNCS(krb_get_err_text) +# If we can't find connect, try looking in -lsocket, -lnsl, and -linet. +# The Irix 5 libc.so has connect and gethostbyname, but Irix 5 also has +# libsocket.so which has a bad implementation of gethostbyname (it +# only looks in /etc/hosts), so we only look for -lsocket if we need +# it. +unset ac_cv_func_connect +AC_CHECK_FUNC(connect, :, +[case "$LIBS" in +*-lnsl*) ;; +*) AC_CHECK_LIB(nsl_s, printf) ;; +esac +case "$LIBS" in +*-lnsl*) ;; +*) AC_CHECK_LIB(nsl, printf) ;; +esac +case "$LIBS" in +*-lsocket*) ;; +*) AC_CHECK_LIB(socket, connect) ;; +esac +case "$LIBS" in +*-linet*) ;; +*) AC_CHECK_LIB(inet, connect) ;; +esac +unset ac_cv_func_connect +AC_CHECK_FUNCS(connect)]) + +AC_CHECK_FUNC(gethostname, :, LIBOBJS="$LIBOBJS hostname.o") + +# If we have connect(), we want the full client & server arrangement. +if test "$ac_cv_func_connect" = yes; then +AC_DEFINE(CLIENT_SUPPORT) +AC_DEFINE(SERVER_SUPPORT) +# Define AUTH_SERVER_SUPPORT only if we could locate the crypt() function +unset ac_cv_func_crypt +AC_CHECK_FUNC(crypt, :, +[case "$LIBS" in +*-lcrypt*) ;; +*) AC_CHECK_LIB(crypt, crypt) ;; +esac +unset ac_cv_func_crypt +AC_CHECK_FUNCS(crypt)]) + if test "$ac_cv_func_crypt" = yes; then + AC_DEFINE(AUTH_SERVER_SUPPORT) + fi +fi + +test -f src/options.h && ( + AC_MSG_WARN(saving ./src/options.h in ./src/options.h-SAVED) + AC_MSG_WARN(You may wish to check that local options have not been lost.) + AC_MSG_WARN(Do not re-run ./configure or ./config.status until you have....) + cp ./src/options.h ./src/options.h-SAVED +) + +AC_OUTPUT(Makefile lib/Makefile src/Makefile doc/Makefile \ + man/Makefile tools/Makefile tools/pcl-cvs/Makefile \ + contrib/Makefile contrib/elib/Makefile \ + windows-NT/Makefile os2/Makefile macintosh/Makefile stamp-h) diff --git a/contrib/cvs/contrib/ChangeLog b/contrib/cvs/contrib/ChangeLog new file mode 100644 index 0000000..80db5b8 --- /dev/null +++ b/contrib/cvs/contrib/ChangeLog @@ -0,0 +1,193 @@ +Sun Apr 14 11:30:36 1996 Karl Fogel <kfogel@floss.red-bean.com> + + * Removed pcl-cvs/ subdir; see tools/ subdir in the top-level from + now on. + Added elib/ subdir. + + * Makefile.in (dist-dir): Removed all references to pcl-cvs/ + subdir. + +Wed Mar 6 10:20:28 1996 Greg A. Woods <woods@most.weird.com> + + * log_accum.pl: ($MAILER): use sendmail directly to allow other + headers to be included + * log_accum.pl (mail_notification): add support to allow settting + of Reply-To and Date header fields in the sent mail; remove $mailto + argument and use the global variable (as with $replyto). + * log_accum.pl: add -R option for mail_notification()'s optional + Reply-To value [default to $login] + +Fri Mar 1 01:51:56 1996 Benjamin J. Lee <benjamin@cyclic.com> + + * listener.c: added as mentioned in ../README.VMS + +Mon Feb 19 13:37:36 1996 Jim Kingdon <kingdon@harvey.cyclic.com> + + * README: Don't just tell people "we don't want your script"; tell + them what to do instead. + +Thu Feb 1 14:28:16 1996 Karl Fogel <kfogel@floss.red-bean.com> + + * Makefile.in (DISTFILES): added `rcs2sccs.sh', as mentioned in + README. + +Thu Jan 18 09:39:16 1996 Jim Kingdon <kingdon@harvey.cyclic.com> + + * README: Talk about submitting changes to contrib directory. + +Tue Nov 14 15:28:25 1995 Greg A. Woods <woods@most.weird.com> + + * README: fix some spelling and other typos + + * Makefile.in: if I need reminding to run cvsinit.... + +Tue Nov 14 13:47:40 1995 Greg A. Woods <woods@most.weird.com> + + * log_accum.pl: + - Fix 'cvs status' to use global -Qq options + - fix up a couple of comments, incl., my proper address + + * log.pl: add a CVSid and fix a couple of comments + +Sun Oct 1 02:02:57 1995 Peter Wemm <peter@haywire.dialix.com> + + * Makefile.in: supply a suffix rule to deal with .sh "source" + +Sat Jul 29 17:29:13 1995 James Kingdon <kingdon@harvey.cyclic.com> + + * log.pl: Use global options -Qq, not command options -Qq. + + * Makefile.in (install): Look for $(PROGS) and + $(CONTRIB_PROGS) in build dir, not srcdir. + +Fri Jul 28 19:48:45 1995 Paul Eggert <eggert@twinsun.com> + + * rcs2log.sh: Sync with latest Emacs snapshot. + +Thu Jul 27 20:29:30 1995 Jim Blandy <jimb@totoro.cyclic.com> + + * rcs2log.sh: import of initial WNT port work + +Fri Jul 14 22:38:44 1995 Jim Blandy <jimb@totoro.cyclic.com> + + * rcs-to-cvs.sh: Changes from David J. Mackenzie. + Set permissions on new repository files correctly. + Ignore *~ files. + +Thu Jul 13 23:04:12 CDT 1995 Jim Meyering (meyering@comco.com) + + * Makefile.in (.pl, .csh): *Never* redirect output directly to + the target (usu $@) of a rule. Instead, redirect to a temporary + file, and then move that temporary to the target. I chose to + name temporary files $@-t. Remember to be careful that the length + of the temporary file name not exceed the 14-character limit. + +Sun Jul 9 21:16:53 1995 Karl Fogel <kfogel@floss.cyclic.com> + + These are actually Greg Woods' changes: + + * clmerge.pl, cvscheck.sh, descend.sh, dirfns.shar, rcs-to-cvs.sh, + rcs2log.sh, sccs2rcs.csh: renamed from the corresponding files + sans extensions. + + * rcs2sccs.sh: new file. + +Sun Jul 9 19:03:00 1995 Greg A. Woods <woods@most.weird.com> + + * rcs2log.sh: oops, one more thing that should not have been + there. + - fix interpreter file syntax. + - remove "fix" for separating filenames and comments + + * Makefile.in: hmm... thought rcs2log was in RCS-5.7 for some + reason -- it's not, so we'll install it from here.... + - fix typo -- that's what you get for re-doing changes by hand! + - updates to support proper transformation and installation of + renamed files (from previous local changes) + + * .cvsignore: one more target noted... + + * sccs2rcs.csh: set up the interpreter file for updating by + Makefile (from previous local changes) + + * log_accum.pl, log.pl, commit_prep.pl: + - set up the interpreter file for updating by Makefile + - various modifications, updates, and enhancements + (from previous local changes) + + * rcslock.pl, mfpipe.pl, cvs_acls.pl, cln_hist.pl, clmerge.pl: + - set up the interpreter file for updating by Makefile + (from previous local changes) + - include changes from 1.5 here too, if any + + * README: + - remove extensions from filenames to match installed names + (from previous local changes) + + * .cvsignore: - added $(CONTRIB_PROGS) (from previous local changes) + + +Thu Jun 29 10:43:07 1995 James Kingdon <kingdon@harvey.cyclic.com> + + * Makefile.in (distclean): Also remove pcl-cvs/Makefile. + +Thu Jun 8 15:32:29 1995 Jim Kingdon (kingdon@lioth.cygnus.com) + + * intro.doc: Added. + * Makefile.in (DISTFILES): Add intro.doc. + +Sat May 27 08:46:00 1995 Jim Meyering (meyering@comco.com) + + * Makefile.in (Makefile): Regenerate only Makefile in current + directory when Makefile.in is out of date. Depend on ../config.status. + +Mon May 8 13:06:29 1995 Bryan O'Sullivan <bos@serpentine.com> + + * README: added an entry for ccvs-rsh.pl. + +Sun Apr 30 23:50:32 1995 Bryan O'Sullivan <bos@serpentine.com> + + * ccvs-rsh.pl: fixed a typo and added more flexible use of + CVS_PROXY_USER. + +Sun Apr 30 14:56:21 1995 Jim Blandy <jimb@totoro.bio.indiana.edu> + + * clmerge: Changes from Tom Tromey --- fix bug in date comparison + function. + +Sat Apr 29 20:53:08 1995 Bryan O'Sullivan <bos@serpentine.com> + + * ccvs-rsh.pl: created. See the file itself for documentation. + + * Makefile.in (DISTFILES): added ccvs-rsh.pl to the list of + files to install. + +Fri Apr 28 22:32:45 1995 Jim Blandy <jimb@totoro.bio.indiana.edu> + + * Makefile.in (DISTFILES): Brought up-to-date with current + directory contents. + (dist-dir): Renamed from dist-dir; use DISTDIR variable, passed + from parent. + +Mon Feb 13 13:32:07 1995 Jim Blandy <jimb@totoro.bio.indiana.edu> + + * rcs2log: rcs2log was originally in this tree; how did it get + deleted? Anyway, this is the version distributed with Emacs + 19.28, hacked to support CVS and Remote CVS. + +Mon Jul 26 13:18:23 1993 David J. Mackenzie (djm@thepub.cygnus.com) + + * rcs-to-cvs: Rewrite in sh. + +Wed Jul 14 21:16:40 1993 David J. Mackenzie (djm@thepub.cygnus.com) + + * rcs-to-cvs: Don't source .cshrc or hardcode paths. + Make respository dir if needed. Don't suppress errors + (such as prompts) from co. + +Wed Feb 26 18:04:40 1992 K. Richard Pixley (rich@cygnus.com) + + * Makefile.in, configure.in: removed traces of namesubdir, + -subdirs, $(subdir), $(unsubdir), some rcs triggers. Forced + copyrights to '92, changed some from Cygnus to FSF. + diff --git a/contrib/cvs/contrib/Makefile.in b/contrib/cvs/contrib/Makefile.in new file mode 100644 index 0000000..a29dec0 --- /dev/null +++ b/contrib/cvs/contrib/Makefile.in @@ -0,0 +1,134 @@ +# Makefile for GNU CVS contributed sources. +# Do not use this makefile directly, but only from `../Makefile'. +# Copyright (C) 1986, 1988-1990 Free Software Foundation, Inc. + +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2, or (at your option) +# any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +# $CVSid: @(#)Makefile.in 1.6 94/10/22 $ + +SHELL = /bin/sh + +srcdir = @srcdir@ +top_srcdir = @top_srcdir@ +VPATH = @srcdir@ + +prefix = @prefix@ +exec_prefix = @exec_prefix@ + +# Where to install the executables. +bindir = $(exec_prefix)/bin + +# Where to put the system-wide .cvsrc file +libdir = $(prefix)/lib + +# Where to put the manual pages. +mandir = $(prefix)/man + +# where to find command interpreters +perl_path = @perl_path@ +csh_path = @csh_path@ + +# Use cp if you don't have install. +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ + +DISTFILES = \ + ChangeLog README .cvsignore intro.doc \ + Makefile.in clmerge.pl cln_hist.pl commit_prep.pl cvs_acls.pl \ + cvscheck.sh cvscheck.man cvshelp.man descend.sh descend.man \ + dirfns.shar log.pl log_accum.pl mfpipe.pl rcs-to-cvs.sh rcs2log.sh \ + rcslock.pl sccs2rcs.csh rcs2sccs.sh + +# files installed in $(libdir)/cvs/contrib +# +CONTRIB_FILES = README intro.doc cvscheck.man + +# things we actually build and install.... +# +PROGS = rcs2log +CONTRIB_PROGS = clmerge cln_hist commit_prep cvs_acls cvscheck log log_accum \ + mfpipe rcs-to-cvs rcs2log rcslock sccs2rcs + +.SUFFIXES: .pl .sh .csh + +.pl: + rm -f $@ + sed -e 's,xPERL_PATHx,$(perl_path),' $< > $@-t + mv $@-t $@ + chmod +x $@ + +.csh: + rm -f $@ + sed -e 's,xCSH_PATHx,$(csh_path),' $< > $@-t + mv $@-t $@ + chmod +x $@ + +.sh: + rm -f $@ + cp $< $@ + chmod +x $@ + +all: Makefile $(PROGS) $(CONTRIB_PROGS) +.PHONY: all + +install: all $(libdir)/cvs/contrib + for f in $(CONTRIB_FILES) ; do\ + $(INSTALL_DATA) $(srcdir)/$$f $(libdir)/cvs/contrib/$$f; \ + done + for f in $(CONTRIB_PROGS) ; do\ + $(INSTALL_PROGRAM) $$f $(libdir)/cvs/contrib/$$f; \ + done + for f in $(PROGS) ; do\ + $(INSTALL_PROGRAM) $$f $(bindir)/$$f; \ + done + @echo "You might consider running 'cvsinit' to upgrade your repository(s)...." +.PHONY: install + +$(libdir)/cvs/contrib: + $(top_srcdir)/mkinstalldirs $(libdir)/cvs/contrib + +tags: +.PHONY: tags + +TAGS: +.PHONY: TAGS + +ls: + @echo $(DISTFILES) +.PHONY: ls + +clean: + /bin/rm -f *.o core +.PHONY: clean + +distclean: clean + rm -f Makefile elib/Makefile $(PROGS) $(CONTRIB_PROGS) +.PHONY: distclean + +realclean: distclean +.PHONY: realclean + +dist-dir: + mkdir ${DISTDIR} + for i in ${DISTFILES}; do \ + ln $(srcdir)/$${i} ${DISTDIR}; \ + done + cd elib; ${MAKE} dist-dir DISTDIR="../${DISTDIR}/elib" +.PHONY: dist-dir + +subdir = contrib +Makefile: ../config.status Makefile.in + cd .. && CONFIG_FILES=$(subdir)/$@ CONFIG_HEADERS= ./config.status diff --git a/contrib/cvs/contrib/README b/contrib/cvs/contrib/README new file mode 100644 index 0000000..d453f8d --- /dev/null +++ b/contrib/cvs/contrib/README @@ -0,0 +1,106 @@ +$CVSid: @(#)README 1.12 94/09/25 $ + +This "contrib" directory is a place holder for code/scripts sent to +me by contributors around the world. This README file will be kept +up-to-date from release to release. BUT, I must point out that these +contributions are really, REALLY UNSUPPORTED. In fact, I probably +don't even know what they do. Nor do I guarantee to have tried them, +or ported them to work with this CVS distribution. If you have questions, +you might contact the author, but you should not necessarily expect +a reply. USE AT YOUR OWN RISK -- and all that stuff. + +"Unsupported" also means that noone has volunteered to accept and +check in changes to this directory. So submissions for new scripts to +add here are unlikely to be accepted. Suggested changes to the +existing scripts here conceivably might, but that isn't clear either. +The exception is pcl-cvs; that is more actively maintained (see +pcl-cvs/README). If you have some software that works with CVS that +you wish to offer it is suggested that you make it available by FTP or +HTTP and then announce it on the info-cvs mailing list. There is also +a web page of software related to CVS at +http://www.loria.fr/~molli/cvs-index.html which would presumably be +willing to list your software. + +Contents of this directory: + + README This file. + log A perl script suitable for including in your + $CVSROOT/CVSROOT/loginfo file for logging commit + changes. Includes the RCS revision of the change + as part of the log. + Contributed by Kevin Samborn <samborn@sunrise.com>. + pcl-cvs A directory that contains GNU Emacs lisp code which + implements a CVS-mode for emacs. + Contributed by Per Cederqvist <ceder@lysator.liu.se>. + commit_prep A perl script, to be combined with log_accum.pl, to + log_accum provide for a way to combine the individual log + messages of a multi-directory "commit" into a + single log message, and mail the result somewhere. + Can also do other checks for $Id and that you are + committing the correct revision of the file. + Read the comments carefully. + Contributed by David Hampton <hampton@cisco.com>. + mfpipe Another perl script for logging. Allows you to + pipe the log message to a file and/or send mail + to some alias. + Contributed by John Clyne <clyne@niwot.scd.ucar.edu>. + rcs-to-cvs Script to import sources that may have been under + RCS control already. + Contributed by Per Cederqvist <ceder@lysator.liu.se>. + cvscheck Identifies files added, changed, or removed in a + cvscheck.man checked out CVS tree; also notices unknown files. + Contributed by Lowell Skoog <fluke!lowell@uunet.uu.net> + cvshelp.man An introductory manual page written by Lowell Skoog + <fluke!lowell@uunet.uu.net>. It is most likely + out-of-date relative to CVS 1.3, but still may be + useful. + dirfns A shar file which contains some code that might + help your system support opendir/readdir/closedir, + if it does not already. + Copied from the C-News distribution. + rcslock A perl script that can be added to your commitinfo + file that tries to determine if your RCS file is + currently locked by someone else, as might be the + case for a binary file. + Contributed by John Rouillard <rouilj@cs.umb.edu>. + ccvs-rsh A Perl script which allows "rsh pipelines" to + be built in order to use Cyclic CVS from + behind some varieties of firewall. + cvs_acls A perl script that implements Access Control Lists + by using the "commitinfo" hook provided with the + "cvs commit" command. + Contributed by David G. Grubbs <dgg@ksr.com>. + descend A shell script that can be used to recursively + descend.man descend through a directory. In CVS 1.2, this was + very useful, since many of the commands were not + recursive. In CVS 1.3 (and later), however, most of + the commands are recursive. However, this may still + come in handy. + Contributed by Lowell Skoog <fluke!lowell@uunet.uu.net> + cln_hist A perl script to compress your + $CVSROOT/CVSROOT/history file, as it can grow quite + large after extended use. + Contributed by David G. Grubbs <dgg@ksr.com> + sccs2rcs A C-shell script that can convert (some) SCCS files + into RCS files, retaining the info contained in the + SCCS file (like dates, author, and log message). + Contributed by Ken Cox <kenstir@viewlogic.com>. + intro.doc A user's view of what you need to know to get + started with CVS. + Contributed by <Steven.Pemberton@cwi.nl>. + rcs2sccs A shell script to convert simple RCS files into + SCCS files, originally gleaned off the network + somewhere (originally by "kenc") and modified by + Jerry Jelinek <jerry@rmtc.Central.Sun.COM> and + Brian Berliner <berliner@sun.com> to increase + robustness and add support for one-level of branches. + rcs2log A shell script to create a ChangeLog-format file + given only a set of RCS files. + Contributed by Paul Eggert <eggert@twinsun.com>. + clmerge A perl script to handle merge conflicts in GNU + style ChangeLog files . + Contributed by Tom Tromey <tromey@busco.lanl.gov>. + listener A program which listens to a TCP port, authenticates + by hostname, then runs a subprocess whose input/output + is redirected through the port. + Contributed by Benjamin J. Lee <benjamin@cyclic.com> diff --git a/contrib/cvs/contrib/clmerge.pl b/contrib/cvs/contrib/clmerge.pl new file mode 100644 index 0000000..ac81371 --- /dev/null +++ b/contrib/cvs/contrib/clmerge.pl @@ -0,0 +1,152 @@ +#! xPERL_PATHx + +# Merge conflicted ChangeLogs +# tromey Mon Aug 15 1994 + +# Usage is: +# +# cl-merge [-i] file ... +# +# With -i, it works in place (backups put in a ~ file). Otherwise the +# merged ChangeLog is printed to stdout. + +# Please report any bugs to me. I wrote this yesterday, so there are no +# guarantees about its performance. I recommend checking its output +# carefully. If you do send a bug report, please include the failing +# ChangeLog, so I can include it in my test suite. +# +# Tom +# --- +# tromey@busco.lanl.gov Member, League for Programming Freedom +# Sadism and farce are always inexplicably linked. +# -- Alexander Theroux + + +# Month->number mapping. Used for sorting. +%months = ('Jan', 0, + 'Feb', 1, + 'Mar', 2, + 'Apr', 3, + 'May', 4, + 'Jun', 5, + 'Jul', 6, + 'Aug', 7, + 'Sep', 8, + 'Oct', 9, + 'Nov', 10, + 'Dec', 11); + +# If '-i' is given, do it in-place. +if ($ARGV[0] eq '-i') { + shift (@ARGV); + $^I = '~'; +} + +$lastkey = ''; +$lastval = ''; +$conf = 0; +%conflist = (); + +$tjd = 0; + +# Simple state machine. The states: +# +# 0 Not in conflict. Just copy input to output. +# 1 Beginning an entry. Next non-blank line is key. +# 2 In entry. Entry beginner transitions to state 1. +while (<>) { + if (/^<<<</ || /^====/) { + # Start of a conflict. + + # Copy last key into array. + if ($lastkey ne '') { + $conflist{$lastkey} = $lastval; + + $lastkey = ''; + $lastval = ''; + } + + $conf = 1; + } elsif (/^>>>>/) { + # End of conflict. Output. + + # Copy last key into array. + if ($lastkey ne '') { + $conflist{$lastkey} = $lastval; + + $lastkey = ''; + $lastval = ''; + } + + foreach (reverse sort clcmp keys %conflist) { + print STDERR "doing $_" if $tjd; + print $_; + print $conflist{$_}; + } + + $lastkey = ''; + $lastval = ''; + $conf = 0; + %conflist = (); + } elsif ($conf == 1) { + # Beginning an entry. Skip empty lines. Error if not a real + # beginner. + if (/^$/) { + # Empty line; just skip at this point. + } elsif (/^[MTWFS]/) { + # Looks like the name of a day; assume opener and move to + # "in entry" state. + $lastkey = $_; + $conf = 2; + print STDERR "found $_" if $tjd; + } else { + die ("conflict crosses entry boundaries: $_"); + } + } elsif ($conf == 2) { + # In entry. Copy into variable until we see beginner line. + if (/^[MTWFS]/) { + # Entry beginner line. + + # Copy last key into array. + if ($lastkey ne '') { + $conflist{$lastkey} = $lastval; + + $lastkey = ''; + $lastval = ''; + } + + $lastkey = $_; + print STDERR "found $_" if $tjd; + $lastval = ''; + } else { + $lastval .= $_; + } + } else { + # Just copy. + print; + } +} + +# Compare ChangeLog time strings like <=>. +# +# 0 1 2 3 +# Thu Aug 11 13:22:42 1994 Tom Tromey (tromey@creche.colorado.edu) +# 0123456789012345678901234567890 +# +sub clcmp { + # First check year. + $r = substr ($a, 20, 4) <=> substr ($b, 20, 4); + + # Now check month. + $r = $months{substr ($a, 4, 3)} <=> $months{substr ($b, 4, 3)} if !$r; + + # Now check day. + $r = substr ($a, 8, 2) <=> substr ($b, 8, 2) if !$r; + + # Now check time (3 parts). + $r = substr ($a, 11, 2) <=> substr ($b, 11, 2) if !$r; + $r = substr ($a, 14, 2) <=> substr ($b, 14, 2) if !$r; + $r = substr ($a, 17, 2) <=> substr ($b, 17, 2) if !$r; + + $r; +} diff --git a/contrib/cvs/contrib/cln_hist.pl b/contrib/cvs/contrib/cln_hist.pl new file mode 100644 index 0000000..ff49d0a --- /dev/null +++ b/contrib/cvs/contrib/cln_hist.pl @@ -0,0 +1,92 @@ +#! xPERL_PATHx +# -*-Perl-*- +# +# $Id: cln_hist.pl,v 1.2 1995/07/10 02:01:26 kfogel Exp $ +# Contributed by David G. Grubbs <dgg@ksr.com> +# +# Clean up the history file. 10 Record types: MAR OFT WUCG +# +# WUCG records are thrown out. +# MAR records are retained. +# T records: retain only last tag with same combined tag/module. +# +# Two passes: Walk through the first time and remember the +# 1. Last Tag record with same "tag" and "module" names. +# 2. Last O record with unique user/module/directory, unless followed +# by a matching F record. +# + +$r = $ENV{"CVSROOT"}; +$c = "$r/CVSROOT"; +$h = "$c/history"; + +eval "print STDERR \$die='Unknown parameter $1\n' if !defined \$$1; \$$1=\$';" + while ($ARGV[0] =~ /^(\w+)=/ && shift(@ARGV)); +exit 255 if $die; # process any variable=value switches + +%tags = (); +%outs = (); + +# +# Move history file to safe place and re-initialize a new one. +# +rename($h, "$h.bak"); +open(XX, ">$h"); +close(XX); + +# +# Pass1 -- remember last tag and checkout. +# +open(HIST, "$h.bak"); +while (<HIST>) { + next if /^[MARWUCG]/; + + # Save whole line keyed by tag|module + if (/^T/) { + @tmp = split(/\|/, $_); + $tags{$tmp[4] . '|' . $tmp[5]} = $_; + } + # Save whole line + if (/^[OF]/) { + @tmp = split(/\|/, $_); + $outs{$tmp[1] . '|' . $tmp[2] . '|' . $tmp[5]} = $_; + } +} + +# +# Pass2 -- print out what we want to save. +# +open(SAVE, ">$h.work"); +open(HIST, "$h.bak"); +while (<HIST>) { + next if /^[FWUCG]/; + + # If whole line matches saved (i.e. "last") one, print it. + if (/^T/) { + @tmp = split(/\|/, $_); + next if $tags{$tmp[4] . '|' . $tmp[5]} ne $_; + } + # Save whole line + if (/^O/) { + @tmp = split(/\|/, $_); + next if $outs{$tmp[1] . '|' . $tmp[2] . '|' . $tmp[5]} ne $_; + } + + print SAVE $_; +} + +# +# Put back the saved stuff +# +system "cat $h >> $h.work"; + +if (-s $h) { + rename ($h, "$h.interim"); + print "history.interim has non-zero size.\n"; +} else { + unlink($h); +} + +rename ("$h.work", $h); + +exit(0); diff --git a/contrib/cvs/contrib/commit_prep.pl b/contrib/cvs/contrib/commit_prep.pl new file mode 100644 index 0000000..5272c04 --- /dev/null +++ b/contrib/cvs/contrib/commit_prep.pl @@ -0,0 +1,216 @@ +#! xPERL_PATHx +# -*-Perl-*- +# +#ident "@(#)cvs/contrib:$Name: $:$Id: commit_prep.pl,v 1.2 1995/07/10 02:01:29 kfogel Exp $" +# +# Perl filter to handle pre-commit checking of files. This program +# records the last directory where commits will be taking place for +# use by the log_accum.pl script. For new files, it forces the +# existence of a RCS "Id" keyword in the first ten lines of the file. +# For existing files, it checks version number in the "Id" line to +# prevent losing changes because an old version of a file was copied +# into the direcory. +# +# Possible future enhancements: +# +# Check for cruft left by unresolved conflicts. Search for +# "^<<<<<<<$", "^-------$", and "^>>>>>>>$". +# +# Look for a copyright and automagically update it to the +# current year. [[ bad idea! -- woods ]] +# +# +# Contributed by David Hampton <hampton@cisco.com> +# +# Hacked on lots by Greg A. Woods <woods@web.net> + +# +# Configurable options +# + +# Constants (remember to protect strings from RCS keyword substitution) +# +$LAST_FILE = "/tmp/#cvs.lastdir"; # must match name in log_accum.pl +$ENTRIES = "CVS/Entries"; + +# Patterns to find $Log keywords in files +# +$LogString1 = "\\\$\\Log: .* \\\$"; +$LogString2 = "\\\$\\Log\\\$"; +$NoLog = "%s - contains an RCS \$Log keyword. It must not!\n"; + +# pattern to match an RCS Id keyword line with an existing ID +# +$IDstring = "\"@\\(#\\)[^:]*:.*\\\$\Id: .*\\\$\""; +$NoId = " +%s - Does not contain a properly formatted line with the keyword \"Id:\". + I.e. no lines match \"" . $IDstring . "\". + Please see the template files for an example.\n"; + +# pattern to match an RCS Id keyword line for a new file (i.e. un-expanded) +# +$NewId = "\"@(#)[^:]*:.*\\$\Id\\$\""; + +$NoName = " +%s - The ID line should contain only \"@(#)module/path:\$Name\$:\$\Id\$\" + for a newly created file.\n"; + +$BadName = " +%s - The file name '%s' in the ID line does not match + the actual filename.\n"; + +$BadVersion = " +%s - How dare you!!! You replaced your copy of the file '%s', + which was based upon version %s, with an %s version based + upon %s. Please move your '%s' out of the way, perform an + update to get the current version, and them merge your changes + into that file, then try the commit again.\n"; + +# +# Subroutines +# + +sub write_line { + local($filename, $line) = @_; + open(FILE, ">$filename") || die("Cannot open $filename, stopped"); + print(FILE $line, "\n"); + close(FILE); +} + +sub check_version { + local($i, $id, $rname, $version); + local($filename, $cvsversion) = @_; + + open(FILE, "<$filename") || return(0); + + @all_lines = (); + $idpos = -1; + $newidpos = -1; + for ($i = 0; <FILE>; $i++) { + chop; + push(@all_lines, $_); + if ($_ =~ /$IDstring/) { + $idpos = $i; + } + if ($_ =~ /$NewId/) { + $newidpos = $i; + } + } + + if (grep(/$LogString1/, @all_lines) || grep(/$LogString2/, @all_lines)) { + print STDERR sprintf($NoLog, $filename); + return(1); + } + + if ($debug != 0) { + print STDERR sprintf("file = %s, version = %d.\n", $filename, $cvsversion{$filename}); + } + + if ($cvsversion{$filename} == 0) { + if ($newidpos != -1 && $all_lines[$newidpos] !~ /$NewId/) { + print STDERR sprintf($NoName, $filename); + return(1); + } + return(0); + } + + if ($idpos == -1) { + print STDERR sprintf($NoId, $filename); + return(1); + } + + $line = $all_lines[$idpos]; + $pos = index($line, "Id: "); + if ($debug != 0) { + print STDERR sprintf("%d in '%s'.\n", $pos, $line); + } + ($id, $rname, $version) = split(' ', substr($line, $pos)); + if ($rname ne "$filename,v") { + print STDERR sprintf($BadName, $filename, substr($rname, 0, length($rname)-2)); + return(1); + } + if ($cvsversion{$filename} < $version) { + print STDERR sprintf($BadVersion, $filename, $filename, $cvsversion{$filename}, + "newer", $version, $filename); + return(1); + } + if ($cvsversion{$filename} > $version) { + print STDERR sprintf($BadVersion, $filename, $filename, $cvsversion{$filename}, + "older", $version, $filename); + return(1); + } + return(0); +} + +# +# Main Body +# + +$id = getpgrp(); # You *must* use a shell that does setpgrp()! + +# Check each file (except dot files) for an RCS "Id" keyword. +# +$check_id = 0; + +# Record the directory for later use by the log_accumulate stript. +# +$record_directory = 0; + +# parse command line arguments +# +while (@ARGV) { + $arg = shift @ARGV; + + if ($arg eq '-d') { + $debug = 1; + print STDERR "Debug turned on...\n"; + } elsif ($arg eq '-c') { + $check_id = 1; + } elsif ($arg eq '-r') { + $record_directory = 1; + } else { + push(@files, $arg); + } +} + +$directory = shift @files; + +if ($debug != 0) { + print STDERR "dir - ", $directory, "\n"; + print STDERR "files - ", join(":", @files), "\n"; + print STDERR "id - ", $id, "\n"; +} + +# Suck in the CVS/Entries file +# +open(ENTRIES, $ENTRIES) || die("Cannot open $ENTRIES.\n"); +while (<ENTRIES>) { + local($filename, $version) = split('/', substr($_, 1)); + $cvsversion{$filename} = $version; +} + +# Now check each file name passed in, except for dot files. Dot files +# are considered to be administrative files by this script. +# +if ($check_id != 0) { + $failed = 0; + foreach $arg (@files) { + if (index($arg, ".") == 0) { + next; + } + $failed += &check_version($arg); + } + if ($failed) { + print STDERR "\n"; + exit(1); + } +} + +# Record this directory as the last one checked. This will be used +# by the log_accumulate script to determine when it is processing +# the final directory of a multi-directory commit. +# +if ($record_directory != 0) { + &write_line("$LAST_FILE.$id", $directory); +} +exit(0); diff --git a/contrib/cvs/contrib/cvs_acls.pl b/contrib/cvs/contrib/cvs_acls.pl new file mode 100644 index 0000000..bcb544d --- /dev/null +++ b/contrib/cvs/contrib/cvs_acls.pl @@ -0,0 +1,143 @@ +#! xPERL_PATHx +# -*-Perl-*- +# +# $Id: cvs_acls.pl,v 1.2 1995/07/10 02:01:33 kfogel Exp $ +# +# Access control lists for CVS. dgg@ksr.com (David G. Grubbs) +# +# CVS "commitinfo" for matching repository names, running the program it finds +# on the same line. More information is available in the CVS man pages. +# +# ==== INSTALLATION: +# +# To use this program as I intended, do the following four things: +# +# 0. Install PERL. :-) +# +# 1. Put one line, as the *only* non-comment line, in your commitinfo file: +# +# DEFAULT /usr/local/bin/cvs_acls +# +# 2. Install this file as /usr/local/bin/cvs_acls and make it executable. +# +# 3. Create a file named $CVSROOT/CVSROOT/avail. +# +# ==== FORMAT OF THE avail FILE: +# +# The avail file determines whether you may commit files. It contains lines +# read from top to bottom, keeping track of a single "bit". The "bit" +# defaults to "on". It can be turned "off" by "unavail" lines and "on" by +# "avail" lines. ==> Last one counts. +# +# Any line not beginning with "avail" or "unavail" is ignored. +# +# Lines beginning with "avail" or "unavail" are assumed to be '|'-separated +# triples: (All spaces and tabs are ignored in a line.) +# +# {avail.*,unavail.*} [| user,user,... [| repos,repos,...]] +# +# 1. String starting with "avail" or "unavail". +# 2. Optional, comma-separated list of usernames. +# 3. Optional, comma-separated list of repository pathnames. +# These are pathnames relative to $CVSROOT. They can be directories or +# filenames. A directory name allows access to all files and +# directories below it. +# +# Example: (Text from the ';;' rightward may not appear in the file.) +# +# unavail ;; Make whole repository unavailable. +# avail|dgg ;; Except for user "dgg". +# avail|fred, john|bin/ls ;; Except when "fred" or "john" commit to +# ;; the module whose repository is "bin/ls" +# +# PROGRAM LOGIC: +# +# CVS passes to @ARGV an absolute directory pathname (the repository +# appended to your $CVSROOT variable), followed by a list of filenames +# within that directory. +# +# We walk through the avail file looking for a line that matches both +# the username and repository. +# +# A username match is simply the user's name appearing in the second +# column of the avail line in a space-or-comma separate list. +# +# A repository match is either: +# - One element of the third column matches $ARGV[0], or some +# parent directory of $ARGV[0]. +# - Otherwise *all* file arguments ($ARGV[1..$#ARGV]) must be +# in the file list in one avail line. +# - In other words, using directory names in the third column of +# the avail file allows committing of any file (or group of +# files in a single commit) in the tree below that directory. +# - If individual file names are used in the third column of +# the avail file, then files must be committed individually or +# all files specified in a single commit must all appear in +# third column of a single avail line. +# + +$debug = 0; +$cvsroot = $ENV{'CVSROOT'}; +$availfile = $cvsroot . "/CVSROOT/avail"; +$myname = $ENV{"USER"} if !($myname = $ENV{"LOGNAME"}); + +eval "print STDERR \$die='Unknown parameter $1\n' if !defined \$$1; \$$1=\$';" + while ($ARGV[0] =~ /^(\w+)=/ && shift(@ARGV)); +exit 255 if $die; # process any variable=value switches + +die "Must set CVSROOT\n" if !$cvsroot; +($repos = shift) =~ s:^$cvsroot/::; +grep($_ = $repos . '/' . $_, @ARGV); + +print "$$ Repos: $repos\n","$$ ==== ",join("\n$$ ==== ",@ARGV),"\n" if $debug; + +$exit_val = 0; # Good Exit value + +$universal_off = 0; +open (AVAIL, $availfile) || exit(0); # It is ok for avail file not to exist +while (<AVAIL>) { + chop; + next if /^\s*\#/; + next if /^\s*$/; + ($flagstr, $u, $m) = split(/[\s,]*\|[\s,]*/, $_); + + # Skip anything not starting with "avail" or "unavail" and complain. + (print "Bad avail line: $_\n"), next + if ($flagstr !~ /^avail/ && $flagstr !~ /^unavail/); + + # Set which bit we are playing with. ('0' is OK == Available). + $flag = (($& eq "avail") ? 0 : 1); + + # If we find a "universal off" flag (i.e. a simple "unavail") remember it + $universal_off = 1 if ($flag && !$u && !$m); + + # $myname considered "in user list" if actually in list or is NULL + $in_user = (!$u || grep ($_ eq $myname, split(/[\s,]+/,$u))); + print "$$ \$myname($myname) in user list: $_\n" if $debug && $in_user; + + # Module matches if it is a NULL module list in the avail line. If module + # list is not null, we check every argument combination. + if (!($in_repo = !$m)) { + @tmp = split(/[\s,]+/,$m); + for $j (@tmp) { + # If the repos from avail is a parent(or equal) dir of $repos, OK + $in_repo = 1, last if ($repos eq $j || $repos =~ /^$j\//); + } + if (!$in_repo) { + $in_repo = 1; + for $j (@ARGV) { + last if !($in_repo = grep ($_ eq $j, @tmp)); + } + } + } + print "$$ \$repos($repos) in repository list: $_\n" if $debug && $in_repo; + + $exit_val = $flag if ($in_user && $in_repo); + print "$$ ==== \$exit_val = $exit_val\n$$ ==== \$flag = $flag\n" if $debug; +} +close(AVAIL); +print "$$ ==== \$exit_val = $exit_val\n" if $debug; +print "**** Access denied: Insufficient Karma ($myname|$repos)\n" if $exit_val; +print "**** Access allowed: Personal Karma exceeds Environmental Karma.\n" + if $universal_off && !$exit_val; +exit($exit_val); diff --git a/contrib/cvs/contrib/cvscheck.man b/contrib/cvs/contrib/cvscheck.man new file mode 100644 index 0000000..61a064a --- /dev/null +++ b/contrib/cvs/contrib/cvscheck.man @@ -0,0 +1,53 @@ +.\" $Id: cvscheck.man,v 1.1.1.3 1995/08/28 16:20:24 jimb Exp $ +.\" Contributed by Lowell Skoog <fluke!lowell@uunet.uu.net> +.TH CVSCHECK LOCAL "4 March 1991" FLUKE +.SH NAME +cvscheck \- identify files added, changed, or removed in a CVS working +directory +.SH SYNOPSIS +.B cvscheck +.SH DESCRIPTION +This command is a housekeeping aid. It should be run in a working +directory that has been checked out using CVS. It identifies files +that have been added, changed, or removed in the working directory, but +not CVS +.BR commit ted. +It also determines whether the files have been CVS +.BR add ed +or CVS +.BR remove d. +For directories, this command determines only whether they have been +.BR add ed. +It operates in the current directory only. +.LP +This command provides information that is available using CVS +.B status +and CVS +.BR diff . +The advantage of +.B cvscheck +is that its output is very concise. It saves you the strain (and +potential error) of interpreting the output of CVS +.B status +and +.BR diff . +.LP +See +.BR cvs (local) +or +.BR cvshelp (local) +for instructions on how to add or remove a file or directory in a +CVS-controlled package. +.SH DIAGNOSTICS +The exit status is 0 if no files have been added, changed, or removed +from the current directory. Otherwise, the command returns a count of +the adds, changes, and deletes. +.SH SEE ALSO +.BR cvs (local), +.BR cvshelp (local) +.SH AUTHOR +Lowell Skoog +.br +Software Technology Group +.br +Technical Computing diff --git a/contrib/cvs/contrib/cvscheck.sh b/contrib/cvs/contrib/cvscheck.sh new file mode 100644 index 0000000..96dba6e --- /dev/null +++ b/contrib/cvs/contrib/cvscheck.sh @@ -0,0 +1,84 @@ +#! /bin/sh +# $Id: cvscheck.sh,v 1.1 1995/07/10 02:26:29 kfogel Exp $ +# +# cvscheck - identify files added, changed, or removed +# in CVS working directory +# +# Contributed by Lowell Skoog <fluke!lowell@uunet.uu.net> +# +# This program should be run in a working directory that has been +# checked out using CVS. It identifies files that have been added, +# changed, or removed in the working directory, but not "cvs +# committed". It also determines whether the files have been "cvs +# added" or "cvs removed". For directories, it is only practical to +# determine whether they have been added. + +name=cvscheck +changes=0 + +# If we can't run CVS commands in this directory +cvs status . > /dev/null 2>&1 +if [ $? != 0 ] ; then + + # Bail out + echo "$name: there is no version here; bailing out" 1>&2 + exit 1 +fi + +# Identify files added to working directory +for file in .* * ; do + + # Skip '.' and '..' + if [ $file = '.' -o $file = '..' ] ; then + continue + fi + + # If a regular file + if [ -f $file ] ; then + if cvs status $file | grep -s '^From:[ ]*New file' ; then + echo "file added: $file - not CVS committed" + changes=`expr $changes + 1` + elif cvs status $file | grep -s '^From:[ ]*no entry for' ; then + echo "file added: $file - not CVS added, not CVS committed" + changes=`expr $changes + 1` + fi + + # Else if a directory + elif [ -d $file -a $file != CVS.adm ] ; then + + # Move into it + cd $file + + # If CVS commands don't work inside + cvs status . > /dev/null 2>&1 + if [ $? != 0 ] ; then + echo "directory added: $file - not CVS added" + changes=`expr $changes + 1` + fi + + # Move back up + cd .. + fi +done + +# Identify changed files +changedfiles=`cvs diff | egrep '^diff' | awk '{print $3}'` +for file in $changedfiles ; do + echo "file changed: $file - not CVS committed" + changes=`expr $changes + 1` +done + +# Identify files removed from working directory +removedfiles=`cvs status | egrep '^File:[ ]*no file' | awk '{print $4}'` + +# Determine whether each file has been cvs removed +for file in $removedfiles ; do + if cvs status $file | grep -s '^From:[ ]*-' ; then + echo "file removed: $file - not CVS committed" + else + echo "file removed: $file - not CVS removed, not CVS committed" + fi + changes=`expr $changes + 1` +done + +exit $changes diff --git a/contrib/cvs/contrib/cvshelp.man b/contrib/cvs/contrib/cvshelp.man new file mode 100644 index 0000000..2cfae1f --- /dev/null +++ b/contrib/cvs/contrib/cvshelp.man @@ -0,0 +1,562 @@ +.\" $Id: cvshelp.man,v 1.1.1.3 1995/08/28 16:20:28 jimb Exp $ +.\" Contributed by Lowell Skoog <fluke!lowell@uunet.uu.net> +.\" Full space in nroff; half space in troff +.de SP +.if n .sp +.if t .sp .5 +.. +.\" Start a command example +.de XS +.SP +.in +.5i +.ft B +.nf +.. +.\" End a command example +.de XE +.fi +.ft P +.in -.5i +.SP +.. +.TH CVSHELP LOCAL "17 March 1991" FLUKE +.SH NAME +cvshelp \- advice on using the Concurrent Versions System +.SH DESCRIPTION +This man page is based on experience using CVS. +It is bound to change as we gain more experience. +If you come up with better advice than is found here, +contact the Software Technology +Group and we will add it to this page. +.SS "Getting Started" +Use the following steps to prepare to use CVS: +.TP +\(bu +Take a look at the CVS manual page to see what it can do for you, and +if it fits your environment (or can possibly be made to fit your +environment). +.XS +man cvs +.XE +If things look good, continue on... +.TP +\(bu +Setup the master source repository. Choose a directory with +ample disk space available for source files. This is where the RCS +`,v' files will be stored. Say you choose +.B /src/master +as the root +of your source repository. Make the +.SB CVSROOT.adm +directory in the root of the source repository: +.XS +mkdir /src/master/CVSROOT.adm +.XE +.TP +\(bu +Populate this directory with the +.I loginfo +and +.I modules +files from the +.B "/usr/doc/local/cvs" +directory. Edit these files to reflect your local source repository +environment \- they may be quite small initially, but will grow as +sources are added to your source repository. Turn these files into +RCS controlled files: +.XS +cd /src/master/CVSROOT.adm +ci \-m'Initial loginfo file' loginfo +ci \-m'Initial modules file' modules +.XE +.TP +\(bu +Run the command: +.XS +mkmodules /src/master/CVSROOT.adm +.XE +This will build the +.BR ndbm (3) +file for the modules database. +.TP +\(bu +Remember to edit the +.I modules +file manually when sources are checked +in with +.B checkin +or CVS +.BR add . +A copy of the +.I modules +file for editing can be retrieved with the command: +.XS +cvs checkout CVSROOT.adm +.XE +.TP +\(bu +Have all users of the CVS system set the +.SM CVSROOT +environment variable appropriately to reflect the placement of your +source repository. If the above example is used, the following +commands can be placed in a +.I .login +or +.I .profile +file: +.XS +setenv CVSROOT /src/master +.XE +for csh users, and +.XS +CVSROOT=/src/master; export CVSROOT +.XE +for sh users. +.SS "Placing Locally Written Sources Under CVS Control" +Say you want to place the `whizbang' sources under +CVS control. Say further that the sources have never +been under revision control before. +.TP +\(bu +Move the source hierarchy (lock, stock, and barrel) +into the master source repository: +.XS +mv ~/whizbang $CVSROOT +.XE +.TP +\(bu +Clean out unwanted object files: +.XS +cd $CVSROOT/whizbang +make clean +.XE +.TP +\(bu +Turn every file in the hierarchy into an RCS controlled file: +.XS +descend \-f 'ci \-t/dev/null \-m"Placed under CVS control" \-nV\fR\fIx\fR\fB_\fR\fIy\fR\fB *' +.XE +In this example, the initial release tag is \fBV\fIx\fB_\fIy\fR, +representing version \fIx\fR.\fIy\fR. +.LP +You can use CVS on sources that are already under RCS control. +The following example shows how. +In this example, the source package is called `skunkworks'. +.TP +\(bu +Move the source hierarchy into the master source +repository: +.XS +mv ~/skunkworks $CVSROOT +.XE +.TP +\(bu +Clean out unwanted object files: +.XS +cd $CVSROOT/skunkworks +make clean +.XE +.TP +\(bu +Clean out unwanted working files, leaving only the RCS `,v' files: +.XS +descend \-r rcsclean +.XE +Note: If any working files have been checked out and changed, +.B rcsclean +will fail. Check in the modified working files +and run the command again. +.TP +\(bu +Get rid of +.SB RCS +subdirectories. CVS does not use them. +.XS +descend \-r \-f 'mv RCS/*,v .' +descend \-r \-f 'rmdir RCS' +.XE +.TP +\(bu +Delete any unwanted files that remain in the source hierarchy. Then +make sure all files are under RCS control: +.XS +descend \-f 'ci \-t/dev/null \-m"Placed under CVS control" \-n\fR\fItag\fR\fB *' +.XE +.I tag +is the latest symbolic revision tag that you applied to your package +(if any). Note: This command will probably generate lots of error +messages (for directories and existing RCS files) that you can +ignore. +.SS "Placing a Third-Party Source Distribution Under CVS Control" +The +.B checkin +command checks third-party sources into CVS. The +difference between third-party sources and locally +written sources is that third-party sources must be checked into a +separate branch (called the +.IR "vendor branch" ) +of the RCS tree. This makes it possible to merge local changes to +the sources with later releases from the vendor. +.TP +\(bu +Save the original distribution kit somewhere. For example, if the +master source repository is +.B /src/master +the distribution kit could be saved in +.BR /src/dist . +Organize the distribution directory so that each release +is clearly identifiable. +.TP +\(bu +Unpack the package in a scratch directory, for example +.BR ~/scratch . +.TP +\(bu +Create a repository for the package. +In this example, the package is called `Bugs-R-Us 4.3'. +.XS +mkdir $CVSROOT/bugs +.XE +.TP +\(bu +Check in the unpacked files: +.XS +cd ~/scratch +checkin \-m 'Bugs-R-Us 4.3 distribution' bugs VENDOR V4_3 +.XE +There is nothing magic about the tag `VENDOR', which is applied to +the vendor branch. You can use whatever tag you want. `VENDOR' is a +useful convention. +.TP +\(bu +Never modify vendor files before checking them in. +Check in the files +.I exactly +as you unpacked them. +If you check in locally modified files, future vendor releases may +wipe out your local changes. +.SS "Working With CVS-Controlled Sources" +To use or edit the sources, you must check out a private copy. +For the following examples, the master files are assumed to reside in +.BR "$CVSROOT/behemoth" . +The working directory is +.BR "~/work" . +See +.BR cvs (local) +for more details on the commands mentioned below. +.TP +.I "To Check Out Working Files +Use CVS +.BR checkout : +.XS +cd ~/work +cvs checkout behemoth +.XE +There is nothing magic about the working directory. CVS will check +out sources anywhere you like. Once you have a working copy of the +sources, you can compile or edit them as desired. +.TP +.I "To Display Changes You Have Made" +Use CVS +.BR diff +to display detailed changes, equivalent to +.BR rcsdiff (local). +You can also use +.BR cvscheck (local) +to list files added, changed, and removed in +the directory, but not yet +.BR commit ted. +You must be in a directory containing working files. +.TP +.I "To Display Revision Information" +Use CVS +.BR log , +which is equivalent to +.BR rlog (local). +You must be in a directory containing working files. +.TP +.I "To Update Working Files" +Use CVS +.BR update +in a directory containing working files. +This command brings your working files up +to date with changes checked into the +master repository since you last checked out or updated +your files. +.TP +.I "To Check In Your Changes" +Use CVS +.BR commit +in a directory containing working files. +This command checks your changes into the master repository. +You can specify files by name or use +.XS +cvs commit \-a +.XE +to +.B commit +all the files you have changed. +.TP +.I "To Add a File" +Add the file to the working directory. +Use CVS +.B add +to mark the file as added. +Use CVS +.B commit +to add the file to the master repository. +.TP +.I "To Remove a File" +Remove the file from the working directory. +Use CVS +.B remove +to mark the file as removed. +Use CVS +.B commit +to move the file from its current location in the master repository +to the CVS +.IR Attic +directory. +.TP +.I "To Add a Directory" +Add the directory to the working directory. +Use CVS +.B add +to add the directory to the master repository. +.TP +.I "To Remove a Directory" +.br +You shouldn't remove directories under CVS. You should instead remove +their contents and then prune them (using the +.B \-f +and +.B \-p +options) when you +.B checkout +or +.B update +your working files. +.TP +.I "To Tag a Release" +Use CVS +.B tag +to apply a symbolic tag to the latest revision of each file in the +master repository. For example: +.XS +cvs tag V2_1 behemoth +.XE +.TP +.I "To Retrieve an Exact Copy of a Previous Release" +During a CVS +.B checkout +or +.BR update , +use the +.B \-r +option to retrieve revisions associated with a symbolic tag. +Use the +.B \-f +option to ignore all RCS files that do not contain the +tag. +Use the +.B \-p +option to prune directories that wind up empty because none +of their files matched the tag. Example: +.XS +cd ~/work +cvs checkout \-r V2_1 \-f \-p behemoth +.XE +.SS "Logging Changes" +It is a good idea to keep a change log together with the +sources. As a minimum, the change log should name and describe each +tagged release. The change log should also be under CVS control and +should be tagged along with the sources. +.LP +.BR cvslog (local) +can help. This command logs +changes reported during CVS +.B commit +operations. It automatically +updates a change log file in your working directory. When you are +finished making changes, you (optionally) edit the change log file and +then commit it to the master repository. +.LP +Note: You must edit the change log to describe a new release +and +.B commit +it to the master repository +.I before +.BR tag ging +the release using CVS. Otherwise, the release description will not be +included in the tagged package. +.LP +See +.BR cvslog (local) +for more information. +.SS "Merging a Subsequent Third-Party Distribution" +The initial steps in this process are identical to placing a +third-party distribution under CVS for the first time: save the +distribution kit and unpack the package in a scratch directory. From +that point the steps diverge. +The following example considers release 5.0 of the +Bugs-R-Us package. +.TP +\(bu +Check in the sources after unpacking them: +.XS +cd ~/scratch +checkin \-m 'Bugs-R-Us 5.0 distribution' bugs VENDOR V5_0 \\ + | tee ~/WARNINGS +.XE +It is important to save the output of +.B checkin +in a file +because it lists the sources that have been locally modified. +It is best to save the file in a different directory (for example, +your home directory). Otherwise, +.B checkin +will try to check it into the master repository. +.TP +\(bu +In your usual working directory, check out a fresh copy of the +distribution that you just checked in. +.XS +cd ~/work +cvs checkout \-r VENDOR bugs +.XE +The +.B checkout +command shown above retrieves the latest revision on the vendor branch. +.TP +\(bu +See the `WARNINGS' file for a list of all locally modified +sources. +For each locally modified source, +look at the differences between +the new distribution and the latest local revision: +.XS +cvs diff \-r \fR\fILocalRev file\fR\fB +.XE +In this command, +.I LocalRev +is the latest +numeric or symbolic revision +on the RCS trunk of +.IR file . +You can use CVS +.B log +to get the revision history. +.TP +\(bu +If your local modifications to a file have been incorporated into +the vendor's distribution, then you should reset the default RCS +branch for that file to the vendor branch. CVS doesn't provide a +mechanism to do this. You have to do it by hand in the master +repository: +.XS +rcs \-bVENDOR \fR\fIfile\fR\fB,v +.XE +.TP +\(bu +If your local modifications need to be merged with the +new distribution, use CVS +.B join +to do it: +.XS +cvs join \-r VENDOR \fR\fIfile\fR\fB +.XE +The resulting file will be placed in your working directory. +Edit it to resolve any overlaps. +.TP +\(bu +Test the merged package. +.TP +\(bu +Commit all modified files to the repository: +.XS +cvs commit \-a +.XE +.TP +\(bu +Tag the repository with a new local tag. +.SS "Applying Patches to Third-Party Sources" +Patches are handled in a manner very similar to complete +third-party distributions. This example considers patches applied to +Bugs-R-Us release 5.0. +.TP +\(bu +Save the patch files together with the distribution kit +to which they apply. +The patch file names should clearly indicate the patch +level. +.TP +\(bu +In a scratch directory, check out the last `clean' vendor copy \- the +highest revision on the vendor branch with +.IR "no local changes" : +.XS +cd ~/scratch +cvs checkout \-r VENDOR bugs +.XE +.TP +\(bu +Use +.BR patch (local) +to apply the patches. You should now have an image of the +vendor's software just as though you had received a complete, +new release. +.TP +\(bu +Proceed with the steps described for merging a subsequent third-party +distribution. +.TP +\(bu +Note: When you get to the step that requires you +to check out the new distribution after you have +checked it into the vendor branch, you should move to a different +directory. Do not attempt to +.B checkout +files in the directory in +which you applied the patches. If you do, CVS will try to merge the +changes that you made during patching with the version being checked +out and things will get very confusing. Instead, +go to a different directory (like your working directory) and +check out the files there. +.SS "Advice to Third-Party Source Hackers" +As you can see from the preceding sections, merging local changes +into third-party distributions remains difficult, and probably +always will. This fact suggests some guidelines: +.TP +\(bu +Minimize local changes. +.I Never +make stylistic changes. +Change makefiles only as much as needed for installation. Avoid +overhauling anything. Pray that the vendor does the same. +.TP +\(bu +Avoid renaming files or moving them around. +.TP +\(bu +Put independent, locally written files like help documents, local +tools, or man pages in a sub-directory called `local-additions'. +Locally written files that are linked into an existing executable +should be added right in with the vendor's sources (not in a +`local-additions' directory). +If, in the future, +the vendor distributes something +equivalent to your locally written files +you can CVS +.B remove +the files from the `local-additions' directory at that time. +.SH SEE ALSO +.BR cvs (local), +.BR checkin (local), +.BR cvslog (local), +.BR cvscheck (local) +.SH AUTHOR +Lowell Skoog +.br +Software Technology Group +.br +Technical Computing diff --git a/contrib/cvs/contrib/descend.man b/contrib/cvs/contrib/descend.man new file mode 100644 index 0000000..5ac46f4 --- /dev/null +++ b/contrib/cvs/contrib/descend.man @@ -0,0 +1,115 @@ +.\" $Id: descend.man,v 1.1.1.3 1995/08/28 16:20:31 jimb Exp $ +.TH DESCEND 1 "31 March 1992" +.SH NAME +descend \- walk directory tree and execute a command at each node +.SH SYNOPSIS +.B descend +[ +.B \-afqrv +] +.I command +[ +.I directory +\&.\|.\|. +] +.SH DESCRIPTION +.B descend +walks down a directory tree and executes a command at each node. It +is not as versatile as +.BR find (1), +but it has a simpler syntax. If no +.I directory +is specified, +.B descend +starts at the current one. +.LP +Unlike +.BR find , +.B descend +can be told to skip the special directories associated with RCS, +CVS, and SCCS. This makes +.B descend +especially handy for use with these packages. It can be used with +other commands too, of course. +.LP +.B descend +is a poor man's way to make any command recursive. Note: +.B descend +does not follow symbolic links to directories unless they are +specified on the command line. +.SH OPTIONS +.TP 15 +.B \-a +.I All. +Descend into directories that begin with '.'. +.TP +.B \-f +.I Force. +Ignore errors during descent. Normally, +.B descend +quits when an error occurs. +.TP +.B \-q +.I Quiet. +Suppress the message `In directory +.IR directory ' +that is normally printed during the descent. +.TP +.B \-r +.I Restricted. +Don't descend into the special directories +.SB RCS, +.SB CVS, +.SB CVS.adm, +and +.SB SCCS. +.TP +.B \-v +.I Verbose. +Print +.I command +before executing it. +.SH EXAMPLES +.TP 15 +.B "descend ls" +Cheap substitute for `ls -R'. +.TP 15 +.B "descend -f 'rm *' tree" +Strip `tree' of its leaves. This command descends the `tree' +directory, removing all regular files. Since +.BR rm (1) +does not remove directories, this command leaves the directory +structure of `tree' intact, but denuded. The +.B \-f +option is required to keep +.B descend +from quitting. You could use `rm \-f' instead. +.TP +.B "descend -r 'co RCS/*'" /project/src/ +Check out every RCS file under the directory +.BR "/project/src" . +.TP +.B "descend -r 'cvs diff'" +Perform CVS `diff' operation on every directory below (and including) +the current one. +.SH DIAGNOSTICS +Returns 1 if errors occur (and the +.B \-f +option is not used). Otherwise returns 0. +.SH SEE ALSO +.BR find (1), +.BR rcsintro (1), +.BR cvs (1), +.BR sccs (1) +.SH AUTHOR +Lowell Skoog +.br +Software Technology Group +.br +John Fluke Mfg. Co., Inc. +.SH BUGS +Shell metacharacters in +.I command +may have bizarre effects. In particular, compound commands +(containing ';', '[', and ']' characters) will not work. It is best +to enclose complicated commands in single quotes \(aa\ \(aa. diff --git a/contrib/cvs/contrib/descend.sh b/contrib/cvs/contrib/descend.sh new file mode 100644 index 0000000..e6a7880 --- /dev/null +++ b/contrib/cvs/contrib/descend.sh @@ -0,0 +1,116 @@ +#! /bin/sh +# $Id: descend.sh,v 1.1 1995/07/10 02:26:32 kfogel Exp $ +# +# descend - walk down a directory tree and execute a command at each node + +fullname=$0 +name=descend +usage="Usage: $name [-afqrv] command [directory ...]\n +\040\040-a\040\040All: descend into directories starting with '.'\n +\040\040-f\040\040Force: ignore errors during descent\n +\040\040-q\040\040Quiet: don't print directory names\n +\040\040-r\040\040Restricted: don't descend into RCS, CVS.adm, SCCS directories\n +\040\040-v\040\040Verbose: print command before executing it" + +# Scan for options +while getopts afqrv option; do + case $option in + a) + alldirs=$option + options=$options" "-$option + ;; + f) + force=$option + options=$options" "-$option + ;; + q) + verbose= + quiet=$option + options=$options" "-$option + ;; + r) + restricted=$option + options=$options" "-$option + ;; + v) + verbose=$option + quiet= + options=$options" "-$option + ;; + \?) + /usr/5bin/echo $usage 1>&2 + exit 1 + ;; + esac +done +shift `expr $OPTIND - 1` + +# Get command to execute +if [ $# -lt 1 ] ; then + /usr/5bin/echo $usage 1>&2 + exit 1 +else + command=$1 + shift +fi + +# If no directory specified, use '.' +if [ $# -lt 1 ] ; then + default_dir=. +fi + +# For each directory specified +for dir in $default_dir "$@" ; do + + # Spawn sub-shell so we return to starting directory afterward + (cd $dir + + # Execute specified command + if [ -z "$quiet" ] ; then + echo In directory `hostname`:`pwd` + fi + if [ -n "$verbose" ] ; then + echo $command + fi + eval "$command" || if [ -z "$force" ] ; then exit 1; fi + + # Collect dot file names if necessary + if [ -n "$alldirs" ] ; then + dotfiles=.* + else + dotfiles= + fi + + # For each file in current directory + for file in $dotfiles * ; do + + # Skip '.' and '..' + if [ "$file" = "." -o "$file" = ".." ] ; then + continue + fi + + # If a directory but not a symbolic link + if [ -d "$file" -a ! -h "$file" ] ; then + + # If not skipping this type of directory + if [ \( "$file" != "RCS" -a \ + "$file" != "SCCS" -a \ + "$file" != "CVS" -a \ + "$file" != "CVS.adm" \) \ + -o -z "$restricted" ] ; then + + # Recursively descend into it + $fullname $options "$command" "$file" \ + || if [ -z "$force" ] ; then exit 1; fi + fi + + # Else if a directory AND a symbolic link + elif [ -d "$file" -a -h "$file" ] ; then + + if [ -z "$quiet" ] ; then + echo In directory `hostname`:`pwd`/$file: symbolic link: skipping + fi + fi + done + ) || if [ -z "$force" ] ; then exit 1; fi +done diff --git a/contrib/cvs/contrib/dirfns.shar b/contrib/cvs/contrib/dirfns.shar new file mode 100644 index 0000000..8324c41 --- /dev/null +++ b/contrib/cvs/contrib/dirfns.shar @@ -0,0 +1,481 @@ +echo 'directory.3': +sed 's/^X//' >'directory.3' <<'!' +X.TH DIRECTORY 3 imported +X.DA 9 Oct 1985 +X.SH NAME +Xopendir, readdir, telldir, seekdir, rewinddir, closedir \- high-level directory operations +X.SH SYNOPSIS +X.B #include <sys/types.h> +X.br +X.B #include <ndir.h> +X.PP +X.SM +X.B DIR +X.B *opendir(filename) +X.br +X.B char *filename; +X.PP +X.SM +X.B struct direct +X.B *readdir(dirp) +X.br +X.B DIR *dirp; +X.PP +X.SM +X.B long +X.B telldir(dirp) +X.br +X.B DIR *dirp; +X.PP +X.SM +X.B seekdir(dirp, loc) +X.br +X.B DIR *dirp; +X.br +X.B long loc; +X.PP +X.SM +X.B rewinddir(dirp) +X.br +X.B DIR *dirp; +X.PP +X.SM +X.B closedir(dirp) +X.br +X.B DIR *dirp; +X.SH DESCRIPTION +XThis library provides high-level primitives for directory scanning, +Xsimilar to those available for 4.2BSD's (very different) directory system. +X.\"The purpose of this library is to simulate +X.\"the new flexible length directory names of 4.2bsd UNIX +X.\"on top of the old directory structure of v7. +XIt incidentally provides easy portability to and from 4.2BSD (insofar +Xas such portability is not compromised by other 4.2/VAX dependencies). +X.\"It allows programs to be converted immediately +X.\"to the new directory access interface, +X.\"so that they need only be relinked +X.\"when moved to 4.2bsd. +X.\"It is obtained with the loader option +X.\".BR \-lndir . +X.PP +X.I Opendir +Xopens the directory named by +X.I filename +Xand associates a +X.I directory stream +Xwith it. +X.I Opendir +Xreturns a pointer to be used to identify the +X.I directory stream +Xin subsequent operations. +XThe pointer +X.SM +X.B NULL +Xis returned if +X.I filename +Xcannot be accessed or is not a directory. +X.PP +X.I Readdir +Xreturns a pointer to the next directory entry. +XIt returns +X.B NULL +Xupon reaching the end of the directory or detecting +Xan invalid +X.I seekdir +Xoperation. +X.PP +X.I Telldir +Xreturns the current location associated with the named +X.I directory stream. +X.PP +X.I Seekdir +Xsets the position of the next +X.I readdir +Xoperation on the +X.I directory stream. +XThe new position reverts to the one associated with the +X.I directory stream +Xwhen the +X.I telldir +Xoperation was performed. +XValues returned by +X.I telldir +Xare good only for the lifetime of the DIR pointer from +Xwhich they are derived. +XIf the directory is closed and then reopened, +Xthe +X.I telldir +Xvalue may be invalidated +Xdue to undetected directory compaction in 4.2BSD. +XIt is safe to use a previous +X.I telldir +Xvalue immediately after a call to +X.I opendir +Xand before any calls to +X.I readdir. +X.PP +X.I Rewinddir +Xresets the position of the named +X.I directory stream +Xto the beginning of the directory. +X.PP +X.I Closedir +Xcauses the named +X.I directory stream +Xto be closed, +Xand the structure associated with the DIR pointer to be freed. +X.PP +XA +X.I direct +Xstructure is as follows: +X.PP +X.RS +X.nf +Xstruct direct { +X /* unsigned */ long d_ino; /* inode number of entry */ +X unsigned short d_reclen; /* length of this record */ +X unsigned short d_namlen; /* length of string in d_name */ +X char d_name[MAXNAMLEN + 1]; /* name must be no longer than this */ +X}; +X.fi +X.RE +X.PP +XThe +X.I d_reclen +Xfield is meaningless in non-4.2BSD systems and should be ignored. +XThe use of a +X.I long +Xfor +X.I d_ino +Xis also a 4.2BSDism; +X.I ino_t +X(see +X.IR types (5)) +Xshould be used elsewhere. +XThe macro +X.I DIRSIZ(dp) +Xgives the minimum memory size needed to hold the +X.I direct +Xvalue pointed to by +X.IR dp , +Xwith the minimum necessary allocation for +X.IR d_name . +X.PP +XThe preferred way to search the current directory for entry ``name'' is: +X.PP +X.RS +X.nf +X len = strlen(name); +X dirp = opendir("."); +X if (dirp == NULL) { +X fprintf(stderr, "%s: can't read directory .\\n", argv[0]); +X return NOT_FOUND; +X } +X while ((dp = readdir(dirp)) != NULL) +X if (dp->d_namlen == len && strcmp(dp->d_name, name) == 0) { +X closedir(dirp); +X return FOUND; +X } +X closedir(dirp); +X return NOT_FOUND; +X.RE +X.\".SH LINKING +X.\"This library is accessed by specifying ``-lndir'' as the +X.\"last argument to the compile line, e.g.: +X.\".PP +X.\" cc -I/usr/include/ndir -o prog prog.c -lndir +X.SH "SEE ALSO" +Xopen(2), +Xclose(2), +Xread(2), +Xlseek(2) +X.SH HISTORY +XWritten by +XKirk McKusick at Berkeley (ucbvax!mckusick). +XMiscellaneous bug fixes from elsewhere. +XThe size of the data structure has been decreased to avoid excessive +Xspace waste under V7 (where filenames are 14 characters at most). +XFor obscure historical reasons, the include file is also available +Xas +X.IR <ndir/sys/dir.h> . +XThe Berkeley version lived in a separate library (\fI\-lndir\fR), +Xwhereas ours is +Xpart of the C library, although the separate library is retained to +Xmaximize compatibility. +X.PP +XThis manual page has been substantially rewritten to be informative in +Xthe absence of a 4.2BSD manual. +X.SH BUGS +XThe +X.I DIRSIZ +Xmacro actually wastes a bit of space due to some padding requirements +Xthat are an artifact of 4.2BSD. +X.PP +XThe returned value of +X.I readdir +Xpoints to a static area that will be overwritten by subsequent calls. +X.PP +XThere are some unfortunate name conflicts with the \fIreal\fR V7 +Xdirectory structure definitions. +! +echo 'dir.h': +sed 's/^X//' >'dir.h' <<'!' +X/* dir.h 4.4 82/07/25 */ +X +X/* +X * A directory consists of some number of blocks of DIRBLKSIZ +X * bytes, where DIRBLKSIZ is chosen such that it can be transferred +X * to disk in a single atomic operation (e.g. 512 bytes on most machines). +X * +X * Each DIRBLKSIZ byte block contains some number of directory entry +X * structures, which are of variable length. Each directory entry has +X * a struct direct at the front of it, containing its inode number, +X * the length of the entry, and the length of the name contained in +X * the entry. These are followed by the name padded to a 4 byte boundary +X * with null bytes. All names are guaranteed null terminated. +X * The maximum length of a name in a directory is MAXNAMLEN. +X * +X * The macro DIRSIZ(dp) gives the amount of space required to represent +X * a directory entry. Free space in a directory is represented by +X * entries which have dp->d_reclen >= DIRSIZ(dp). All DIRBLKSIZ bytes +X * in a directory block are claimed by the directory entries. This +X * usually results in the last entry in a directory having a large +X * dp->d_reclen. When entries are deleted from a directory, the +X * space is returned to the previous entry in the same directory +X * block by increasing its dp->d_reclen. If the first entry of +X * a directory block is free, then its dp->d_ino is set to 0. +X * Entries other than the first in a directory do not normally have +X * dp->d_ino set to 0. +X */ +X#define DIRBLKSIZ 512 +X#ifdef VMUNIX +X#define MAXNAMLEN 255 +X#else +X#define MAXNAMLEN 14 +X#endif +X +Xstruct direct { +X /* unsigned */ long d_ino; /* inode number of entry */ +X unsigned short d_reclen; /* length of this record */ +X unsigned short d_namlen; /* length of string in d_name */ +X char d_name[MAXNAMLEN + 1]; /* name must be no longer than this */ +X}; +X +X/* +X * The DIRSIZ macro gives the minimum record length which will hold +X * the directory entry. This requires the amount of space in struct direct +X * without the d_name field, plus enough space for the name with a terminating +X * null byte (dp->d_namlen+1), rounded up to a 4 byte boundary. +X */ +X#undef DIRSIZ +X#define DIRSIZ(dp) \ +X ((sizeof (struct direct) - (MAXNAMLEN+1)) + (((dp)->d_namlen+1 + 3) &~ 3)) +X +X#ifndef KERNEL +X/* +X * Definitions for library routines operating on directories. +X */ +Xtypedef struct _dirdesc { +X int dd_fd; +X long dd_loc; +X long dd_size; +X char dd_buf[DIRBLKSIZ]; +X} DIR; +X#ifndef NULL +X#define NULL 0 +X#endif +Xextern DIR *opendir(); +Xextern struct direct *readdir(); +Xextern long telldir(); +X#ifdef void +Xextern void seekdir(); +Xextern void closedir(); +X#endif +X#define rewinddir(dirp) seekdir((dirp), (long)0) +X#endif KERNEL +! +echo 'makefile': +sed 's/^X//' >'makefile' <<'!' +XDIR = closedir.o opendir.o readdir.o seekdir.o telldir.o +XCFLAGS=-O -I. -Dvoid=int +XDEST=.. +X +Xall: $(DIR) +X +Xmv: $(DIR) +X mv $(DIR) $(DEST) +X +Xcpif: dir.h +X cp dir.h /usr/include/ndir.h +X +Xclean: +X rm -f *.o +! +echo 'closedir.c': +sed 's/^X//' >'closedir.c' <<'!' +Xstatic char sccsid[] = "@(#)closedir.c 4.2 3/10/82"; +X +X#include <sys/types.h> +X#include <dir.h> +X +X/* +X * close a directory. +X */ +Xvoid +Xclosedir(dirp) +X register DIR *dirp; +X{ +X close(dirp->dd_fd); +X dirp->dd_fd = -1; +X dirp->dd_loc = 0; +X free((char *)dirp); +X} +! +echo 'opendir.c': +sed 's/^X//' >'opendir.c' <<'!' +X/* Copyright (c) 1982 Regents of the University of California */ +X +Xstatic char sccsid[] = "@(#)opendir.c 4.4 11/12/82"; +X +X#include <sys/types.h> +X#include <sys/stat.h> +X#include <dir.h> +X +X/* +X * open a directory. +X */ +XDIR * +Xopendir(name) +X char *name; +X{ +X register DIR *dirp; +X register int fd; +X struct stat statbuf; +X char *malloc(); +X +X if ((fd = open(name, 0)) == -1) +X return NULL; +X if (fstat(fd, &statbuf) == -1 || !(statbuf.st_mode & S_IFDIR)) { +X close(fd); +X return NULL; +X } +X if ((dirp = (DIR *)malloc(sizeof(DIR))) == NULL) { +X close (fd); +X return NULL; +X } +X dirp->dd_fd = fd; +X dirp->dd_loc = 0; +X dirp->dd_size = 0; /* so that telldir will work before readdir */ +X return dirp; +X} +! +echo 'readdir.c': +sed 's/^X//' >'readdir.c' <<'!' +X/* Copyright (c) 1982 Regents of the University of California */ +X +Xstatic char sccsid[] = "@(#)readdir.c 4.3 8/8/82"; +X +X#include <sys/types.h> +X#include <dir.h> +X +X/* +X * read an old stlye directory entry and present it as a new one +X */ +X#define ODIRSIZ 14 +X +Xstruct olddirect { +X ino_t od_ino; +X char od_name[ODIRSIZ]; +X}; +X +X/* +X * get next entry in a directory. +X */ +Xstruct direct * +Xreaddir(dirp) +X register DIR *dirp; +X{ +X register struct olddirect *dp; +X static struct direct dir; +X +X for (;;) { +X if (dirp->dd_loc == 0) { +X dirp->dd_size = read(dirp->dd_fd, dirp->dd_buf, +X DIRBLKSIZ); +X if (dirp->dd_size <= 0) { +X dirp->dd_size = 0; +X return NULL; +X } +X } +X if (dirp->dd_loc >= dirp->dd_size) { +X dirp->dd_loc = 0; +X continue; +X } +X dp = (struct olddirect *)(dirp->dd_buf + dirp->dd_loc); +X dirp->dd_loc += sizeof(struct olddirect); +X if (dp->od_ino == 0) +X continue; +X dir.d_ino = dp->od_ino; +X strncpy(dir.d_name, dp->od_name, ODIRSIZ); +X dir.d_name[ODIRSIZ] = '\0'; /* insure null termination */ +X dir.d_namlen = strlen(dir.d_name); +X dir.d_reclen = DIRBLKSIZ; +X return (&dir); +X } +X} +! +echo 'seekdir.c': +sed 's/^X//' >'seekdir.c' <<'!' +Xstatic char sccsid[] = "@(#)seekdir.c 4.9 3/25/83"; +X +X#include <sys/param.h> +X#include <dir.h> +X +X/* +X * seek to an entry in a directory. +X * Only values returned by "telldir" should be passed to seekdir. +X */ +Xvoid +Xseekdir(dirp, loc) +X register DIR *dirp; +X long loc; +X{ +X long curloc, base, offset; +X struct direct *dp; +X extern long lseek(); +X +X curloc = telldir(dirp); +X if (loc == curloc) +X return; +X base = loc & ~(DIRBLKSIZ - 1); +X offset = loc & (DIRBLKSIZ - 1); +X (void) lseek(dirp->dd_fd, base, 0); +X dirp->dd_size = 0; +X dirp->dd_loc = 0; +X while (dirp->dd_loc < offset) { +X dp = readdir(dirp); +X if (dp == NULL) +X return; +X } +X} +! +echo 'telldir.c': +sed 's/^X//' >'telldir.c' <<'!' +Xstatic char sccsid[] = "@(#)telldir.c 4.1 2/21/82"; +X +X#include <sys/types.h> +X#include <dir.h> +X +X/* +X * return a pointer into a directory +X */ +Xlong +Xtelldir(dirp) +X DIR *dirp; +X{ +X long lseek(); +X +X return (lseek(dirp->dd_fd, 0L, 1) - dirp->dd_size + dirp->dd_loc); +X} +! +echo done diff --git a/contrib/cvs/contrib/intro.doc b/contrib/cvs/contrib/intro.doc new file mode 100644 index 0000000..a6d4ec1 --- /dev/null +++ b/contrib/cvs/contrib/intro.doc @@ -0,0 +1,112 @@ +Date: Tue, 16 Jun 1992 17:05:23 +0200 +From: Steven.Pemberton@cwi.nl +Message-Id: <9206161505.AA06927.steven@sijs.cwi.nl> +To: berliner@Sun.COM +Subject: cvs + +INTRODUCTION TO USING CVS + + CVS is a system that lets groups of people work simultaneously on + groups of files (for instance program sources). + + It works by holding a central 'repository' of the most recent version + of the files. You may at any time create a personal copy of these + files; if at a later date newer versions of the files are put in the + repository, you can 'update' your copy. + + You may edit your copy of the files freely. If new versions of the + files have been put in the repository in the meantime, doing an update + merges the changes in the central copy into your copy. + (It can be that when you do an update, the changes in the + central copy clash with changes you have made in your own + copy. In this case cvs warns you, and you have to resolve the + clash in your copy.) + + When you are satisfied with the changes you have made in your copy of + the files, you can 'commit' them into the central repository. + (When you do a commit, if you haven't updated to the most + recent version of the files, cvs tells you this; then you have + to first update, resolve any possible clashes, and then redo + the commit.) + +USING CVS + + Suppose that a number of repositories have been stored in + /usr/src/cvs. Whenever you use cvs, the environment variable + CVSROOT must be set to this (for some reason): + + CVSROOT=/usr/src/cvs + export CVSROOT + +TO CREATE A PERSONAL COPY OF A REPOSITORY + + Suppose you want a copy of the files in repository 'views' to be + created in your directory src. Go to the place where you want your + copy of the directory, and do a 'checkout' of the directory you + want: + + cd $HOME/src + cvs checkout views + + This creates a directory called (in this case) 'views' in the src + directory, containing a copy of the files, which you may now work + on to your heart's content. + +TO UPDATE YOUR COPY + + Use the command 'cvs update'. + + This will update your copy with any changes from the central + repository, telling you which files have been updated (their names + are displayed with a U before them), and which have been modified + by you and not yet committed (preceded by an M). You will be + warned of any files that contain clashes, the clashes will be + marked in the file surrounded by lines of the form <<<< and >>>>. + +TO COMMIT YOUR CHANGES + + Use the command 'cvs commit'. + + You will be put in an editor to make a message that describes the + changes that you have made (for future reference). Your changes + will then be added to the central copy. + +ADDING AND REMOVING FILES + + It can be that the changes you want to make involve a completely + new file, or removing an existing one. The commands to use here + are: + + cvs add <filename> + cvs remove <filename> + + You still have to do a commit after these commands. You may make + any number of new files in your copy of the repository, but they + will not be committed to the central copy unless you do a 'cvs add'. + +OTHER USEFUL COMMANDS AND HINTS + + To see the commit messages for files, and who made them, use: + + cvs log [filenames] + + To see the differences between your version and the central version: + + cvs diff [filenames] + + To give a file a new name, rename it and do an add and a remove. + + To lose your changes and go back to the version from the + repository, delete the file and do an update. + + After an update where there have been clashes, your original + version of the file is saved as .#file.version. + + All the cvs commands mentioned accept a flag '-n', that doesn't do + the action, but lets you see what would happen. For instance, you + can use 'cvs -n update' to see which files would be updated. + +MORE INFORMATION + + This is necessarily a very brief introduction. See the manual page + (man cvs) for full details. diff --git a/contrib/cvs/contrib/log.pl b/contrib/cvs/contrib/log.pl new file mode 100644 index 0000000..5e3bf48 --- /dev/null +++ b/contrib/cvs/contrib/log.pl @@ -0,0 +1,169 @@ +#! xPERL_PATHx +# -*-Perl-*- +# +#ident "$CVSid$" +# +# XXX: FIXME: handle multiple '-f logfile' arguments +# +# XXX -- I HATE Perl! This *will* be re-written in shell/awk/sed soon! +# + +# Usage: log.pl [[-m user] ...] [-s] -f logfile 'dirname file ...' +# +# -m user - for each user to receive cvs log reports +# (multiple -m's permitted) +# -s - to prevent "cvs status -v" messages +# -f logfile - for the logfile to append to (mandatory, +# but only one logfile can be specified). + +# here is what the output looks like: +# +# From: woods@kuma.domain.top +# Subject: CVS update: testmodule +# +# Date: Wednesday November 23, 1994 @ 14:15 +# Author: woods +# +# Update of /local/src-CVS/testmodule +# In directory kuma:/home/kuma/woods/work.d/testmodule +# +# Modified Files: +# test3 +# Added Files: +# test6 +# Removed Files: +# test4 +# Log Message: +# - wow, what a test +# +# (and for each file the "cvs status -v" output is appended unless -s is used) +# +# ================================================================== +# File: test3 Status: Up-to-date +# +# Working revision: 1.41 Wed Nov 23 14:15:59 1994 +# Repository revision: 1.41 /local/src-CVS/cvs/testmodule/test3,v +# Sticky Options: -ko +# +# Existing Tags: +# local-v2 (revision: 1.7) +# local-v1 (revision: 1.1.1.2) +# CVS-1_4A2 (revision: 1.1.1.2) +# local-v0 (revision: 1.2) +# CVS-1_4A1 (revision: 1.1.1.1) +# CVS (branch: 1.1.1) + +$cvsroot = $ENV{'CVSROOT'}; + +# turn off setgid +# +$) = $(; + +$dostatus = 1; + +# parse command line arguments +# +while (@ARGV) { + $arg = shift @ARGV; + + if ($arg eq '-m') { + $users = "$users " . shift @ARGV; + } elsif ($arg eq '-f') { + ($logfile) && die "Too many '-f' args"; + $logfile = shift @ARGV; + } elsif ($arg eq '-s') { + $dostatus = 0; + } else { + ($donefiles) && die "Too many arguments!\n"; + $donefiles = 1; + @files = split(/ /, $arg); + } +} + +# the first argument is the module location relative to $CVSROOT +# +$modulepath = shift @files; + +$mailcmd = "| Mail -s 'CVS update: $modulepath'"; + +# Initialise some date and time arrays +# +@mos = (January,February,March,April,May,June,July,August,September, + October,November,December); +@days = (Sunday,Monday,Tuesday,Wednesday,Thursday,Friday,Saturday); + +($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime; + +# get a login name for the guy doing the commit.... +# +$login = getlogin || (getpwuid($<))[0] || "nobody"; + +# open log file for appending +# +open(OUT, ">>" . $logfile) || die "Could not open(" . $logfile . "): $!\n"; + +# send mail, if there's anyone to send to! +# +if ($users) { + $mailcmd = "$mailcmd $users"; + open(MAIL, $mailcmd) || die "Could not Exec($mailcmd): $!\n"; +} + +# print out the log Header +# +print OUT "\n"; +print OUT "****************************************\n"; +print OUT "Date:\t$days[$wday] $mos[$mon] $mday, 19$year @ $hour:" . sprintf("%02d", $min) . "\n"; +print OUT "Author:\t$login\n\n"; + +if (MAIL) { + print MAIL "\n"; + print MAIL "Date:\t$days[$wday] $mos[$mon] $mday, 19$year @ $hour:" . sprintf("%02d", $min) . "\n"; + print MAIL "Author:\t$login\n\n"; +} + +# print the stuff from logmsg that comes in on stdin to the logfile +# +open(IN, "-"); +while (<IN>) { + print OUT $_; + if (MAIL) { + print MAIL $_; + } +} +close(IN); + +print OUT "\n"; + +# after log information, do an 'cvs -Qq status -v' on each file in the arguments. +# +if ($dostatus != 0) { + while (@files) { + $file = shift @files; + if ($file eq "-") { + print OUT "[input file was '-']\n"; + if (MAIL) { + print MAIL "[input file was '-']\n"; + } + last; + } + open(RCS, "-|") || exec 'cvs', '-nQq', 'status', '-v', $file; + while (<RCS>) { + print OUT; + if (MAIL) { + print MAIL; + } + } + close(RCS); + } +} + +close(OUT); +die "Write to $logfile failed" if $?; + +close(MAIL); +die "Pipe to $mailcmd failed" if $?; + +## must exit cleanly +## +exit 0; diff --git a/contrib/cvs/contrib/log_accum.pl b/contrib/cvs/contrib/log_accum.pl new file mode 100644 index 0000000..d5c6783 --- /dev/null +++ b/contrib/cvs/contrib/log_accum.pl @@ -0,0 +1,560 @@ +#! xPERL_PATHx +# -*-Perl-*- +# +#ident "@(#)ccvs/contrib:$Name: $:$Id: log_accum.pl,v 1.4 1996/03/06 15:27:09 woods Exp $" +# +# Perl filter to handle the log messages from the checkin of files in +# a directory. This script will group the lists of files by log +# message, and mail a single consolidated log message at the end of +# the commit. +# +# This file assumes a pre-commit checking program that leaves the +# names of the first and last commit directories in a temporary file. +# +# Contributed by David Hampton <hampton@cisco.com> +# +# hacked greatly by Greg A. Woods <woods@planix.com> + +# Usage: log_accum.pl [-d] [-s] [-M module] [[-m mailto] ...] [[-R replyto] ...] [-f logfile] +# -d - turn on debugging +# -m mailto - send mail to "mailto" (multiple) +# -R replyto - set the "Reply-To:" to "replyto" (multiple) +# -M modulename - set module name to "modulename" +# -f logfile - write commit messages to logfile too +# -s - *don't* run "cvs status -v" for each file + +# +# Configurable options +# + +# set this to something that takes a whole message on stdin +$MAILER = "/usr/lib/sendmail -t"; + +# +# End user configurable options. +# + +# Constants (don't change these!) +# +$STATE_NONE = 0; +$STATE_CHANGED = 1; +$STATE_ADDED = 2; +$STATE_REMOVED = 3; +$STATE_LOG = 4; + +$LAST_FILE = "/tmp/#cvs.lastdir"; + +$CHANGED_FILE = "/tmp/#cvs.files.changed"; +$ADDED_FILE = "/tmp/#cvs.files.added"; +$REMOVED_FILE = "/tmp/#cvs.files.removed"; +$LOG_FILE = "/tmp/#cvs.files.log"; + +$FILE_PREFIX = "#cvs.files"; + +# +# Subroutines +# + +sub cleanup_tmpfiles { + local($wd, @files); + + $wd = `pwd`; + chdir("/tmp") || die("Can't chdir('/tmp')\n"); + opendir(DIR, "."); + push(@files, grep(/^$FILE_PREFIX\..*\.$id$/, readdir(DIR))); + closedir(DIR); + foreach (@files) { + unlink $_; + } + unlink $LAST_FILE . "." . $id; + + chdir($wd); +} + +sub write_logfile { + local($filename, @lines) = @_; + + open(FILE, ">$filename") || die("Cannot open log file $filename.\n"); + print FILE join("\n", @lines), "\n"; + close(FILE); +} + +sub append_to_logfile { + local($filename, @lines) = @_; + + open(FILE, ">$filename") || die("Cannot open log file $filename.\n"); + print FILE join("\n", @lines), "\n"; + close(FILE); +} + +sub format_names { + local($dir, @files) = @_; + local(@lines); + + $format = "\t%-" . sprintf("%d", length($dir)) . "s%s "; + + $lines[0] = sprintf($format, $dir, ":"); + + if ($debug) { + print STDERR "format_names(): dir = ", $dir, "; files = ", join(":", @files), ".\n"; + } + foreach $file (@files) { + if (length($lines[$#lines]) + length($file) > 65) { + $lines[++$#lines] = sprintf($format, " ", " "); + } + $lines[$#lines] .= $file . " "; + } + + @lines; +} + +sub format_lists { + local(@lines) = @_; + local(@text, @files, $lastdir); + + if ($debug) { + print STDERR "format_lists(): ", join(":", @lines), "\n"; + } + @text = (); + @files = (); + $lastdir = shift @lines; # first thing is always a directory + if ($lastdir !~ /.*\/$/) { + die("Damn, $lastdir doesn't look like a directory!\n"); + } + foreach $line (@lines) { + if ($line =~ /.*\/$/) { + push(@text, &format_names($lastdir, @files)); + $lastdir = $line; + @files = (); + } else { + push(@files, $line); + } + } + push(@text, &format_names($lastdir, @files)); + + @text; +} + +sub append_names_to_file { + local($filename, $dir, @files) = @_; + + if (@files) { + open(FILE, ">>$filename") || die("Cannot open file $filename.\n"); + print FILE $dir, "\n"; + print FILE join("\n", @files), "\n"; + close(FILE); + } +} + +sub read_line { + local($line); + local($filename) = @_; + + open(FILE, "<$filename") || die("Cannot open file $filename.\n"); + $line = <FILE>; + close(FILE); + chop($line); + $line; +} + +sub read_logfile { + local(@text); + local($filename, $leader) = @_; + + open(FILE, "<$filename"); + while (<FILE>) { + chop; + push(@text, $leader.$_); + } + close(FILE); + @text; +} + +sub build_header { + local($header); + local($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time); + $header = sprintf("CVSROOT:\t%s\nModule name:\t%s\nChanges by:\t%s@%s\t%02d/%02d/%02d %02d:%02d:%02d", + $cvsroot, + $modulename, + $login, $hostdomain, + $year%100, $mon+1, $mday, + $hour, $min, $sec); +} + +sub mail_notification { + local(@text) = @_; + + # if only we had strftime()... stuff stolen from perl's ctime.pl: + local($[) = 0; + + @DoW = ('Sun','Mon','Tue','Wed','Thu','Fri','Sat'); + @MoY = ('Jan','Feb','Mar','Apr','May','Jun', + 'Jul','Aug','Sep','Oct','Nov','Dec'); + + # Determine what time zone is in effect. + # Use GMT if TZ is defined as null, local time if TZ undefined. + # There's no portable way to find the system default timezone. + # + $TZ = defined($ENV{'TZ'}) ? ( $ENV{'TZ'} ? $ENV{'TZ'} : 'GMT' ) : ''; + + # Hack to deal with 'PST8PDT' format of TZ + # Note that this can't deal with all the esoteric forms, but it + # does recognize the most common: [:]STDoff[DST[off][,rule]] + # + if ($TZ =~ /^([^:\d+\-,]{3,})([+-]?\d{1,2}(:\d{1,2}){0,2})([^\d+\-,]{3,})?/) { + $TZ = $isdst ? $4 : $1; + $tzoff = sprintf("%05d", -($2) * 100); + } + + # perl-4.036 doesn't have the $zone or $gmtoff... + ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst, $zone, $gmtoff) = + ($TZ eq 'GMT') ? gmtime(time) : localtime(time); + + $year += ($year < 70) ? 2000 : 1900; + + if ($gmtoff != 0) { + $tzoff = sprintf("%05d", ($gmtoff / 60) * 100); + } + if ($zone ne '') { + $TZ = $zone; + } + + # ok, let's try.... + $rfc822date = sprintf("%s, %2d %s %4d %2d:%02d:%02d %s (%s)", + $DoW[$wday], $mday, $MoY[$mon], $year, + $hour, $min, $sec, $tzoff, $TZ); + + open(MAIL, "| $MAILER"); + print MAIL "Date: " . $rfc822date . "\n"; + print MAIL "Subject: CVS Update: " . $modulename . "\n"; + print MAIL "To: " . $mailto . "\n"; + print MAIL "From: " . $login . "@" . $hostdomain . "\n"; + print MAIL "Reply-To: " . $replyto . "\n"; + print MAIL "\n"; + print MAIL join("\n", @text), "\n"; + close(MAIL); +} + +sub write_commitlog { + local($logfile, @text) = @_; + + open(FILE, ">>$logfile"); + print FILE join("\n", @text), "\n"; + close(FILE); +} + +# +# Main Body +# + +# Initialize basic variables +# +$debug = 0; +$id = getpgrp(); # note, you *must* use a shell which does setpgrp() +$state = $STATE_NONE; +$login = getlogin || (getpwuid($<))[0] || "nobody"; +chop($hostname = `hostname`); +chop($domainname = `domainname`); +$hostdomain = $hostname . $domainname; +$cvsroot = $ENV{'CVSROOT'}; +$do_status = 1; +$modulename = ""; + +# parse command line arguments (file list is seen as one arg) +# +while (@ARGV) { + $arg = shift @ARGV; + + if ($arg eq '-d') { + $debug = 1; + print STDERR "Debug turned on...\n"; + } elsif ($arg eq '-m') { + if ($mailto eq '') { + $mailto = shift @ARGV; + } else { + $mailto = $mailto . ", " . shift @ARGV; + } + } elsif ($arg eq '-R') { + if ($replyto eq '') { + $replyto = shift @ARGV; + } else { + $replyto = $replyto . ", " . shift @ARGV; + } + } elsif ($arg eq '-M') { + $modulename = shift @ARGV; + } elsif ($arg eq '-s') { + $do_status = 0; + } elsif ($arg eq '-f') { + ($commitlog) && die("Too many '-f' args\n"); + $commitlog = shift @ARGV; + } else { + ($donefiles) && die("Too many arguments! Check usage.\n"); + $donefiles = 1; + @files = split(/ /, $arg); + } +} +($mailto) || die("No mail recipient specified (use -m)\n"); +if ($replyto eq '') { + $replyto = $login; +} + +# for now, the first "file" is the repository directory being committed, +# relative to the $CVSROOT location +# +@path = split('/', $files[0]); + +# XXX there are some ugly assumptions in here about module names and +# XXX directories relative to the $CVSROOT location -- really should +# XXX read $CVSROOT/CVSROOT/modules, but that's not so easy to do, since +# XXX we have to parse it backwards. +# +if ($modulename eq "") { + $modulename = $path[0]; # I.e. the module name == top-level dir +} +if ($#path == 0) { + $dir = "."; +} else { + $dir = join('/', @path); +} +$dir = $dir . "/"; + +if ($debug) { + print STDERR "module - ", $modulename, "\n"; + print STDERR "dir - ", $dir, "\n"; + print STDERR "path - ", join(":", @path), "\n"; + print STDERR "files - ", join(":", @files), "\n"; + print STDERR "id - ", $id, "\n"; +} + +# Check for a new directory first. This appears with files set as follows: +# +# files[0] - "path/name/newdir" +# files[1] - "-" +# files[2] - "New" +# files[3] - "directory" +# +if ($files[2] =~ /New/ && $files[3] =~ /directory/) { + local(@text); + + @text = (); + push(@text, &build_header()); + push(@text, ""); + push(@text, $files[0]); + push(@text, ""); + + while (<STDIN>) { + chop; # Drop the newline + push(@text, $_); + } + + &mail_notification($mailto, @text); + + exit 0; +} + +# Check for an import command. This appears with files set as follows: +# +# files[0] - "path/name" +# files[1] - "-" +# files[2] - "Imported" +# files[3] - "sources" +# +if ($files[2] =~ /Imported/ && $files[3] =~ /sources/) { + local(@text); + + @text = (); + push(@text, &build_header()); + push(@text, ""); + push(@text, $files[0]); + push(@text, ""); + + while (<STDIN>) { + chop; # Drop the newline + push(@text, $_); + } + + &mail_notification(@text); + + exit 0; +} + +# Iterate over the body of the message collecting information. +# +while (<STDIN>) { + chop; # Drop the newline + + if (/^In directory/) { + push(@log_lines, $_); + push(@log_lines, ""); + next; + } + + if (/^Modified Files/) { $state = $STATE_CHANGED; next; } + if (/^Added Files/) { $state = $STATE_ADDED; next; } + if (/^Removed Files/) { $state = $STATE_REMOVED; next; } + if (/^Log Message/) { $state = $STATE_LOG; next; } + + s/^[ \t\n]+//; # delete leading whitespace + s/[ \t\n]+$//; # delete trailing whitespace + + if ($state == $STATE_CHANGED) { push(@changed_files, split); } + if ($state == $STATE_ADDED) { push(@added_files, split); } + if ($state == $STATE_REMOVED) { push(@removed_files, split); } + if ($state == $STATE_LOG) { push(@log_lines, $_); } +} + +# Strip leading and trailing blank lines from the log message. Also +# compress multiple blank lines in the body of the message down to a +# single blank line. +# +while ($#log_lines > -1) { + last if ($log_lines[0] ne ""); + shift(@log_lines); +} +while ($#log_lines > -1) { + last if ($log_lines[$#log_lines] ne ""); + pop(@log_lines); +} +for ($i = $#log_lines; $i > 0; $i--) { + if (($log_lines[$i - 1] eq "") && ($log_lines[$i] eq "")) { + splice(@log_lines, $i, 1); + } +} + +if ($debug) { + print STDERR "Searching for log file index..."; +} +# Find an index to a log file that matches this log message +# +for ($i = 0; ; $i++) { + local(@text); + + last if (! -e "$LOG_FILE.$i.$id"); # the next available one + @text = &read_logfile("$LOG_FILE.$i.$id", ""); + last if ($#text == -1); # nothing in this file, use it + last if (join(" ", @log_lines) eq join(" ", @text)); # it's the same log message as another +} +if ($debug) { + print STDERR " found log file at $i.$id, now writing tmp files.\n"; +} + +# Spit out the information gathered in this pass. +# +&append_names_to_file("$CHANGED_FILE.$i.$id", $dir, @changed_files); +&append_names_to_file("$ADDED_FILE.$i.$id", $dir, @added_files); +&append_names_to_file("$REMOVED_FILE.$i.$id", $dir, @removed_files); +&write_logfile("$LOG_FILE.$i.$id", @log_lines); + +# Check whether this is the last directory. If not, quit. +# +if ($debug) { + print STDERR "Checking current dir against last dir.\n"; +} +$_ = &read_line("$LAST_FILE.$id"); + +if ($_ ne $cvsroot . "/" . $files[0]) { + if ($debug) { + print STDERR sprintf("Current directory %s is not last directory %s.\n", $cvsroot . "/" .$files[0], $_); + } + exit 0; +} +if ($debug) { + print STDERR sprintf("Current directory %s is last directory %s -- all commits done.\n", $files[0], $_); +} + +# +# End Of Commits! +# + +# This is it. The commits are all finished. Lump everything together +# into a single message, fire a copy off to the mailing list, and drop +# it on the end of the Changes file. +# + +# +# Produce the final compilation of the log messages +# +@text = (); +@status_txt = (); +push(@text, &build_header()); +push(@text, ""); + +for ($i = 0; ; $i++) { + last if (! -e "$LOG_FILE.$i.$id"); # we're done them all! + @lines = &read_logfile("$CHANGED_FILE.$i.$id", ""); + if ($#lines >= 0) { + push(@text, "Modified files:"); + push(@text, &format_lists(@lines)); + } + @lines = &read_logfile("$ADDED_FILE.$i.$id", ""); + if ($#lines >= 0) { + push(@text, "Added files:"); + push(@text, &format_lists(@lines)); + } + @lines = &read_logfile("$REMOVED_FILE.$i.$id", ""); + if ($#lines >= 0) { + push(@text, "Removed files:"); + push(@text, &format_lists(@lines)); + } + if ($#text >= 0) { + push(@text, ""); + } + @lines = &read_logfile("$LOG_FILE.$i.$id", "\t"); + if ($#lines >= 0) { + push(@text, "Log message:"); + push(@text, @lines); + push(@text, ""); + } + if ($do_status) { + local(@changed_files); + + @changed_files = (); + push(@changed_files, &read_logfile("$CHANGED_FILE.$i.$id", "")); + push(@changed_files, &read_logfile("$ADDED_FILE.$i.$id", "")); + push(@changed_files, &read_logfile("$REMOVED_FILE.$i.$id", "")); + + if ($debug) { + print STDERR "main: pre-sort changed_files = ", join(":", @changed_files), ".\n"; + } + sort(@changed_files); + if ($debug) { + print STDERR "main: post-sort changed_files = ", join(":", @changed_files), ".\n"; + } + + foreach $dofile (@changed_files) { + if ($dofile =~ /\/$/) { + next; # ignore the silly "dir" entries + } + if ($debug) { + print STDERR "main(): doing 'cvs -nQq status -v $dofile'\n"; + } + open(STATUS, "-|") || exec 'cvs', '-nQq', 'status', '-v', $dofile; + while (<STATUS>) { + chop; + push(@status_txt, $_); + } + } + } +} + +# Write to the commitlog file +# +if ($commitlog) { + &write_commitlog($commitlog, @text); +} + +if ($#status_txt >= 0) { + push(@text, @status_txt); +} + +# Mailout the notification. +# +&mail_notification(@text); + +# cleanup +# +if (! $debug) { + &cleanup_tmpfiles(); +} + +exit 0; diff --git a/contrib/cvs/contrib/mfpipe.pl b/contrib/cvs/contrib/mfpipe.pl new file mode 100644 index 0000000..bae7a72 --- /dev/null +++ b/contrib/cvs/contrib/mfpipe.pl @@ -0,0 +1,88 @@ +#! xPERL_PATHx +# -*-Perl-*- +# +# From: clyne@niwot.scd.ucar.EDU (John Clyne) +# Date: Fri, 28 Feb 92 09:54:21 MST +# +# BTW, i wrote a perl script that is similar to 'nfpipe' except that in +# addition to logging to a file it provides a command line option for mailing +# change notices to a group of users. Obviously you probably wouldn't want +# to mail every change. But there may be certain directories that are commonly +# accessed by a group of users who would benefit from an email notice. +# Especially if they regularly beat on the same directory. Anyway if you +# think anyone would be interested here it is. +# +# $Id: mfpipe.pl,v 1.2 1995/07/10 02:01:57 kfogel Exp $ +# +# +# File: mfpipe +# +# Author: John Clyne +# National Center for Atmospheric Research +# PO 3000, Boulder, Colorado +# +# Date: Wed Feb 26 18:34:53 MST 1992 +# +# Description: Tee standard input to mail a list of users and to +# a file. Used by CVS logging. +# +# Usage: mfpipe [-f file] [user@host...] +# +# Environment: CVSROOT +# Path to CVS root. +# +# Files: +# +# +# Options: -f file +# Capture output to 'file' +# + +$header = "Log Message:\n"; + +$mailcmd = "| mail -s 'CVS update notice'"; +$whoami = `whoami`; +chop $whoami; +$date = `date`; +chop $date; + +$cvsroot = $ENV{'CVSROOT'}; + +while (@ARGV) { + $arg = shift @ARGV; + + if ($arg eq '-f') { + $file = shift @ARGV; + } + else { + $users = "$users $arg"; + } +} + +if ($users) { + $mailcmd = "$mailcmd $users"; + open(MAIL, $mailcmd) || die "Execing $mail: $!\n"; +} + +if ($file) { + $logfile = "$cvsroot/LOG/$file"; + open(FILE, ">> $logfile") || die "Opening $logfile: $!\n"; +} + +print FILE "$whoami $date--------BEGIN LOG ENTRY-------------\n" if ($logfile); + +while (<>) { + print FILE $log if ($log && $logfile); + + print FILE $_ if ($logfile); + print MAIL $_ if ($users); + + $log = "log: " if ($_ eq $header); +} + +close FILE; +die "Write failed" if $?; +close MAIL; +die "Mail failed" if $?; + +exit 0; diff --git a/contrib/cvs/contrib/rcs-to-cvs.sh b/contrib/cvs/contrib/rcs-to-cvs.sh new file mode 100644 index 0000000..3af83d7 --- /dev/null +++ b/contrib/cvs/contrib/rcs-to-cvs.sh @@ -0,0 +1,185 @@ +#! /bin/sh +# +# $Id: rcs-to-cvs.sh,v 1.2 1995/07/15 03:40:34 jimb Exp $ +# Based on the CVS 1.0 checkin csh script. +# Contributed by Per Cederqvist <ceder@signum.se>. +# Rewritten in sh by David MacKenzie <djm@cygnus.com>. +# +# Copyright (c) 1989, Brian Berliner +# +# You may distribute under the terms of the GNU General Public License. +# +############################################################################# +# +# Check in sources that previously were under RCS or no source control system. +# +# The repository is the directory where the sources should be deposited. +# +# Traverses the current directory, ensuring that an +# identical directory structure exists in the repository directory. It +# then checks the files in in the following manner: +# +# 1) If the file doesn't yet exist, check it in as revision 1.1 +# +# The script also is somewhat verbose in letting the user know what is +# going on. It prints a diagnostic when it creates a new file, or updates +# a file that has been modified on the trunk. +# +# Bugs: doesn't put the files in branch 1.1.1 +# doesn't put in release and vendor tags +# +############################################################################# + +usage="Usage: rcs-to-cvs [-v] [-m message] [-f message_file] repository" +vbose=0 +message="" +message_file=/usr/tmp/checkin.$$ +got_one=0 + +if [ $# -lt 1 ]; then + echo "$usage" >&2 + exit 1 +fi + +while [ $# -ne 0 ]; do + case "$1" in + -v) + vbose=1 + ;; + -m) + shift + echo $1 > $message_file + got_one=1 + ;; + -f) + shift + message_file=$1 + got_one=2 + ;; + *) + break + esac + shift +done + +if [ $# -lt 1 ]; then + echo "$usage" >&2 + exit 1 +fi + +repository=$1 +shift + +if [ -z "$CVSROOT" ]; then + echo "Please the environmental variable CVSROOT to the root" >&2 + echo " of the tree you wish to update" >&2 + exit 1 +fi + +if [ $got_one -eq 0 ]; then + echo "Please Edit this file to contain the RCS log information" >$message_file + echo "to be associated with this directory (please remove these lines)">>$message_file + ${EDITOR-/usr/ucb/vi} $message_file + got_one=1 +fi + +# Ya gotta share. +umask 0 + +update_dir=${CVSROOT}/${repository} +[ ! -d ${update_dir} ] && mkdir $update_dir + +if [ -d SCCS ]; then + echo SCCS files detected! >&2 + exit 1 +fi +if [ -d RCS ]; then + co RCS/* +fi + +for name in * .[a-zA-Z0-9]* +do + case "$name" in + RCS | *~ | \* | .\[a-zA-Z0-9\]\* ) continue ;; + esac + echo $name + if [ $vbose -ne 0 ]; then + echo "Updating ${repository}/${name}" + fi + if [ -d "$name" ]; then + if [ ! -d "${update_dir}/${name}" ]; then + echo "WARNING: Creating new directory ${repository}/${name}" + mkdir "${update_dir}/${name}" + if [ $? -ne 0 ]; then + echo "ERROR: mkdir failed - aborting" >&2 + exit 1 + fi + fi + cd "$name" + if [ $? -ne 0 ]; then + echo "ERROR: Couldn\'t cd to $name - aborting" >&2 + exit 1 + fi + if [ $vbose -ne 0 ]; then + $0 -v -f $message_file "${repository}/${name}" + else + $0 -f $message_file "${repository}/${name}" + fi + if [ $? -ne 0 ]; then + exit 1 + fi + cd .. + else # if not directory + if [ ! -f "$name" ]; then + echo "WARNING: $name is neither a regular file" + echo " nor a directory - ignored" + continue + fi + file="${update_dir}/${name},v" + comment="" + if grep -s '\$Log.*\$' "${name}"; then # If $Log keyword + myext=`echo $name | sed 's,.*\.,,'` + [ "$myext" = "$name" ] && myext= + case "$myext" in + c | csh | e | f | h | l | mac | me | mm | ms | p | r | red | s | sh | sl | cl | ml | el | tex | y | ye | yr | "" ) + ;; + + * ) + echo "For file ${file}:" + grep '\$Log.*\$' "${name}" + echo -n "Please insert a comment leader for file ${name} > " + read comment + ;; + esac + fi + if [ ! -f "$file" ]; then # If not exists in repository + if [ ! -f "${update_dir}/Attic/${name},v" ]; then + echo "WARNING: Creating new file ${repository}/${name}" + if [ -f RCS/"${name}",v ]; then + echo "MSG: Copying old rcs file." + cp RCS/"${name}",v "$file" + else + if [ -n "${comment}" ]; then + rcs -q -i -c"${comment}" -t${message_file} -m'.' "$file" + fi + ci -q -u1.1 -t${message_file} -m'.' "$file" + if [ $? -ne 0 ]; then + echo "ERROR: Initial check-in of $file failed - aborting" >&2 + exit 1 + fi + fi + else + file="${update_dir}/Attic/${name},v" + echo "WARNING: IGNORED: ${repository}/Attic/${name}" + continue + fi + else # File existed + echo "ERROR: File exists in repository: Ignored: $file" + continue + fi + fi +done + +[ $got_one -eq 1 ] && rm -f $message_file + +exit 0 diff --git a/contrib/cvs/contrib/rcs2log.sh b/contrib/cvs/contrib/rcs2log.sh new file mode 100644 index 0000000..ccea907 --- /dev/null +++ b/contrib/cvs/contrib/rcs2log.sh @@ -0,0 +1,592 @@ +#! /bin/sh + +# RCS to ChangeLog generator + +# Generate a change log prefix from RCS files and the ChangeLog (if any). +# Output the new prefix to standard output. +# You can edit this prefix by hand, and then prepend it to ChangeLog. + +# Ignore log entries that start with `#'. +# Clump together log entries that start with `{topic} ', +# where `topic' contains neither white space nor `}'. + +# Author: Paul Eggert <eggert@twinsun.com> + +# $Id: rcs2log.sh,v 1.2 1995/07/28 19:48:45 eggert Exp $ + +# Copyright 1992, 1993, 1994, 1995 Free Software Foundation, Inc. + +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2, or (at your option) +# any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; see the file COPYING. If not, write to +# the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + +tab=' ' +nl=' +' + +# Parse options. + +# defaults +: ${AWK=awk} +: ${TMPDIR=/tmp} +hostname= # name of local host (if empty, will deduce it later) +indent=8 # indent of log line +length=79 # suggested max width of log line +logins= # login names for people we know fullnames and mailaddrs of +loginFullnameMailaddrs= # login<tab>fullname<tab>mailaddr triplets +recursive= # t if we want recursive rlog +rlog_options= # options to pass to rlog +tabwidth=8 # width of horizontal tab + +while : +do + case $1 in + -i) indent=${2?}; shift;; + -h) hostname=${2?}; shift;; + -l) length=${2?}; shift;; + -[nu]) # -n is obsolescent; it is replaced by -u. + case $1 in + -n) case ${2?}${3?}${4?} in + *"$tab"* | *"$nl"*) + echo >&2 "$0: -n '$2' '$3' '$4': tabs, newlines not allowed" + exit 1 + esac + loginFullnameMailaddrs=$loginFullnameMailaddrs$nl$2$tab$3$tab$4 + shift; shift; shift;; + -u) + # If $2 is not tab-separated, use colon for separator. + case ${2?} in + *"$nl"*) + echo >&2 "$0: -u '$2': newlines not allowed" + exit 1;; + *"$tab"*) + t=$tab;; + *) + t=: + esac + case $2 in + *"$t"*"$t"*"$t"*) + echo >&2 "$0: -u '$2': too many fields" + exit 1;; + *"$t"*"$t"*) + ;; + *) + echo >&2 "$0: -u '$2': not enough fields" + exit 1 + esac + loginFullnameMailaddrs=$loginFullnameMailaddrs$nl$2 + shift + esac + logins=$logins$nl$login + ;; + -r) rlog_options=$rlog_options$nl${2?}; shift;; + -R) recursive=t;; + -t) tabwidth=${2?}; shift;; + -*) echo >&2 "$0: usage: $0 [options] [file ...] +Options: + [-h hostname] [-i indent] [-l length] [-R] [-r rlog_option] + [-t tabwidth] [-u 'login<TAB>fullname<TAB>mailaddr']..." + exit 1;; + *) break + esac + shift +done + +month_data=' + m[0]="Jan"; m[1]="Feb"; m[2]="Mar" + m[3]="Apr"; m[4]="May"; m[5]="Jun" + m[6]="Jul"; m[7]="Aug"; m[8]="Sep" + m[9]="Oct"; m[10]="Nov"; m[11]="Dec" + + # days in non-leap year thus far, indexed by month (0-12) + mo[0]=0; mo[1]=31; mo[2]=59; mo[3]=90 + mo[4]=120; mo[5]=151; mo[6]=181; mo[7]=212 + mo[8]=243; mo[9]=273; mo[10]=304; mo[11]=334 + mo[12]=365 +' + + +# Put rlog output into $rlogout. + +# If no rlog options are given, +# log the revisions checked in since the first ChangeLog entry. +case $rlog_options in +'') + date=1970 + if test -s ChangeLog + then + # Add 1 to seconds to avoid duplicating most recent log. + e=' + /^... ... [ 0-9][0-9] [ 0-9][0-9]:[0-9][0-9]:[0-9][0-9] [0-9]+ /{ + '"$month_data"' + year = $5 + for (i=0; i<=11; i++) if (m[i] == $2) break + dd = $3 + hh = substr($0,12,2) + mm = substr($0,15,2) + ss = substr($0,18,2) + ss++ + if (ss == 60) { + ss = 0 + mm++ + if (mm == 60) { + mm = 0 + hh++ + if (hh == 24) { + hh = 0 + dd++ + monthdays = mo[i+1] - mo[i] + if (i == 1 && year%4 == 0 && (year%100 != 0 || year%400 == 0)) monthdays++ + if (dd == monthdays + 1) { + dd = 1 + i++ + if (i == 12) { + i = 0 + year++ + } + } + } + } + } + # Output comma instead of space to avoid CVS 1.5 bug. + printf "%d/%02d/%02d,%02d:%02d:%02d\n", year,i+1,dd,hh,mm,ss + exit + } + ' + d=`$AWK "$e" <ChangeLog` || exit + case $d in + ?*) date=$d + esac + fi + datearg="-d>$date" +esac + +# If CVS is in use, examine its repository, not the normal RCS files. +if test ! -f CVS/Repository +then + rlog=rlog + repository= +else + rlog='cvs log' + repository=`sed 1q <CVS/Repository` || exit + test ! -f CVS/Root || CVSROOT=`cat <CVS/Root` || exit + case $CVSROOT in + *:/*) + # remote repository + ;; + *) + # local repository + case $repository in + /*) ;; + *) repository=${CVSROOT?}/$repository + esac + if test ! -d "$repository" + then + echo >&2 "$0: $repository: bad repository (see CVS/Repository)" + exit 1 + fi + esac +fi + +# With no arguments, examine all files under the RCS directory. +case $# in +0) + case $repository in + '') + oldIFS=$IFS + IFS=$nl + case $recursive in + t) + RCSdirs=`find . -name RCS -type d -print` + filesFromRCSfiles='s|,v$||; s|/RCS/|/|; s|^\./||' + files=` + { + case $RCSdirs in + ?*) find $RCSdirs -type f -print + esac + find . -name '*,v' -print + } | + sort -u | + sed "$filesFromRCSfiles" + `;; + *) + files= + for file in RCS/.* RCS/* .*,v *,v + do + case $file in + RCS/. | RCS/..) continue;; + RCS/.\* | RCS/\* | .\*,v | \*,v) test -f "$file" || continue + esac + files=$files$nl$file + done + case $files in + '') exit 0 + esac + esac + set x $files + shift + IFS=$oldIFS + esac +esac + +llogout=$TMPDIR/rcs2log$$l +rlogout=$TMPDIR/rcs2log$$r +trap exit 1 2 13 15 +trap "rm -f $llogout $rlogout; exit 1" 0 + +case $rlog_options in +?*) $rlog $rlog_options ${1+"$@"} >$rlogout;; +'') $rlog "$datearg" ${1+"$@"} >$rlogout +esac || exit + + +# Get the full name of each author the logs mention, and set initialize_fullname +# to awk code that initializes the `fullname' awk associative array. +# Warning: foreign authors (i.e. not known in the passwd file) are mishandled; +# you have to fix the resulting output by hand. + +initialize_fullname= +initialize_mailaddr= + +case $loginFullnameMailaddrs in +?*) + case $loginFullnameMailaddrs in + *\"* | *\\*) + sed 's/["\\]/\\&/g' >$llogout <<EOF || exit +$loginFullnameMailaddrs +EOF + loginFullnameMailaddrs=`cat $llogout` + esac + + oldIFS=$IFS + IFS=$nl + for loginFullnameMailaddr in $loginFullnameMailaddrs + do + case $loginFullnameMailaddr in + *"$tab"*) IFS=$tab;; + *) IFS=: + esac + set x $loginFullnameMailaddr + login=$2 + fullname=$3 + mailaddr=$4 + initialize_fullname="$initialize_fullname + fullname[\"$login\"] = \"$fullname\"" + initialize_mailaddr="$initialize_mailaddr + mailaddr[\"$login\"] = \"$mailaddr\"" + done + IFS=$oldIFS +esac + +case $llogout in +?*) sort -u -o $llogout <<EOF || exit +$logins +EOF +esac +output_authors='/^date: / { + if ($2 ~ /^[0-9]*[-\/][0-9][0-9][-\/][0-9][0-9]$/ && $3 ~ /^[0-9][0-9]:[0-9][0-9]:[0-9][0-9][-+0-9:]*;$/ && $4 == "author:" && $5 ~ /^[^;]*;$/) { + print substr($5, 1, length($5)-1) + } +}' +authors=` + $AWK "$output_authors" <$rlogout | + case $llogout in + '') sort -u;; + ?*) sort -u | comm -23 - $llogout + esac +` +case $authors in +?*) + cat >$llogout <<EOF || exit +$authors +EOF + initialize_author_script='s/["\\]/\\&/g; s/.*/author[\"&\"] = 1/' + initialize_author=`sed -e "$initialize_author_script" <$llogout` + awkscript=' + BEGIN { + alphabet = "abcdefghijklmnopqrstuvwxyz" + ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + '"$initialize_author"' + } + { + if (author[$1]) { + fullname = $5 + if (fullname ~ /[0-9]+-[^(]*\([0-9]+\)$/) { + # Remove the junk from fullnames like "0000-Admin(0000)". + fullname = substr(fullname, index(fullname, "-") + 1) + fullname = substr(fullname, 1, index(fullname, "(") - 1) + } + if (fullname ~ /,[^ ]/) { + # Some sites put comma-separated junk after the fullname. + # Remove it, but leave "Bill Gates, Jr" alone. + fullname = substr(fullname, 1, index(fullname, ",") - 1) + } + abbr = index(fullname, "&") + if (abbr) { + a = substr($1, 1, 1) + A = a + i = index(alphabet, a) + if (i) A = substr(ALPHABET, i, 1) + fullname = substr(fullname, 1, abbr-1) A substr($1, 2) substr(fullname, abbr+1) + } + + # Quote quotes and backslashes properly in full names. + # Do not use gsub; traditional awk lacks it. + quoted = "" + rest = fullname + for (;;) { + p = index(rest, "\\") + q = index(rest, "\"") + if (p) { + if (q && q<p) p = q + } else { + if (!q) break + p = q + } + quoted = quoted substr(rest, 1, p-1) "\\" substr(rest, p, 1) + rest = substr(rest, p+1) + } + + printf "fullname[\"%s\"] = \"%s%s\"\n", $1, quoted, rest + author[$1] = 0 + } + } + ' + + initialize_fullname=` + (cat /etc/passwd; ypmatch $authors passwd) 2>/dev/null | + $AWK -F: "$awkscript" + `$initialize_fullname +esac + + +# Function to print a single log line. +# We don't use awk functions, to stay compatible with old awk versions. +# `Log' is the log message (with \n replaced by \r). +# `files' contains the affected files. +printlogline='{ + + # Following the GNU coding standards, rewrite + # * file: (function): comment + # to + # * file (function): comment + if (Log ~ /^\([^)]*\): /) { + i = index(Log, ")") + files = files " " substr(Log, 1, i) + Log = substr(Log, i+3) + } + + # If "label: comment" is too long, break the line after the ":". + sep = " " + if ('"$length"' <= '"$indent"' + 1 + length(files) + index(Log, CR)) sep = "\n" indent_string + + # Print the label. + printf "%s*%s:", indent_string, files + + # Print each line of the log, transliterating \r to \n. + while ((i = index(Log, CR)) != 0) { + logline = substr(Log, 1, i-1) + if (logline ~ /[^'"$tab"' ]/) { + printf "%s%s\n", sep, logline + } else { + print "" + } + sep = indent_string + Log = substr(Log, i+1) + } +}' + +case $hostname in +'') + hostname=`( + hostname || uname -n || uuname -l || cat /etc/whoami + ) 2>/dev/null` || { + echo >&2 "$0: cannot deduce hostname" + exit 1 + } +esac + + +# Process the rlog output, generating ChangeLog style entries. + +# First, reformat the rlog output so that each line contains one log entry. +# Transliterate \n to \r so that multiline entries fit on a single line. +# Discard irrelevant rlog output. +$AWK <$rlogout ' + BEGIN { repository = "'"$repository"'" } + /^RCS file:/ { + if (repository != "") { + filename = $3 + if (substr(filename, 1, length(repository) + 1) == repository "/") { + filename = substr(filename, length(repository) + 2) + } + if (filename ~ /,v$/) { + filename = substr(filename, 1, length(filename) - 2) + } + } + } + /^Working file:/ { if (repository == "") filename = $3 } + /^date: /, /^(-----------*|===========*)$/ { + if ($0 ~ /^branches: /) { next } + if ($0 ~ /^date: [0-9][- +\/0-9:]*;/) { + date = $2 + if (date ~ /-/) { + # An ISO format date. Replace all "-"s with "/"s. + newdate = "" + while ((i = index(date, "-")) != 0) { + newdate = newdate substr(date, 1, i-1) "/" + date = substr(date, i+1) + } + date = newdate date + } + # Ignore any time zone; ChangeLog has no room for it. + time = substr($3, 1, 8) + author = substr($5, 1, length($5)-1) + printf "%s %s %s %s %c", filename, date, time, author, 13 + next + } + if ($0 ~ /^(-----------*|===========*)$/) { print ""; next } + printf "%s%c", $0, 13 + } +' | + +# Now each line is of the form +# FILENAME YYYY/MM/DD HH:MM:SS AUTHOR \rLOG +# where \r stands for a carriage return, +# and each line of the log is terminated by \r instead of \n. +# Sort the log entries, first by date+time (in reverse order), +# then by author, then by log entry, and finally by file name (just in case). +sort +1 -3r +3 +0 | + +# Finally, reformat the sorted log entries. +$AWK ' + BEGIN { + # Some awk variants do not understand "\r" or "\013", so we have to + # put a carriage return directly in the file. + CR="
" # <-- There is a single CR between the " chars here. + + # Initialize the fullname and mailaddr associative arrays. + '"$initialize_fullname"' + '"$initialize_mailaddr"' + + # Initialize indent string. + indent_string = "" + i = '"$indent"' + if (0 < '"$tabwidth"') + for (; '"$tabwidth"' <= i; i -= '"$tabwidth"') + indent_string = indent_string "\t" + while (1 <= i--) + indent_string = indent_string " " + + # Set up date conversion tables. + # RCS uses a nice, clean, sortable format, + # but ChangeLog wants the traditional, ugly ctime format. + + # January 1, 0 AD (Gregorian) was Saturday = 6 + EPOCH_WEEKDAY = 6 + # Of course, there was no 0 AD, but the algorithm works anyway. + + w[0]="Sun"; w[1]="Mon"; w[2]="Tue"; w[3]="Wed" + w[4]="Thu"; w[5]="Fri"; w[6]="Sat" + + '"$month_data"' + } + + { + newlog = substr($0, 1 + index($0, CR)) + + # Ignore log entries prefixed by "#". + if (newlog ~ /^#/) { next } + + if (Log != newlog || date != $2 || author != $4) { + + # The previous log and this log differ. + + # Print the old log. + if (date != "") '"$printlogline"' + + # Logs that begin with "{clumpname} " should be grouped together, + # and the clumpname should be removed. + # Extract the new clumpname from the log header, + # and use it to decide whether to output a blank line. + newclumpname = "" + sep = "\n" + if (date == "") sep = "" + if (newlog ~ /^\{[^'"$tab"' }]*}['"$tab"' ]/) { + i = index(newlog, "}") + newclumpname = substr(newlog, 1, i) + while (substr(newlog, i+1) ~ /^['"$tab"' ]/) i++ + newlog = substr(newlog, i+1) + if (clumpname == newclumpname) sep = "" + } + printf sep + clumpname = newclumpname + + # Get ready for the next log. + Log = newlog + if (files != "") + for (i in filesknown) + filesknown[i] = 0 + files = "" + } + if (date != $2 || author != $4) { + # The previous date+author and this date+author differ. + # Print the new one. + date = $2 + author = $4 + + # Convert nice RCS date like "1992/01/03 00:03:44" + # into ugly ctime date like "Fri Jan 3 00:03:44 1992". + # Calculate day of week from Gregorian calendar. + i = index($2, "/") + year = substr($2, 1, i-1) + 0 + monthday = substr($2, i+1) + i = index(monthday, "/") + month = substr(monthday, 1, i-1) + 0 + day = substr(monthday, i+1) + 0 + leap = 0 + if (2 < month && year%4 == 0 && (year%100 != 0 || year%400 == 0)) leap = 1 + days_since_Sunday_before_epoch = EPOCH_WEEKDAY + year * 365 + int((year + 3) / 4) - int((year + 99) / 100) + int((year + 399) / 400) + mo[month-1] + leap + day - 1 + + # Print "date fullname (email address)". + # Get fullname and email address from associative arrays; + # default to author and author@hostname if not in arrays. + if (fullname[author]) + auth = fullname[author] + else + auth = author + printf "%s %s %2d %s %d %s ", w[days_since_Sunday_before_epoch%7], m[month-1], day, $3, year, auth + if (mailaddr[author]) + printf "<%s>\n\n", mailaddr[author] + else + printf "<%s@%s>\n\n", author, "'"$hostname"'" + } + if (! filesknown[$1]) { + filesknown[$1] = 1 + if (files == "") files = " " $1 + else files = files ", " $1 + } + } + END { + # Print the last log. + if (date != "") { + '"$printlogline"' + printf "\n" + } + } +' && + + +# Exit successfully. + +exec rm -f $llogout $rlogout diff --git a/contrib/cvs/contrib/rcs2sccs.sh b/contrib/cvs/contrib/rcs2sccs.sh new file mode 100644 index 0000000..af70138 --- /dev/null +++ b/contrib/cvs/contrib/rcs2sccs.sh @@ -0,0 +1,143 @@ +#! /bin/sh +# +# +# OrigId: rcs2sccs,v 1.12 90/10/04 20:52:23 kenc Exp Locker: kenc +# $Id: rcs2sccs.sh,v 1.1 1995/07/10 02:26:45 kfogel Exp $ + +############################################################ +# Error checking +# +if [ ! -d SCCS ] ; then + mkdir SCCS +fi + +logfile=/tmp/rcs2sccs_$$_log +rm -f $logfile +tmpfile=/tmp/rcs2sccs_$$_tmp +rm -f $tmpfile +emptyfile=/tmp/rcs2sccs_$$_empty +echo -n "" > $emptyfile +initialfile=/tmp/rcs2sccs_$$_init +echo "Initial revision" > $initialfile +sedfile=/tmp/rcs2sccs_$$_sed +rm -f $sedfile +revfile=/tmp/rcs2sccs_$$_rev +rm -f $revfile +commentfile=/tmp/rcs2sccs_$$_comment +rm -f $commentfile + +# create the sed script +cat > $sedfile << EOF +s,;Id;,%Z%%M% %I% %E%,g +s,;SunId;,%Z%%M% %I% %E%,g +s,;RCSfile;,%M%,g +s,;Revision;,%I%,g +s,;Date;,%E%,g +s,;Id:.*;,%Z%%M% %I% %E%,g +s,;SunId:.*;,%Z%%M% %I% %E%,g +s,;RCSfile:.*;,%M%,g +s,;Revision:.*;,%I%,g +s,;Date:.*;,%E%,g +EOF +sed -e 's/;/\\$/g' $sedfile > $tmpfile +cp $tmpfile $sedfile +############################################################ +# Loop over every RCS file in RCS dir +# +for vfile in *,v; do + # get rid of the ",v" at the end of the name + file=`echo $vfile | sed -e 's/,v$//'` + + # work on each rev of that file in ascending order + firsttime=1 + rlog $file | grep "^revision [0-9][0-9]*\." | awk '{print $2}' | sed -e 's/\./ /g' | sort -n -u +0 +1 +2 +3 +4 +5 +6 +7 +8 | sed -e 's/ /./g' > $revfile + for rev in `cat $revfile`; do + if [ $? != 0 ]; then + echo ERROR - revision + exit + fi + # get file into current dir and get stats + date=`rlog -r$rev $file | grep "^date: " | awk '{print $2; exit}' | sed -e 's/^19//'` + time=`rlog -r$rev $file | grep "^date: " | awk '{print $3; exit}' | sed -e 's/;//'` + author=`rlog -r$rev $file | grep "^date: " | awk '{print $5; exit}' | sed -e 's/;//'` + date="$date $time" + echo "" + rlog -r$rev $file | sed -e '/^branches: /d' -e '1,/^date: /d' -e '/^===========/d' -e 's/$/\\/' | awk '{if ((total += length($0) + 1) < 510) print $0}' > $commentfile + echo "==> file $file, rev=$rev, date=$date, author=$author" + rm -f $file + co -r$rev $file >> $logfile 2>&1 + if [ $? != 0 ]; then + echo ERROR - co + exit + fi + echo checked out of RCS + + # add SCCS keywords in place of RCS keywords + sed -f $sedfile $file > $tmpfile + if [ $? != 0 ]; then + echo ERROR - sed + exit + fi + echo performed keyword substitutions + rm -f $file + cp $tmpfile $file + + # check file into SCCS + if [ "$firsttime" = "1" ]; then + firsttime=0 + echo about to do sccs admin + echo sccs admin -n -i$file $file < $commentfile + sccs admin -n -i$file $file < $commentfile >> $logfile 2>&1 + if [ $? != 0 ]; then + echo ERROR - sccs admin + exit + fi + echo initial rev checked into SCCS + else + case $rev in + *.*.*.*) + brev=`echo $rev | sed -e 's/\.[0-9]*$//'` + sccs admin -fb $file 2>>$logfile + echo sccs get -e -p -r$brev $file + sccs get -e -p -r$brev $file >/dev/null 2>>$logfile + ;; + *) + echo sccs get -e -p $file + sccs get -e -p $file >/dev/null 2>> $logfile + ;; + esac + if [ $? != 0 ]; then + echo ERROR - sccs get + exit + fi + sccs delta $file < $commentfile >> $logfile 2>&1 + if [ $? != 0 ]; then + echo ERROR - sccs delta -r$rev $file + exit + fi + echo checked into SCCS + fi + sed -e "s;^d D $rev ../../.. ..:..:.. [^ ][^ ]*;d D $rev $date $author;" SCCS/s.$file > $tmpfile + rm -f SCCS/s.$file + cp $tmpfile SCCS/s.$file + chmod 444 SCCS/s.$file + sccs admin -z $file + if [ $? != 0 ]; then + echo ERROR - sccs admin -z + exit + fi + done + rm -f $file +done + + +############################################################ +# Clean up +# +echo cleaning up... +rm -f $tmpfile $emptyfile $initialfile $sedfile $commentfile +echo =================================================== +echo " Conversion Completed Successfully" +echo =================================================== + +rm -f *,v diff --git a/contrib/cvs/contrib/rcslock.pl b/contrib/cvs/contrib/rcslock.pl new file mode 100644 index 0000000..01e349f --- /dev/null +++ b/contrib/cvs/contrib/rcslock.pl @@ -0,0 +1,235 @@ +#! xPERL_PATHx +# -*-Perl-*- + +# Author: John Rouillard (rouilj@cs.umb.edu) +# Supported: Yeah right. (Well what do you expect for 2 hours work?) +# Blame-to: rouilj@cs.umb.edu +# Complaints to: Anybody except Brian Berliner, he's blameless for +# this script. +# Acknowlegements: The base code for this script has been acquired +# from the log.pl script. + +# rcslock.pl - A program to prevent commits when a file to be ckecked +# in is locked in the repository. + +# There are times when you need exclusive access to a file. This +# often occurs when binaries are checked into the repository, since +# cvs's (actually rcs's) text based merging mechanism won't work. This +# script allows you to use the rcs lock mechanism (rcs -l) to make +# sure that no changes to a repository are able to be committed if +# those changes would result in a locked file being changed. + +# WARNING: +# This script will work only if locking is set to strict. +# + +# Setup: +# Add the following line to the commitinfo file: + +# ALL /local/location/for/script/lockcheck [options] + +# Where ALL is replaced by any suitable regular expression. +# Options are -v for verbose info, or -d for debugging info. +# The %s will provide the repository directory name and the names of +# all changed files. + +# Use: +# When a developer needs exclusive access to a version of a file, s/he +# should use "rcs -l" in the repository tree to lock the version they +# are working on. CVS will automagically release the lock when the +# commit is performed. + +# Method: +# An "rlog -h" is exec'ed to give info on all about to be +# committed files. This (header) information is parsed to determine +# if any locks are outstanding and what versions of the file are +# locked. This filename, version number info is used to index an +# associative array. All of the files to be committed are checked to +# see if any locks are outstanding. If locks are outstanding, the +# version number of the current file (taken from the CVS/Entries +# subdirectory) is used in the key to determine if that version is +# locked. If the file being checked in is locked by the person doing +# the checkin, the commit is allowed, but if the lock is held on that +# version of a file by another person, the commit is not allowed. + +$ext = ",v"; # The extension on your rcs files. + +$\="\n"; # I hate having to put \n's at the end of my print statements +$,=' '; # Spaces should occur between arguments to print when printed + +# turn off setgid +# +$) = $(; + +# +# parse command line arguments +# +require 'getopts.pl'; + +&Getopts("vd"); # verbose or debugging + +# Verbose is useful when debugging +$opt_v = $opt_d if defined $opt_d; + +# $files[0] is really the name of the subdirectory. +# @files = split(/ /,$ARGV[0]); +@files = @ARGV[0..$#ARGV]; +$cvsroot = $ENV{'CVSROOT'}; + +# +# get login name +# +$login = getlogin || (getpwuid($<))[0] || "nobody"; + +# +# save the current directory since we have to return here to parse the +# CVS/Entries file if a lock is found. +# +$pwd = `/bin/pwd`; +chop $pwd; + +print "Starting directory is $pwd" if defined $opt_d ; + +# +# cd to the repository directory and check on the files. +# +print "Checking directory ", $files[0] if defined $opt_v ; + +if ( $files[0] =~ /^\// ) +{ + print "Directory path is $files[0]" if defined $opt_d ; + chdir $files[0] || die "Can't change to repository directory $files[0]" ; +} +else +{ + print "Directory path is $cvsroot/$files[0]" if defined $opt_d ; + chdir ($cvsroot . "/" . $files[0]) || + die "Can't change to repository directory $files[0] in $cvsroot" ; +} + + +# Open the rlog process and apss all of the file names to that one +# process to cut down on exec overhead. This may backfire if there +# are too many files for the system buffer to handle, but if there are +# that many files, chances are that the cvs repository is not set up +# cleanly. + +print "opening rlog -h @files[1..$#files] |" if defined $opt_d; + +open( RLOG, "rlog -h @files[1..$#files] |") || die "Can't run rlog command" ; + +# Create the locks associative array. The elements in the array are +# of two types: +# +# The name of the RCS file with a value of the total number of locks found +# for that file, +# or +# +# The name of the rcs file concatenated with the version number of the lock. +# The value of this element is the name of the locker. + +# The regular expressions used to split the rcs info may have to be changed. +# The current ones work for rcs 5.6. + +$lock = 0; + +while (<RLOG>) +{ + chop; + next if /^$/; # ditch blank lines + + if ( $_ =~ /^RCS file: (.*)$/ ) + { + $curfile = $1; + next; + } + + if ( $_ =~ /^locks: strict$/ ) + { + $lock = 1 ; + next; + } + + if ( $lock ) + { + # access list: is the line immediately following the list of locks. + if ( /^access list:/ ) + { # we are done getting lock info for this file. + $lock = 0; + } + else + { # We are accumulating lock info. + + # increment the lock count + $locks{$curfile}++; + # save the info on the version that is locked. $2 is the + # version number $1 is the name of the locker. + $locks{"$curfile" . "$2"} = $1 + if /[ ]*([a-zA-Z._]*): ([0-9.]*)$/; + + print "lock by $1 found on $curfile version $2" if defined $opt_d; + + } + } +} + +# Lets go back to the starting directory and see if any locked files +# are ones we are interested in. + +chdir $pwd; + +# fo all of the file names (remember $files[0] is the directory name +foreach $i (@files[1..$#files]) +{ + if ( defined $locks{$i . $ext} ) + { # well the file has at least one lock outstanding + + # find the base version number of our file + &parse_cvs_entry($i,*entry); + + # is our version of this file locked? + if ( defined $locks{$i . $ext . $entry{"version"}} ) + { # if so, it is by us? + if ( $login ne ($by = $locks{$i . $ext . $entry{"version"}}) ) + {# crud somebody else has it locked. + $outstanding_lock++ ; + print "$by has file $i locked for version " , $entry{"version"}; + } + else + { # yeah I have it locked. + print "You have a lock on file $i for version " , $entry{"version"} + if defined $opt_v; + } + } + } +} + +exit $outstanding_lock; + + +### End of main program + +sub parse_cvs_entry +{ # a very simple minded hack at parsing an entries file. +local ( $file, *entry ) = @_; +local ( @pp ); + + +open(ENTRIES, "< CVS/Entries") || die "Can't open entries file"; + +while (<ENTRIES>) + { + if ( $_ =~ /^\/$file\// ) + { + @pp = split('/'); + + $entry{"name"} = $pp[1]; + $entry{"version"} = $pp[2]; + $entry{"dates"} = $pp[3]; + $entry{"name"} = $pp[4]; + $entry{"name"} = $pp[5]; + $entry{"sticky"} = $pp[6]; + return; + } + } +} diff --git a/contrib/cvs/contrib/sccs2rcs.csh b/contrib/cvs/contrib/sccs2rcs.csh new file mode 100644 index 0000000..0f31893 --- /dev/null +++ b/contrib/cvs/contrib/sccs2rcs.csh @@ -0,0 +1,277 @@ +#! xCSH_PATHx -f +# +# Sccs2rcs is a script to convert an existing SCCS +# history into an RCS history without losing any of +# the information contained therein. +# It has been tested under the following OS's: +# SunOS 3.5, 4.0.3, 4.1 +# Ultrix-32 2.0, 3.1 +# +# Things to note: +# + It will NOT delete or alter your ./SCCS history under any circumstances. +# +# + Run in a directory where ./SCCS exists and where you can +# create ./RCS +# +# + /usr/local/bin is put in front of the default path. +# (SCCS under Ultrix is set-uid sccs, bad bad bad, so +# /usr/local/bin/sccs here fixes that) +# +# + Date, time, author, comments, branches, are all preserved. +# +# + If a command fails somewhere in the middle, it bombs with +# a message -- remove what it's done so far and try again. +# "rm -rf RCS; sccs unedit `sccs tell`; sccs clean" +# There is no recovery and exit is far from graceful. +# If a particular module is hanging you up, consider +# doing it separately; move it from the current area so that +# the next run will have a better chance or working. +# Also (for the brave only) you might consider hacking +# the s-file for simpler problems: I've successfully changed +# the date of a delta to be in sync, then run "sccs admin -z" +# on the thing. +# +# + After everything finishes, ./SCCS will be moved to ./old-SCCS. +# +# This file may be copied, processed, hacked, mutilated, and +# even destroyed as long as you don't tell anyone you wrote it. +# +# Ken Cox +# Viewlogic Systems, Inc. +# kenstir@viewlogic.com +# ...!harvard!cg-atla!viewlog!kenstir +# +# Various hacks made by Brian Berliner before inclusion in CVS contrib area. +# +# $Id: sccs2rcs.csh,v 1.1 1995/07/10 02:26:48 kfogel Exp $ + + +#we'll assume the user set up the path correctly +# for the Pmax, /usr/ucb/sccs is suid sccs, what a pain +# /usr/local/bin/sccs should override /usr/ucb/sccs there +set path = (/usr/local/bin $path) + + +############################################################ +# Error checking +# +if (! -w .) then + echo "Error: ./ not writeable by you." + exit 1 +endif +if (! -d SCCS) then + echo "Error: ./SCCS directory not found." + exit 1 +endif +set edits = (`sccs tell`) +if ($#edits) then + echo "Error: $#edits file(s) out for edit...clean up before converting." + exit 1 +endif +if (-d RCS) then + echo "Warning: RCS directory exists" + if (`ls -a RCS | wc -l` > 2) then + echo "Error: RCS directory not empty + exit 1 + endif +else + mkdir RCS +endif + +sccs clean + +set logfile = /tmp/sccs2rcs_$$_log +rm -f $logfile +set tmpfile = /tmp/sccs2rcs_$$_tmp +rm -f $tmpfile +set emptyfile = /tmp/sccs2rcs_$$_empty +echo -n "" > $emptyfile +set initialfile = /tmp/sccs2rcs_$$_init +echo "Initial revision" > $initialfile +set sedfile = /tmp/sccs2rcs_$$_sed +rm -f $sedfile +set revfile = /tmp/sccs2rcs_$$_rev +rm -f $revfile + +# the quotes surround the dollar signs to fool RCS when I check in this script +set sccs_keywords = (\ + '%W%[ ]*%G%'\ + '%W%[ ]*%E%'\ + '%W%'\ + '%Z%%M%[ ]*%I%[ ]*%G%'\ + '%Z%%M%[ ]*%I%[ ]*%E%'\ + '%M%[ ]*%I%[ ]*%G%'\ + '%M%[ ]*%I%[ ]*%E%'\ + '%M%'\ + '%I%'\ + '%G%'\ + '%E%'\ + '%U%') +set rcs_keywords = (\ + '$'Id'$'\ + '$'Id'$'\ + '$'Id'$'\ + '$'SunId'$'\ + '$'SunId'$'\ + '$'Id'$'\ + '$'Id'$'\ + '$'RCSfile'$'\ + '$'Revision'$'\ + '$'Date'$'\ + '$'Date'$'\ + '') + + +############################################################ +# Get some answers from user +# +echo "" +echo "Do you want to be prompted for a description of each" +echo "file as it is checked in to RCS initially?" +echo -n "(y=prompt for description, n=null description) [y] ?" +set ans = $< +if ((_$ans == _) || (_$ans == _y) || (_$ans == _Y)) then + set nodesc = 0 +else + set nodesc = 1 +endif +echo "" +echo "The default keyword substitutions are as follows and are" +echo "applied in the order specified:" +set i = 1 +while ($i <= $#sccs_keywords) +# echo ' '\"$sccs_keywords[$i]\"' ==> '\"$rcs_keywords[$i]\" + echo " $sccs_keywords[$i] ==> $rcs_keywords[$i]" + @ i = $i + 1 +end +echo "" +echo -n "Do you want to change them [n] ?" +set ans = $< +if ((_$ans != _) && (_$ans != _n) && (_$ans != _N)) then + echo "You can't always get what you want." + echo "Edit this script file and change the variables:" + echo ' $sccs_keywords' + echo ' $rcs_keywords' +else + echo "good idea." +endif + +# create the sed script +set i = 1 +while ($i <= $#sccs_keywords) + echo "s,$sccs_keywords[$i],$rcs_keywords[$i],g" >> $sedfile + @ i = $i + 1 +end + +onintr ERROR + +############################################################ +# Loop over every s-file in SCCS dir +# +foreach sfile (SCCS/s.*) + # get rid of the "s." at the beginning of the name + set file = `echo $sfile:t | sed -e "s/^..//"` + + # work on each rev of that file in ascending order + set firsttime = 1 + sccs prs $file | grep "^D " | awk '{print $2}' | sed -e 's/\./ /g' | sort -n -u +0 +1 +2 +3 +4 +5 +6 +7 +8 | sed -e 's/ /./g' > $revfile + foreach rev (`cat $revfile`) + if ($status != 0) goto ERROR + + # get file into current dir and get stats + set date = `sccs prs -r$rev $file | grep "^D " | awk '{printf("19%s %s", $3, $4); exit}'` + set author = `sccs prs -r$rev $file | grep "^D " | awk '{print $5; exit}'` + echo "" + echo "==> file $file, rev=$rev, date=$date, author=$author" + sccs edit -r$rev $file >>& $logfile + if ($status != 0) goto ERROR + echo checked out of SCCS + + # add RCS keywords in place of SCCS keywords + sed -f $sedfile $file > $tmpfile + if ($status != 0) goto ERROR + echo performed keyword substitutions + cp $tmpfile $file + + # check file into RCS + if ($firsttime) then + set firsttime = 0 + if ($nodesc) then + echo about to do ci + echo ci -f -r$rev -d"$date" -w$author -t$emptyfile $file + ci -f -r$rev -d"$date" -w$author -t$emptyfile $file < $initialfile >>& $logfile + if ($status != 0) goto ERROR + echo initial rev checked into RCS without description + else + echo "" + echo Enter a brief description of the file $file \(end w/ Ctrl-D\): + cat > $tmpfile + ci -f -r$rev -d"$date" -w$author -t$tmpfile $file < $initialfile >>& $logfile + if ($status != 0) goto ERROR + echo initial rev checked into RCS + endif + else + # get RCS lock + set lckrev = `echo $rev | sed -e 's/\.[0-9]*$//'` + if ("$lckrev" =~ [0-9]*.*) then + # need to lock the brach -- it is OK if the lock fails + rcs -l$lckrev $file >>& $logfile + else + # need to lock the trunk -- must succeed + rcs -l $file >>& $logfile + if ($status != 0) goto ERROR + endif + echo got lock + sccs prs -r$rev $file | grep "." > $tmpfile + # it's OK if grep fails here and gives status == 1 + # put the delta message in $tmpfile + ed $tmpfile >>& $logfile <<EOF +/COMMENTS +1,.d +w +q +EOF + ci -f -r$rev -d"$date" -w$author $file < $tmpfile >>& $logfile + if ($status != 0) goto ERROR + echo checked into RCS + endif + sccs unedit $file >>& $logfile + if ($status != 0) goto ERROR + end + rm -f $file +end + + +############################################################ +# Clean up +# +echo cleaning up... +mv SCCS old-SCCS +rm -f $tmpfile $emptyfile $initialfile $sedfile +echo =================================================== +echo " Conversion Completed Successfully" +echo "" +echo " SCCS history now in old-SCCS/" +echo =================================================== +set exitval = 0 +goto cleanup + +ERROR: +foreach f (`sccs tell`) + sccs unedit $f +end +echo "" +echo "" +echo Danger\! Danger\! +echo Some command exited with a non-zero exit status. +echo Log file exists in $logfile. +echo "" +echo Incomplete history in ./RCS -- remove it +echo Original unchanged history in ./SCCS +set exitval = 1 + +cleanup: +# leave log file +rm -f $tmpfile $emptyfile $initialfile $sedfile $revfile + +exit $exitval diff --git a/contrib/cvs/cvs-format.el b/contrib/cvs/cvs-format.el new file mode 100644 index 0000000..cdbd842 --- /dev/null +++ b/contrib/cvs/cvs-format.el @@ -0,0 +1,81 @@ +;; -*- lisp-interaction -*- +;; -*- emacs-lisp -*- +;; +;; +;; originally from... +;; Rich's personal .emacs file. feel free to copy. +;; +;; Last Mod Wed Feb 5 16:11:47 PST 1992, by rich@cygnus.com +;; + +;; +;; +;; This section sets constants used by c-mode for formating +;; +;; + +;; If `c-auto-newline' is non-`nil', newlines are inserted both +;;before and after braces that you insert, and after colons and semicolons. +;;Correct C indentation is done on all the lines that are made this way. + +(setq c-auto-newline nil) + + +;;*Non-nil means TAB in C mode should always reindent the current line, +;;regardless of where in the line point is when the TAB command is used. +;;It might be desirable to set this to nil for CVS, since unlike GNU +;; CVS often uses comments over to the right separated by TABs. +;; Depends some on whether you're in the habit of using TAB to +;; reindent. +;(setq c-tab-always-indent nil) + +;;; It seems to me that +;;; `M-x set-c-style BSD RET' +;;; or +;;; (set-c-style "BSD") +;;; takes care of the indentation parameters correctly. + + +;; C does not have anything analogous to particular function names for which +;;special forms of indentation are desirable. However, it has a different +;;need for customization facilities: many different styles of C indentation +;;are in common use. +;; +;; There are six variables you can set to control the style that Emacs C +;;mode will use. +;; +;;`c-indent-level' +;; Indentation of C statements within surrounding block. The surrounding +;; block's indentation is the indentation of the line on which the +;; open-brace appears. + +(setq c-indent-level 4) + +;;`c-continued-statement-offset' +;; Extra indentation given to a substatement, such as the then-clause of +;; an if or body of a while. + +(setq c-continued-statement-offset 4) + +;;`c-brace-offset' +;; Extra indentation for line if it starts with an open brace. + +(setq c-brace-offset -4) + +;;`c-brace-imaginary-offset' +;; An open brace following other text is treated as if it were this far +;; to the right of the start of its line. + +(setq c-brace-imaginary-offset 0) + +;;`c-argdecl-indent' +;; Indentation level of declarations of C function arguments. + +(setq c-argdecl-indent 4) + +;;`c-label-offset' +;; Extra indentation for line that is a label, or case or default. + +(setq c-label-offset -4) + +;;;; eof diff --git a/contrib/cvs/doc/ChangeLog b/contrib/cvs/doc/ChangeLog new file mode 100644 index 0000000..01efb1a --- /dev/null +++ b/contrib/cvs/doc/ChangeLog @@ -0,0 +1,569 @@ +Wed May 1 15:38:26 1996 Jim Kingdon <kingdon@harvey.cyclic.com> + + * cvs.texinfo (Tags): Document un-revision of all-uppercase tag + names. + +Wed Apr 24 08:41:51 1996 Jim Kingdon <kingdon@harvey.cyclic.com> + + * cvs.texinfo (Password authentication security): Rewrite sentence + on complex and unknown security bugs to clarify that it is + referring to people who have been give access to cvs, not to holes + in the authentication method (which is relatively simple). + +Tue Apr 23 09:31:29 1996 Jim Kingdon <kingdon@harvey.cyclic.com> + + * cvs.texinfo (Wrappers): Talk about what -m does (and does not + do). Other minor edits. + +Wed Apr 17 15:27:03 1996 Jim Kingdon <kingdon@harvey.cyclic.com> + + * cvs.texinfo (rcsinfo): Rewrite paragraph concerning remote CVS. + * cvsclient.texi (Responses): Document Template response. + +Sun Apr 14 16:01:39 1996 Karl Fogel <kfogel@floss.red-bean.com> + + * .cvsignore: added CVSvn.texi. + +Wed Apr 10 16:56:21 1996 Jim Kingdon <kingdon@harvey.cyclic.com> + + * cvs.texinfo (~/.cvsrc): Mention setting global options with "cvs". + + * cvs.texinfo (release): Change "modules" to "directories". + Release does not take module names as arguments. + + * cvs.texinfo (Creating a branch): Add comments about how we + should better document tagging the branchpoint. + +Tue Apr 9 19:59:45 1996 Jim Kingdon <kingdon@harvey.cyclic.com> + + * cvs.texinfo (Top): Use @value{CVSVN}, not a vague refenece to 1.4. + + * cvs.texinfo (From other version control systems): New node. + +Mon Apr 8 15:59:37 1996 Jim Kingdon <kingdon@harvey.cyclic.com> + + * cvsclient.texi (Connection and Authentication): Revise kerberos + and pserver sections to reflect the fact that port 2401 is now + officially registered. + +Thu Mar 28 09:51:13 1996 Jim Kingdon <kingdon@harvey.cyclic.com> + + * cvs.texinfo (History browsing): Reinstate this node. Try to get + it into some minimally useful state (it still needs a lot of + work). + (annotate): New node, subnode of History browing. + + * cvsclient.texi (Requests): Add annotate request. + +Tue Mar 26 08:46:39 1996 Jim Kingdon <kingdon@harvey.cyclic.com> + + * cvs.texinfo: In various examples, change tag names to avoid tag + names reserved to CVS. + + * cvs.texinfo (Tags): Document what is a valid tag name. + + * cvs.texinfo (Substitution modes): Try to describe how the + various keyword expansion settings interract. + (Binary files): Suggest cvs update -A, not removing file and then + updating it, to get effect of new keyword expansion options. + + * cvs.texinfo (admin options): Mention CVS's use of `dead' state. + +Thu Mar 21 08:25:17 1996 Jim Kingdon <kingdon@harvey.cyclic.com> + + * cvs.texinfo (Environment variables): Expand introduction to RCS + environment variables. Expand and correct CVS_SERVER_SLEEP. + + * cvs.texinfo (Environment variables): Remove POSIXLY_CORRECT; cvs + requires options to precede arguments regardless of it. + +Thu Mar 21 08:18:42 1996 Norbert Kiesel <nk@col.sw-ley.de> + + * cvs.texinfo: Remove paragrahps about a forthcoming CVS + newsgroup and about sending patches to think.com. + (Environment): Document some more (all?) used environment + variables. + +Wed Mar 20 09:44:21 1996 Jim Kingdon <kingdon@harvey.cyclic.com> + + * cvsclient.texi (Introduction): New node. + * Makefile.in: Add cruft to reflect fact that cvsclient.texi now + uses CVSvn.texi. + +Mon Mar 18 14:43:53 1996 Jim Kingdon <kingdon@harvey.cyclic.com> + + * cvsclient.texi (Requests): Add Case request. + +Wed Mar 13 16:01:47 1996 Jim Kingdon <kingdon@harvey.cyclic.com> + + * cvsclient.texi (Connection and Authentication): New node. + + * cvsclient.texi (Requests): Expand discussion of Root a bit. + + * cvs.texinfo (Setting up): Don't refer to INSTALL file; revise to + reflect some information which had been in the INSTALL file. + + * cvs.texinfo (history file): Update to reflect cvsinit -> cvs + init. Adjust discussion of whether history file format is + documented. + (Setting up): Update to reflect cvsinit -> cvs init. + + * cvsclient.texi (Requests): Document init request. + +Thu Feb 29 10:08:31 1996 Jim Kingdon <kingdon@harvey.cyclic.com> + + * cvs.texinfo (loginfo example): Adjust example to reflect the way + that CVS actually works. Add comments questioning whether that is + the best behavior. + + * cvs.texinfo (cvsignore): Document additions to default ignore list. + +Mon Feb 26 13:48:01 1996 Jim Kingdon <kingdon@harvey.cyclic.com> + + * cvsclient.texi (Filenames): New node, documents / vs \, etc. + +Wed Feb 24 1996 Marcus Daniels <marcus@sayre.sysc.pdx.edu> + + * cvs.texinfo (Password authentication server): Mention + support for imaginary usernames. + +Thu Feb 15 16:34:56 1996 Jim Kingdon <kingdon@harvey.cyclic.com> + + * cvs.texinfo (Variables): Add new internal variable $USER. + +Wed Feb 14 22:52:58 1996 Jim Kingdon <kingdon@harvey.cyclic.com> + + * cvs.texinfo (export, admin): Document -k option to cvs export. + + * cvs.texinfo (admin options): Mention using -l, -u, -L, and -U in + conjunction with rcslock.pl. + +Mon Feb 12 16:38:41 1996 Jim Kingdon <kingdon@harvey.cyclic.com> + + * cvs.texinfo: Remove references to mkmodules. + +Sun Feb 11 12:31:36 1996 Jim Kingdon <kingdon@harvey.cyclic.com> + + * cvsclient.texi: Add Set request. + + * cvs.texinfo (Variables): Rewrite to reflect user variables + replacing environment variables; motivate the discussion better. + (Global options): Add -s option. + +Sat Feb 10 11:18:37 1996 Jim Blandy <jimb@totoro.cyclic.com> + + * cvs.texinfo (Variables): Fix @table commands. + +Fri Feb 9 17:31:18 1996 Jim Kingdon <kingdon@harvey.cyclic.com> + + * cvs.texinfo (Variables): New node. + + * Makefile.in (CVSvn.texi): New rule. + (OBJDIR_DISTFILES): Add CVSvn.texi. + (cvs.dvi,cvs.info): Add cruft to deal with it being in build dir + or srcdir. + * cvs.texinfo: Include CVSvn.texi and use the version number from + it instead of a hardcoded version number and date. + +Thu Feb 1 13:28:03 1996 Jim Kingdon <kingdon@harvey.cyclic.com> + + * cvs.texinfo (Sticky tags): Expand so it really documents the + features it is talking about rather than referring to "Appendix + A". Add example of how to restore the old version of a dead + file. In various other parts of the manual refer to this node, in + some cases deleting duplicative text. In the case of cvs admin + -b, mention vendor branch usage. + (Removing files): Discuss removing files (in user-visible terms, + not in terms of the Attic and such). + (remove): Remove node; merge contents into Removing files. + +Tue Jan 30 17:52:06 1996 Jim Blandy <jimb@totoro.cyclic.com> + + * cvs.texinfo: Tweak @top node, to make file compatible with both + makeinfo and texinfo-format-buffer. Perhaps we should fix the + formatters to agree on what constitutes valid texinfo. + +Mon Jan 29 16:38:33 1996 Jim Kingdon <kingdon@harvey.cyclic.com> + + * cvsclient.texi (Requirements): New node, to talk about required + versus optional parts of the protocol. + +Sun Jan 28 09:00:34 1996 Jim Kingdon <kingdon@harvey.cyclic.com> + + * cvsclient.texi (Modes): Add discussion what what the mode really + means (across diverse operating systems). + +Tue Jan 23 12:54:57 1996 Jim Kingdon <kingdon@harvey.cyclic.com> + + * cvs.texinfo: Per mail from Per Cederqvist, change author to "Per + Cederqvist et al". Also remove sentence about Signum shipping + hardcopy manuals and add information on Cyclic. Change version + number to 1.6.87. + +Fri Jan 12 15:29:39 1996 Vince Demarco <vdemarco@bou.shl.com> + + * cvs.texinfo: Fix the documentation for the com/uncom change + to wrap/unwrap. make everything consistant + +Wed Jan 10 16:11:54 1996 Jim Kingdon <kingdon@harvey.cyclic.com> + + * cvs.texinfo (Concurrency): Add index entries; minor clarification. + +Tue Jan 9 16:03:39 1996 Jim Kingdon <kingdon@harvey.cyclic.com> + + * cvs.texinfo (Getting Notified): Document users file. + + * cvs.texinfo (cvsignore): Add *.obj to list of ignored files. + +Wed Jan 3 17:01:58 1996 Jim Kingdon <kingdon@harvey.cyclic.com> + + * cvs.texinfo (import): Adjust list of ignored files to match + recent change to CVS (CVS* -> CVS CVS.adm). Consolidate + discussion of ignored files in one place (with xrefs from others). + + * cvsclient.texi: Remove How To node. It was out of date + (again!), and I am getting sick of trying to update it (internals + documentation should be in the comments, where it at least has a + fighting chance of staying up to date). + (Protocol): Say what \n and \t mean in this document. + +Tue Jan 2 23:39:32 1996 Jim Kingdon <kingdon@harvey.cyclic.com> + + * cvs.texinfo (Wrappers): Change comb/uncom to wrap/unwrap. + +Mon Jan 2 23:00:00 1996 Vince Demarco <vdemarco@bou.shl.com> + + * cvs.texinfo: update the Wrappers documentation so it isn't + so NEXTSTEP centric. The wrappers code has alot of other + general uses. The new version of the documentation tryes + to show that to the reader. + +Mon Jan 1 13:09:39 1996 Jim Kingdon <kingdon@harvey.cyclic.com> + + * cvsclient.texi (Responses): Clarify that Module-expansion is not + suitable for passing to co. + +Sun Dec 31 10:53:47 1995 Jim Kingdon <kingdon@harvey.cyclic.com> + + * cvs.texinfo (Password authentication server): Suggest specifying + -b in inetd.conf. + + * cvs.texinfo (Password authentication): Variety of cleanups and + minor fixes, including shorter node names. + +Sun Dec 24 02:37:51 1995 Karl Fogel <kfogel@floss.cyclic.com> + + * cvs.texinfo (Using the client with password authentication): + tixed fypos. + +Sun Dec 24 00:00:16 1995 Karl Fogel <kfogel@floss.cyclic.com> + + * cvs.texinfo (Remote repositories): use @code{rsh} most places, + because it is the name of a program, and because I am a pedant. + Refer to new node "Password authenticated". + (Password authenticated): new node. + (Setting up the server for password authentication): new node. + (Using the client with password authentication): new node. + (Security considerations with password authentication): new node. + + These are all really long node names, but it seems necessary that + they be descriptive in case they're referenced elsewhere. If you + can think of a way out of this, please change them. + +Thu Dec 21 12:09:34 1995 Jim Kingdon <kingdon@harvey.cyclic.com> + + * cvsclient.texi (Requests): Add Questionable. Revise + documentation of export and update to explain role of -I option. + +Tue Dec 19 16:44:18 1995 Jim Kingdon <kingdon@harvey.cyclic.com> + + * cvs.texinfo: Update binary files info for -kb. + +Mon Dec 11 12:20:55 1995 Jim Kingdon <kingdon@harvey.cyclic.com> + + * cvsclient.texi (Responses): Add Notified and Mode. + (Requests): Add Notify, noop, watch-on, watch-off, watch-add, + watch-remove, watchers, and editors. + * cvs.texinfo (Watches): New node, to describe new developer + communication features. + +Thu Nov 23 08:59:09 1995 Jim Kingdon <kingdon@harvey.cyclic.com> + + * cvs.texinfo (admin options): In saying that cvs admin -o is not + such a good way to undo a change, refer to the section which + describes the preferred way. + +Thu Nov 13 16:39:03 1995 Fred Fish <fnf@cygnus.com> + + * Makefile.in: Remove extraneous tab from empty line. + +Mon Nov 13 15:00:26 1995 Jim Kingdon <kingdon@harvey.cyclic.com> + + * cvs.texinfo (Concurrency): New node, to describe user-visible + behaviors associated with cvs locks. + + * cvs.texinfo (Remote repositories): Add more details of how to + set things up (with rsh and kerberos). + +Thu Nov 9 11:41:37 1995 Jim Kingdon <kingdon@harvey.cyclic.com> + + * cvs.texinfo: Remove -Q and -q options from command synopses. + +Wed Nov 8 09:38:00 1995 Jim Kingdon <kingdon@harvey.cyclic.com> + + * cvsclient.texi (Notes): Revise paragraph on server memory use + problem. + +Tue Nov 7 16:26:39 1995 Jim Kingdon <kingdon@harvey.cyclic.com> + + * cvs.texinfo: Document merging more than once from a branch; + miscellaneous cleanups. + +Mon Oct 30 13:12:53 1995 Jim Kingdon <kingdon@harvey.cyclic.com> + + * cvs.texinfo (modules): Document -e. + +Thu Oct 26 11:15:40 1995 Jim Kingdon <kingdon@harvey.cyclic.com> + + * cvs.texinfo (Tags): Update "version" vs. "revision" for CVS 1.5. + (Index,BUGS): Change bug reporting address from Per Cederqvist to + bug-cvs@prep.ai.mit.edu. + +Wed Oct 25 15:37:05 1995 Jim Kingdon <kingdon@harvey.cyclic.com> + + * cvs.texinfo: Miscellaneous minor changes (clean up CVS/Root + stuff, don't say release requires a module entry, etc.). + +Tue Oct 24 11:01:22 1995 Jim Kingdon <kingdon@harvey.cyclic.com> + + * cvs.texinfo: More precisely describe scope of document. + * cvsclient.texi: Describe scope of document + +Thu Oct 12 11:25:40 1995 Karl Fogel <kfogel@totoro.cyclic.com> + + * cvs.texinfo: cover page now refers to CVS 1.6, and "last + updated" date has been upped to today. + +Wed Oct 11 22:30:10 1995 Jim Kingdon <kingdon@harvey.cyclic.com> + + * Makefile.in (info): Look for *.info* either in build dir or in + srcdir. + +Mon Oct 2 17:10:49 1995 Norbert Kiesel <nk@col.sw-ley.de> + + * cvs.texinfo (admin): Describe usage of CVS_ADMIN_GROUP to + restrict usage of admin. + +Fri Oct 6 21:17:50 1995 Jim Kingdon <kingdon@harvey.cyclic.com> + + * cvs.texinfo (~/.cvsrc): Document change to command name matching. + +Thu Oct 5 18:03:41 1995 Jim Kingdon <kingdon@harvey.cyclic.com> + + * Makefile.in (install-info): Add comment about srcdir. + +Wed Sep 13 12:45:53 1995 Jim Kingdon <kingdon@harvey.cyclic.com> + + * cvs.texinfo (Moving files): Rewrite "Outside" node to clarify + that history is still there and describe how to get it. Assorted + cleanups. + +Tue Sep 12 19:02:47 1995 Jim Kingdon <kingdon@harvey.cyclic.com> + + * cvs.texinfo (Removing files): Remove section on limitations + which are gone now that we have death support. + +Wed Aug 30 12:32:29 1995 Karl Fogel <kfogel@floss.cyclic.com> + + * cvs.texinfo (Remote Repositories): new node, referred to from + `Basics' and `Repository'. + (Repository): documented new `-d' vs. `$CVSROOT' vs. `CVS/Root' + behavior. + (commitinfo): document client/server-case behavior. + (editinfo): document client/server-case behavior. + (loginfo): document client/server-case behavior. + (rcsinfo): document client/server-case behavior. + +Mon Aug 21 00:23:45 1995 Jim Kingdon <kingdon@harvey.cyclic.com> + + * cvsclient.texi (How To): The way to force rsh is to set + CVS_CLIENT_PORT to -1, not to some bogus value. + +Tue Aug 15 17:12:08 1995 Karl Fogel <kfogel@floss.cyclic.com> + + * cvs.texinfo + (Basic concepts): talk about remote repositories. + (Repository): same. + +Mon Jul 24 19:09:12 1995 James Kingdon <kingdon@harvey.cyclic.com> + + * cvs.texinfo: Remove references to -q and -Q command options. + +Fri Jul 21 10:33:07 1995 Vince DeMarco <vdemarco@bou.shl.com> + + * cvs.texinfo: Changes for CVSEDITOR and wrappers. + +Thu Jul 13 23:04:12 CDT 1995 Jim Meyering (meyering@comco.com) + + * Makefile.in (cvs-paper.ps): *Never* redirect output directly to + the target (usu $@) of a rule. Instead, redirect to a temporary + file, and then move that temporary to the target. I chose to + name temporary files $@-t. Remember to be careful that the length + of the temporary file name not exceed the 14-character limit. + +Sun Jul 9 19:03:00 1995 Greg A. Woods <woods@most.weird.com> + + * doc/cvs.texinfo: + - document '-q' for 'cvs status' + - correction to regexp use in *info files + - correction to use of 'cvsinit' script + (from previous local changes) + +Tue Jun 20 18:57:55 1995 James Kingdon <kingdon@harvey.cyclic.com> + + * Makefile.in (dist-dir): Depend on $(OBJDIR_DISTFILES). + +Fri Jun 16 21:56:16 1995 Karl Fogel <kfogel@cyclic.com> + and Jim Meyering <meyering@comco.com> + + * update.c (update_file_proc): If noexec, just write 'C', don't merge. + +Fri Jun 16 07:56:04 1995 Jim Kingdon (kingdon@cyclic.com) + + * cvs-paper.ps: Added. + +Sat May 27 08:46:00 1995 Jim Meyering (meyering@comco.com) + + * Makefile.in (Makefile): Regenerate only Makefile in current + directory when Makefile.in is out of date. Depend on ../config.status. + +Sat May 27 08:08:18 1995 Jim Meyering (meyering@comco.com) + + * doc/Makefile.in (realclean): Remove more postscript and info files. + +Fri Apr 28 22:44:06 1995 Jim Blandy <jimb@totoro.bio.indiana.edu> + + * Makefile.in (DISTFILES): Updated. + (doc): Depend on cvsclient.ps too. + (cvs.aux, cvsclient.aux): Add target. + (cvsclient.dvi): Don't nuke the aux file. They're small and + helpful. + (cvsclient.ps): New target. + (dist-dir): Renamed from dist; changed to work with DISTDIR + variable from parent. + +Sun Apr 23 22:13:18 1995 Noel Cragg <noel@vo.com> + + * Makefile: Added more files to the `clean' target. + * .cvsignore: Added the same files. + +Mon Nov 28 10:22:46 1994 Jim Kingdon (kingdon@lioth.cygnus.com) + + * cvsclient.texi (Notes): Remove item about commit options; now + fixed. Rewrite paragraph about server memory usage. + + * cvsclient.texi (Responses): Add Set-checkin-prog and + Set-update-prog. + (Requests): Add Checkin-prog and Update-prog. + * cvsclient.texi (TODO): Remove last item (it is fixed) and node. + +Fri Nov 18 16:51:36 1994 Jim Kingdon (kingdon@lioth.cygnus.com) + + * cvsclient.texi (Requests): Add Max-dotdot. + +Thu Nov 3 07:04:24 1994 Jim Kingdon (kingdon@lioth.cygnus.com) + + * cvsclient.texi (Protocol): Add Directory request. + (TODO): Remove item about renaming directories. + (Protocol): Change @subheading to @node/@section. + +Fri Oct 28 07:51:13 1994 Jim Kingdon (kingdon@lioth.cygnus.com) + + * cvsclient.texi (Protocol): Add expand-module request and + Module-expansion response. + (Protocol Notes, TODO): Remove items about cvs co funkiness. + +Wed Oct 12 19:49:36 1994 Jim Kingdon (kingdon@lioth.cygnus.com) + + * cvsclient.texi (Protocol): Add Copy-file response. + + * cvsclient.texi (How To): Correct item about where declaration + of cvs commands go. + + * cvsclient.texi (Protocol): Add new commands. Merge description + of how commands work which was duplicated among the various + commands. Formatting cleanups. + (TODO): Remove item about bad error message on checking in a + nonexistent file; this works now (presumably fixed by the + Unchanged stuff). + (Notes): Remove thing about trying unsupported commands via NFS, + rdist, etc. Also remove item about some commands not being + supported. There are no unsupported commands anymore. + +Tue Sep 13 13:28:52 1994 Jim Kingdon (kingdon@lioth.cygnus.com) + + * cvsclient.texi (Protocol): Document New-entry response. + +Mon Sep 12 06:35:15 1994 Jim Kingdon (kingdon@lioth.cygnus.com) + + * cvsclient.texi (Protocol): Clarify that checksum is of patched + file, not patch itself. Fix typo (valid-requests -> Valid-requests). + + * cvsclient.texi (Protocol): Document Sticky request and + Set-sticky and Clear-sticky responses. + (Notes): Remove sticky tags from todo list. + +Thu Sep 8 14:23:58 1994 Jim Kingdon (kingdon@lioth.cygnus.com) + + * cvsclient.texi (Protocol): Document Static-directory requests + and Set-static-directory and Clear-static-directory responses. + (Notes): Remove Entries.Static support from todo list. + + * cvsclient.texi (Protocol): Document Unchanged and UseUnchanged + requests. Update documentation of Entry and Lost accordingly. + +Mon Aug 22 14:08:21 1994 Jim Kingdon (kingdon@lioth.cygnus.com) + + * cvsclient.texi (Goals): Remove mention of rsh. + (Protocol Notes, TODO): Remove compression item. + (Protocol): Document "status" request. + (TODO): Remove suggestion to add "cvs status". + +Tue Jul 19 10:02:53 1994 Ian Lance Taylor (ian@sanguine.cygnus.com) + + * Makefile.in (install-info): Do not depend upon installdirs. + +Fri Jul 15 12:56:53 1994 Ian Lance Taylor (ian@sanguine.cygnus.com) + + * Makefile.in (all): Do not depend upon info. + (install): Do not depend upon install-info. + +Thu Jul 7 20:43:12 1994 Ian Lance Taylor (ian@sanguine.cygnus.com) + + * cvsclient.texi (Protocol): Add Checksum response. + +Thu Jun 30 15:16:50 1994 Jim Kingdon (kingdon@lioth.cygnus.com) + + * cvsclient.texi (Protocol): Add Global_option request. + +Wed Jun 29 14:09:42 1994 Ian Lance Taylor (ian@sanguine.cygnus.com) + + * cvsclient.texi: Describe sending patches, including the dummy + update-patches request and the Patched response. Mention Kerberos + authentication using ``cvs kserver''. Some other minor changes. + +Tue Jun 28 15:21:06 1994 Jim Kingdon (kingdon@lioth.cygnus.com) + + * cvsclient.texi (Protocol Notes): Remove note about sending diffs + in Updated; Ian did it. Remove note about adding encryption to rsh. + +Sat May 7 10:44:30 1994 Jim Kingdon (kingdon@lioth.cygnus.com) + + * cvsclient.texi (Protocol): Document Modified without Entry. Add + `add' and `remove' and `Remove-entry'. Formatting cleanups. + +Tue Apr 19 01:29:04 1994 John Gilmore (gnu@cygnus.com) + + * cvsclient.texi: New node How To; cleanups throughout. + * Makefile.in: Add dependencies on cvsclient.texi. + diff --git a/contrib/cvs/doc/ChangeLog.fsf b/contrib/cvs/doc/ChangeLog.fsf new file mode 100644 index 0000000..2f14099 --- /dev/null +++ b/contrib/cvs/doc/ChangeLog.fsf @@ -0,0 +1,38 @@ +Thu Sep 15 14:19:50 1994 david d `zoo' zuhn <zoo@monad.armadillo.com> + + * Makefile.in: define TEXI2DVI + +Sat Dec 18 01:23:39 1993 david d zuhn (zoo@monad.armadillo.com) + + * cvs.texinfo: document -k SUBST options to 'cvs import'; + regularize use @sc{cvs} + + * Makefile.in (VPATH): don't use $(srcdir), but @srcdir@ instead + (install-info): grab all info files, not just *.info + +Mon Oct 11 16:23:54 1993 Jim Kingdon (kingdon@lioth.cygnus.com) + + * cvsclient.texi: New node TODO; various other changes. + +Wed Feb 26 18:04:40 1992 K. Richard Pixley (rich@cygnus.com) + + * Makefile.in, configure.in: removed traces of namesubdir, + -subdirs, $(subdir), $(unsubdir), some rcs triggers. Forced + copyrights to '92, changed some from Cygnus to FSF. + +Tue Dec 10 04:07:10 1991 K. Richard Pixley (rich at rtl.cygnus.com) + + * Makefile.in: infodir belongs in datadir. + +Thu Dec 5 22:46:01 1991 K. Richard Pixley (rich at rtl.cygnus.com) + + * Makefile.in: idestdir and ddestdir go away. Added copyrights + and shift gpl to v2. Added ChangeLog if it didn't exist. docdir + and mandir now keyed off datadir by default. + +Wed Nov 27 02:45:18 1991 K. Richard Pixley (rich at sendai) + + * brought Makefile.in's up to standards.text. + + * fresh changelog. + diff --git a/contrib/cvs/doc/Makefile.in b/contrib/cvs/doc/Makefile.in new file mode 100644 index 0000000..11c7051 --- /dev/null +++ b/contrib/cvs/doc/Makefile.in @@ -0,0 +1,203 @@ +# Makefile for GNU CVS documentation. +# Do not use this makefile directly, but only from `../Makefile'. +# Copyright (C) 1986, 1988-1990 Free Software Foundation, Inc. + +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2, or (at your option) +# any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +# $CVSid: @(#)Makefile.in 1.8 94/10/22 $ + +SHELL = /bin/sh + +srcdir = @srcdir@ +top_srcdir = @top_srcdir@ +VPATH = @srcdir@ + +prefix = @prefix@ +exec_prefix = @exec_prefix@ + +infodir = $(prefix)/info + +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ + +DISTFILES = \ + .cvsignore ChangeLog ChangeLog.fsf Makefile.in \ + cvs-paper.ms cvs-paper.ps \ + cvs.texinfo \ + cvsclient.texi + +OBJDIR_DISTFILES = cvs.ps cvs.info cvs.aux \ + cvsclient.ps cvsclient.info cvsclient.aux CVSvn.texi + +# these are part of the texinfo distribution +MAKEINFO=makeinfo +TEXI2DVI = texi2dvi + +# where to find texinfo; +TEXIDIR=${gdbdir}/../texinfo + +SET_TEXINPUTS = TEXINPUTS=.:$(srcdir):$$TEXINPUTS + +# Don Knuth's TeX formatter +TEX = tex + +# auxiliary program for sorting Texinfo indices +TEXINDEX = texindex + +DVIPS = dvips +DVIPSFLAGS = + +ROFF = groff + +# CYGNUS LOCAL: all does not depend upon info +all: +.PHONY: all + +# CYGNUS LOCAL: install does not depend on install-info +install: all +.PHONY: install + +doc: cvs.ps cvs-paper.ps cvsclient.ps + +info: cvs.info cvsclient.info + +cvs.info: cvs.texinfo CVSvn.texi + if [ ! -f ./CVSvn.texi ]; then \ + ln -s $(srcdir)/CVSvn.texi . || \ + ln $(srcdir)/CVSvn.texi . || \ + cp $(srcdir)/CVSvn.texi . ; else true; fi + $(MAKEINFO) $(srcdir)/cvs.texinfo -o cvs.info + +cvsclient.info: cvsclient.texi CVSvn.texi + if [ ! -f ./CVSvn.texi ]; then \ + ln -s $(srcdir)/CVSvn.texi . || \ + ln $(srcdir)/CVSvn.texi . || \ + cp $(srcdir)/CVSvn.texi . ; else true; fi + $(MAKEINFO) $(srcdir)/cvsclient.texi -o cvsclient.info + +# Version of the protocol suitable for emailing +cvsclient.txt: cvsclient.texi CVSvn.texi + if [ ! -f ./CVSvn.texi ]; then \ + ln -s $(srcdir)/CVSvn.texi . || \ + ln $(srcdir)/CVSvn.texi . || \ + cp $(srcdir)/CVSvn.texi . ; else true; fi + $(MAKEINFO) $(srcdir)/cvsclient.texi --no-headers -o cvsclient.txt + +# If the user gets a distribution (which contains *.info), unpacks +# it, and builds it in a seperate build dir, then *.info* are in srcdir. +# If the user builds *.info (e.g. after editing *.texi), then *.info* are +# in the build dir. +install-info: info + test -f cvs.info || cd $(srcdir); \ + for i in *.info* ; do \ + $(INSTALL_DATA) $$i $(infodir)/$$i ; \ + done + +installdirs: + $(SHELL) $(top_srcdir)/mkinstalldirs $(infodir) +.PHONY: installdirs + +dvi: cvs.dvi cvsclient.dvi + +CVSvn.texi: $(top_srcdir)/src/version.c + echo "@set CVSVN `sed < $(top_srcdir)/src/version.c \ + -e '/version_string/!d' \ + -e 's/[^0-9.]*\([0-9.]*\).*/\1/' \ + -e q`" >CVSvn.new + mv CVSvn.new CVSvn.texi + +cvs.dvi cvs.aux: cvs.texinfo CVSvn.texi + if [ ! -f ./CVSvn.texi ]; then \ + ln -s $(srcdir)/CVSvn.texi . || \ + ln $(srcdir)/CVSvn.texi . || \ + cp $(srcdir)/CVSvn.texi . ; else true; fi + $(TEXI2DVI) $(srcdir)/cvs.texinfo + +cvsclient.dvi cvsclient.aux: cvsclient.texi CVSvn.texi + if [ ! -f ./CVSvn.texi ]; then \ + ln -s $(srcdir)/CVSvn.texi . || \ + ln $(srcdir)/CVSvn.texi . || \ + cp $(srcdir)/CVSvn.texi . ; else true; fi + $(SET_TEXINPUTS) $(TEX) cvsclient.texi + $(SET_TEXINPUTS) $(TEX) cvsclient.texi + $(TEXINDEX) cvsclient.?? + $(SET_TEXINPUTS) $(TEX) cvsclient.texi + rm -f cvsclient.?? cvsclient.log cvsclient.toc cvsclient.??s + +cvs.ps: cvs.dvi + $(DVIPS) $(DVIPSFLAGS) cvs.dvi -o cvs.ps + +cvs-paper.ps: cvs-paper.ms + $(ROFF) -t -p -ms -Tps $(srcdir)/cvs-paper.ms > $@-t + mv $@-t $@ + +cvsclient.ps: cvsclient.dvi + $(DVIPS) $(DVIPSFLAGS) cvsclient.dvi -o cvsclient.ps + +tags: +.PHONY: tags + +TAGS: +.PHONY: TAGS + +ls: + @echo $(DISTFILES) +.PHONY: ls + +clean: + rm -f *.o core + rm -f cvs.cp cvs.fn cvs.ky cvs.pg cvs.tp cvs.vr + rm -f cvs.cps cvs.fns cvs.kys cvs.pgs cvs.tps cvs.vrs + rm -f cvs.aux cvs.dvi cvs.log cvs.toc + rm -f cvsclient.cp cvsclient.fn cvsclient.ky cvsclient.pg + rm -f cvsclient.tp cvsclient.vr cvsclient.cps cvsclient.fns + rm -f cvsclient.kys cvsclient.pgs cvsclient.tps cvsclient.vrs + rm -f cvsclient.aux cvsclient.dvi cvsclient.log cvsclient.toc + +.PHONY: clean + +distclean: clean + rm -f Makefile +.PHONY: distclean + +realclean: distclean + rm -f cvs.info* cvs.ps cvs-paper.ps cvsclient.info* cvsclient.ps +.PHONY: realclean + +dist-dir: $(DISTFILES) $(OBJDIR_DISTFILES) + mkdir ${DISTDIR} + for i in ${DISTFILES}; do \ + ln $(srcdir)/$${i} ${DISTDIR}; \ + done + ln ${OBJDIR_DISTFILES} ${DISTDIR} + if [ -f cvs.info-1 ]; \ + then ln -f cvs.info-* ${DISTDIR}; \ + else : Pacify Ultrix sh; \ + fi + if [ -f cvsclient.info-1 ]; \ + then ln -f cvsclient.info-* ${DISTDIR}; \ + else : Pacify Ultrix sh; \ + fi +.PHONY: dist-dir + +subdir = doc +Makefile: ../config.status Makefile.in + cd .. && CONFIG_FILES=$(subdir)/$@ CONFIG_HEADERS= ./config.status + +#../config.status: ../configure +# cd .. ; $(SHELL) config.status --recheck + +#../configure: ../configure.in +# cd $(top_srcdir) ; autoconf diff --git a/contrib/cvs/doc/cvs-paper.ms b/contrib/cvs/doc/cvs-paper.ms new file mode 100644 index 0000000..567179b --- /dev/null +++ b/contrib/cvs/doc/cvs-paper.ms @@ -0,0 +1,1073 @@ +.\" soelim cvs.ms | pic | tbl | troff -ms +.\" @(#)cvs.ms 1.2 92/01/30 +.\" +.\" troff source to the cvs USENIX article, Winter 1990, Washington, D.C. +.\" Copyright (c) 1989, Brian Berliner +.\" +.\" 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 1, 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. +.\" +.\" You should have received a copy of the GNU General Public License +.\" along with this program; if not, write to the Free Software +.\" Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +.\" +.\" The author can be reached at: berliner@prisma.com +.\" +.de SP +.if n .sp +.if t .sp .5 +.. +.de hl +.br +.in +0.5i +\l'\\n(LLu-1i' +.in -0.5i +.sp +.. +.OH "" +.nr PS 11 +.nr PO 1.25i +.pl -0.2i +.TL +.ps 14 +.ft B +.nf +CVS II: +Parallelizing Software Development +.fi +.ft +.ps +.AU +.ps 12 +.ft I +Brian Berliner +.ft +.ps +.AI +.ps 12 +.ft I +Prisma, Inc. +5465 Mark Dabling Blvd. +Colorado Springs, CO 80918 +berliner@prisma.com +.ft +.ps +.AB +The program described in this paper fills a need in the UNIX +community for a freely available tool to manage software revision and +release control in a multi-developer, multi-directory, multi-group +environment. +This tool also addresses the increasing need for tracking third-party vendor +source distributions while trying to maintain local modifications to +earlier releases. +.AE +.NH +Background +.PP +In large software development projects, it is usually necessary for more +than one software developer to be modifying (usually different) modules of the +code at the same time. +Some of these code modifications are done in an +experimental sense, at least until the code functions correctly, and some +testing of the entire program is usually necessary. +Then, the modifications are returned to a master source repository +so that others in the project can +enjoy the new bug-fix or functionality. +In order to manage such a project, some sort of revision control system is +necessary. +.PP +Specifically, UNIX\** +.FS +UNIX is a registered trademark of AT&T. +.FE +kernel development is an excellent example of the +problems that an adequate revision control system must address. +The SunOS\** +.FS +SunOS is a trademark of Sun Microsystems, Inc. +.FE +kernel is composed of over a thousand files spread across a +hierarchy of dozens of directories.\** +.FS +Yes, the SunOS 4.0 kernel is composed of over a \fIthousand\fP files! +.FE +Pieces of the kernel must be edited +by many software developers within an organization. +While undesirable in +theory, it is not uncommon to have two or more people making +modifications to the same file within the kernel sources in +order to facilitate a desired change. +Existing revision control systems like +.SM +RCS +.LG +[Tichy] or +.SM +SCCS +.LG +[Bell] serialize file modifications by +allowing only one developer to have a writable copy of a particular file at +any one point in time. +That developer is said to +have \*Qlocked\*U the file for his exclusive use, and no other developer is +allowed to check out a writable copy of the file until the locking +developer has finished impeding others' productivity. +Development pressures of productivity and deadlines +often force organizations to require that multiple developers be able to +simultaneously edit +copies of the same revision controlled file. +.PP +The necessity for multiple developers to modify the same file concurrently +questions the value of serialization-based policies in traditional revision +control. +This paper discusses the approach that +Prisma took in adapting a standard revision control system, +.SM +RCS\c +.LG +, along with an existing public-domain collection of shell scripts that sits +atop +.SM +RCS +.LG +and provides the basic conflict-resolution algorithms. +The resulting +program, \fBcvs\fP, addresses not only the issue of conflict-resolution in +a multi-developer open-editing environment, but also the issues of +software release control and vendor source support and integration. +.NH +The CVS Program +.PP +\fBcvs\fP +(Concurrent Versions System) +is a front end to the +.SM +RCS +.LG +revision control system which extends +the notion of revision control from a collection of files in a single +directory to a hierarchical collection of directories each containing +revision controlled files. +Directories and files in the \fBcvs\fP system can be combined together in +many ways to form a software release. +\fBcvs\fP +provides the functions necessary to manage these software releases and to +control the concurrent editing of source files among multiple software +developers. +.PP +The six major features of \fBcvs\fP are listed below, and will be +described in more detail in the following sections: +.RS +.IP 1. +Concurrent access and conflict-resolution algorithms to guarantee that +source changes are not \*Qlost.\*U +.IP 2. +Support for tracking third-party vendor source distributions while +maintaining the local modifications made to those sources. +.IP 3. +A flexible module database that provides a symbolic mapping of names to +components of a larger software distribution. +This symbolic mapping provides for location independence within the software +release and, for example, allows one to check out a copy of the \*Qdiff\*U +program without ever knowing that the sources to \*Qdiff\*U actually reside +in the \*Qbin/diff\*U directory. +.IP 4. +Configurable logging support allows all \*Qcommitted\*U source file changes +to be logged using an arbitrary program to save the log messages in a file, +notesfile, or news database. +.IP 5. +A software release can be symbolically tagged and checked out at any time +based on that tag. +An exact copy of a previous software release can be checked out at +any time, \fIregardless\fP of whether files or directories have been +added/removed from the \*Qcurrent\*U software release. +As well, +a \*Qdate\*U can be used to check out the \fIexact\fP version of the software +release as of the specified date. +.IP 6. +A \*Qpatch\*U format file [Wall] can be produced between two software +releases, even if the releases span multiple directories. +.RE +.PP +The sources maintained by \fBcvs\fP are kept within a single directory +hierarchy known as the \*Qsource repository.\*U +This \*Qsource repository\*U holds the actual +.SM +RCS +.LG +\*Q,v\*U files directly, as well as a special per-repository directory +(\c +.SM +CVSROOT.adm\c +.LG +) which contains a small number of administrative files that describe the +repository and how it can be accessed. +See Figure 1 for a picture of the \fBcvs\fP tree. +.KF +.hl +.DS B +.PS +line from 4.112,9.200 to 5.550,8.887 +line from 5.447,8.884 to 5.550,8.887 to 5.458,8.933 +line from 4.112,9.200 to 4.550,8.950 +line from 4.451,8.978 to 4.550,8.950 to 4.476,9.021 +line from 4.112,9.200 to 3.737,8.887 +line from 3.798,8.971 to 3.737,8.887 to 3.830,8.932 +line from 3.612,8.762 to 4.737,8.137 +line from 4.638,8.164 to 4.737,8.137 to 4.662,8.208 +line from 3.612,8.762 to 3.737,8.137 +line from 3.693,8.231 to 3.737,8.137 to 3.742,8.240 +line from 3.612,8.762 to 2.612,8.200 +line from 2.687,8.271 to 2.612,8.200 to 2.712,8.227 +line from 2.362,9.262 to 2.737,8.950 +line from 2.645,8.995 to 2.737,8.950 to 2.677,9.033 +line from 2.362,9.262 to 1.925,8.950 +line from 1.992,9.028 to 1.925,8.950 to 2.021,8.988 +line from 3.362,9.762 to 4.050,9.387 +line from 3.950,9.413 to 4.050,9.387 to 3.974,9.457 +line from 3.362,9.762 to 2.487,9.387 +line from 2.570,9.450 to 2.487,9.387 to 2.589,9.404 +.ps 11 +"newfs.c,v" at 4.487,8.043 ljust +.ps 11 +"mkfs.c,v" at 3.487,8.043 ljust +.ps 11 +"Makefile,v" at 2.237,8.043 ljust +.ps 11 +"newfs" at 3.487,8.793 ljust +.ps 11 +"halt.c,v" at 5.487,8.793 ljust +.ps 11 +"Makefile,v" at 4.237,8.793 ljust +.ps 11 +"modules,v" at 2.487,8.793 ljust +.ps 11 +"loginfo,v" at 1.488,8.793 ljust +.ps 11 +"etc" at 3.987,9.293 ljust +.ps 11 +"CVSROOT.adm" at 1.988,9.293 ljust +.ps 11 +"/src/master" at 2.987,9.793 ljust +.PE +.DE +.hl +.ce 100 +.LG +\fBFigure 1.\fP +.SM +\fBcvs\fP Source Repository +.ce 0 +.sp +.KE +.NH 2 +Software Conflict Resolution\** +.FS +The basic conflict-resolution algorithms +used in the \fBcvs\fP program find their roots +in the original work done by Dick Grune at Vrije Universiteit in Amsterdam +and posted to \fBcomp.sources.unix\fP in the volume 6 release sometime in 1986. +This original version of \fBcvs\fP was a collection of shell scripts that +combined to form a front end to the +.SM +RCS +.LG +programs. +.FE +.PP +\fBcvs\fP allows several software developers to edit personal copies of a +revision controlled file concurrently. +The revision number of each checked out file is maintained independently +for each user, and \fBcvs\fP forces the checked out file to be current with +the \*Qhead\*U revision before it can be \*Qcommitted\*U as a permanent change. +A checked out file is brought up-to-date with the \*Qhead\*U revision using +the \*Qupdate\*U command of \fBcvs\fP. +This command compares the \*Qhead\*U revision number with that of the user's +file and performs an +.SM +RCS +.LG +merge operation if they are not the same. +The result of the merge is a file that contains the user's modifications +and those modifications that were \*Qcommitted\*U after the user +checked out his version of the file (as well as a backup copy of the +user's original file). +\fBcvs\fP points out any conflicts during the merge. +It is the user's responsibility to resolve these conflicts +and to \*Qcommit\*U his/her changes when ready. +.PP +Although the \fBcvs\fP conflict-resolution algorithm was defined in 1986, +it is remarkably similar to the \*QCopy-Modify-Merge\*U scenario included +with NSE\** +.FS +NSE is the Network Software Environment, a product of Sun Microsystems, Inc. +.FE +and described in [Honda] and [Courington]. +The following explanation from [Honda] also applies to \fBcvs\fP: +.QP +Simply stated, a developer copies an object without locking it, modifies +the copy, and then merges the modified copy with the original. +This paradigm allows developers to work in isolation from one another since +changes are made to copies of objects. +Because locks are not used, development is not serialized and can proceed +in parallel. +Developers, however, must merge objects after the changes have been made. +In particular, a developer must resolve conflicts when the same object has +been modified by someone else. +.PP +In practice, Prisma has found that conflicts that occur when the same +object has been modified by someone else are quite rare. +When they do happen, the changes made by the other developer are usually +easily resolved. +This practical use has shown that the \*QCopy-Modify-Merge\*U paradigm is a +correct and useful one. +.NH 2 +Tracking Third-Party Source Distributions +.PP +Currently, a large amount of software is based on source +distributions from a third-party distributor. +It is often the case that local modifications are to be made to this +distribution, \fIand\fP that the vendor's future releases should be +tracked. +Rolling your local modifications forward into the new vendor release is a +time-consuming task, but \fBcvs\fP can ease this burden somewhat. +The \fBcheckin\fP program of \fBcvs\fP initially sets up a source +repository by integrating the source modules directly from the vendor's +release, preserving the directory hierarchy of the vendor's distribution. +The branch support of +.SM +RCS +.LG +is used to build this vendor release as a branch of the main +.SM +RCS +.LG +trunk. +Figure 2 shows how the \*Qhead\*U tracks a sample vendor +branch when no local modifications have been made to the file. +.KF +.hl +.DS B +.PS +ellipse at 3.237,6.763 wid 1.000 ht 0.500 +dashwid = 0.050i +line dashed from 3.237,7.513 to 3.737,7.513 to 3.737,9.762 to 4.237,9.762 +line from 4.138,9.737 to 4.237,9.762 to 4.138,9.787 +line dashed from 2.237,8.262 to 3.237,8.262 to 3.237,7.013 +line from 3.212,7.112 to 3.237,7.013 to 3.262,7.112 +line from 3.737,6.763 to 4.237,6.763 +line from 4.138,6.737 to 4.237,6.763 to 4.138,6.788 +line from 2.237,6.763 to 2.737,6.763 +line from 2.637,6.737 to 2.737,6.763 to 2.637,6.788 +line from 1.738,6.013 to 1.738,6.513 +line from 1.762,6.413 to 1.738,6.513 to 1.713,6.413 +line from 1.238,7.013 to 2.237,7.013 to 2.237,6.513 to 1.238,6.513 to 1.238,7.013 +line from 4.237,9.012 to 5.237,9.012 to 5.237,8.512 to 4.237,8.512 to 4.237,9.012 +line from 4.237,8.012 to 5.237,8.012 to 5.237,7.513 to 4.237,7.513 to 4.237,8.012 +line from 4.237,7.013 to 5.237,7.013 to 5.237,6.513 to 4.237,6.513 to 4.237,7.013 +line from 4.737,7.013 to 4.737,7.513 +line from 4.763,7.413 to 4.737,7.513 to 4.712,7.413 +line from 4.737,8.012 to 4.737,8.512 +line from 4.763,8.412 to 4.737,8.512 to 4.712,8.412 +line from 4.237,10.012 to 5.237,10.012 to 5.237,9.512 to 4.237,9.512 to 4.237,10.012 +line from 4.737,9.012 to 4.737,9.512 +line from 4.763,9.412 to 4.737,9.512 to 4.712,9.412 +line from 5.987,5.013 to 5.987,6.013 to 0.988,6.013 to 0.988,5.013 to 5.987,5.013 +.ps 11 +"\"HEAD\"" at 1.550,8.231 ljust +.ps 11 +"'SunOS'" at 2.987,6.293 ljust +.ps 11 +"1.1.1" at 3.050,6.793 ljust +.ps 11 +"1.1" at 1.613,6.793 ljust +.ps 11 +"1.1.1.1" at 4.487,6.793 ljust +.ps 11 +"1.1.1.2" at 4.487,7.793 ljust +.ps 11 +"1.1.1.3" at 4.487,8.793 ljust +.ps 11 +"1.1.1.4" at 4.487,9.793 ljust +.ps 11 +"'SunOS_4_0'" at 5.487,6.793 ljust +.ps 11 +"'SunOS_4_0_1'" at 5.487,7.793 ljust +.ps 11 +"'YAPT_5_5C'" at 5.487,8.793 ljust +.ps 11 +"'SunOS_4_0_3'" at 5.487,9.793 ljust +.ps 11 +"rcsfile.c,v" at 2.987,5.543 ljust +.PE +.DE +.hl +.ce 100 +.LG +\fBFigure 2.\fP +.SM +\fBcvs\fP Vendor Branch Example +.ce 0 +.sp .3 +.KE +Once this is done, developers can check out files and make local changes to +the vendor's source distribution. +These local changes form a new branch to the tree which is then used as the +source for future check outs. +Figure 3 shows how the \*Qhead\*U moves to the main +.SM +RCS +.LG +trunk when a local modification is made. +.KF +.hl +.DS B +.PS +ellipse at 3.237,6.763 wid 1.000 ht 0.500 +dashwid = 0.050i +line dashed from 2.800,9.075 to 1.738,9.075 to 1.738,8.012 +line from 1.713,8.112 to 1.738,8.012 to 1.762,8.112 +line from 1.738,7.013 to 1.738,7.513 +line from 1.762,7.413 to 1.738,7.513 to 1.713,7.413 +line from 1.238,8.012 to 2.237,8.012 to 2.237,7.513 to 1.238,7.513 to 1.238,8.012 +line from 3.737,6.763 to 4.237,6.763 +line from 4.138,6.737 to 4.237,6.763 to 4.138,6.788 +line from 2.237,6.763 to 2.737,6.763 +line from 2.637,6.737 to 2.737,6.763 to 2.637,6.788 +line from 1.738,6.013 to 1.738,6.513 +line from 1.762,6.413 to 1.738,6.513 to 1.713,6.413 +line from 1.238,7.013 to 2.237,7.013 to 2.237,6.513 to 1.238,6.513 to 1.238,7.013 +line from 4.237,9.012 to 5.237,9.012 to 5.237,8.512 to 4.237,8.512 to 4.237,9.012 +line from 4.237,8.012 to 5.237,8.012 to 5.237,7.513 to 4.237,7.513 to 4.237,8.012 +line from 4.237,7.013 to 5.237,7.013 to 5.237,6.513 to 4.237,6.513 to 4.237,7.013 +line from 4.737,7.013 to 4.737,7.513 +line from 4.763,7.413 to 4.737,7.513 to 4.712,7.413 +line from 4.737,8.012 to 4.737,8.512 +line from 4.763,8.412 to 4.737,8.512 to 4.712,8.412 +line from 4.237,10.012 to 5.237,10.012 to 5.237,9.512 to 4.237,9.512 to 4.237,10.012 +line from 4.737,9.012 to 4.737,9.512 +line from 4.763,9.412 to 4.737,9.512 to 4.712,9.412 +line from 5.987,5.013 to 5.987,6.013 to 0.988,6.013 to 0.988,5.013 to 5.987,5.013 +.ps 11 +"1.2" at 1.613,7.793 ljust +.ps 11 +"\"HEAD\"" at 2.862,9.043 ljust +.ps 11 +"'SunOS'" at 2.987,6.293 ljust +.ps 11 +"1.1.1" at 3.050,6.793 ljust +.ps 11 +"1.1" at 1.613,6.793 ljust +.ps 11 +"1.1.1.1" at 4.487,6.793 ljust +.ps 11 +"1.1.1.2" at 4.487,7.793 ljust +.ps 11 +"1.1.1.3" at 4.487,8.793 ljust +.ps 11 +"1.1.1.4" at 4.487,9.793 ljust +.ps 11 +"'SunOS_4_0'" at 5.487,6.793 ljust +.ps 11 +"'SunOS_4_0_1'" at 5.487,7.793 ljust +.ps 11 +"'YAPT_5_5C'" at 5.487,8.793 ljust +.ps 11 +"'SunOS_4_0_3'" at 5.487,9.793 ljust +.ps 11 +"rcsfile.c,v" at 2.987,5.543 ljust +.PE +.DE +.hl +.ce 100 +.LG +\fBFigure 3.\fP +.SM +\fBcvs\fP Local Modification to Vendor Branch +.ce 0 +.sp +.KE +.PP +When a new version of the vendor's source distribution arrives, the +\fBcheckin\fP program adds the new and changed vendor's files to the +already existing source repository. +For files that have not been changed locally, the new file from the +vendor becomes the current \*Qhead\*U revision. +For files that have been modified locally, \fBcheckin\fP warns that the +file must be merged with the new vendor release. +The \fBcvs\fP \*Qjoin\*U command is a useful tool that aids this process by +performing the necessary +.SM +RCS +.LG +merge, as is done above when performing an \*Qupdate.\*U +.PP +There is also limited support for \*Qdual\*U derivations for source files. +See Figure 4 for a sample dual-derived file. +.KF +.hl +.DS B +.PS +ellipse at 2.337,8.575 wid 0.700 ht 0.375 +ellipse at 2.312,9.137 wid 0.700 ht 0.375 +line from 1.225,9.012 to 1.225,9.363 +line from 1.250,9.263 to 1.225,9.363 to 1.200,9.263 +line from 0.875,9.725 to 1.600,9.725 to 1.600,9.363 to 0.875,9.363 to 0.875,9.725 +line from 0.875,9.012 to 1.600,9.012 to 1.600,8.650 to 0.875,8.650 to 0.875,9.012 +line from 4.050,10.200 to 4.775,10.200 to 4.775,9.850 to 4.050,9.850 to 4.050,10.200 +line from 4.050,9.475 to 4.775,9.475 to 4.775,9.113 to 4.050,9.113 to 4.050,9.475 +line from 4.050,8.762 to 4.775,8.762 to 4.775,8.400 to 4.050,8.400 to 4.050,8.762 +line from 4.425,8.762 to 4.425,9.113 +line from 4.450,9.013 to 4.425,9.113 to 4.400,9.013 +line from 4.425,9.475 to 4.425,9.850 +line from 4.450,9.750 to 4.425,9.850 to 4.400,9.750 +line from 3.050,10.000 to 3.775,10.000 to 3.775,9.637 to 3.050,9.637 to 3.050,10.000 +line from 3.050,9.312 to 3.775,9.312 to 3.775,8.950 to 3.050,8.950 to 3.050,9.312 +line from 0.713,7.325 to 0.713,8.075 to 4.925,8.075 to 4.925,7.325 to 0.713,7.325 +line from 1.238,8.075 to 1.238,8.637 +line from 1.262,8.537 to 1.238,8.637 to 1.213,8.537 +line from 1.613,8.825 to 1.975,8.575 +line from 1.878,8.611 to 1.975,8.575 to 1.907,8.652 +line from 2.675,8.575 to 4.050,8.575 +line from 3.950,8.550 to 4.050,8.575 to 3.950,8.600 +line from 2.675,9.137 to 3.050,9.137 +line from 2.950,9.112 to 3.050,9.137 to 2.950,9.162 +line from 3.425,9.325 to 3.425,9.637 +line from 3.450,9.537 to 3.425,9.637 to 3.400,9.537 +line from 1.613,8.825 to 1.925,9.137 +line from 1.872,9.049 to 1.925,9.137 to 1.837,9.084 +.ps 11 +"'BSD'" at 2.138,9.481 ljust +.ps 11 +"1.2" at 1.113,9.543 ljust +.ps 11 +"1.1" at 1.125,8.831 ljust +.ps 11 +"1.1.1.1" at 4.175,8.543 ljust +.ps 11 +"1.1.1.2" at 4.175,9.281 ljust +.ps 11 +"1.1.1.3" at 4.175,9.993 ljust +.ps 11 +"1.1.2.2" at 3.175,9.793 ljust +.ps 11 +"1.1.2.1" at 3.175,9.106 ljust +.ps 11 +"rcsfile.c,v" at 2.425,7.706 ljust +.ps 11 +"1.1.1" at 2.175,8.568 ljust +.ps 11 +"'SunOS'" at 2.125,8.243 ljust +.ps 11 +"1.1.2" at 2.163,9.131 ljust +.PE +.DE +.hl +.ce 100 +.LG +\fBFigure 4.\fP +.SM +\fBcvs\fP Support For \*QDual\*U Derivations +.ce 0 +.sp +.KE +This example tracks the SunOS distribution but includes major changes from +Berkeley. +These BSD files are saved directly in the +.SM +RCS +.LG +file off a new branch. +.NH 2 +Location Independent Module Database +.PP +\fBcvs\fP contains support for a simple, yet powerful, \*Qmodule\*U database. +For reasons of efficiency, this database is stored in \fBndbm\fP\|(3) format. +The module database is used to apply names to collections of directories +and files as a matter of convenience for checking out pieces of a large +software distribution. +The database records the physical location of the sources as a form of +information hiding, allowing one to check out whole directory hierarchies +or individual files without regard for their actual location within the +global source distribution. +.PP +Consider the following small sample of a module database, which must be +tailored manually to each specific source repository environment: +.DS +\f(CW #key [-option argument] directory [files...] + diff bin/diff + libc lib/libc + sys -o sys/tools/make_links sys + modules -i mkmodules CVSROOT.adm modules + kernel -a sys lang/adb + ps bin Makefile ps.c\fP +.DE +.PP +The \*Qdiff\*U and \*Qlibc\*U modules refer to whole directory hierarchies that +are extracted on check out. +The \*Qsys\*U module extracts the \*Qsys\*U hierarchy, and runs the +\*Qmake_links\*U program at the end of the check out process (the \fI-o\fP +option specifies a program to run on check\fIo\fPut). +The \*Qmodules\*U module allows one to edit the module database file and +runs the \*Qmkmodules\*U program on check\fIi\fPn to regenerate the +\fBndbm\fP database that \fBcvs\fP uses. +The \*Qkernel\*U module is an alias (as the \fI-a\fP option specifies) +which causes the remaining arguments after the \fI-a\fP to be interpreted +exactly as if they had been specified on the command line. +This is useful for objects that require shared pieces of code from far away +places to be compiled (as is the case with the kernel debugger, \fBkadb\fP, +which shares code with the standard \fBadb\fP debugger). +The \*Qps\*U module shows that the source for \*Qps\*U lives in the \*Qbin\*U +directory, but only \fIMakefile\fP and \fIps.c\fP are required to build the +object. +.PP +The module database at Prisma is now populated for the entire UNIX +distribution and thereby allows us to issue the +following convenient commands to check out components of the UNIX +distribution without regard for their actual location within the master source +repository: +.DS +\f(CW example% cvs checkout diff + example% cvs checkout libc ps + example% cd diff; make\fP +.DE +.PP +In building the module database file, it is quite possible to have name +conflicts within a global software distribution. +For example, SunOS provides two \fBcat\fP programs: +one for the standard environment, \fI/bin/cat\fP, and one for the System V +environment, \fI/usr/5bin/cat\fP. +We resolved this conflict by naming the standard \fBcat\fP module +\*Qcat\*U, and the System V \fBcat\fP module \*Q5cat\*U. +Similar name modifications must be applied to other conflicting names, as +might be found between a utility program and a library function, though +Prisma chose not to include individual library functions within the module +database at this time. +.NH 2 +Configurable Logging Support +.PP +The \fBcvs\fP \*Qcommit\*U command is used to make a permanent change to the +master source repository (where the +.SM +RCS +.LG +\*Q,v\*U files live). +Whenever a \*Qcommit\*U is done, the log message for the change is carefully +logged by an arbitrary program (in a file, notesfile, news database, or +mail). +For example, a collection of these updates can be used to produce release +notices. +\fBcvs\fP can be configured to send log updates through one or more filter +programs, based on a regular expression match on the directory that is +being changed. +This allows multiple related or unrelated projects to exist within a single +\fBcvs\fP source repository tree, with each different project sending its +\*Qcommit\*U reports to a unique log device. +.PP +A sample logging configuration file might look as follows: +.DS +\f(CW #regex filter-program + DEFAULT /usr/local/bin/nfpipe -t %s utils.updates + ^diag /usr/local/bin/nfpipe -t %s diag.updates + ^local /usr/local/bin/nfpipe -t %s local.updates + ^perf /usr/local/bin/nfpipe -t %s perf.updates + ^sys /usr/local/bin/nfpipe -t %s kernel.updates\fP +.DE +.PP +This sample allows the diagnostics and performance groups to +share the same source repository with the kernel and utilities groups. +Changes that they make are sent directly to their own notesfile [Essick] +through the \*Qnfpipe\*U program. +A sufficiently simple title is substituted for the \*Q%s\*U argument before +the filter program is executed. +This logging configuration file is tailored manually to each specific +source repository environment. +.NH 2 +Tagged Releases and Dates +.PP +Any release can be given a symbolic tag name that is stored directly in the +.SM +RCS +.LG +files. +This tag can be used at any time to get an exact copy of any previous +release. +With equal ease, one can also extract an exact copy of the source files as +of any arbitrary date in the past as well. +Thus, all that's required to tag the current kernel, and to tag the kernel +as of the Fourth of July is: +.DS +\f(CW example% cvs tag TEST_KERNEL kernel + example% cvs tag -D 'July 4' PATRIOTIC_KERNEL kernel\fP +.DE +The following command would retrieve an exact copy of the test kernel at +some later date: +.DS +\f(CW example% cvs checkout -fp -rTEST_KERNEL kernel\fP +.DE +The \fI-f\fP option causes only files that match the specified tag to be +extracted, while the \fI-p\fP option automatically prunes empty directories. +Consequently, directories added to the kernel after the test kernel was +tagged are not included in the newly extracted copy of the test kernel. +.PP +The \fBcvs\fP date support has exactly the same interface as that provided +with +.SM +RCS\c +.LG +, however \fBcvs\fP must process the \*Q,v\*U files directly due to the +special handling required by the vendor branch support. +The standard +.SM +RCS +.LG +date handling only processes one branch (or the main trunk) when checking +out based on a date specification. +\fBcvs\fP must instead process the current \*Qhead\*U branch and, if a +match is not found, proceed to look for a match on the vendor branch. +This, combined with reasons of performance, is why \fBcvs\fP processes +revision (symbolic and numeric) and date specifications directly from the +\*Q,v\*U files. +.NH 2 +Building \*Qpatch\*U Source Distributions +.PP +\fBcvs\fP can produce a \*Qpatch\*U format [Wall] output file which can be +used to bring a previously released software distribution current with the +newest release. +This patch file supports an entire directory hierarchy within a single +patch, as well as being able to add whole new files to the previous +release. +One can combine symbolic revisions and dates together to display changes in +a very generic way: +.DS +\f(CW example% cvs patch -D 'December 1, 1988' \e + -D 'January 1, 1989' sys\fP +.DE +This example displays the kernel changes made in the month of December, +1988. +To release a patch file, for example, to take the \fBcvs\fP distribution +from version 1.0 to version 1.4 might be done as follows: +.DS +\f(CW example% cvs patch -rCVS_1_0 -rCVS_1_4 cvs\fP +.DE +.NH +CVS Experience +.NH 2 +Statistics +.PP +A quick summary of the scale that \fBcvs\fP is addressing today +can be found in Table 1. +.KF +.TS +box center tab(:); +c s +c s +c | c +l | n . +\fB\s+2Revision Control Statistics at Prisma +as of 11/11/89\fP\s-2 +_ +How Many...:Total += +Files:17243 +Directories:1005 +Lines of code:3927255 +Removed files:131 +Software developers:14 +Software groups:6 +Megabytes of source:128 +.TE +.ce 100 +.LG +\fBTable 1.\fP +.SM +\fBcvs\fP Statistics +.ce 0 +.sp .3 +.KE +Table 2 shows the history of files changed or added and the number +of source lines affected by the change at Prisma. +Only changes made to the kernel sources are included. +.KF +.TS +box center tab(:); +c s s s s +c s s s s +c || c | c || c | c +c || c | c || c | c +l || n | n || n | n. +\fB\s+2Prisma Kernel Source File Changes +By Month, 1988-1989\fP\s-2 +_ +Month:# Changed:# Lines:# Added:# Lines +\^:Files:Changed:Files:Added += +Dec:87:3619:68:9266 +Jan:39:4324:0:0 +Feb:73:1578:5:3550 +Mar:99:5301:18:11461 +Apr:112:7333:11:5759 +May:138:5371:17:13986 +Jun:65:2261:27:12875 +Jul:34:2000:1:58 +Aug:65:6378:8:4724 +Sep:266:23410:113:39965 +Oct:22:621:1:155 +Total:1000:62196:269:101799 +.TE +.ce 100 +.LG +\fBTable 2.\fP +.SM +\fBcvs\fP Usage History for the Kernel +.ce 0 +.sp +.KE +The large number of source file changes made in September are the result of +merging the SunOS 4.0.3 sources into the kernel. +This merge process is described in section 3.3. +.NH 2 +Performance +.PP +The performance of \fBcvs\fP is currently quite reasonable. +Little effort has been expended on tuning \fBcvs\fP, although performance +related decisions were made during the \fBcvs\fP design. +For example, \fBcvs\fP parses the +.SM +RCS +.LG +\*Q,v\*U files directly instead of running an +.SM +RCS +.LG +process. +This includes following branches as well as integrating with the vendor +source branches and the main trunk when checking out files based on a date. +.PP +Checking out the entire kernel source tree (1223 files/59 directories) +currently takes 16 wall clock minutes on a Sun-4/280. +However, bringing the tree up-to-date with the current kernel sources, once +it has been checked out, takes only 1.5 wall clock minutes. +Updating the \fIcomplete\fP 128 MByte source tree under \fBcvs\fP control +(17243 files/1005 directories) takes roughly 28 wall clock minutes and +utilizes one-third of the machine. +For now this is entirely acceptable; improvements on these numbers will +possibly be made in the future. +.NH 2 +The SunOS 4.0.3 Merge +.PP +The true test of the \fBcvs\fP vendor branch support came with the arrival +of the SunOS 4.0.3 source upgrade tape. +As described above, the \fBcheckin\fP program was used to install the new +sources and the resulting output file listed the files that had been +locally modified, needing to be merged manually. +For the kernel, there were 94 files in conflict. +The \fBcvs\fP \*Qjoin\*U command was used on each of the 94 conflicting +files, and the remaining conflicts were resolved. +.PP +The \*Qjoin\*U command performs an \fBrcsmerge\fP operation. +This in turn uses \fI/usr/lib/diff3\fP to produce a three-way diff file. +As it happens, the \fBdiff3\fP program has a hard-coded limit of 200 +source-file changes maximum. +This proved to be too small for a few of the kernel files that needed +merging by hand, due to the large number of local changes that Prisma had +made. +The \fBdiff3\fP problem was solved by increasing the hard-coded limit by an +order of magnitude. +.PP +The SunOS 4.0.3 kernel source upgrade distribution contained +346 files, 233 of which were modifications to previously released files, +and 113 of which were newly added files. +\fBcheckin\fP added the 113 new files to the source repository +without intervention. +Of the 233 modified files, 139 dropped in cleanly by \fBcheckin\fP, since +Prisma had not made any local changes to them, and 94 required manual +merging due to local modifications. +The 233 modified files consisted of 20,766 lines of differences. +It took one developer two days to manually merge the 94 files using the +\*Qjoin\*U command and resolving conflicts manually. +An additional day was required for kernel debugging. +The entire process of merging over 20,000 lines of differences was +completed in less than a week. +This one time-savings alone was justification enough for the \fBcvs\fP +development effort; we expect to gain even more when tracking future SunOS +releases. +.NH +Future Enhancements and Current Bugs +.PP +Since \fBcvs\fP was designed to be incomplete, for reasons of design +simplicity, there are naturally a good +number of enhancements that can be made to make it more useful. +As well, some nuisances exist in the current implementation. +.RS +.IP \(bu 3 +\fBcvs\fP does not currently \*Qremember\*U who has a checked out a copy of a +module. +As a result, it is impossible to know who might be working on the same +module that you are. +A simple-minded database that is updated nightly would likely suffice. +.IP \(bu 3 +Signal processing, keyboard interrupt handling in particular, is currently +somewhat weak. +This is due to the heavy use of the \fBsystem\fP\|(3) library +function to execute +.SM +RCS +.LG +programs like \fBco\fP and \fBci\fP. +It sometimes takes multiple interrupts to make \fBcvs\fP quit. +This can be fixed by using a home-grown \fBsystem\fP\|() replacement. +.IP \(bu 3 +Security of the source repository is currently not dealt with directly. +The usual UNIX approach of user-group-other security permissions through +the file system is utilized, but nothing else. +\fBcvs\fP could likely be a set-group-id executable that checks a +protected database to verify user access permissions for particular objects +before allowing any operations to affect those objects. +.IP \(bu 3 +With every checked-out directory, \fBcvs\fP maintains some administrative +files that record the current revision numbers of the checked-out files as +well as the location of the respective source repository. +\fBcvs\fP does not recover nicely at all if these administrative files are +removed. +.IP \(bu 3 +The source code for \fBcvs\fP has been tested extensively on Sun-3 and +Sun-4 systems, all running SunOS 4.0 or later versions of the operating +system. +Since the code has not yet been compiled under other platforms, the overall +portability of the code is still questionable. +.IP \(bu 3 +As witnessed in the previous section, the \fBcvs\fP method for tracking +third party vendor source distributions can work quite nicely. +However, if the vendor changes the directory structure or the file names +within the source distribution, \fBcvs\fP has no way of matching the old +release with the new one. +It is currently unclear as to how to solve this, though it is certain to +happen in practice. +.RE +.NH +Availability +.PP +The \fBcvs\fP program sources can be found in a recent posting to the +\fBcomp.sources.unix\fP newsgroup. +It is also currently available via anonymous ftp from \*Qprisma.com\*U. +Copying rights for \fBcvs\fP will be covered by the GNU General Public +License. +.NH +Summary +.PP +Prisma has used \fBcvs\fP since December, 1988. +It has evolved to meet our specific needs of revision and release control. +We will make our code freely available so that others can +benefit from our work, and can enhance \fBcvs\fP to meet broader needs yet. +.PP +Many of the other software release and revision control systems, like the +one described in [Glew], appear to use a collection of tools that are +geared toward specific environments \(em one set of tools for the kernel, +one set for \*Qgeneric\*U software, one set for utilities, and one set for +kernel and utilities. +Each of these tool sets apparently handle some specific aspect of the +problem uniquely. +\fBcvs\fP took a somewhat different approach. +File sharing through symbolic or hard links is not addressed; instead, the +disk space is simply burned since it is \*Qcheap.\*U +Support for producing objects for multiple architectures is not addressed; +instead, a parallel checked-out source tree must be used for each +architecture, again wasting disk space to simplify complexity and ease of +use \(em punting on this issue allowed \fIMakefile\fPs to remain +unchanged, unlike the approach taken in [Mahler], thereby maintaining closer +compatibility with the third-party vendor sources. +\fBcvs\fP is essentially a source-file server, making no assumptions or +special handling of the sources that it controls. +To \fBcvs\fP: +.QP +A source is a source, of course, of course, unless of course the source is +Mr. Ed.\** +.FS +\fBcvs\fP, of course, does not really discriminate against Mr. Ed.\** +.FE +.FS +Yet. +.FE +.LP +Sources are maintained, saved, and retrievable at any time based on +symbolic or numeric revision or date in the past. +It is entirely up to \fBcvs\fP wrapper programs to provide for release +environments and such. +.PP +The major advantage of \fBcvs\fP over the +many other similar systems that have already been designed is the +simplicity of \fBcvs\fP. +\fBcvs\fP contains only three programs that do all the work of release +and revision control, and two manually-maintained administrative +files for each source repository. +Of course, the deciding factor of any tool is whether people use it, and if +they even \fIlike\fP to use it. +At Prisma, \fBcvs\fP prevented members of the kernel +group from killing each other. +.NH +Acknowledgements +.PP +Many thanks to Dick Grune at Vrije Universiteit in Amsterdam for his work +on the original version of \fBcvs\fP and for making it available to the +world. +Thanks to Jeff Polk of Prisma for helping with the design of the module +database, vendor branch support, and for writing the \fBcheckin\fP shell +script. +Thanks also to the entire software group at Prisma for taking the +time to review the paper and correct my grammar. +.NH +References +.IP [Bell] 12 +Bell Telephone Laboratories. +\*QSource Code Control System User's Guide.\*U +\fIUNIX System III Programmer's Manual\fP, October 1981. +.IP [Courington] 12 +Courington, W. +\fIThe Network Software Environment\fP, +Sun Technical Report FE197-0, Sun Microsystems Inc, February 1989. +.IP [Essick] 12 +Essick, Raymond B. and Robert Bruce Kolstad. +\fINotesfile Reference Manual\fP, +Department of Computer Science Technical Report #1081, +University of Illinois at Urbana-Champaign, Urbana, Illinois, +1982, p. 26. +.IP [Glew] 12 +Glew, Andy. +\*QBoxes, Links, and Parallel Trees: +Elements of a Configuration Management System.\*U +\fIWorkshop Proceedings of the Software Management Conference\fP, USENIX, +New Orleans, April 1989. +.IP [Grune] 12 +Grune, Dick. +Distributed the original shell script version of \fBcvs\fP in the +\fBcomp.sources.unix\fP volume 6 release in 1986. +.IP [Honda] 12 +Honda, Masahiro and Terrence Miller. +\*QSoftware Management Using a CASE Environment.\*U +\fIWorkshop Proceedings of the Software Management Conference\fP, USENIX, +New Orleans, April 1989. +.IP [Mahler] 12 +Mahler, Alex and Andreas Lampen. +\*QAn Integrated Toolset for Engineering Software Configurations.\*U +\fIProceedings of the ACM SIGSOFT/SIGPLAN Software Engineering Symposium on +Practical Software Development Environments\fP, ACM, Boston, November 1988. +Described is the \fBshape\fP toolkit posted to the +\fBcomp.sources.unix\fP newsgroup in the volume 19 release. +.IP [Tichy] 12 +Tichy, Walter F. +\*QDesign, Implementation, and Evaluation of a Revision Control System.\*U +\fIProceedings of the 6th International Conference on Software +Engineering\fP, IEEE, Tokyo, September 1982. +.IP [Wall] 12 +Wall, Larry. +The \fBpatch\fP program is an indispensable tool for applying a diff file +to an original. +Can be found on uunet.uu.net in ~ftp/pub/patch.tar. diff --git a/contrib/cvs/doc/cvs.texinfo b/contrib/cvs/doc/cvs.texinfo new file mode 100644 index 0000000..29bc5f3 --- /dev/null +++ b/contrib/cvs/doc/cvs.texinfo @@ -0,0 +1,7738 @@ +\input texinfo @c -*-texinfo-*- +@comment cvs.texinfo,v 1.6 1995/10/12 23:39:26 kfogel Exp +@comment Documentation for CVS. +@comment Copyright (C) 1992, 1993 Signum Support AB +@comment Copyright (C) 1993 Free Software Foundation, Inc. + +@comment This file is part of the CVS distribution. + +@comment CVS is free software; you can redistribute it and/or modify +@comment it under the terms of the GNU General Public License as published by +@comment the Free Software Foundation; either version 1, or (at your option) +@comment any later version. + +@comment CVS is distributed in the hope that it will be useful, +@comment but WITHOUT ANY WARRANTY; without even the implied warranty of +@comment MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +@comment GNU General Public License for more details. + +@comment You should have received a copy of the GNU General Public License +@comment along with CVS; see the file COPYING. If not, write to +@comment the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + +@afourpaper +@setfilename cvs.info +@include CVSvn.texi +@settitle CVS---Concurrent Versions System +@setchapternewpage odd + +@c -- TODO list: +@c -- Fix all lines that match "^@c -- " +@c -- Document how CVS finds the binaries it executes. +@c Things to include in the index: +@c Finding RCS binaries +@c Path to RCS binaries +@c RCS, how CVS finds them +@c s/RCS/diff/ +@c -- More on binary files + +@ifinfo +Copyright @copyright{} 1992, 1993 Signum Support AB +Copyright @copyright{} 1993, 1994 Free Software Foundation, Inc. + +Permission is granted to make and distribute verbatim copies of +this manual provided the copyright notice and this permission notice +are preserved on all copies. + +@ignore +Permission is granted to process this file through Tex and print the +results, provided the printed document carries copying permission +notice identical to this one except for the removal of this paragraph +(this paragraph not being relevant to the printed manual). + +@end ignore +Permission is granted to copy and distribute modified versions of this +manual under the conditions for verbatim copying, provided also that the +section entitled ``GNU General Public License'' is included exactly as +in the original, and provided that the entire resulting derived work is +distributed under the terms of a permission notice identical to this one. + +Permission is granted to copy and distribute translations of this manual +into another language, under the above conditions for modified versions, +except that the section entitled ``GNU General Public License'' and +this permission notice may be included in translations approved by the +Free Software Foundation instead of in the original English. +@end ifinfo + +@comment The titlepage section does not appear in the Info file. +@titlepage +@sp 4 +@comment The title is printed in a large font. +@center @titlefont{Version Management} +@sp +@center @titlefont{with} +@sp +@center @titlefont{CVS} +@sp 2 +@center for @sc{cvs} @value{CVSVN} +@comment -release- +@sp 3 +@center Per Cederqvist et al + +@comment The following two commands start the copyright page +@comment for the printed manual. This will not appear in the Info file. +@page +@vskip 0pt plus 1filll +Copyright @copyright{} 1992, 1993 Signum Support AB + +Permission is granted to make and distribute verbatim copies of +this manual provided the copyright notice and this permission notice +are preserved on all copies. + +Permission is granted to copy and distribute modified versions of this +manual under the conditions for verbatim copying, provided also that the +section entitled ``GNU General Public License'' is included exactly as +in the original, and provided that the entire resulting derived work is +distributed under the terms of a permission notice identical to this one. + +Permission is granted to copy and distribute translations of this manual +into another language, under the above conditions for modified versions, +except that the section entitled ``GNU General Public License'' and +this permission notice may be included in translations approved by the +Free Software Foundation instead of in the original English. +@end titlepage + +@comment ================================================================ +@comment The real text starts here +@comment ================================================================ + +@ifinfo +@c --------------------------------------------------------------------- +@node Top +@top +@c Note: there is a space after that @top command. +@c The texinfo-format-buffer Emacs function and +@c the makeinfo shell command disagree on what arguments +@c @top takes; @top followed by a single space is +@c something they can both cope with. + +This info manual describes how to use and administer +@sc{cvs} version @value{CVSVN}. +@end ifinfo + +@menu +* Preface:: About this manual +* What is CVS?:: What is CVS? +* Basic concepts:: Basic concepts of revision management +* A sample session:: A tour of basic CVS usage +* Repository:: Where all your sources are stored +* Starting a new project:: Starting a project with CVS +* Multiple developers:: How CVS helps a group of developers +* Branches:: Parallel development explained +* Merging:: How to move changes between branches +* Recursive behavior:: CVS descends directories +* Adding files:: Adding files to a module +* Removing files:: Removing files from a module +* Tracking sources:: Tracking third-party sources +* Moving files:: Moving and renaming files +* Moving directories:: Moving and renaming directories +* History browsing:: Viewing the history of files in various ways +* Keyword substitution:: CVS can include the revision inside the file +* Binary files:: CVS can handle binary files +* Revision management:: Policy questions for revision management +* Invoking CVS:: Reference manual for CVS commands +* Administrative files:: Reference manual for the Administrative files +* Environment variables:: All environment variables which affect CVS +* Troubleshooting:: Some tips when nothing works +* Copying:: GNU GENERAL PUBLIC LICENSE +* Index:: Index +@end menu + +@c --------------------------------------------------------------------- +@node Preface +@unnumbered About this manual +@cindex Preface +@cindex About this manual + +Up to this point, one of the weakest parts of @sc{cvs} +has been the documentation. @sc{cvs} is a complex +program. Previous versions of the manual were written +in the manual page format, which is not really well +suited for such a complex program. + +When writing this manual, I had several goals in mind: + +@itemize @bullet +@item +No knowledge of @sc{rcs} should be necessary. + +@item +No previous knowledge of revision control software +should be necessary. All terms, such as @dfn{revision +numbers}, @dfn{revision trees} and @dfn{merging} are +explained as they are introduced. + +@item +The manual should concentrate on the things @sc{cvs} users +want to do, instead of what the @sc{cvs} commands can do. +The first part of this manual leads you through things +you might want to do while doing development, and +introduces the relevant @sc{cvs} commands as they are +needed. + +@item +Information should be easy to find. In the reference +manual in the appendices almost all information about +every @sc{cvs} command is gathered together. There is also +an extensive index, and a lot of cross references. +@end itemize + +@cindex Signum Support +@cindex Support, getting CVS support +This manual was contributed by Signum Support AB in +Sweden. Signum is yet another in the growing list of +companies that support free software. You are free to +copy both this manual and the @sc{cvs} program. +@xref{Copying}, for the details. Signum Support offers +@c -- Check this reference! It has been bogus in the past. +support contracts and binary distribution for many +programs, such as @sc{cvs}, @sc{gnu} Emacs, the +@sc{gnu} C compiler and others. Write to us for +more information. + +@example +Signum Support AB +Box 2044 +S-580 02 Linkoping +Sweden + +Email: info@@signum.se +Phone: +46 (0)13 - 21 46 00 +Fax: +46 (0)13 - 21 47 00 +@end example + +Another company selling support for @sc{cvs} is Cyclic +Software, web: @code{http://www.cyclic.com/}, email: +@code{info@@cyclic.com}. + +@c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +@menu +* Checklist:: +* Credits:: +* BUGS:: +@end menu + +@node Checklist +@unnumberedsec Checklist for the impatient reader + +@sc{cvs} is a complex system. You will need to read +the manual to be able to use all of its capabilities. +There are dangers that can easily be avoided if you +know about them, and this manual tries to warn you +about them. This checklist is intended to help you +avoid the dangers without reading the entire manual. +If you intend to read the entire manual you can skip +this table. + +@table @asis +@item Binary files +@sc{cvs} can handle binary files, but +you must have @sc{rcs} release 5.5 or later and +a release of @sc{gnu} diff that supports the @samp{-a} +flag (release 1.15 and later are OK). You must also +configure both @sc{rcs} and @sc{cvs} to handle binary +files when you install them. + +Keword substitution can be a source of trouble with +binary files. @xref{Keyword substitution}, for +solutions. + +@item The @code{admin} command +Uncareful use of the @code{admin} command can cause +@sc{cvs} to cease working. @xref{admin}, before trying +to use it. +@end table + +@c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +@node Credits +@unnumberedsec Credits + +@cindex Contributors (manual) +@cindex Credits (manual) +Roland Pesch, Cygnus Support <@t{pesch@@cygnus.com}> +wrote the manual pages which were distributed with +@sc{cvs} 1.3. Appendix A and B contain much text that +was extracted from them. He also read an early draft +of this manual and contributed many ideas and +corrections. + +The mailing-list @code{info-cvs} is sometimes +informative. I have included information from postings +made by the following persons: +David G. Grubbs <@t{dgg@@think.com}>. + +Some text has been extracted from the man pages for +@sc{rcs}. + +The @sc{cvs} @sc{faq} (@pxref{What is CVS?}) by David +G. Grubbs has been used as a check-list to make sure +that this manual is as complete as possible. (This +manual does however not include all of the material in +the @sc{faq}). The @sc{faq} contains a lot of useful +information. + +In addition, the following persons have helped by +telling me about mistakes I've made: +Roxanne Brunskill <@t{rbrunski@@datap.ca}>, +Kathy Dyer <@t{dyer@@phoenix.ocf.llnl.gov}>, +Karl Pingle <@t{pingle@@acuson.com}>, +Thomas A Peterson <@t{tap@@src.honeywell.com}>, +Inge Wallin <@t{ingwa@@signum.se}>, +Dirk Koschuetzki <@t{koschuet@@fmi.uni-passau.de}> +and Michael Brown <@t{brown@@wi.extrel.com}>. + +@c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +@node BUGS +@unnumberedsec BUGS + +@cindex Bugs, known in this manual +@cindex Known bugs in this manual +This manual is known to have room for improvement. +Here is a list of known deficiencies: + +@itemize @bullet +@item +In the examples, the output from @sc{cvs} is sometimes +displayed, sometimes not. + +@item +The input that you are supposed to type in the examples +should have a different font than the output from the +computer. + +@item +This manual should be clearer about what file +permissions you should set up in the repository, and +about setuid/setgid. + +@item +Some of the chapters are not yet complete. They are +noted by comments in the @file{cvs.texinfo} file. + +@item +@cindex Reporting bugs (manual) +@cindex Bugs, reporting (manual) +@cindex Errors, reporting (manual) +This list is not complete. If you notice any error, +omission, or something that is unclear, please send +mail to @t{bug-cvs@@prep.ai.mit.edu}. +@end itemize + +I hope that you will find this manual useful, despite +the above-mentioned shortcomings. + +@flushright + +Linkoping, October 1993 +Per Cederqvist +@end flushright + +@c --------------------------------------------------------------------- +@node What is CVS? +@chapter What is CVS? +@cindex What is CVS? +@cindex Introduction to CVS +@cindex CVS, introduction to + +@sc{cvs} is a version control system. Using it, you can +record the history of your source files. + +@c -- /// +@c -- ///Those who cannot remember the past are condemned to repeat it. +@c -- /// -- George Santayana +@c -- ////// + +@c -- Insert history quote here! +For example, bugs sometimes creep in when +software is modified, and you might not detect the bug +until a long time after you make the modification. +With @sc{cvs}, you can easily retrieve old versions to see +exactly which change caused the bug. This can +sometimes be a big help. + +You could of course save every version of every file +you have ever created. This would +however waste an enormous amount of disk space. @sc{cvs} +stores all the versions of a file in a single file in a +clever way that only stores the differences between +versions. + +@sc{cvs} also helps you if you are part of a group of people working +on the same project. It is all too easy to overwrite +each others' changes unless you are extremely careful. +Some editors, like @sc{gnu} Emacs, try to make sure that +the same file is never modified by two people at the +same time. Unfortunately, if someone is using another +editor, that safeguard will not work. @sc{cvs} solves this problem +by insulating the different developers from each other. Every +developer works in his own directory, and @sc{cvs} merges +the work when each developer is done. + +@cindex History of CVS +@cindex CVS, history of +@cindex Credits (CVS program) +@cindex Contributors (CVS program) +@sc{cvs} started out as a bunch of shell scripts written by +Dick Grune, posted to @code{comp.sources.unix} in the volume 6 +release of December, 1986. While no actual code from +these shell scripts is present in the current version +of @sc{cvs} much of the @sc{cvs} conflict resolution algorithms +come from them. + +In April, 1989, Brian Berliner designed and coded @sc{cvs}. +Jeff Polk later helped Brian with the design of the @sc{cvs} +module and vendor branch support. + +@cindex Source, getting CVS source +You can get @sc{cvs} via anonymous ftp from a number of +sites, for instance @t{prep.ai.mit.edu} in +@file{pub/gnu}. + +@cindex Mailing list +@cindex List, mailing list +There is a mailing list for @sc{cvs} where bug reports +can be sent, questions can be asked, an FAQ is posted, +and discussion about future enhancements to @sc{cvs} +take place. To submit a message to the list, write to +<@t{info-cvs@@prep.ai.mit.edu}>. To subscribe or +unsubscribe, write to +<@t{info-cvs-request@@prep.ai.mit.edu}>. Please be +specific about your email address. + + +@c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +@unnumberedsec CVS is not@dots{} + +@sc{cvs} can do a lot of things for you, but it does +not try to be everything for everyone. + +@table @asis +@item @sc{cvs} is not a build system. + +Though the structure of your repository and modules +file interact with your build system +(e.g. @file{Makefile}s), they are essentially +independent. + +@sc{cvs} does not dictate how you build anything. It +merely stores files for retrieval in a tree structure +you devise. + +@sc{cvs} does not dictate how to use disk space in the +checked out working directories. If you write your +@file{Makefile}s or scripts in every directory so they +have to know the relative positions of everything else, +you wind up requiring the entire repository to be +checked out. That's simply bad planning. + +If you modularize your work, and construct a build +system that will share files (via links, mounts, +@code{VPATH} in @file{Makefile}s, etc.), you can +arrange your disk usage however you like. + +But you have to remember that @emph{any} such system is +a lot of work to construct and maintain. @sc{cvs} does +not address the issues involved. You must use your +brain and a collection of other tools to provide a +build scheme to match your plans. + +Of course, you should place the tools created to +support such a build system (scripts, @file{Makefile}s, +etc) under @sc{cvs}. + +@item @sc{cvs} is not a substitute for management. + +Your managers and project leaders are expected to talk +to you frequently enough to make certain you are aware +of schedules, merge points, branch names and release +dates. If they don't, @sc{cvs} can't help. + +@sc{cvs} is an instrument for making sources dance to +your tune. But you are the piper and the composer. No +instrument plays itself or writes its own music. + +@item @sc{cvs} is not a substitute for developer communication. + +When faced with conflicts within a single file, most +developers manage to resolve them without too much +effort. But a more general definition of ``conflict'' +includes problems too difficult to solve without +communication between developers. + +@sc{cvs} cannot determine when simultaneous changes +within a single file, or across a whole collection of +files, will logically conflict with one another. Its +concept of a @dfn{conflict} is purely textual, arising +when two changes to the same base file are near enough +to spook the merge (i.e. @code{diff3}) command. + +@sc{cvs} does not claim to help at all in figuring out +non-textual or distributed conflicts in program logic. + +For example: Say you change the arguments to function +@code{X} defined in file @file{A}. At the same time, +someone edits file @file{B}, adding new calls to +function @code{X} using the old arguments. You are +outside the realm of @sc{cvs}'s competence. + +Acquire the habit of reading specs and talking to your +peers. + + +@item @sc{cvs} is not a configuration management system. + +@sc{cvs} is a source control system. The phrase +``configuration management'' is a marketing term, not +an industry-recognized set of functions. + +A true ``configuration management system'' would contain +elements of the following: + +@itemize @bullet +@item Source control. +@item Dependency tracking. +@item Build systems (i.e. What to build and how to find +things during a build. What is shared? What is local?) +@item Bug tracking. +@item Automated Testing procedures. +@item Release Engineering documentation and procedures. +@item Tape Construction. +@item Customer Installation. +@item A way for users to run different versions of the same +software on the same host at the same time. +@end itemize + +@sc{cvs} provides only the first. +@end table + +This section is taken from release 2.3 of the @sc{cvs} +@sc{faq}. + +@c --------------------------------------------------------------------- +@node Basic concepts +@chapter Basic concepts +@cindex Modules (intro) +@cindex Repository (intro) + +@sc{cvs} stores all files in a centralized +@dfn{repository}: a directory (such as +@file{/usr/local/cvsroot} or +@file{user@@remotehost:/usr/local/cvsroot}) which is +populated with a hierarchy of files and directories. +(@pxref{Remote repositories} for information about +keeping the repository on a remote machine.) + +Normally, you never access any of the files in the +repository directly. Instead, you use @sc{cvs} +commands to get your own copy of the files, and then +work on that copy. When you've finished a set of +changes, you check (or @dfn{commit}) them back into the +repository. + +The files in the repository are organized in +@dfn{modules}. Each module is made up of one or more +files, and can include files from several directories. +A typical usage is to define one module per project. + +@menu +* Revision numbers:: The meaning of a revision number +* Versions revisions releases:: Terminology used in this manual +@end menu + +@c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +@node Revision numbers +@section Revision numbers +@cindex Revision numbers +@cindex Revision tree +@cindex Linear development +@cindex Number, revision- +@cindex Decimal revision number +@cindex Main trunk (intro) +@cindex Branch number +@cindex Number, branch + +Each version of a file has a unique @dfn{revision +number}. Revision numbers look like @samp{1.1}, +@samp{1.2}, @samp{1.3.2.2} or even @samp{1.3.2.2.4.5}. +A revision number always has an even number of +period-separated decimal integers. By default revision +1.1 is the first revision of a file. Each successive +revision is given a new number by increasing the +rightmost number by one. The following figure displays +a few revisions, with newer revisions to the right. + +@example + +-----+ +-----+ +-----+ +-----+ +-----+ + ! 1.1 !----! 1.2 !----! 1.3 !----! 1.4 !----! 1.5 ! + +-----+ +-----+ +-----+ +-----+ +-----+ +@end example + +@sc{cvs} is not limited to linear development. The +@dfn{revision tree} can be split into @dfn{branches}, +where each branch is a self-maintained line of +development. Changes made on one branch can easily be +moved back to the main trunk. + +Each branch has a @dfn{branch number}, consisting of an +odd number of period-separated decimal integers. The +branch number is created by appending an integer to the +revision number where the corresponding branch forked +off. Having branch numbers allows more than one branch +to be forked off from a certain revision. + +@need 3500 +All revisions on a branch have revision numbers formed +by appending an ordinal number to the branch number. +The following figure illustrates branching with an +example. + +@example +@group + +-------------+ + Branch 1.2.2.3.2 -> ! 1.2.2.3.2.1 ! + / +-------------+ + / + / + +---------+ +---------+ +---------+ +---------+ +Branch 1.2.2 -> _! 1.2.2.1 !----! 1.2.2.2 !----! 1.2.2.3 !----! 1.2.2.4 ! + / +---------+ +---------+ +---------+ +---------+ + / + / ++-----+ +-----+ +-----+ +-----+ +-----+ +! 1.1 !----! 1.2 !----! 1.3 !----! 1.4 !----! 1.5 ! <- The main trunk ++-----+ +-----+ +-----+ +-----+ +-----+ + ! + ! + ! +---------+ +---------+ +---------+ +Branch 1.2.4 -> +---! 1.2.4.1 !----! 1.2.4.2 !----! 1.2.4.3 ! + +---------+ +---------+ +---------+ + +@end group +@end example + +@c -- However, at least for me the figure is not enough. I suggest more +@c -- text to accompany it. "A picture is worth a thousand words", so you +@c -- have to make sure the reader notices the couple of hundred words +@c -- *you* had in mind more than the others! + +@c -- Why an even number of segments? This section implies that this is +@c -- how the main trunk is distinguished from branch roots, but you never +@c -- explicitly say that this is the purpose of the [by itself rather +@c -- surprising] restriction to an even number of segments. + +The exact details of how the branch number is +constructed is not something you normally need to be +concerned about, but here is how it works: When +@sc{cvs} creates a branch number it picks the first +unused even integer, starting with 2. So when you want +to create a branch from revision 6.4 it will be +numbered 6.4.2. All branch numbers ending in a zero +(such as 6.4.0) are used internally by @sc{cvs} +(@pxref{Magic branch numbers}). The branch 1.1.1 has a +special meaning. @xref{Tracking sources}. + +@c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +@node Versions revisions releases +@section Versions, revisions and releases +@cindex Revisions, versions and releases +@cindex Versions, revisions and releases +@cindex Releases, revisions and versions + +A file can have several versions, as described above. +Likewise, a software product can have several versions. +A software product is often given a version number such +as @samp{4.1.1}. + +Versions in the first sense are called @dfn{revisions} +in this document, and versions in the second sense are +called @dfn{releases}. To avoid confusion, the word +@dfn{version} is almost never used in this document. + +@c --------------------------------------------------------------------- +@node A sample session +@chapter A sample session +@cindex A sample session +@cindex Example of a work-session +@cindex Getting started +@cindex Work-session, example of +@cindex tc, Trivial Compiler (example) +@cindex Trivial Compiler (example) + +This section describes a typical work-session using +@sc{cvs}. It assumes that a repository is set up +(@pxref{Repository}). + +Suppose you are working on a simple compiler. The source +consists of a handful of C files and a @file{Makefile}. +The compiler is called @samp{tc} (Trivial Compiler), +and the repository is set up so that there is a module +called @samp{tc}. + +@menu +* Getting the source:: Creating a workspace +* Committing your changes:: Making your work available to others +* Cleaning up:: Cleaning up +* Viewing differences:: Viewing differences +@end menu + +@c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +@node Getting the source +@section Getting the source +@cindex Getting the source +@cindex Checking out source +@cindex Fetching source +@cindex Source, getting from CVS +@cindex Checkout, example + +The first thing you must do is to get your own working copy of the +source for @samp{tc}. For this, you use the @code{checkout} command: + +@example +$ cvs checkout tc +@end example + +@noindent +This will create a new directory called @file{tc} and populate it with +the source files. + +@example +$ cd tc +$ ls tc +CVS Makefile backend.c driver.c frontend.c parser.c +@end example + +The @file{CVS} directory is used internally by +@sc{cvs}. Normally, you should not modify or remove +any of the files in it. + +You start your favorite editor, hack away at @file{backend.c}, and a couple +of hours later you have added an optimization pass to the compiler. +A note to @sc{rcs} and @sc{sccs} users: There is no need to lock the files that +you want to edit. @xref{Multiple developers} for an explanation. + +@c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +@node Committing your changes +@section Committing your changes +@cindex Committing changes +@cindex Log message entry +@cindex CVSEDITOR, environment variable +@cindex EDITOR, environment variable + +When you have checked that the compiler is still compilable you decide +to make a new version of @file{backend.c}. + +@example +$ cvs commit backend.c +@end example + +@noindent +@sc{cvs} starts an editor, to allow you to enter a log +message. You type in ``Added an optimization pass.'', +save the temporary file, and exit the editor. + +The environment variable @code{$CVSEDITOR} determines +which editor is started. If @code{$CVSEDITOR} is not +set, then if the environment variable @code{$EDITOR} is +set, it will be used. If both @code{$CVSEDITOR} and +@code{$EDITOR} are not set then the editor defaults to +@code{vi}. If you want to avoid the overhead of +starting an editor you can specify the log message on +the command line using the @samp{-m} flag instead, like +this: + +@example +$ cvs commit -m "Added an optimization pass" backend.c +@end example + +@c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +@node Cleaning up +@section Cleaning up +@cindex Cleaning up +@cindex Working copy, removing +@cindex Removing your working copy +@cindex Releasing your working copy + +Before you turn to other tasks you decide to remove your working copy of +tc. One acceptable way to do that is of course + +@example +$ cd .. +$ rm -r tc +@end example + +@noindent +but a better way is to use the @code{release} command (@pxref{release}): + +@example +$ cd .. +$ cvs release -d tc +M driver.c +? tc +You have [1] altered files in this repository. +Are you sure you want to release (and delete) module `tc': n +** `release' aborted by user choice. +@end example + +The @code{release} command checks that all your modifications have been +committed. If history logging is enabled it also makes a note in the +history file. @xref{history file}. + +When you use the @samp{-d} flag with @code{release}, it +also removes your working copy. + +In the example above, the @code{release} command wrote a couple of lines +of output. @samp{? tc} means that the file @file{tc} is unknown to @sc{cvs}. +That is nothing to worry about: @file{tc} is the executable compiler, +and it should not be stored in the repository. @xref{cvsignore}, +for information about how to make that warning go away. +@xref{release output}, for a complete explanation of +all possible output from @code{release}. + +@samp{M driver.c} is more serious. It means that the +file @file{driver.c} has been modified since it was +checked out. + +The @code{release} command always finishes by telling +you how many modified files you have in your working +copy of the sources, and then asks you for confirmation +before deleting any files or making any note in the +history file. + +You decide to play it safe and answer @kbd{n @key{RET}} +when @code{release} asks for confirmation. + +@c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +@node Viewing differences +@section Viewing differences +@cindex Viewing differences +@cindex Diff + +You do not remember modifying @file{driver.c}, so you want to see what +has happened to that file. + +@example +$ cd tc +$ cvs diff driver.c +@end example + +This command runs @code{diff} to compare the version of @file{driver.c} +that you checked out with your working copy. When you see the output +you remember that you added a command line option that enabled the +optimization pass. You check it in, and release the module. + +@example +$ cvs commit -m "Added an optimization pass" driver.c +Checking in driver.c; +/usr/local/cvsroot/tc/driver.c,v <-- driver.c +new revision: 1.2; previous revision: 1.1 +done +$ cd .. +$ cvs release -d tc +? tc +You have [0] altered files in this repository. +Are you sure you want to release (and delete) module `tc': y +@end example + +@c --------------------------------------------------------------------- +@node Repository +@chapter The Repository +@cindex Repository, example +@cindex Layout of repository +@cindex Typical repository +@cindex CVSROOT, environment variable +@cindex .profile +@cindex .cshrc +@cindex .tcshrc +@cindex .bashrc +@cindex /usr/local/cvsroot +@cindex cvsroot + +Figure 3 below shows a typical setup of a repository. +Only directories are shown below. + +@example +@t{/usr} + | + +--@t{local} + | | + | +--@t{cvsroot} + | | | + | | +--@t{CVSROOT} + | (administrative files) + | + +--@t{gnu} + | | + | +--@t{diff} + | | (source code to @sc{gnu} diff) + | | + | +--@t{rcs} + | | (source code to @sc{rcs}) + | | + | +--@t{cvs} + | (source code to @sc{cvs}) + | + +--@t{yoyodyne} + | + +--@t{tc} + | | + | +--@t{man} + | | + | +--@t{testing} + | + +--(other Yoyodyne software) +@end example + + +There are a couple of different ways to tell @sc{cvs} +where to find the repository. You can name the +repository on the command line explicitly, with the +@code{-d} (for "directory") option: + +@example +cvs -d /usr/local/cvsroot checkout yoyodyne/tc +@end example + + Or you can set the @code{$CVSROOT} environment +variable to an absolute path to the root of the +repository, @file{/usr/local/cvsroot} in this example. +To set @code{$CVSROOT}, all @code{csh} and @code{tcsh} +users should have this line in their @file{.cshrc} or +@file{.tcshrc} files: + +@example +setenv CVSROOT /usr/local/cvsroot +@end example + +@noindent +@code{sh} and @code{bash} users should instead have these lines in their +@file{.profile} or @file{.bashrc}: + +@example +CVSROOT=/usr/local/cvsroot +export CVSROOT +@end example + + A repository specified with @code{-d} will +override the @code{$CVSROOT} environment variable. +Once you've checked a working copy out from the +repository, it will remember where its repository is +(the information is recorded in the +@file{CVS/Root} file in the working copy). + +The @code{-d} option and the @file{CVS/Root} file +both override the @code{$CVSROOT} environment variable; +however, @sc{CVS} will complain if the @file{-d} +argument and the @file{CVS/Root} file disagree. + +There is nothing magical about the name +@file{/usr/local/cvsroot}. You can choose to place the +repository anywhere you like. +@xref{Remote repositories} to learn how the repository can be on a +different machine than your working copy of the sources. + +The repository is split in two parts. @file{$CVSROOT/CVSROOT} contains +administrative files for @sc{cvs}. The other directories contain the actual +user-defined modules. + +@menu +* User modules:: The structure of the repository +* Intro administrative files:: Defining modules +* Multiple repositories:: Multiple repositories +* Creating a repository:: Creating a repository +* Remote repositories:: Accessing repositories on remote machines +@end menu + +@c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +@node User modules +@section User modules +@cindex User modules +@cindex Repository, user parts + +@example + @code{$CVSROOT} + | + +--@t{yoyodyne} + | | + | +--@t{tc} + | | | + +--@t{Makefile,v} + +--@t{backend.c,v} + +--@t{driver.c,v} + +--@t{frontend.c,v} + +--@t{parser.c,v} + +--@t{man} + | | + | +--@t{tc.1,v} + | + +--@t{testing} + | + +--@t{testpgm.t,v} + +--@t{test2.t,v} +@end example + +@cindex History files +@cindex RCS history files +@cindex RCS, CVS uses RCS +The figure above shows the contents of the @samp{tc} +module inside the repository. As you can see all file +names end in @samp{,v}. The files are @dfn{history +files}. They contain, among other things, enough +information to recreate any revision of the file, a log +of all commit messages and the user-name of the person +who committed the revision. @sc{cvs} uses the +facilities of @sc{rcs}, a simpler version control +system, to maintain these files. For a full +description of the file format, see the @code{man} page +@cite{rcsfile(5)}. +@c -- Use this format for all references to man pages, +@c -- or use something better! + +@menu +* File permissions:: File permissions +@end menu + +@c . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . +@node File permissions +@subsection File permissions +@c -- Move this to @node Setting up +@cindex Security +@cindex File permissions +@cindex Group +All @samp{,v} files are created read-only, and you +should not change the permission of those files. The +directories inside the repository should be writable by +the persons that have permission to modify the files in +each directory. This normally means that you must +create a UNIX group (see group(5)) consisting of the +persons that are to edit the files in a project, and +set up the repository so that it is that group that +owns the directory. + +This means that you can only control access to files on +a per-directory basis. + +@sc{cvs} tries to set up reasonable file permissions +for new directories that are added inside the tree, but +you must fix the permissions manually when a new +directory should have different permissions than its +parent directory. + +@cindex setuid +@cindex setgid +Since @sc{cvs} was not written to be run setuid, it is +unsafe to try to run it setuid. You cannot use the +setuid features of @sc{rcs} together with @sc{cvs}. + +@c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +@node Intro administrative files +@section The administrative files +@cindex Administrative files (intro) +@cindex Modules file +@cindex CVSROOT, module name +@cindex Defining modules (intro) + +The directory @file{$CVSROOT/CVSROOT} contains some @dfn{administrative +files}. @xref{Administrative files}, for a complete description. +You can use @sc{cvs} without any of these files, but +some commands work better when at least the +@file{modules} file is properly set up. + +The most important of these files is the @file{modules} +file. It defines all modules in the repository. This +is a sample @file{modules} file. + +@c FIXME: The CVSROOT line is a goofy example now that +@c mkmodules doesn't exist. +@example +CVSROOT CVSROOT +modules CVSROOT modules +cvs gnu/cvs +rcs gnu/rcs +diff gnu/diff +tc yoyodyne/tc +@end example + +The @file{modules} file is line oriented. In its simplest form each +line contains the name of the module, whitespace, and the directory +where the module resides. The directory is a path relative to +@code{$CVSROOT}. The last for lines in the example +above are examples of such lines. + +@c FIXME: might want to introduce the concept of options in modules file +@c (the old example which was here, -i mkmodules, is obsolete). + +The line that defines the module called @samp{modules} +uses features that are not explained here. +@xref{modules}, for a full explanation of all the +available features. + +@subsection Editing administrative files +@cindex Editing administrative files +@cindex Administrative files, editing them + +You edit the administrative files in the same way that you would edit +any other module. Use @samp{cvs checkout CVSROOT} to get a working +copy, edit it, and commit your changes in the normal way. + +It is possible to commit an erroneous administrative +file. You can often fix the error and check in a new +revision, but sometimes a particularly bad error in the +administrative file makes it impossible to commit new +revisions. +@c @xref{Bad administrative files} for a hint +@c about how to solve such situations. +@c -- administrative file checking-- + +@c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +@node Multiple repositories +@section Multiple repositories +@cindex Multiple repositories +@cindex Repositories, multiple +@cindex Many repositories +@cindex Parallel repositories +@cindex Disjoint repositories +@cindex CVSROOT, multiple repositories + +In some situations it is a good idea to have more than +one repository, for instance if you have two +development groups that work on separate projects +without sharing any code. All you have to do to have +several repositories is to specify the appropriate +repository, using the @code{CVSROOT} environment +variable, the @samp{-d} option to @sc{cvs}, or (once +you have checked out a working directories) by +simply allowing @sc{cvs} to use the repository that was +used to check out the working directory (@pxref{Repository}). + +Notwithstanding, it can be confusing to have two or +more repositories. + +None of the examples in this manual show multiple +repositories. + +@c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +@node Creating a repository +@section Creating a repository +@c -- Well, how do you do? + +See the instructions in the @file{INSTALL} file in the +@sc{cvs} distribution. + +@c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +@node Remote repositories +@section Remote repositories +@cindex Repositories, remote +@cindex Remote repositories +@cindex Client/Server Operation + + Your working copy of the sources can be on a +different machine than the repository. Generally, +using a remote repository is just like using a local +one, except that the format of the repository name is: + +@example + user@@hostname:/path/to/repository +@end example + +The details of exactly what needs to be set up depend +on how you are connecting to the server. + +@menu +* Connecting via rsh:: Using the @code{rsh} program to connect +* Password authenticated:: Direct connections using passwords +* Kerberos authenticated:: Direct connections with kerberos +@end menu + +@node Connecting via rsh +@subsection Connecting with rsh + +@cindex rsh +CVS uses the @file{rsh} protocol to perform these +operations, so the remote user host needs to have a +@file{.rhosts} file which grants access to the local +user. + +For example, suppose you are the user @file{mozart} on +the local machine @file{anklet.grunge.com}, and the +server machine is @file{chainsaw.brickyard.com}. On +chainsaw, put the following line into the file +@file{.rhosts} in @file{bach}'s home directory: + +@example +anklet.grunge.com mozart +@end example + +Then test that @code{rsh} is working with + +@example +rsh -l bach chainsaw.brickyard.com echo $PATH +@end example + +@cindex CVS_SERVER +Next you have to make sure that @code{rsh} will be able +to find the server. Make sure that the path which +@code{rsh} printed in the above example includes the +directory containing a program named @code{cvs} which +is the server. You need to set the path in +@file{.bashrc}, @file{.cshrc}, etc., not @file{.login} +or @file{.profile}. Alternately, you can set the +environment variable @code{CVS_SERVER} on the client +machine to the filename of the server you want to use, +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. + +Continuing our example, supposing you want to access +the module @file{foo} in the repository +@file{/usr/local/cvsroot/}, on machine +@file{chainsaw.brickyard.com}, you are ready to go: + +@example +cvs -d bach@@chainsaw.brickyard.com:/user/local/cvsroot checkout foo +@end example + +(The @file{bach@@} can be omitted if the username is +the same on both the local and remote hosts.) + +@node Password authenticated +@subsection Direct connection with password authentication + +The @sc{cvs} client can also connect to the server +using a password protocol. This is particularly useful +if using @code{rsh} is not feasible (for example, +the server is behind a firewall), and Kerberos also is +not available. + + To use this method, it is necessary to make +some adjustments on both the server and client sides. + +@menu +* Password authentication server:: Setting up the server +* Password authentication client:: Using the client +* Password authentication security:: What this method does and does not do +@end menu + +@node Password authentication server +@subsubsection Setting up the server for password authentication + +@cindex Pserver (subcommand) +@cindex password server, setting up +@cindex authenticating server, setting up +On the server side, the file @file{/etc/inetd.conf} +needs to be edited so @code{inetd} knows to run the +command @code{cvs pserver} when it receives a +connection on the right port. By default, the port +number is 2401; it would be different if your client +were compiled with @code{CVS_AUTH_PORT} defined to +something else, though. + + If your @code{inetd} allows raw port numbers in +@file{/etc/inetd.conf}, then the following (all on a +single line in @file{inetd.conf}) should be sufficient: + +@example +2401 stream tcp nowait root /usr/local/bin/cvs +cvs -b /usr/local/bin pserver +@end example + +The @samp{-b} option specifies the directory which contains +the @sc{rcs} binaries on the server. + + If your @code{inetd} wants a symbolic service +name instead of a raw port number, then put this in +@file{/etc/services}: + +@example +cvspserver 2401/tcp +@end example + + and put @code{cvspserver} instead of +@code{2401} in @file{inetd.conf}. + + Once the above is taken care of, restart your +@code{inetd}, or do whatever is necessary to force it +to reread its initialization files. + +@cindex CVS passwd file +@cindex passwd file +Because the client stores and transmits passwords in +cleartext (almost---see @ref{Password authentication +security} for details), a separate @sc{cvs} password +file may be used, so people don't compromise their +regular passwords when they access the repository. +This file is @file{$CVSROOT/CVSROOT/passwd} +(@pxref{Intro administrative files}). Its format is +similar to @file{/etc/passwd}, except that it only has +two fields, username and password. For example: + +@example +bach:ULtgRLXo7NRxs +cwang:1sOp854gDF3DY +@end example + +The password is encrypted according to the standard +Unix @code{crypt()} function, so it is possible to +paste in passwords directly from regular Unix +@file{passwd} files. + +When authenticating a password, the server first checks +for the user in the @sc{cvs} @file{passwd} file. If it +finds the user, it compares against that password. If +it does not find the user, or if the @sc{cvs} +@file{passwd} file does not exist, then the server +tries to match the password using the system's +user-lookup routine. When using the @sc{cvs} +@file{passwd} file, the server runs under as the +username specified in the the third argument in the +entry, or as the first argument if there is no third +argument (in this way @sc{cvs} allows imaginary +usernames provided the @sc{cvs} @file{passwd} file +indicates corresponding valid system usernames). In +any case, @sc{cvs} will have no privileges which the +(valid) user would not have. + +Right now, the only way to put a password in the +@sc{cvs} @file{passwd} file is to paste it there from +somewhere else. Someday, there may be a @code{cvs +passwd} command. + +@node Password authentication client +@subsubsection Using the client with password authentication +@cindex Login (subcommand) +@cindex password client, using +@cindex authenticated client, using +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 +the password for later transactions with the server. +The @code{cvs login} command needs to know the +username, server hostname, and full repository path, +and it gets this information from the repository +argument or the @code{CVSROOT} environment variable. + +@code{cvs login} is interactive --- it prompts for a +password: + +@example +cvs -d bach@@chainsaw.brickyard.com:/usr/local/cvsroot login +CVS password: +@end example + +The password is checked with the server; if it is +correct, the @code{login} succeeds, else it fails, +complaining that the password was incorrect. + +Once you have logged in, you can force @sc{cvs} to +connect directly to the server and authenticate with +the stored password by prefixing the repository with +@samp{:pserver:}: + +@example +cvs -d :pserver:bach@@chainsaw.brickyard.com:/usr/local/cvsroot checkout foo +@end example + +The @samp{:pserver:} is necessary because without it, +@sc{cvs} will assume it should use @code{rsh} to +connect with the server (@pxref{Connecting via rsh}). +(Once you have a working copy checked out and are +running @sc{cvs} commands from within it, there is no +longer any need to specify the repository explicitly, +because @sc{cvs} records it in the working copy's +@file{CVS} subdirectory.) + +@cindex CVS_PASSFILE, environment variable +Passwords are stored by default in the file +@file{$HOME/.cvspass}. Its format is human-readable, +but don't edit it unless you know what you are doing. +The passwords are not stored in cleartext, but are +trivially encoded to protect them from "innocent" +compromise (i.e., inadvertently being seen by a system +administrator who happens to look at that file). + +The @code{CVS_PASSFILE} environment variable overrides +this default. If you use this variable, make sure you +set it @emph{before} @code{cvs login} is run. If you +were to set it after running @code{cvs login}, then +later @sc{cvs} commands would be unable to look up the +password for transmission to the server. + +@cindex CVS_PASSWORD, environment variable +The @code{CVS_PASSWORD} environment variable overrides +@emph{all} stored passwords. If it is set, @sc{cvs} +will use it for all password-authenticated +connections. + +@node Password authentication security +@subsubsection Security considerations with password authentication + +The passwords are stored on the client side in a +trivial encoding of the cleartext, and transmitted in +the same encoding. The encoding is done only to +prevent inadvertent password compromises (i.e., a +system administrator accidentally looking at the file), +and will not prevent even a naive attacker from gaining +the password. + +The separate @sc{cvs} password file (@pxref{Password +authentication server}) allows people +to use a different password for repository access than +for login access. On the other hand, once a user has +access to the repository, she can execute programs on +the server system through a variety of means. Thus, repository +access implies fairly broad system access as well. It +might be possible to modify @sc{cvs} to prevent that, +but no one has done so as of this writing. +Furthermore, there may be other ways in which having +access to @sc{cvs} allows people to gain more general +access to the system; noone has done a careful audit. + +In summary, anyone who gets the password gets +repository access, and some measure of general system +access as well. The password is available to anyone +who can sniff network packets or read a protected +(i.e., user read-only) file. If you want real +security, get Kerberos. + +@node Kerberos authenticated +@subsection Direct connection with kerberos + +@cindex kerberos +The main disadvantage of using rsh is that all the data +needs to pass through additional programs, so it may be +slower. So if you have kerberos installed you can +connect via a direct @sc{tcp} connection, +authenticating with kerberos (note that the data +transmitted is @emph{not} encrypted). + +To do this, @sc{cvs} needs to be compiled with kerberos +support; when configuring @sc{cvs} it tries to detect +whether kerberos is present or you can use the +@file{--with-krb4} flag to configure. + +@cindex CVS_CLIENT_PORT +You need to edit @code{inetd.conf} on the server +machine to run @code{cvs kserver}. The client uses +port 1999 by default; if you want to use another port +specify it in the @code{CVS_CLIENT_PORT} environment +variable on the client. Set @code{CVS_CLIENT_PORT} to +@samp{-1} to force an rsh connection. + +@cindex kinit +When you want to use @sc{cvs}, get a ticket in the +usual way (generally @code{kinit}); it must be a ticket +which allows you to log into the server machine. Then +you are ready to go: + +@example +cvs -d chainsaw.brickyard.com:/user/local/cvsroot checkout foo +@end example + +If @sc{cvs} fails to connect, it will fall back to +trying rsh. + +@c --------------------------------------------------------------------- +@node Starting a new project +@chapter Starting a project with CVS +@cindex Starting a project with CVS +@cindex Creating a project + +@comment --moduledb-- +Since @sc{cvs} 1.x is bad at renaming files and moving +them between directories, the first thing you do when +you start a new project should be to think through your +file organization. It is not impossible---just +awkward---to rename or move files. +@xref{Moving files}. + +What to do next depends on the situation at hand. + +@menu +* Setting up the files:: Getting the files into the repository +* Defining the module:: How to make a module of the files +@end menu +@c -- File permissions! + +@c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +@node Setting up the files +@section Setting up the files + +The first step is to create the files inside the repository. This can +be done in a couple of different ways. + +@c -- The contributed scripts +@menu +* From files:: This method is useful with old projects + where files already exists. +* From other version control systems:: Old projects where you want to + preserve history from another system. +* From scratch:: Creating a module from scratch. +@end menu + +@c . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . +@node From files +@subsection Creating a module from a number of files +@cindex Importing files + +When you begin using @sc{cvs}, you will probably already have several +projects that can be +put under @sc{cvs} control. In these cases the easiest way is to use the +@code{import} command. An example is probably the easiest way to +explain how to use it. If the files you want to install in +@sc{cvs} reside in @file{@var{dir}}, and you want them to appear in the +repository as @file{$CVSROOT/yoyodyne/@var{dir}}, you can do this: + +@example +$ cd @var{dir} +$ cvs import -m "Imported sources" yoyodyne/@var{dir} yoyo start +@end example + +Unless you supply a log message with the @samp{-m} +flag, @sc{cvs} starts an editor and prompts for a +message. The string @samp{yoyo} is a @dfn{vendor tag}, +and @samp{start} is a @dfn{release tag}. They may fill +no purpose in this context, but since @sc{cvs} requires +them they must be present. @xref{Tracking sources}, for +more information about them. + +You can now verify that it worked, and remove your +original source directory. + +@example +$ cd .. +$ mv @var{dir} @var{dir}.orig +$ cvs checkout yoyodyne/@var{dir} # @r{Explanation below} +$ ls -R yoyodyne +$ rm -r @var{dir}.orig +@end example + +@noindent +Erasing the original sources is a good idea, to make sure that you do +not accidentally edit them in @var{dir}, bypassing @sc{cvs}. +Of course, it would be wise to make sure that you have +a backup of the sources before you remove them. + +The @code{checkout} command can either take a module +name as argument (as it has done in all previous +examples) or a path name relative to @code{$CVSROOT}, +as it did in the example above. + +It is a good idea to check that the permissions +@sc{cvs} sets on the directories inside @samp{$CVSROOT} +are reasonable, and that they belong to the proper +groups. @xref{File permissions}. + +@c The node name is too long, but I am having trouble +@c thinking of something more concise. +@node From other version control systems +@subsection Creating Files From Other Version Control Systems +@cindex Importing files, from other version control systesm + +If you have a project which you are maintaining with +another version control system, such as @sc{rcs}, you +may wish to put the files from that project into +@sc{cvs}, and preserve the revision history of the +files. + +@table @asis +@cindex RCS, importing files from +@item From RCS +If you have been using @sc{rcs}, find the @sc{rcs} +files---usually a file named @file{foo.c} will have its +@sc{rcs} file in @file{RCS/foo.c,v} (but it could be +other places; consult the @sc{rcs} documentation for +details). Then create the appropriate directories in +@sc{cvs} if they do not already exist. Then copy the +files into the appropriate directories in the @sc{cvs} +repository (the name in the repository must be the name +of the source file with @samp{,v} added; the files go +directly in the appopriate directory of the repository, +not in an @file{RCS} subdirectory). This is one of the +few times when it is a good idea to access the @sc{cvs} +repository directly, rather than using @sc{cvs} +commands. Then you are ready to check out a new +working directory. +@c Someday there probably should be a "cvs import -t +@c rcs" or some such. It could even create magic +@c branches. It could also do something about the case +@c where the RCS file had a (non-magic) "0" branch. + +@c How many is "many"? Or do they just import RCS files? +@item From another version control system +Many version control systems have the ability to export +@sc{rcs} files in the standard format. If yours does, +export the @sc{rcs} files and then follow the above +instructions. + +@cindex SCCS, importing files from +@item From SCCS +There is a script in the @file{contrib} directory of +the @sc{cvs} source distribution called @file{sccs2rcs} +which converts @sc{sccs} files to @sc{rcs} files. +Note: you must run it on a machine which has both +@sc{sccs} and @sc{rcs} installed, and like everything +else in contrib it is unsupported (your mileage may +vary). +@end table + +@c . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . +@node From scratch +@subsection Creating a module from scratch + +For a new project, the easiest thing to do is probably +to create an empty directory structure, like this: + +@example +$ mkdir tc +$ mkdir tc/man +$ mkdir tc/testing +@end example + +After that, you use the @code{import} command to create +the corresponding (empty) directory structure inside +the repository: + +@example +$ cd tc +$ cvs import -m "Created directory structure" yoyodyne/@var{dir} yoyo start +@end example + +Then, use @code{add} to add files (and new directories) +as they appear. + +Check that the permissions @sc{cvs} sets on the +directories inside @samp{$CVSROOT} are reasonable. + +@c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +@node Defining the module +@section Defining the module +@cindex Defining a module +@cindex Editing the modules file +@cindex Module, defining +@cindex Modules file, changing + +The next step is to define the module in the +@file{modules} file. This is not strictly necessary, +but modules can be convenient in grouping together +related files and directories. + +In simple cases these steps are sufficient to define a module. + +@enumerate +@item +Get a working copy of the modules file. + +@example +$ cvs checkout modules +$ cd modules +@end example + +@item +Edit the file and insert a line that defines the module. @xref{Intro +administrative files}, for an introduction. @xref{modules}, for a full +description of the modules file. You can use the +following line to define the module @samp{tc}: + +@example +tc yoyodyne/tc +@end example + +@item +Commit your changes to the modules file. + +@example +$ cvs commit -m "Added the tc module." modules +@end example + +@item +Release the modules module. + +@example +$ cd .. +$ cvs release -d modules +@end example +@end enumerate + +@c --------------------------------------------------------------------- +@node Multiple developers +@chapter Multiple developers +@cindex Multiple developers +@cindex Team of developers +@cindex File locking +@cindex Locking files +@cindex Working copy + +When more than one person works on a software project +things often get complicated. Often, two people try to +edit the same file simultaneously. Some other version +control systems (including @sc{rcs} and @sc{sccs}) +try to solve that particular problem by introducing +@dfn{file locking}, so that only one person can edit +each file at a time. Unfortunately, file locking can +be very counter-productive. If two persons want +to edit different parts of a file, there may be no +reason to prevent either of them from doing so. + +@sc{cvs} does not use file locking. Instead, it allows many +people to edit their own @dfn{working copy} of a file +simultaneously. The first person that commits his +changes has no automatic way of knowing that another has started to +edit it. Others will get an error message when they +try to commit the file. They must then use @sc{cvs} +commands to bring their working copy up to date with +the repository revision. This process is almost +automatic, and explained in this chapter. + +There are many ways to organize a team of developers. +@sc{cvs} does not try to enforce a certain +organization. It is a tool that can be used in several +ways. It is often useful to inform the group of +commits you have done. @sc{cvs} has several ways of +automating that process. @xref{Informing others}. +@xref{Revision management}, for more tips on how to use +@sc{cvs}. + +@menu +* File status:: A file can be in several states +* Updating a file:: Bringing a file up-to-date +* Conflicts example:: An informative example +* Informing others:: To cooperate you must inform +* Concurrency:: Simultaneous repository access +* Watches:: Mechanisms to track who is editing files +@end menu + +@c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +@node File status +@section File status +@cindex File status +@cindex Status of a file +@cindex Four states of a file + +After you have checked out a file out from @sc{cvs}, it is in +one of these four states: + +@table @asis +@cindex Up-to-date +@item Up-to-date +The file is identical with the latest revision in the +repository. +@c -- The above is not always true if branching is used. + +@item Locally modified +@cindex Locally modified +You have edited the file, and not yet committed your changes. + +@item Needing update +@cindex Needing update +Someone else has committed a newer revision to the repository. + +@item Needing merge +@cindex Needing merge +Someone else have committed a newer revision to the repository, and you +have also made modifications to the file. +@c -- What about "added" "removed" and so on? +@end table + +You can use the @code{status} command to find out the status of a given +file. @xref{status}. + +@c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +@node Updating a file +@section Bringing a file up to date +@cindex Bringing a file up to date +@cindex Updating a file +@cindex Merging a file +@cindex update, introduction + +When you want to update or merge a file, use the @code{update} +command. For files that are not up to date this is roughly equivalent +to a @code{checkout} command: the newest revision of the file is +extracted from the repository and put in your working copy of the +module. + +Your modifications to a file are never lost when you +use @code{update}. If no newer revision exists, +running @code{update} has no effect. If you have +edited the file, and a newer revision is available, +@sc{cvs} will merge all changes into your working copy. + +For instance, imagine that you checked out revision 1.4 and started +editing it. In the meantime someone else committed revision 1.5, and +shortly after that revision 1.6. If you run @code{update} on the file +now, @sc{cvs} will incorporate all changes between revision 1.4 and 1.6 into +your file. + +@cindex Overlap +If any of the changes between 1.4 and 1.6 were made too +close to any of the changes you have made, an +@dfn{overlap} occurs. In such cases a warning is +printed, and the resulting file includes both +versions of the lines that overlap, delimited by +special markers. +@xref{update}, for a complete description of the +@code{update} command. + +@c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +@node Conflicts example +@section Conflicts example +@cindex Merge, an example +@cindex Example of merge +@cindex driver.c (merge example) + +Suppose revision 1.4 of @file{driver.c} contains this: + +@example +#include <stdio.h> + +void main() +@{ + parse(); + if (nerr == 0) + gencode(); + else + fprintf(stderr, "No code generated.\n"); + exit(nerr == 0 ? 0 : 1); +@} +@end example + +@noindent +Revision 1.6 of @file{driver.c} contains this: + +@example +#include <stdio.h> + +int main(int argc, + char **argv) +@{ + parse(); + if (argc != 1) + @{ + fprintf(stderr, "tc: No args expected.\n"); + exit(1); + @} + if (nerr == 0) + gencode(); + else + fprintf(stderr, "No code generated.\n"); + exit(!!nerr); +@} +@end example + +@noindent +Your working copy of @file{driver.c}, based on revision +1.4, contains this before you run @samp{cvs update}: +@c -- Really include "cvs"? + +@example +#include <stdlib.h> +#include <stdio.h> + +void main() +@{ + init_scanner(); + parse(); + if (nerr == 0) + gencode(); + else + fprintf(stderr, "No code generated.\n"); + exit(nerr == 0 ? EXIT_SUCCESS : EXIT_FAILURE); +@} +@end example + +@noindent +You run @samp{cvs update}: +@c -- Really include "cvs"? + +@example +$ cvs update driver.c +RCS file: /usr/local/cvsroot/yoyodyne/tc/driver.c,v +retrieving revision 1.4 +retrieving revision 1.6 +Merging differences between 1.4 and 1.6 into driver.c +rcsmerge warning: overlaps during merge +cvs update: conflicts found in driver.c +C driver.c +@end example + +@noindent +@cindex Conflicts (merge example) +@sc{cvs} tells you that there were some conflicts. +Your original working file is saved unmodified in +@file{.#driver.c.1.4}. The new version of +@file{driver.c} contains this: + +@example +#include <stdlib.h> +#include <stdio.h> + +int main(int argc, + char **argv) +@{ + init_scanner(); + parse(); + if (argc != 1) + @{ + fprintf(stderr, "tc: No args expected.\n"); + exit(1); + @} + if (nerr == 0) + gencode(); + else + fprintf(stderr, "No code generated.\n"); +<<<<<<< driver.c + exit(nerr == 0 ? EXIT_SUCCESS : EXIT_FAILURE); +======= + exit(!!nerr); +>>>>>>> 1.6 +@} +@end example + +@noindent +@cindex Markers, conflict +@cindex Conflict markers +@cindex <<<<<<< +@cindex >>>>>>> +@cindex ======= + +Note how all non-overlapping modifications are incorporated in your working +copy, and that the overlapping section is clearly marked with +@samp{<<<<<<<}, @samp{=======} and @samp{>>>>>>>}. + +@cindex Resolving a conflict +@cindex Conflict resolution +You resolve the conflict by editing the file, removing the markers and +the erroneous line. Suppose you end up with this file: +@c -- Add xref to the pcl-cvs manual when it talks +@c -- about this. +@example +#include <stdlib.h> +#include <stdio.h> + +int main(int argc, + char **argv) +@{ + init_scanner(); + parse(); + if (argc != 1) + @{ + fprintf(stderr, "tc: No args expected.\n"); + exit(1); + @} + if (nerr == 0) + gencode(); + else + fprintf(stderr, "No code generated.\n"); + exit(nerr == 0 ? EXIT_SUCCESS : EXIT_FAILURE); +@} +@end example + +@noindent +You can now go ahead and commit this as revision 1.7. + +@example +$ cvs commit -m "Initialize scanner. Use symbolic exit values." driver.c +Checking in driver.c; +/usr/local/cvsroot/yoyodyne/tc/driver.c,v <-- driver.c +new revision: 1.7; previous revision: 1.6 +done +@end example + +@cindex emerge +If you use release 1.04 or later of pcl-cvs (a @sc{gnu} +Emacs front-end for @sc{cvs}) you can use an Emacs +package called emerge to help you resolve conflicts. +See the documentation for pcl-cvs. + +@c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +@node Informing others +@section Informing others about commits +@cindex Informing others +@cindex Spreading information +@cindex Mail, automatic mail on commit + +It is often useful to inform others when you commit a +new revision of a file. The @samp{-i} option of the +@file{modules} file, or the @file{loginfo} file, can be +used to automate this process. @xref{modules}. +@xref{loginfo}. You can use these features of @sc{cvs} +to, for instance, instruct @sc{cvs} to mail a +message to all developers, or post a message to a local +newsgroup. +@c -- More text would be nice here. + +@node Concurrency +@section Several developers simultaneously attempting to run CVS + +@cindex locks, cvs +If several developers try to run @sc{cvs} at the same +time, one may get the following message: + +@example +[11:43:23] waiting for bach's lock in /usr/local/cvsroot/foo +@end example + +@sc{cvs} will try again every 30 seconds, and either +continue with the operation or print the message again, +if it still needs to wait. If a lock seems to stick +around for an undue amount of time, find the person +holding the lock and ask them about the cvs command +they are running. If they aren't running a cvs +command, look for and remove files starting with +@file{#cvs.tfl}, @file{#cvs.rfl}, or @file{#cvs.wfl} +from the repository. + +Note that these locks are to protect @sc{cvs}'s +internal data structures and have no relationship to +the word @dfn{lock} in the sense used by @sc{rcs}--a +way to prevent other developers from working on a +particular file. + +Any number of people can be reading from a given +repository at a time; only when someone is writing do +the locks prevent other people from reading or writing. + +@cindex Atomic transactions, lack of +@cindex Transactions, atomic, lack of +One might hope for the following property + +@example +If someone commits some changes in one cvs command, +then an update by someone else will either get all the +changes, or none of them. +@end example + +but @sc{cvs} does @emph{not} have this property. For +example, given the files + +@example +a/one.c +a/two.c +b/three.c +b/four.c +@end example + +if someone runs + +@example +cvs ci a/two.c b/three.c +@end example + +and someone else runs @code{cvs update} at the same +time, the person running @code{update} might get only +the change to @file{b/three.c} and not the change to +@file{a/two.c}. + +@node Watches +@section Mechanisms to track who is editing files +@cindex Watches + +For many groups, use of @sc{cvs} in its default mode is +perfectly satisfactory. Users may sometimes go to +check in a modification only to find that another +modification has intervened, but they deal with it and +proceed with their check in. Other groups prefer to be +able to know who is editing what files, so that if two +people try to edit the same file they can choose to +talk about who is doing what when rather than be +surprised at check in time. The features in this +section allow such coordination, while retaining the +ability of two developers to edit the same file at the +same time. + +For maximum benefit developers should use @code{cvs +edit} (not @code{chmod}) to make files read-write to +edit them, and @code{cvs release} (not @code{rm}) to +discard a working directory which is no longer in use, +but @sc{cvs} is not able to enforce this behavior. + +@c I'm a little dissatisfied with this presentation, +@c because "watch on"/"edit"/"editors" are one set of +@c functionality, and "watch add"/"watchers" is another +@c which is somewhat orthogonal even though they interact in +@c various ways. But I think it might be +@c confusing to describe them separately (e.g. "watch +@c add" with loginfo). I don't know. + +@menu +* Setting a watch:: Telling CVS to watch certain files +* Getting Notified:: Telling CVS to notify you +* Editing files:: How to edit a file which is being watched +* Watch information:: Information about who is watching and editing +* Watches Compatibility:: Watches interact poorly with CVS 1.6 or earlier +@end menu + +@node Setting a watch +@subsection Telling CVS to watch certain files + +To enable the watch features, you first specify that +certain files are to be watched. + +@cindex watch on (subcommand) +@deffn Command {cvs watch on} [@code{-l}] files @dots{} + +Specify that developers should run @code{cvs edit} +before editing @var{files}. CVS will create working +copies of @var{files} read-only, to remind developers +to run the @code{cvs edit} command before working on +them. + +If @var{files} includes the name of a directory, CVS +arranges to watch all files added to the corresponding +repository directory, and sets a default for files +added in the future; this allows the user to set +notification policies on a per-directory basis. The +contents of the directory are processed recursively, +unless the @code{-l} option is given. + +If @var{files} is omitted, it defaults to the current directory. + +@cindex watch off (subcommand) +@end deffn + +@deffn Command {cvs watch off} [@code{-l}] files @dots{} + +Do not provide notification about work on @var{files}. CVS will create +working copies of @var{files} read-write. + +The @var{files} and @code{-l} arguments are processed as for @code{cvs +watch on}. + +@end deffn + +@node Getting Notified +@subsection Telling CVS to notify you + +You can tell @sc{cvs} that you want to receive +notifications about various actions taken on a file. +You can do this without using @code{cvs watch on} for +the file, but generally you will want to use @code{cvs +watch on}, so that developers use the @code{cvs edit} +command. + +@cindex watch add (subcommand) +@deffn Command {cvs watch add} [@code{-a} action] [@code{-l}] files @dots{} + +Add the current user to the list of people to receive notification of +work done on @var{files}. + +The @code{-a} option specifies what kinds of events CVS should notify +the user about. @var{action} is one of the following: + +@table @code + +@item edit +Another user has applied the @code{cvs edit} command (described +below) to a file. + +@item unedit +Another user has applied the @code{cvs unedit} command (described +below) or the @code{cvs release} command to a file, or has deleted +the file and allowed @code{cvs update} to recreate it. + +@item commit +Another user has committed changes to a file. + +@item all +All of the above. + +@item none +None of the above. (This is useful with @code{cvs edit}, +described below.) + +@end table + +The @code{-a} option may appear more than once, or not at all. If +omitted, the action defaults to @code{all}. + +The @var{files} and @code{-l} option are processed as for the +@code{cvs watch} commands. + +@end deffn + + +@cindex watch remove (subcommand) +@deffn Command {cvs watch remove} [@code{-a} action] [@code{-l}] files @dots{} + +Remove a notification request established using @code{cvs watch add}; +the arguments are the same. If the @code{-a} option is present, only +watches for the specified actions are removed. + +@end deffn + +When the conditions exist for notification, @sc{cvs} +calls the @file{notify} administrative file, passing it +the user to receive the notification and the user who +is taking the action which results in notification. +Normally @file{notify} will just send an email message. + +@cindex users (admin file) +Note that if you set this up in the straightforward +way, users receive notifications on the server machine. +One could of course write a @file{notify} script which +directed notifications elsewhere, but to make this +easy, @sc{cvs} allows you to associate a notification +address for each user. To do so create a file +@file{users} in @file{CVSROOT} with a line for each +user in the format @var{user}:@var{value}. Then +instead of passing the name of the user to be notified +to @file{notify}, @sc{cvs} will pass the @var{value} +(normally an email address on some other machine). + +@node Editing files +@subsection How to edit a file which is being watched + +Since a file which is being watched is checked out +read-only, you cannot simply edit it. To make it +read-write, and inform others that you are planning +to edit it, use the @code{cvs edit} command. + +@cindex edit (subcommand) +@deffn Command {cvs edit} [options] files @dots{} + +Prepare to edit the working files @var{files}. CVS makes the +@var{files} read-write, and notifies users who have requested +@code{edit} notification for any of @var{files}. + +The @code{cvs edit} command accepts the same @var{options} as the +@code{cvs watch add} command, and establishes a temporary watch for the +user on @var{files}; CVS will remove the watch when @var{files} are +@code{unedit}ed or @code{commit}ted. If the user does not wish to +receive notifications, she should specify @code{-a none}. + +The @var{files} and @code{-l} option are processed as for the @code{cvs +watch} commands. + +@end deffn + +Normally when you are done with a set of changes, you +use the @code{cvs commit} command, which checks in your +changes and returns the watched files to their usual +read-only state. But if you instead decide to abandon +your changes, or not to make any changes, you can use +the @code{cvs unedit} command. + +@cindex unedit (subcommand) +@deffn Command {cvs unedit} [@code{-l}] files @dots{} + +Abandon work on the working files @var{files}, and revert them to the +repository versions on which they are based. CVS makes those +@var{files} read-only for which users have requested notification using +@code{cvs watch on}. CVS notifies users who have requested @code{unedit} +notification for any of @var{files}. + +The @var{files} and @code{-l} option are processed as for the +@code{cvs watch} commands. + +@end deffn + +When using client/server @sc{cvs}, you can use the +@code{cvs edit} and @code{cvs unedit} commands even if +@sc{cvs} is unable to succesfully communicate with the +server; the notifications will be sent upon the next +successful @sc{cvs} command. + +@node Watch information +@subsection Information about who is watching and editing + +@cindex watchers (subcommand) +@deffn Command {cvs watchers} [@code{-l}] files @dots{} + +List the users currently watching changes to @var{files}. The report +includes the files being watched, and the mail address of each watcher. + +The @var{files} and @code{-l} arguments are processed as for the +@code{cvs watch} commands. + +@end deffn + + +@cindex editors (subcommand) +@deffn Command {cvs editors} [@code{-l}] files @dots{} + +List the users currently working on @var{files}. The report +includes the mail address of each user, the time when the user began +working with the file, and the host and path of the working directory +containing the file. + +The @var{files} and @code{-l} arguments are processed as for the +@code{cvs watch} commands. + +@end deffn + +@node Watches Compatibility +@subsection Using watches with old versions of CVS + +@cindex CVS 1.6, and watches +If you use the watch features on a repository, it +creates @file{CVS} directories in the repository and +stores the information about watches in that directory. +If you attempt to use @sc{cvs} 1.6 or earlier with the +repository, you get an error message such as + +@example +cvs update: cannot open CVS/Entries for reading: No such file or directory +@end example + +and your operation will likely be aborted. To use the +watch features, you must upgrade all copies of @sc{cvs} +which use that repository in local or server mode. If +you cannot upgrade, use the @code{watch off} and +@code{watch remove} commands to remove all watches, and +that will restore the repository to a state which +@sc{cvs} 1.6 can cope with. + +@c --------------------------------------------------------------------- +@node Branches +@chapter Branches +@cindex Branches +@cindex Main trunk and branches +@cindex Revision tree, making branches + +So far, all revisions shown in this manual have been on +the @dfn{main trunk} +of the revision tree, i.e., all revision numbers +have been of the form @var{x}.@var{y}. One useful +feature, especially when maintaining several releases +of a software product at once, is the ability to make +branches on the revision tree. @dfn{Tags}, symbolic +names for revisions, will also be +introduced in this chapter. + +@menu +* Tags:: Tags--Symbolic revisions +* Branches motivation:: What branches are good for +* Creating a branch:: Creating a branch +* Sticky tags:: Sticky tags +@end menu + +@c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +@node Tags +@section Tags--Symbolic revisions +@cindex Tags + +The revision numbers live a life of their own. They +need not have anything at all to do with the release +numbers of your software product. Depending +on how you use @sc{cvs} the revision numbers might change several times +between two releases. As an example, some of the +source files that make up @sc{rcs} 5.6 have the following +revision numbers: +@cindex RCS revision numbers + +@example +ci.c 5.21 +co.c 5.9 +ident.c 5.3 +rcs.c 5.12 +rcsbase.h 5.11 +rcsdiff.c 5.10 +rcsedit.c 5.11 +rcsfcmp.c 5.9 +rcsgen.c 5.10 +rcslex.c 5.11 +rcsmap.c 5.2 +rcsutil.c 5.10 +@end example + +@cindex tag, command, introduction +@cindex Tag, symbolic name +@cindex Symbolic name (tag) +@cindex Name, symbolic (tag) +You can use the @code{tag} command to give a symbolic name to a +certain revision of a file. You can use the @samp{-v} flag to the +@code{status} command to see all tags that a file has, and +which revision numbers they represent. Tag names can +contain uppercase and lowercase letters, digits, +@samp{-}, and @samp{_}. The two tag names @code{BASE} +and @code{HEAD} are reserved for use by @sc{cvs}. It +is expected that future names which are special to +@sc{cvs} will contain characters such as @samp{%} or +@samp{=}, rather than being named analogously to +@code{BASE} and @code{HEAD}, to avoid conflicts with +actual tag names. +@c FIXME: is the above list of valid characters in tag +@c names complete? + +@cindex Adding a tag +@cindex tag, example +The following example shows how you can add a tag to a +file. The commands must be issued inside your working +copy of the module. That is, you should issue the +command in the directory where @file{backend.c} +resides. + +@example +$ cvs tag release-0-4 backend.c +T backend.c +$ cvs status -v backend.c +=================================================================== +File: backend.c Status: Up-to-date + + Version: 1.4 Tue Dec 1 14:39:01 1992 + RCS Version: 1.4 /usr/local/cvsroot/yoyodyne/tc/backend.c,v + Sticky Tag: (none) + Sticky Date: (none) + Sticky Options: (none) + + Existing Tags: + release-0-4 (revision: 1.4) + +@end example + +There is seldom reason to tag a file in isolation. A more common use is +to tag all the files that constitute a module with the same tag at +strategic points in the development life-cycle, such as when a release +is made. + +@example +$ cvs tag release-1-0 . +cvs tag: Tagging . +T Makefile +T backend.c +T driver.c +T frontend.c +T parser.c +@end example + +(When you give @sc{cvs} a directory as argument, it generally applies the +operation to all the files in that directory, and (recursively), to any +subdirectories that it may contain. @xref{Recursive behavior}.) + +@cindex Retrieving an old revision using tags +@cindex Tag, retrieving old revisions +The @code{checkout} command has a flag, @samp{-r}, that lets you check out +a certain revision of a module. This flag makes it easy to +retrieve the sources that make up release 1.0 of the module @samp{tc} at +any time in the future: + +@example +$ cvs checkout -r release-1-0 tc +@end example + +@noindent +This is useful, for instance, if someone claims that there is a bug in +that release, but you cannot find the bug in the current working copy. + +You can also check out a module as it was at any given date. +@xref{checkout options}. + +When you tag more than one file with the same tag you +can think about the tag as "a curve drawn through a +matrix of filename vs. revision number." Say we have 5 +files with the following revisions: + +@example +@group + file1 file2 file3 file4 file5 + + 1.1 1.1 1.1 1.1 /--1.1* <-*- TAG + 1.2*- 1.2 1.2 -1.2*- + 1.3 \- 1.3*- 1.3 / 1.3 + 1.4 \ 1.4 / 1.4 + \-1.5*- 1.5 + 1.6 +@end group +@end example + +At some time in the past, the @code{*} versions were tagged. +You can think of the tag as a handle attached to the curve +drawn through the tagged revisions. When you pull on +the handle, you get all the tagged revisions. Another +way to look at it is that you "sight" through a set of +revisions that is "flat" along the tagged revisions, +like this: + +@example +@group + file1 file2 file3 file4 file5 + + 1.1 + 1.2 + 1.1 1.3 _ + 1.1 1.2 1.4 1.1 / + 1.2*----1.3*----1.5*----1.2*----1.1 (--- <--- Look here + 1.3 1.6 1.3 \_ + 1.4 1.4 + 1.5 +@end group +@end example + +@c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +@node Branches motivation +@section What branches are good for +@cindex Branches motivation +@cindex What branches are good for +@cindex Motivation for branches + +Suppose that release 1.0 of tc has been made. You are continuing to +develop tc, planning to create release 1.1 in a couple of months. After a +while your customers start to complain about a fatal bug. You check +out release 1.0 (@pxref{Tags}) and find the bug +(which turns out to have a trivial fix). However, the current revision +of the sources are in a state of flux and are not expected to be stable +for at least another month. There is no way to make a +bugfix release based on the newest sources. + +The thing to do in a situation like this is to create a @dfn{branch} on +the revision trees for all the files that make up +release 1.0 of tc. You can then make +modifications to the branch without disturbing the main trunk. When the +modifications are finished you can select to either incorporate them on +the main trunk, or leave them on the branch. + +@c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +@node Creating a branch +@section Creating a branch +@cindex Creating a branch +@cindex Branch, creating a +@cindex rtag, creating a branch using + +@c FIXME: should be more explicit about the value of +@c having a tag on the branchpoint. Also should talk +@c about creating a branch with tag not rtag. +The @code{rtag} command can be used to create a branch. +The @code{rtag} command is much like @code{tag}, but it +does not require that you have a working copy of the +module. @xref{rtag}. (You can also use the @code{tag} +command; @pxref{tag}). + +@example +$ cvs rtag -b -r release-1-0 release-1-0-patches tc +@end example + +The @samp{-b} flag makes @code{rtag} create a branch +(rather than just a symbolic revision name). @samp{-r +release-1-0} says that this branch should be rooted at the node (in +the revision tree) that corresponds to the tag +@samp{release-1-0}. Note that the numeric revision number that matches +@samp{release-1-0} will probably be different from file to file. The +name of the new branch is @samp{release-1-0-patches}, and the +module affected is @samp{tc}. + +To fix the problem in release 1.0, you need a working +copy of the branch you just created. + +@example +$ cvs checkout -r release-1-0-patches tc +$ cvs status -v driver.c backend.c +=================================================================== +File: driver.c Status: Up-to-date + + Version: 1.7 Sat Dec 5 18:25:54 1992 + RCS Version: 1.7 /usr/local/cvsroot/yoyodyne/tc/driver.c,v + Sticky Tag: release-1-0-patches (branch: 1.7.2) + Sticky Date: (none) + Sticky Options: (none) + + Existing Tags: + release-1-0-patches (branch: 1.7.2) + release-1-0 (revision: 1.7) + +=================================================================== +File: backend.c Status: Up-to-date + + Version: 1.4 Tue Dec 1 14:39:01 1992 + RCS Version: 1.4 /usr/local/cvsroot/yoyodyne/tc/backend.c,v + Sticky Tag: release-1-0-patches (branch: 1.4.2) + Sticky Date: (none) + Sticky Options: (none) + + Existing Tags: + release-1-0-patches (branch: 1.4.2) + release-1-0 (revision: 1.4) + release-0-4 (revision: 1.4) + +@end example + +@cindex Branch numbers +As the output from the @code{status} command shows the branch +number is created by adding a digit at the tail of the revision number +it is based on. (If @samp{release-1-0} corresponds to revision 1.4, the +branch's revision number will be 1.4.2. For obscure reasons @sc{cvs} always +gives branches even numbers, starting at 2. +@xref{Revision numbers}). + +@c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +@node Sticky tags +@section Sticky tags +@cindex Sticky tags +@cindex Tags, sticky +@cindex Branches, sticky + +@c FIXME: make this stand alone better; many places +@c @xref to this node. +The @samp{-r release-1-0-patches} flag that was given +to @code{checkout} in the previous example +is @dfn{sticky}, that is, it will apply to subsequent commands +in this directory. If you commit any modifications, they are +committed on the branch. You can later merge the modifications into +the main trunk. @xref{Merging}. + +You can use the @code{status} command to see what +sticky tags or dates are set: + +@c FIXME: This example needs to stand alone better and it +@c would also better if it didn't use -v which only +@c clutters the output in this context. +@example +$ vi driver.c # @r{Fix the bugs} +$ cvs commit -m "Fixed initialization bug" driver.c +Checking in driver.c; +/usr/local/cvsroot/yoyodyne/tc/driver.c,v <-- driver.c +new revision: 1.7.2.1; previous revision: 1.7 +done +$ cvs status -v driver.c +=================================================================== +File: driver.c Status: Up-to-date + + Version: 1.7.2.1 Sat Dec 5 19:35:03 1992 + RCS Version: 1.7.2.1 /usr/local/cvsroot/yoyodyne/tc/driver.c,v + Sticky Tag: release-1-0-patches (branch: 1.7.2) + Sticky Date: (none) + Sticky Options: (none) + + Existing Tags: + release-1-0-patches (branch: 1.7.2) + release-1-0 (revision: 1.7) + +@end example + +@cindex Resetting sticky tags +@cindex Sticky tags, resetting +@cindex Deleting sticky tags +The sticky tags will remain on your working files until +you delete them with @samp{cvs update -A}. The +@samp{-A} option retrieves the version of the file from +the head of the trunk, and forgets any sticky tags, +dates, or options. + +@c Is the fact that CVS works this way a bug or a +@c feature? If a feature, describe how you would use +@c it to do something useful. +Sticky tags are not just for branches. If you check +out a certain revision (such as 1.4) it will also +become sticky. Subsequent @samp{cvs update} will not +retrieve the latest revision until you reset the tag +with @samp{cvs update -A}. Likewise, use of the +@samp{-D} option to @code{update} or @code{checkout} +sets a @dfn{sticky date}, which, similarly, causes that +date to be used for future retrievals. + +@cindex Restoring old version of removed file +@cindex Resurrecting old version of dead file +Many times you will want to retrieve an old version of +a file without setting a sticky tag. The way to do +that is with the @samp{-p} option to @code{checkout} or +@code{update}, which sends the contents of the file to +standard output. For example, suppose you have a file +named @file{file1} which existed as revision 1.1, and +you then removed it (thus adding a dead revision 1.2). +Now suppose you want to add it again, with the same +contents it had previously. Here is how to do it: + +@example +$ cvs update -p -r 1.1 file1 >file1 +=================================================================== +Checking out file1 +RCS: /tmp/cvs-sanity/cvsroot/first-dir/Attic/file1,v +VERS: 1.1 +*************** +$ cvs add file1 +cvs add: version 1.2 of `file1' will be resurrected +cvs add: use 'cvs commit' to add this file permanently +$ cvs commit -m test +Checking in file1; +/tmp/cvs-sanity/cvsroot/first-dir/file1,v <-- file1 +new revision: 1.3; previous revision: 1.2 +done +$ +@end example + +@c --------------------------------------------------------------------- +@node Merging +@chapter Merging +@cindex Merging +@cindex Copying changes +@cindex Branches, copying changes between +@cindex Changes, copying between branches +@cindex Modifications, copying between branches + +You can include the changes made between any two +revisions into your working copy, by @dfn{merging}. +You can then commit that revision, and thus effectively +copy the changes onto another branch. + +@menu +* Merging a branch:: Merging an entire branch +* Merging more than once:: Merging from a branch several times +* Merging two revisions:: Merging differences between two revisions +@end menu + +@c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +@node Merging a branch +@section Merging an entire branch +@cindex Merging a branch +@cindex -j (merging branches) + +You can merge changes made on a branch into your working copy by giving +the @samp{-j @var{branch}} flag to the @code{update} command. With one +@samp{-j @var{branch}} option it merges the changes made between the +point where the branch forked and newest revision on that branch (into +your working copy). + +@cindex Join +The @samp{-j} stands for ``join''. + +@cindex Branch merge example +@cindex Example, branch merge +@cindex Merge, branch example +Consider this revision tree: + +@example ++-----+ +-----+ +-----+ +-----+ +! 1.1 !----! 1.2 !----! 1.3 !----! 1.4 ! <- The main trunk ++-----+ +-----+ +-----+ +-----+ + ! + ! + ! +---------+ +---------+ +Branch R1fix -> +---! 1.2.2.1 !----! 1.2.2.2 ! + +---------+ +---------+ +@end example + +@noindent +The branch 1.2.2 has been given the tag (symbolic name) @samp{R1fix}. The +following example assumes that the module @samp{mod} contains only one +file, @file{m.c}. + +@example +$ cvs checkout mod # @r{Retrieve the latest revision, 1.4} + +$ cvs update -j R1fix m.c # @r{Merge all changes made on the branch,} + # @r{i.e. the changes between revision 1.2} + # @r{and 1.2.2.2, into your working copy} + # @r{of the file.} + +$ cvs commit -m "Included R1fix" # @r{Create revision 1.5.} +@end example + +A conflict can result from a merge operation. If that +happens, you should resolve it before committing the +new revision. @xref{Conflicts example}. + +The @code{checkout} command also supports the @samp{-j @var{branch}} flag. The +same effect as above could be achieved with this: + +@example +$ cvs checkout -j R1fix mod +$ cvs commit -m "Included R1fix" +@end example + +@node Merging more than once +@section Merging from a branch several times + +Continuing our example, the revision tree now looks +like this: + +@example ++-----+ +-----+ +-----+ +-----+ +-----+ +! 1.1 !----! 1.2 !----! 1.3 !----! 1.4 !----! 1.5 ! <- The main trunk ++-----+ +-----+ +-----+ +-----+ +-----+ + ! * + ! * + ! +---------+ +---------+ +Branch R1fix -> +---! 1.2.2.1 !----! 1.2.2.2 ! + +---------+ +---------+ +@end example + +where the starred line represents the merge from the +@samp{R1fix} branch to the main trunk, as just +discussed. + +Now suppose that development continues on the +@samp{R1fix} branch: + +@example ++-----+ +-----+ +-----+ +-----+ +-----+ +! 1.1 !----! 1.2 !----! 1.3 !----! 1.4 !----! 1.5 ! <- The main trunk ++-----+ +-----+ +-----+ +-----+ +-----+ + ! * + ! * + ! +---------+ +---------+ +---------+ +Branch R1fix -> +---! 1.2.2.1 !----! 1.2.2.2 !----! 1.2.2.3 ! + +---------+ +---------+ +---------+ +@end example + +and then you want to merge those new changes onto the +main trunk. If you just use the @code{cvs update -j +R1fix m.c} command again, @sc{cvs} will attempt to +merge again the changes which you have already merged, +which can have undesirable side effects. + +So instead you need to specify that you only want to +merge the changes on the branch which have not yet been +merged into the trunk. To do that you specify two +@samp{-j} options, and @sc{cvs} merges the changes from +the first revision to the second revision. For +example, in this case the simplest way would be + +@example +cvs update -j 1.2.2.2 -j R1fix m.c # @r{Merge changes from 1.2.2.2 to the} + # @r{head of the R1fix branch} +@end example + +The problem with this is that you need to specify the +1.2.2.2 revision manually. A slightly better approach +might be to use the date the last merge was done: + +@example +cvs update -j R1fix:yesterday -j R1fix m.c +@end example + +Better yet, tag the R1fix branch after every merge into +the trunk, and then use that tag for subsequent merges: + +@example +cvs update -j merged_from_R1fix_to_trunk -j R1fix m.c +@end example + +@c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +@node Merging two revisions +@section Merging differences between any two revisions +@cindex Merging two revisions +@cindex Revisions, merging differences between +@cindex Differences, merging + +With two @samp{-j @var{revision}} flags, the @code{update} +(and @code{checkout}) command can merge the differences +between any two revisions into your working file. + +@cindex Undoing a change +@cindex Removing a change +@example +$ cvs update -j 1.5 -j 1.3 backend.c +@end example + +@noindent +will @emph{remove} all changes made between revision +1.3 and 1.5. Note the order of the revisions! + +If you try to use this option when operating on +multiple files, remember that the numeric revisions will +probably be very different between the various files +that make up a module. You almost always use symbolic +tags rather than revision numbers when operating on +multiple files. + +@c --------------------------------------------------------------------- +@node Recursive behavior +@chapter Recursive behavior +@cindex Recursive (directory descending) +@cindex Directory, descending +@cindex Descending directories +@cindex Subdirectories + +Almost all of the subcommands of @sc{cvs} work +recursively when you specify a directory as an +argument. For instance, consider this directory +structure: + +@example + @code{$HOME} + | + +--@t{tc} + | | + +--@t{CVS} + | (internal @sc{cvs} files) + +--@t{Makefile} + +--@t{backend.c} + +--@t{driver.c} + +--@t{frontend.c} + +--@t{parser.c} + +--@t{man} + | | + | +--@t{CVS} + | | (internal @sc{cvs} files) + | +--@t{tc.1} + | + +--@t{testing} + | + +--@t{CVS} + | (internal @sc{cvs} files) + +--@t{testpgm.t} + +--@t{test2.t} +@end example + +@noindent +If @file{tc} is the current working directory, the +following is true: + +@itemize @bullet +@item +@samp{cvs update testing} is equivalent to @samp{cvs +update testing/testpgm.t testing/test2.t} + +@item +@samp{cvs update testing man} updates all files in the +subdirectories + +@item +@samp{cvs update .} or just @samp{cvs update} updates +all files in the @code{tc} module +@end itemize + +If no arguments are given to @code{update} it will +update all files in the current working directory and +all its subdirectories. In other words, @file{.} is a +default argument to @code{update}. This is also true +for most of the @sc{cvs} subcommands, not only the +@code{update} command. + +The recursive behavior of the @sc{cvs} subcommands can be +turned off with the @samp{-l} option. + +@example +$ cvs update -l # @r{Don't update files in subdirectories} +@end example + +@c --------------------------------------------------------------------- +@node Adding files +@chapter Adding files to a module +@cindex Adding files + +To add a new file to a module, follow these steps. + +@itemize @bullet +@item +You must have a working copy of the module. +@xref{Getting the source}. + +@item +Create the new file inside your working copy of the module. + +@item +Use @samp{cvs add @var{filename}} to tell @sc{cvs} that you +want to version control the file. + +@item +Use @samp{cvs commit @var{filename}} to actually check +in the file into the repository. Other developers +cannot see the file until you perform this step. + +@item +If the file contains binary data it might be necessary +to change the default keyword substitution. +@xref{Keyword substitution}. @xref{admin examples}. +@end itemize + +You can also use the @code{add} command to add a new +directory inside a module. + +Unlike most other commands, the @code{add} command is +not recursive. You cannot even type @samp{cvs add +foo/bar}! Instead, you have to + +@example +$ cd foo +$ cvs add bar +@end example + +@xref{add}, for a more complete description of the @code{add} +command. + +@c --------------------------------------------------------------------- +@node Removing files +@chapter Removing files from a module +@cindex Removing files +@cindex Deleting files + +Modules change. New files are added, and old files +disappear. Still, you want to be able to retrieve an +exact copy of old releases of the module. + +Here is what you can do to remove a file from a module, +but remain able to retrieve old revisions: + +@itemize @bullet +@item +Make sure that you have not made any uncommitted +modifications to the file. @xref{Viewing differences}, +for one way to do that. You can also use the +@code{status} or @code{update} command. If you remove +the file without committing your changes, you will of +course not be able to retrieve the file as it was +immediately before you deleted it. + +@item +Remove the file from your working copy of the module. +You can for instance use @code{rm}. + +@item +Use @samp{cvs remove @var{filename}} to tell @sc{cvs} that +you really want to delete the file. + +@item +Use @samp{cvs commit @var{filename}} to actually +perform the removal of the file from the repository. +@end itemize + +When you commit the removal of the file, @sc{cvs} +records the fact that the file no longer exists. It is +possible for a file to exist on only some branches and +not on others, or to re-add another file with the same +name later. CVS will correctly create or not create +the file, based on the @samp{-r} and @samp{-D} options +specified to @code{checkout} or @code{update}. + +@cindex Remove (subcommand) +@deffn Command {cvs remove} [@code{-lR}] files @dots{} + +Schedule file(s) to be removed from the repository +(files which have not already been removed from the +working directory are not processed). This command +does not actually remove the file from the repository +until you commit the removal. The @samp{-R} option +(the default) specifies that it will recurse into +subdirectories; @samp{-l} specifies that it will not. +@end deffn + +Here is an example of removing several files: + +@example +$ cd test +$ rm ?.c +$ cvs remove +cvs remove: Removing . +cvs remove: scheduling a.c for removal +cvs remove: scheduling b.c for removal +cvs remove: use 'cvs commit' to remove these files permanently +$ cvs ci -m "Removed unneeded files" +cvs commit: Examining . +cvs commit: Committing . +@end example + +If you change your mind you can easily resurrect the +file before you commit it, using the @code{add} +command. + +@example +$ ls +CVS ja.h oj.c +$ rm oj.c +$ cvs remove oj.c +cvs remove: scheduling oj.c for removal +cvs remove: use 'cvs commit' to remove this file permanently +$ cvs add oj.c +U oj.c +cvs add: oj.c, version 1.1.1.1, resurrected +@end example + +If you realize your mistake before you run the +@code{remove} command you can use @code{update} to +resurrect the file: + +@example +$ rm oj.c +$ cvs update oj.c +cvs update: warning: oj.c was lost +U oj.c +@end example + +@c --------------------------------------------------------------------- +@node Tracking sources +@chapter Tracking third-party sources +@cindex Third-party sources +@cindex Tracking sources + +If you modify a program to better fit your site, you +probably want to include your modifications when the next +release of the program arrives. @sc{cvs} can help you with +this task. + +@cindex Vendor +@cindex Vendor branch +@cindex Branch, vendor- +In the terminology used in @sc{cvs}, the supplier of the +program is called a @dfn{vendor}. The unmodified +distribution from the vendor is checked in on its own +branch, the @dfn{vendor branch}. @sc{cvs} reserves branch +1.1.1 for this use. + +When you modify the source and commit it, your revision +will end up on the main trunk. When a new release is +made by the vendor, you commit it on the vendor branch +and copy the modifications onto the main trunk. + +Use the @code{import} command to create and update +the vendor branch. After a successful @code{import} +the vendor branch is made the `head' revision, so +anyone that checks out a copy of the file gets that +revision. When a local modification is committed it is +placed on the main trunk, and made the `head' +revision. + +@menu +* First import:: Importing a module for the first time +* Update imports:: Updating a module with the import command +@end menu + +@c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +@node First import +@section Importing a module for the first time +@cindex Importing modules + +Use the @code{import} command to check in the sources +for the first time. When you use the @code{import} +command to track third-party sources, the @dfn{vendor +tag} and @dfn{release tags} are useful. The +@dfn{vendor tag} is a symbolic name for the branch +(which is always 1.1.1, unless you use the @samp{-b +@var{branch}} flag---@xref{import options}). The +@dfn{release tags} are symbolic names for a particular +release, such as @samp{FSF_0_04}. + +@cindex Wdiff (import example) +Suppose you use @code{wdiff} (a variant of @code{diff} +that ignores changes that only involve whitespace), and +are going to make private modifications that you want +to be able to use even when new releases are made in +the future. You start by importing the source to your +repository: + +@example +$ tar xfz wdiff-0.04.tar.gz +$ cd wdiff-0.04 +$ cvs import -m "Import of FSF v. 0.04" fsf/wdiff FSF_DIST WDIFF_0_04 +@end example + +The vendor tag is named @samp{FSF_DIST} in the above +example, and the only release tag assigned is +@samp{WDIFF_0_04}. + +@c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +@node Update imports +@section Updating a module with the import command + +When a new release of the source arrives, you import it into the +repository with the same @code{import} command that you used to set up +the repository in the first place. The only difference is that you +specify a different release tag this time. + +@example +$ tar xfz wdiff-0.05.tar.gz +$ cd wdiff-0.05 +$ cvs import -m "Import of FSF v. 0.05" fsf/wdiff FSF_DIST WDIFF_0_05 +@end example + +For files that have not been modified locally, the newly created +revision becomes the head revision. If you have made local +changes, @code{import} will warn you that you must merge the changes +into the main trunk, and tell you to use @samp{checkout -j} to do so. + +@example +$ cvs checkout -jFSF_DIST:yesterday -jFSF_DIST wdiff +@end example + +@noindent +The above command will check out the latest revision of +@samp{wdiff}, merging the changes made on the vendor branch @samp{FSF_DIST} +since yesterday into the working copy. If any conflicts arise during +the merge they should be resolved in the normal way (@pxref{Conflicts +example}). Then, the modified files may be committed. + +Using a date, as suggested above, assumes that you do +not import more than one release of a product per +day. If you do, you can always use something like this +instead: + +@example +$ cvs checkout -jWDIFF_0_04 -jWDIFF_0_05 wdiff +@end example + +@noindent +In this case, the two above commands are equivalent. + +@c --------------------------------------------------------------------- +@node Moving files +@chapter Moving and renaming files +@cindex Moving files +@cindex Renaming files +@cindex Files, moving + +Moving files to a different directory or renaming them +is not difficult, but some of the ways in which this +works may be non-obvious. (Moving or renaming a +directory is even harder. @xref{Moving directories}). + +The examples below assume that the file @var{old} is renamed to +@var{new}. + +@menu +* Outside:: The normal way to Rename +* Inside:: A tricky, alternative way +* Rename by copying:: Another tricky, alternative way +@end menu + +@c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +@node Outside +@section The Normal way to Rename + +The normal way to move a file is to copy @var{old} to +@var{new}, and then issue the normal @sc{cvs} commands +to remove @var{old} from the repository, and add +@var{new} to it. (Both @var{old} and @var{new} could +contain relative paths, for example @file{foo/bar.c}). + +@example +$ mv @var{old} @var{new} +$ cvs remove @var{old} +$ cvs add @var{new} +$ cvs commit -m "Renamed @var{old} to @var{new}" @var{old} @var{new} +@end example + +This is the simplest way to move a file, it is not +error-prone, and it preserves the history of what was +done. Note that to access the history of the file you +must specify the old or the new name, depending on what +portion of the history you are accessing. For example, +@code{cvs log @var{old}} will give the log up until the +time of the rename. + +When @var{new} is committed its revision numbers will +start at 1.0 again, so if that bothers you, use the +@samp{-r rev} option to commit (@pxref{commit options}) + +@c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +@node Inside +@section Moving the history file + +This method is more dangerous, since it involves moving +files inside the repository. Read this entire section +before trying it out! + +@example +$ cd $CVSROOT/@var{module} +$ mv @var{old},v @var{new},v +@end example + +@noindent +Advantages: + +@itemize @bullet +@item +The log of changes is maintained intact. + +@item +The revision numbers are not affected. +@end itemize + +@noindent +Disadvantages: + +@itemize @bullet +@item +Old releases of the module cannot easily be fetched from the +repository. (The file will show up as @var{new} even +in revisions from the time before it was renamed). + +@item +There is no log information of when the file was renamed. + +@item +Nasty things might happen if someone accesses the history file +while you are moving it. Make sure no one else runs any of the @sc{cvs} +commands while you move it. +@end itemize + +@c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +@node Rename by copying +@section Copying the history file + +This way also involves direct modifications to the +repository. It is safe, but not without drawbacks. + +@example +# @r{Copy the @sc{rcs} file inside the repository} +$ cd $CVSROOT/@var{module} +$ cp @var{old},v @var{new},v +# @r{Remove the old file} +$ cd ~/@var{module} +$ rm @var{old} +$ cvs remove @var{old} +$ cvs commit @var{old} +# @r{Remove all tags from @var{new}} +$ cvs update @var{new} +$ cvs log @var{new} # @r{Remember the tag names} +$ cvs tag -d @var{tag1} +$ cvs tag -d @var{tag2} +@dots{} +@end example + +By removing the tags you will be able to check out old +revisions of the module. + +@noindent +Advantages: + +@itemize @bullet +@item +@c FIXME: Is this true about -D now that we have death +@c support? See 5B.3 in the FAQ. +Checking out old revisions works correctly, as long as +you use @samp{-r@var{tag}} and not @samp{-D@var{date}} +to retrieve the revisions. + +@item +The log of changes is maintained intact. + +@item +The revision numbers are not affected. +@end itemize + +@noindent +Disadvantages: + +@itemize @bullet +@item +You cannot easily see the history of the file across the rename. + +@item +Unless you use the @samp{-r rev} (@pxref{commit +options}) flag when @var{new} is committed its revision +numbers will start at 1.0 again. +@end itemize + +@c --------------------------------------------------------------------- +@node Moving directories +@chapter Moving and renaming directories +@cindex Moving directories +@cindex Renaming directories +@cindex Directories, moving + +If you want to be able to retrieve old versions of the +module, you must move each file in the directory +with the @sc{cvs} commands. @xref{Outside}. The old, empty +directory will remain inside the repository, but it +will not appear in your workspace when you check out +the module in the future. +@c -- rephrase + +If you really want to rename or delete a directory, you +can do it like this: + +@enumerate +@item +Inform everyone who has a copy of the module that the +directory will be renamed. They should commit all +their changes, and remove their working copies of the +module, before you take the steps below. + +@item +Rename the directory inside the repository. + +@example +$ cd $CVSROOT/@var{module} +$ mv @var{old-dir} @var{new-dir} +@end example + +@item +Fix the @sc{cvs} administrative files, if necessary (for +instance if you renamed an entire module). + +@item +Tell everyone that they can check out the module and continue +working. + +@end enumerate + +If someone had a working copy of the module the @sc{cvs} commands will +cease to work for him, until he removes the directory +that disappeared inside the repository. + +It is almost always better to move the files in the +directory instead of moving the directory. If you move the +directory you are unlikely to be able to retrieve old +releases correctly, since they probably depend on the +name of the directories. + +@c --------------------------------------------------------------------- +@node History browsing +@chapter History browsing +@cindex History browsing +@cindex Traceability +@cindex Isolation + +@ignore +@c This is too long for an introduction (goal is +@c one 20x80 character screen), and also mixes up a +@c variety of issues (parallel development, history, +@c maybe even touches on process control). + +@c -- @quote{To lose ones history is to lose ones soul.} +@c -- /// +@c -- ///Those who cannot remember the past are condemned to repeat it. +@c -- /// -- George Santayana +@c -- /// + +@sc{cvs} tries to make it easy for a group of people to work +together. This is done in two ways: + +@itemize @bullet +@item +Isolation---You have your own working copy of the +source. You are not affected by modifications made by +others until you decide to incorporate those changes +(via the @code{update} command---@pxref{update}). + +@item +Traceability---When something has changed, you can +always see @emph{exactly} what changed. +@end itemize + +There are several features of @sc{cvs} that together lead +to traceability: + +@itemize @bullet +@item +Each revision of a file has an accompanying log +message. + +@item +All commits are optionally logged to a central history +database. + +@item +Logging information can be sent to a user-defined +program (@pxref{loginfo}). +@end itemize + +@c -- More text here. + +This chapter should talk about the history file, the +@code{log} command, the usefulness of ChangeLogs +even when you run @sc{cvs}, and things like that. + +@end ignore + +@c kind of lame, in a lot of ways the above text inside +@c the @ignore motivates this chapter better +Once you have used @sc{cvs} to store a version control +history---what files have changed when, how, and by +whom, there are a variety of mechanisms for looking +through the history. + +@menu +* log messages:: Log messages +* history database:: The history database +* user-defined logging:: User-defined logging +* annotate:: What revision modified each line of a file? +@end menu + +@c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +@node log messages +@section Log messages + +@c FIXME: @xref to place where we talk about how to +@c specify message to commit. +Whenever you commit a file you specify a log message. + +@c FIXME: bring the information here, and get rid of or +@c greatly shrink the "log" node. +To look through the log messages which have been +specified for every revision which has been committed, +use the @code{cvs log} command (@pxref{log}). + +@c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +@node history database +@section The history database + +@c FIXME: bring the information from the history file +@c and history nodes here. Rewrite it to be motivated +@c better (start out by clearly explaining what gets +@c logged in history, for example). +You can use the history file (@pxref{history file}) to +log various @sc{cvs} actions. To retrieve the +information from the history file, use the @code{cvs +history} command (@pxref{history}). + +@c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +@node user-defined logging +@section User-defined logging + +@c FIXME: should probably also mention the fact the -l +@c global option can disable most of the mechanisms +@c discussed here (why? What is the -l global option for?). +@c +@c FIXME: probably should centralize this information +@c here, at least to some extent. Maybe by moving the +@c loginfo, etc., nodes here and replacing +@c the "user-defined logging" node with one node for +@c each method. +You can customize @sc{cvs} to log various kinds of +actions, in whatever manner you choose. These +mechanisms operate by executing a script at various +times. The script might append a message to a file +listing the information and the programmer who created +it, or send mail to a group of developers, or, perhaps, +post a message to a particular newsgroup. To log +commits, use the @file{loginfo} file (@pxref{loginfo}). +@c FIXME: What is difference between doing it in the +@c modules file and using loginfo/taginfo? Why should +@c user use one or the other? +To log commits, checkouts, exports, and tags, +respectively, you can also use the @samp{-i}, +@samp{-o}, @samp{-e}, and @samp{-t} options in the +modules file. For a more flexible way of giving +notifications to various users, which requires less in +the way of keeping centralized scripts up to date, use +the @code{cvs watch add} command (@pxref{Getting +Notified}); this command is useful even if you are not +using @code{cvs watch on}. + +@cindex taginfo +The @file{taginfo} file defines programs to execute +when someone executes a @code{tag} or @code{rtag} +command. The @file{taginfo} file has the standard form +for administrative files (@pxref{Administrative +files}), where each line is a regular expression +followed by a command to execute. The arguments passed +to the command are, in order, the @var{tagname}, +@var{operation} (@code{add} for @code{tag}, +@code{mov} for @code{tag -F}, and @code{del} for +@code{tag -d}), @var{repository}, and any remaining are +pairs of @var{filename} @var{revision}. A non-zero +exit of the filter program will cause the tag to be +aborted. + +@node annotate +@section Annotate command +@cindex annotate (subcommand) + +@deffn Command {cvs annotate} [@code{-l}] files @dots{} + +For each file in @var{files}, print the head revision +of the trunk, together with information on the last +modification for each line. The @code{-l} option means +to process the local directory only, not to recurse +(@pxref{Common options}). For example: + +@example +$ cvs annotate ssfile +Annotations for ssfile +*************** +1.1 (mary 27-Mar-96): ssfile line 1 +1.2 (joe 28-Mar-96): ssfile line 2 +@end example + +The file @file{ssfile} currently contains two lines. +The @code{ssfile line 1} line was checked in by +@code{mary} on March 27. Then, on March 28, @code{joe} +added a line @code{ssfile line 2}, without modifying +the @code{ssfile line 1} line. This report doesn't +tell you anything about lines which have been deleted +or replaced; you need to use @code{cvs diff} for that +(@pxref{diff}). + +@end deffn + +@c --------------------------------------------------------------------- +@node Keyword substitution +@chapter Keyword substitution +@cindex Keyword substitution +@cindex Keyword expansion +@cindex Identifying files + +@comment Be careful when editing this chapter. +@comment Remember that this file is kept under +@comment version control, so we must not accidentally +@comment include a valid keyword in the running text. + +As long as you edit source files inside your working +copy of a module you can always find out the state of +your files via @samp{cvs status} and @samp{cvs log}. +But as soon as you export the files from your +development environment it becomes harder to identify +which revisions they are. + +@sc{Rcs} uses a mechanism known as @dfn{keyword +substitution} (or @dfn{keyword expansion}) to help +identifying the files. Embedded strings of the form +@code{$@var{keyword}$} and +@code{$@var{keyword}:@dots{}$} in a file are replaced +with strings of the form +@code{$@var{keyword}:@var{value}$} whenever you obtain +a new revision of the file. + +@menu +* Keyword list:: RCS Keywords +* Using keywords:: Using keywords +* Avoiding substitution:: Avoiding substitution +* Substitution modes:: Substitution modes +* Log keyword:: Problems with the $@asis{}Log$ keyword. +@end menu + +@c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +@node Keyword list +@section RCS Keywords +@cindex RCS keywords + +This is a list of the keywords that @sc{rcs} currently +(in release 5.6.0.1) supports: + +@table @code +@cindex Author keyword +@item $@asis{Author}$ +The login name of the user who checked in the revision. + +@cindex Date keyword +@item $@asis{Date}$ +The date and time (UTC) the revision was checked in. + +@cindex Header keyword +@item $@asis{Header}$ +A standard header containing the full pathname of the +@sc{rcs} file, the revision number, the date (UTC), the +author, the state, and the locker (if locked). Files +will normally never be locked when you use @sc{cvs}. + +@cindex Id keyword +@item $@asis{Id}$ +Same as @code{$@asis{Header}$}, except that the @sc{rcs} +filename is without a path. + +@cindex Locker keyword +@item $@asis{Locker}$ +The login name of the user who locked the revision +(empty if not locked, and thus almost always useless +when you are using @sc{cvs}). + +@cindex Log keyword +@item $@asis{Log}$ +The log message supplied during commit, preceded by a +header containing the @sc{rcs} filename, the revision +number, the author, and the date (UTC). Existing log +messages are @emph{not} replaced. Instead, the new log +message is inserted after @code{$@asis{Log:@dots{}}$}. +Each new line is prefixed with a @dfn{comment leader} +which @sc{rcs} guesses from the file name extension. +It can be changed with @code{cvs admin -c}. +@xref{admin options}. This keyword is useful for +accumulating a complete change log in a source file, +but for several reasons it can be problematic. +@xref{Log keyword}. + +@cindex RCSfile keyword +@item $@asis{RCSfile}$ +The name of the RCS file without a path. + +@cindex Revision keyword +@item $@asis{Revision}$ +The revision number assigned to the revision. + +@cindex Source keyword +@item $@asis{Source}$ +The full pathname of the RCS file. + +@cindex State keyword +@item $@asis{State}$ +The state assigned to the revision. States can be +assigned with @code{cvs admin -s}---@xref{admin options}. + +@end table + +@c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +@node Using keywords +@section Using keywords + +To include a keyword string you simply include the +relevant text string, such as @code{$@asis{Id}$}, inside the +file, and commit the file. @sc{cvs} will automatically +expand the string as part of the commit operation. + +@need 800 +It is common to embed @code{$@asis{}Id$} string in the +C source code. This example shows the first few lines +of a typical file, after keyword substitution has been +performed: + +@example +static char *rcsid="$@asis{}Id: samp.c,v 1.5 1993/10/19 14:57:32 ceder Exp $"; +/* @r{The following lines will prevent @code{gcc} version 2.@var{x}} + @r{from issuing an "unused variable" warning}. */ +#if __GNUC__ == 2 +#define USE(var) static void * use_##var = (&use_##var, (void *) &var) +USE (rcsid); +#endif +@end example + +Even though a clever optimizing compiler could remove +the unused variable @code{rcsid}, most compilers tend +to include the string in the binary. Some compilers +have a @code{#pragma} directive to include literal text +in the binary. + +@cindex Ident (shell command) +The @code{ident} command (which is part of the @sc{rcs} +package) can be used to extract keywords and their +values from a file. This can be handy for text files, +but it is even more useful for extracting keywords from +binary files. + +@example +$ ident samp.c +samp.c: + $@asis{}Id: samp.c,v 1.5 1993/10/19 14:57:32 ceder Exp $ +$ gcc samp.c +$ ident a.out +a.out: + $@asis{}Id: samp.c,v 1.5 1993/10/19 14:57:32 ceder Exp $ +@end example + +@cindex What (shell command) +S@sc{ccs} is another popular revision control system. +It has a command, @code{what}, which is very similar to +@code{ident} and used for the same purpose. Many sites +without @sc{rcs} have @sc{sccs}. Since @code{what} +looks for the character sequence @code{@@(#)} it is +easy to include keywords that are detected by either +command. Simply prefix the @sc{rcs} keyword with the +magic @sc{sccs} phrase, like this: + +@example +static char *id="@@(#) $@asis{}Id: ab.c,v 1.5 1993/10/19 14:57:32 ceder Exp $"; +@end example + +@c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +@node Avoiding substitution +@section Avoiding substitution + +Keyword substitution has its disadvantages. Sometimes +you might want the literal text string +@samp{$@asis{}Author$} to appear inside a file without +@sc{rcs} interpreting it as a keyword and expanding it +into something like @samp{$@asis{}Author: ceder $}. + +There is unfortunately no way to selectively turn off +keyword substitution. You can use @samp{-ko} +(@pxref{Substitution modes}) to turn off keyword +substitution entirely. + +In many cases you can avoid using @sc{rcs} keywords in +the source, even though they appear in the final +product. For example, the source for this manual +contains @samp{$@@asis@{@}Author$} whenever the text +@samp{$@asis{}Author$} should appear. In @code{nroff} +and @code{troff} you can embed the null-character +@code{\&} inside the keyword for a similar effect. + +@c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +@node Substitution modes +@section Substitution modes +@cindex -k (RCS kflags) +@cindex Kflag + +@c FIXME: This could be made more coherent, by expanding it +@c with more examples or something. +Each file has a stored default substitution mode, and +each working directory copy of a file also has a +substitution mode. The former is set by the @samp{-k} +option to @code{cvs add} and @code{cvs admin}; the +latter is set by the -k or -A options to @code{cvs +checkout} or @code{cvs update}. @code{cvs diff} also +has a @samp{-k} option. For some examples, +@xref{Binary files}. + +The modes available are: + +@table @samp +@item -kkv +Generate keyword strings using the default form, e.g. +@code{$@asis{}Revision: 5.7 $} for the @code{Revision} +keyword. + +@item -kkvl +Like @samp{-kkv}, except that a locker's name is always +inserted if the given revision is currently locked. +This option is normally not useful when @sc{cvs} is used. + +@item -kk +Generate only keyword names in keyword strings; omit +their values. For example, for the @code{Revision} +keyword, generate the string @code{$@asis{}Revision$} +instead of @code{$@asis{}Revision: 5.7 $}. This option +is useful to ignore differences due to keyword +substitution when comparing different revisions of a +file. + +@item -ko +Generate the old keyword string, present in the working +file just before it was checked in. For example, for +the @code{Revision} keyword, generate the string +@code{$@asis{}Revision: 1.1 $} instead of +@code{$@asis{}Revision: 5.7 $} if that is how the +string appeared when the file was checked in. + +@item -kb +Like @samp{-ko}, but also inhibit conversion of line +endings between the canonical form in which they are +stored in the repository (linefeed only), and the form +appropriate to the operating system in use on the +client. For systems, like unix, which use linefeed +only to terminate lines, this is the same as +@samp{-ko}. For more information on binary files, see +@ref{Binary files}. + +@item -kv +Generate only keyword values for keyword strings. For +example, for the @code{Revision} keyword, generate the string +@code{5.7} instead of @code{$@asis{}Revision: 5.7 $}. +This can help generate files in programming languages +where it is hard to strip keyword delimiters like +@code{$@asis{}Revision: $} from a string. However, +further keyword substitution cannot be performed once +the keyword names are removed, so this option should be +used with care. + +One often would like to use @samp{-kv} with @code{cvs +export}---@pxref{export}. But be aware that doesn't +handle an export containing binary files correctly. + +@end table + +@c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +@node Log keyword +@section Problems with the $@asis{}Log$ keyword. + +The @code{$@asis{}Log$} keyword is somewhat +controversial. As long as you are working on your +development system the information is easily accessible +even if you do not use the @code{$@asis{}Log$} +keyword---just do a @code{cvs log}. Once you export +the file the history information might be useless +anyhow. + +A more serious concern is that @sc{rcs} is not good at +handling @code{$@asis{}Log$} entries when a branch is +merged onto the main trunk. Conflicts often result +from the merging operation. + +People also tend to "fix" the log entries in the file +(correcting spelling mistakes and maybe even factual +errors). If that is done the information from +@code{cvs log} will not be consistent with the +information inside the file. This may or may not be a +problem in real life. + +It has been suggested that the @code{$@asis{}Log$} +keyword should be inserted @emph{last} in the file, and +not in the files header, if it is to be used at all. +That way the long list of change messages will not +interfere with everyday source file browsing. + +@c --------------------------------------------------------------------- +@node Binary files +@chapter Handling binary files +@cindex Binary files + +There are two issues with using @sc{cvs} to store +binary files. The first is that @sc{cvs} by default +convert line endings between the canonical form in +which they are stored in the repository (linefeed +only), and the form appropriate to the operating system +in use on the client (for example, carriage return +followed by line feed for Windows NT). + +The second is that a binary file might happen to +contain data which looks like a keyword (@pxref{Keyword +substitution}), so keyword expansion must be turned +off. + +The @samp{-kb} option available with some @sc{cvs} +commands insures that neither line ending conversion +nor keyword expansion will be done. If you are using +an old version of @sc{rcs} without this option, and you +are using an operating system, such as unix, which +terminates lines with linefeeds only, you can use +@samp{-ko} instead; if you are on another operating +system, upgrade to a version of @sc{rcs}, such as 5.7 +or later, which supports @samp{-kb}. + +Here is an example of how you can create a new file +using the @samp{-kb} flag: + +@example +$ echo '$@asis{}Id$' > kotest +$ cvs add -kb -m"A test file" kotest +$ cvs ci -m"First checkin; contains a keyword" kotest +@end example + +If a file accidentally gets added without @samp{-kb}, +one can use the @code{cvs admin} command to recover. +For example: + +@example +$ echo '$@asis{}Id$' > kotest +$ cvs add -m"A test file" kotest +$ cvs ci -m"First checkin; contains a keyword" kotest +$ cvs admin -kb kotest +$ cvs update -A kotest +@end example + +When you check in the file @file{kotest} the keywords +are expanded. (Try the above example, and do a +@code{cat kotest} after every command). The @code{cvs +admin -kb} command sets the default keyword +substitution method for this file, but it does not +alter the working copy of the file that you have. The +easiest way to get the unexpanded version of +@file{kotest} is @code{cvs update -A}. + +@c --------------------------------------------------------------------- +@node Revision management +@chapter Revision management +@cindex Revision management + +@c -- This chapter could be expanded a lot. +@c -- Experiences are very welcome! + +If you have read this far, you probably have a pretty +good grasp on what @sc{cvs} can do for you. This +chapter talks a little about things that you still have +to decide. + +If you are doing development on your own using @sc{cvs} +you could probably skip this chapter. The questions +this chapter takes up become more important when more +than one person is working in a repository. + +@menu +* When to commit:: Some discussion on the subject +@end menu + +@c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +@node When to commit +@section When to commit? +@cindex When to commit +@cindex Commit, when to +@cindex Policy + +Your group should decide which policy to use regarding +commits. Several policies are possible, and as your +experience with @sc{cvs} grows you will probably find +out what works for you. + +If you commit files too quickly you might commit files +that do not even compile. If your partner updates his +working sources to include your buggy file, he will be +unable to compile the code. On the other hand, other +persons will not be able to benefit from the +improvements you make to the code if you commit very +seldom, and conflicts will probably be more common. + +It is common to only commit files after making sure +that they can be compiled. Some sites require that the +files pass a test suite. Policies like this can be +enforced using the commitinfo file +(@pxref{commitinfo}), but you should think twice before +you enforce such a convention. By making the +development environment too controlled it might become +too regimented and thus counter-productive to the real +goal, which is to get software written. + +@c --------------------------------------------------------------------- +@node Invoking CVS +@appendix Reference manual for CVS commands +@cindex Command reference +@cindex Reference, commands +@cindex Invoking CVS + +This appendix describes how to invoke @sc{cvs}, and +describes in detail those subcommands of @sc{cvs} which +are not fully described elsewhere. To look up a +particular subcommand, see @ref{Index}. + +@menu +* Structure:: Overall structure of CVS commands +* ~/.cvsrc:: Default options with the ~/.csvrc file +* Global options:: Options you give to the left of cvs_command +* Common options:: Options you give to the right of cvs_command +* add:: Add a new file/directory to the repository +* admin:: Administration front end for rcs +* checkout:: Checkout sources for editing +* commit:: Check files into the repository +* diff:: Run diffs between revisions +* export:: Export sources from CVS, similar to checkout +* history:: Show status of files and users +* import:: Import sources into CVS, using vendor branches +* log:: Print out 'rlog' information for files +* rdiff:: 'patch' format diffs between releases +* release:: Indicate that a Module is no longer in use +* rtag:: Add a tag to a module +* status:: Status info on the revisions +* tag:: Add a tag to checked out version +* update:: Bring work tree in sync with repository +@end menu + +@c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +@node Structure +@appendixsec Overall structure of CVS commands +@cindex Structure +@cindex CVS command structure +@cindex Command structure +@cindex Format of CVS commands + +The first release of @sc{cvs} consisted of a number of shell-scripts. +Today @sc{cvs} is implemented as a single program that is a front-end +to @sc{rcs} and @code{diff}. The overall format of all +@sc{cvs} commands is: + +@example +cvs [ cvs_options ] cvs_command [ command_options ] [ command_args ] +@end example + +@table @code +@item cvs +The program that is a front-end to @sc{rcs}. + +@item cvs_options +Some options that affect all sub-commands of @sc{cvs}. These are +described below. + +@item cvs_command +One of several different sub-commands. Some of the commands have +aliases that can be used instead; those aliases are noted in the +reference manual for that command. There are only two situations +where you may omit @samp{cvs_command}: @samp{cvs -H} elicits a +list of available commands, and @samp{cvs -v} displays version +information on @sc{cvs} itself. + +@item command_options +Options that are specific for the command. + +@item command_args +Arguments to the commands. +@end table + +There is unfortunately some confusion between +@code{cvs_options} and @code{command_options}. +@samp{-l}, when given as a @code{cvs_option}, only +affects some of the commands. When it is given as a +@code{command_option} is has a different meaning, and +is accepted by more commands. In other words, do not +take the above categorization too seriously. Look at +the documentation instead. + +@c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +@node ~/.cvsrc +@appendixsec Default options and the ~/.cvsrc file +@cindex .cvsrc file +@cindex option defaults + +There are some @code{command_options} that are used so +often that you might have set up an alias or some other +means to make sure you always specify that option. One +example (the one that drove the implementation of the +.cvsrc support, actually) is that many people find the +default output of the @samp{diff} command to be very +hard to read, and that either context diffs or unidiffs +are much easier to understand. + +The @file{~/.cvsrc} file is a way that you can add +default options to @code{cvs_commands} within cvs, +instead of relying on aliases or other shell scripts. + +The format of the @file{~/.cvsrc} file is simple. The +file is searched for a line that begins with the same +name as the @code{cvs_command} being executed. If a +match is found, then the remainder of the line is split +up (at whitespace characters) into separate options and +added to the command arguments @emph{before} any +options from the command line. + +If a command has two names (e.g., @code{checkout} and +@code{co}), the official name, not necessarily the one +used on the command line, will be used to match against +the file. So if this is the contents of the user's +@file{~/.cvsrc} file: + +@example +log -N +diff -u +update -P +co -P +@end example + +@noindent +the command @samp{cvs checkout foo} would have the +@samp{-P} option added to the arguments, as well as +@samp{cvs co foo}. + +With the example file above, the output from @samp{cvs +diff foobar} will be in unidiff format. @samp{cvs diff +-c foobar} will provide context diffs, as usual. +Getting "old" format diffs would be slightly more +complicated, because @code{diff} doesn't have an option +to specify use of the "old" format, so you would need +@samp{cvs -f diff foobar}. + +In place of the command name you can use @code{cvs} to +specify global options (@pxref{Global options}). For +example the following line in @file{.cvsrc} + +@example +cvs -z6 +@end example + +causes @sc{cvs} to use compression level 6 + +@c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +@node Global options +@appendixsec Global options +@cindex Options, global +@cindex Global options +@cindex Left-hand options + +The available @samp{cvs_options} (that are given to the +left of @samp{cvs_command}) are: + +@table @code +@cindex RCSBIN, overriding +@cindex Overriding RCSBIN +@item -b @var{bindir} +Use @var{bindir} as the directory where @sc{rcs} programs are +located. Overrides the setting of the @code{$RCSBIN} environment +variable and any precompiled directory. This parameter should be +specified as an absolute pathname. + +@cindex CVSROOT, overriding +@cindex Overriding CVSROOT +@item -d @var{cvs_root_directory} +Use @var{cvs_root_directory} as the root directory +pathname of the repository. Overrides the setting of +the @code{$CVSROOT} environment variable. @xref{Repository}. + +@cindex EDITOR, overriding +@cindex Overriding EDITOR +@item -e @var{editor} +Use @var{editor} to enter revision log information. Overrides the +setting of the @code{$CVSEDITOR} and @code{$EDITOR} environment variables. + +@item -f +Do not read the @file{~/.cvsrc} file. This +option is most often used because of the +non-orthogonality of the @sc{cvs} option set. For +example, the @samp{cvs log} option @samp{-N} (turn off +display of tag names) does not have a corresponding +option to turn the display on. So if you have +@samp{-N} in the @file{~/.cvsrc} entry for @samp{diff}, +you may need to use @samp{-f} to show the tag names. +@footnote{Yes, this really should be fixed, and it's +being worked on} + +@item -H +Display usage information about the specified @samp{cvs_command} +(but do not actually execute the command). If you don't specify +a command name, @samp{cvs -H} displays a summary of all the +commands available. + +@item -l +Do not log the cvs_command in the command history (but execute it +anyway). @xref{history}, for information on command history. + +@cindex Read-only mode +@item -n +Do not change any files. Attempt to execute the +@samp{cvs_command}, but only to issue reports; do not remove, +update, or merge any existing files, or create any new files. + +@item -Q +Cause the command to be really quiet; the command will only +generate output for serious problems. + +@item -q +Cause the command to be somewhat quiet; informational messages, +such as reports of recursion through subdirectories, are +suppressed. + +@cindex Read-only files +@item -r +Make new working files files read-only. Same effect +as if the @code{$CVSREAD} environment variable is set +(@pxref{Environment variables}). The default is to +make working files writable, unless watches are on +(@pxref{Watches}). + +@item -s @var{variable}=@var{value} +Set a user variable (@pxref{Variables}). + +@cindex Trace +@item -t +Trace program execution; display messages showing the steps of +@sc{cvs} activity. Particularly useful with @samp{-n} to explore the +potential impact of an unfamiliar command. + +@item -v +Display version and copyright information for @sc{cvs}. + +@cindex CVSREAD, overriding +@cindex Overriding CVSREAD +@item -w +Make new working files read-write. Overrides the +setting of the @code{$CVSREAD} environment variable. +Files are created read-write by default, unless @code{$CVSREAD} is +set or @samp{-r} is given. + +@item -z @var{gzip-level} +Set the compression level. Only has an effect on the +@sc{cvs} client. + +@end table + +@c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +@node Common options +@appendixsec Common command options +@cindex Common options +@cindex Right-hand options + +This section describes the @samp{command_options} that +are available across several @sc{cvs} commands. These +options are always given to the right of +@samp{cvs_command}. Not all +commands support all of these options; each option is +only supported for commands where it makes sense. +However, when a command has one of these options you +can almost always count on the same behavior of the +option as in other commands. (Other command options, +which are listed with the individual commands, may have +different behavior from one @sc{cvs} command to the other). + +@strong{Warning:} the @samp{history} command is an exception; it supports +many options that conflict even with these standard options. + +@table @code +@cindex Dates +@cindex Time +@cindex Specifying dates +@item -D @var{date_spec} +Use the most recent revision no later than @var{date_spec}. +@var{date_spec} is a single argument, a date description +specifying a date in the past. + +The specification is @dfn{sticky} when you use it to make a +private copy of a source file; that is, when you get a working +file using @samp{-D}, @sc{cvs} records the date you specified, so that +further updates in the same directory will use the same date +(for more information on sticky tags/dates, @pxref{Sticky tags}). + +A wide variety of date formats are supported by the underlying +@sc{rcs} facilities, similar to those described in co(1), but not +exactly the same. The @var{date_spec} is interpreted as being +in the local timezone, unless a specific timezone is specified. +Examples of valid date specifications include: + +@example + 1 month ago + 2 hours ago + 400000 seconds ago + last year + last Monday + yesterday + a fortnight ago + 3/31/92 10:00:07 PST + January 23, 1987 10:05pm + 22:00 GMT +@end example + +@samp{-D} is available with the @code{checkout}, +@code{diff}, @code{export}, @code{history}, +@code{rdiff}, @code{rtag}, and @code{update} commands. +(The @code{history} command uses this option in a +slightly different way; @pxref{history options}). + +Remember to quote the argument to the @samp{-D} +flag so that your shell doesn't interpret spaces as +argument separators. A command using the @samp{-D} +flag can look like this: + +@example +$ cvs diff -D "1 hour ago" cvs.texinfo +@end example + +@cindex Forcing a tag match +@item -f +When you specify a particular date or tag to @sc{cvs} commands, they +normally ignore files that do not contain the tag (or did not +exist prior to the date) that you specified. Use the @samp{-f} option +if you want files retrieved even when there is no match for the +tag or date. (The most recent revision of the file +will be used). + +@need 800 +@samp{-f} is available with these commands: @code{checkout}, +@code{export}, @code{rdiff}, @code{rtag}, and @code{update}. + +@strong{Warning:} The @code{commit} command also has a +@samp{-f} option, but it has a different behavior for +that command. @xref{commit options}. + +@item -H +Help; describe the options available for this command. This is +the only option supported for all @sc{cvs} commands. + +@item -k @var{kflag} +Alter the default @sc{rcs} processing of keywords. +@xref{Keyword substitution}, for the meaning of +@var{kflag}. Your @var{kflag} specification is +@dfn{sticky} when you use it to create a private copy +of a source file; that is, when you use this option +with the @code{checkout} or @code{update} commands, +@sc{cvs} associates your selected @var{kflag} with the +file, and continues to use it with future update +commands on the same file until you specify otherwise. + +The @samp{-k} option is available with the @code{add}, +@code{checkout}, @code{diff} and +@code{update} commands. + +@item -l +Local; run only in current working directory, rather than +recursing through subdirectories. + +@strong{Warning:} this is not the same +as the overall @samp{cvs -l} option, which you can specify to the +left of a cvs command! + +Available with the following commands: @code{checkout}, +@code{commit}, @code{diff}, @code{export}, @code{log}, +@code{remove}, @code{rdiff}, @code{rtag}, +@code{status}, @code{tag}, and @code{update}. + +@cindex Editor, avoiding invocation of +@cindex Avoiding editor invocation +@item -m @var{message} +Use @var{message} as log information, instead of +invoking an editor. + +Available with the following commands: @code{add}, +@code{commit} and @code{import}. + +@item -n +Do not run any checkout/commit/tag program. (A program can be +specified to run on each of these activities, in the modules +database (@pxref{modules}); this option bypasses it). + +@strong{Warning:} this is not the same as the overall @samp{cvs -n} +option, which you can specify to the left of a cvs command! + +Available with the @code{checkout}, @code{commit}, @code{export}, +and @code{rtag} commands. + +@item -P +Prune (remove) directories that are empty after being updated, on +@code{checkout}, or @code{update}. Normally, an empty directory +(one that is void of revision-controlled files) is left alone. +Specifying @samp{-P} will cause these directories to be silently +removed from your checked-out sources. This does not remove the +directory from the repository, only from your checked out copy. +Note that this option is implied by the @samp{-r} or @samp{-D} +options of @code{checkout} and @code{export}. +@c -- implied-- + +@item -p +Pipe the files retrieved from the repository to standard output, +rather than writing them in the current directory. Available +with the @code{checkout} and @code{update} commands. + +@item -W +Specify file names that should be filtered. You can +use this option repeatedly. The spec can be a file +name pattern of the same type that you can specify in +the @file{.cvswrappers} file. +Avaliable with the following commands: @code{import}, +and @code{update}. + +@item -r @var{tag} +Use the revision specified by the @var{tag} argument instead of the +default @dfn{head} revision. As well as arbitrary tags defined +with the @code{tag} or @code{rtag} command, two special tags are +always available: @samp{HEAD} refers to the most recent version +available in the repository, and @samp{BASE} refers to the +revision you last checked out into the current working directory. + +The tag specification is sticky when you use this option +with @code{checkout} or @code{update} to make your own +copy of a file: @sc{cvs} remembers the tag and continues to use it on +future update commands, until you specify otherwise (for more information +on sticky tags/dates, @pxref{Sticky tags}). The +tag can be either a symbolic or numeric tag. +@xref{Tags}. + +Specifying the @samp{-q} global option along with the +@samp{-r} command option is often useful, to suppress +the warning messages when the @sc{rcs} history file +does not contain the specified tag. + +@strong{Warning:} this is not the same as the overall `cvs -r' option, +which you can specify to the left of a cvs command! + +@samp{-r} is available with the @code{checkout}, @code{commit}, +@code{diff}, @code{history}, @code{export}, @code{rdiff}, +@code{rtag}, and @code{update} commands. + +@end table + +@c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +@node add +@appendixsec add---Add a new file/directory to the repository +@cindex Add (subcommand) + +@itemize @bullet +@item +Synopsis: add [-k kflag] [-m 'message'] files@dots{} +@item +Requires: repository, working directory. +@item +Changes: working directory. +@item +Synonym: new +@end itemize + +Use the @code{add} command to create a new file or directory in the +source repository. The files or directories specified with @code{add} +must already exist in the current directory (which must have been +created with the @code{checkout} command). To add a whole new directory +hierarchy to the source repository (for example, files received +from a third-party vendor), use the @code{import} command +instead. @xref{import}. + +If the argument to @code{add} refers to an immediate +sub-directory, the directory is created at the correct place in +the source repository, and the necessary @sc{cvs} administration +files are created in your working directory. If the directory +already exists in the source repository, @code{add} still creates +the administration files in your version of the directory. +This allows you to use @code{add} to add a particular directory +to your private sources even if someone else created that +directory after your checkout of the sources. You can do the +following: + +@example +$ mkdir new_directory +$ cvs add new_directory +$ cvs update new_directory +@end example + +An alternate approach using @code{update} might be: + +@example +$ cvs update -d new_directory +@end example + +(To add any available new directories to your working directory, +it's probably simpler to use @code{checkout} (@pxref{checkout}) +or @samp{update -d} (@pxref{update})). + +The added files are not placed in the source repository until you +use @code{commit} to make the change permanent. Doing an +@code{add} on a file that was removed with the @code{remove} +command will resurrect the file, unless a @code{commit} command +intervened. +@xref{Removing files}, for an example. + + +Unlike most other commands @code{add} never recurses down +directories. It cannot yet handle relative paths. Instead of + +@example +$ cvs add foo/bar.c +@end example + +you have to do + +@example +$ cd foo +$ cvs add bar.c +@end example + +@menu +* add options:: add options +* add examples:: add examples +@end menu + +@c . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . +@node add options +@appendixsubsec add options +@cindex Add options + +There are only two options you can give to @samp{add}: + +@table @code +@item -k @var{kflag} +This option specifies the default way that this file +will be checked out. The @var{kflag} argument +(@pxref{Substitution modes}) is stored in the @sc{rcs} +file and can be changed with @code{admin -k} +(@pxref{admin options}). See @ref{Binary files}, for +information on using this option for binary files. + +@item -m @var{description} +Using this option, you can give a description for the file. This +description appears in the history log (if it is enabled, +@pxref{history file}). It will also be saved in the @sc{rcs} history +file inside the repository when the file is committed. The +@code{log} command displays this description. + +The description can be changed using @samp{admin -t}. +@xref{admin}. + +If you omit the @samp{-m @var{description}} flag, an empty string will be +used. You will not be prompted for a description. +@end table + +@c . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . +@node add examples +@appendixsubsec add examples + +To add the file @file{backend.c} to the repository, with a +description, the following can be used. + +@example +$ cvs add -m "Optimizer and code generation passes." backend.c +$ cvs commit -m "Early version. Not yet compilable." backend.c +@end example + +@c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +@node admin +@appendixsec admin---Administration front end for rcs +@cindex Admin (subcommand) + +@itemize @bullet +@item +Requires: repository, working directory. +@item +Changes: repository. +@item +Synonym: rcs +@end itemize + +This is the @sc{cvs} interface to assorted administrative @sc{rcs} +facilities, documented in rcs(1). @code{admin} simply passes +all its options and arguments to the @code{rcs} command; it does +no filtering or other processing. This command @emph{does} work +recursively, however, so extreme care should be used. + +If there is a group whose name matches a compiled in +value which defaults to @code{cvsadmin}, only members +of that group can use @code{cvs admin}. To disallow +@code{cvs admin} for all users, create a group with no +users in it. + +@menu +* admin options:: admin options +* admin examples:: admin examples +@end menu + +@c . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . +@node admin options +@appendixsubsec admin options + +Not all valid @code{rcs} options are useful together +with @sc{cvs}. Some even makes it impossible to use +@sc{cvs} until you undo the effect! + +This description of the available options is based on +the @samp{rcs(1)} man page, but modified to suit +readers that are more interrested in @sc{cvs} than +@sc{rcs}. + +@table @code +@item -A@var{oldfile} +Might not work together with @sc{cvs}. Append the +access list of @var{oldfile} to the access list of the +@sc{rcs} file. + +@item -a@var{logins} +Might not work together with @sc{cvs}. Append the +login names appearing in the comma-separated list +@var{logins} to the access list of the @sc{rcs} file. + +@item -b[@var{rev}] +When used with bare @sc{rcs}, this +option sets the default branch to @var{rev}; in +@sc{cvs} sticky tags (@pxref{Sticky tags}) are a better +way to decide which branch you want to work on. With +@sc{cvs}, this option can be used to control behavior +with respect to the vendor branch. +@c FIXME: document how you use it with the vendor +@c branch (or fix cvs so that there is a more graceful +@c way to handle the case). + +@item -c@var{string} +Useful with @sc{cvs}. Sets the comment leader to +@var{string}. The comment leader is printed before +every log message line generated by the keyword +@code{$@asis{}Log$} (@pxref{Keyword substitution}). +This is useful for programming languages without +multi-line comments. @sc{Rcs} initially guesses the +value of the comment leader from the file name +extension when the file is first committed. + +@item -e[@var{logins}] +Might not work together with @sc{cvs}. Erase the login +names appearing in the comma-separated list +@var{logins} from the access list of the RCS file. If +@var{logins} is omitted, erase the entire access list. + +@item -I +Run interactively, even if the standard input is not a +terminal. + +@item -i +Useless with @sc{cvs}. When using bare @sc{rcs}, this +is used to create and initialize a new @sc{rcs} file, +without depositing a revision. + +@item -k@var{subst} +Useful with @sc{cvs}. Set the default keyword +substitution to @var{subst}. @xref{Keyword +substitution}. Giving an explicit @samp{-k} option to +@code{cvs update}, @code{cvs export}, or @code{cvs +checkout} overrides this default. + +@cindex Reserved checkouts +@cindex RCS-style locking +@item -l[@var{rev}] +Lock the revision with number @var{rev}. If a branch +is given, lock the latest revision on that branch. If +@var{rev} is omitted, lock the latest revision on the +default branch. + +This can be used in conjunction with the +@file{rcslock.pl} script in the @file{contrib} +directory of the @sc{cvs} source distribution to +provide reserved checkouts (where only one user can be +editing a given file at a time). See the comments in +that file for details (and see the @file{README} file +in that directory for disclaimers about the unsupported +nature of contrib). According to comments in that +file, locking must set to strict (which is the default). + +@item -L +Set locking to strict. Strict locking means that the +owner of an RCS file is not exempt from locking for +checkin. For use with @sc{cvs}, strict locking must be +set; see the discussion under the @samp{-l} option above. + +@cindex Changing a log message +@cindex Replacing a log message +@cindex Correcting a log message +@cindex Fixing a log message +@cindex Log message, correcting +@item -m@var{rev}:@var{msg} +Replace the log message of revision @var{rev} with +@var{msg}. + +@item -N@var{name}[:[@var{rev}]] +Act like @samp{-n}, except override any previous +assignment of @var{name}. + +@item -n@var{name}[:[@var{rev}]] +Associate the symbolic name @var{name} with the branch +or revision @var{rev}. It is normally better to use +@samp{cvs tag} or @samp{cvs rtag} instead. Delete the +symbolic name if both @samp{:} and @var{rev} are +omitted; otherwise, print an error message if +@var{name} is already associated with another number. +If @var{rev} is symbolic, it is expanded before +association. A @var{rev} consisting of a branch number +followed by a @samp{.} stands for the current latest +revision in the branch. A @samp{:} with an empty +@var{rev} stands for the current latest revision on the +default branch, normally the trunk. For example, +@samp{rcs -n@var{name}: RCS/*} associates @var{name} with the +current latest revision of all the named RCS files; +this contrasts with @samp{rcs -n@var{name}:$ RCS/*} which +associates @var{name} with the revision numbers +extracted from keyword strings in the corresponding +working files. + +@cindex Deleting revisions +@cindex Outdating revisions +@cindex Saving space +@item -o@var{range} +Potentially useful, but dangerous, with @sc{cvs} (see below). +Deletes (@dfn{outdates}) the revisions given by +@var{range}. A range consisting of a single revision +number means that revision. A range consisting of a +branch number means the latest revision on that branch. +A range of the form @samp{@var{rev1}:@var{rev2}} means +revisions @var{rev1} to @var{rev2} on the same branch, +@samp{:@var{rev}} means from the beginning of the +branch containing @var{rev} up to and including +@var{rev}, and @samp{@var{rev}:} means from revision +@var{rev} to the end of the branch containing +@var{rev}. None of the outdated revisions may have +branches or locks. + +Due to the way @sc{cvs} handles branches @var{rev} +cannot be specified symbolically if it is a branch. +@xref{Magic branch numbers}, for an explanation. + +Make sure that no-one has checked out a copy of the +revision you outdate. Strange things will happen if he +starts to edit it and tries to check it back in. For +this reason, this option is not a good way to take back +a bogus commit; commit a new revision undoing the bogus +change instead (@pxref{Merging two revisions}). + +@item -q +Run quietly; do not print diagnostics. + +@item -s@var{state}[:@var{rev}] +Useful with @sc{cvs}. Set the state attribute of the +revision @var{rev} to @var{state}. If @var{rev} is a +branch number, assume the latest revision on that +branch. If @var{rev} is omitted, assume the latest +revision on the default branch. Any identifier is +acceptable for @var{state}. A useful set of states is +@samp{Exp} (for experimental), @samp{Stab} (for +stable), and @samp{Rel} (for released). By default, +the state of a new revision is set to @samp{Exp} when +it is created. The state is visible in the output from +@var{cvs log} (@pxref{log}), and in the +@samp{$@asis{}Log$} and @samp{$@asis{}State$} keywords +(@pxref{Keyword substitution}). Note that @sc{cvs} +uses the @code{dead} state for its own purposes; to +take a file to or from the @code{dead} state use +commands like @code{cvs remove} and @code{cvs add}, not +@code{cvs admin -s}. + +@item -t[@var{file}] +Useful with @sc{cvs}. Write descriptive text from the +contents of the named @var{file} into the RCS file, +deleting the existing text. The @var{file} pathname +may not begin with @samp{-}. If @var{file} is omitted, +obtain the text from standard input, terminated by +end-of-file or by a line containing @samp{.} by itself. +Prompt for the text if interaction is possible; see +@samp{-I}. The descriptive text can be seen in the +output from @samp{cvs log} (@pxref{log}). + +@item -t-@var{string} +Similar to @samp{-t@var{file}}. Write descriptive text +from the @var{string} into the @sc{rcs} file, deleting +the existing text. + +@item -U +Set locking to non-strict. Non-strict locking means +that the owner of a file need not lock a revision for +checkin. For use with @sc{cvs}, strict locking must be +set; see the discussion under the @samp{-l} option +above. + +@item -u[@var{rev}] +See the option @samp{-l} above, for a discussion of +using this option with @sc{cvs}. Unlock the revision +with number @var{rev}. If a branch is given, unlock +the latest revision on that branch. If @var{rev} is +omitted, remove the latest lock held by the caller. +Normally, only the locker of a revision may unlock it. +Somebody else unlocking a revision breaks the lock. +This causes a mail message to be sent to the original +locker. The message contains a commentary solicited +from the breaker. The commentary is terminated by +end-of-file or by a line containing @code{.} by itself. + +@item -V@var{n} +Emulate @sc{rcs} version @var{n}. Use -V@var{n} to make +an @sc{rcs} file acceptable to @sc{rcs} version @var{n} +by discarding information that would confuse version +@var{n}. + +@item -x@var{suffixes} +Useless with @sc{cvs}. Use @var{suffixes} to +characterize RCS files. +@end table + + +@c . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . +@node admin examples +@appendixsubsec admin examples + +@appendixsubsubsec Outdating is dangerous + +First, an example of how @emph{not} to use the +@code{admin} command. It is included to stress the +fact that this command can be quite dangerous unless +you know @emph{exactly} what you are doing. + +The @samp{-o} option can be used to @dfn{outdate} old revisions +from the history file. If you are short on disc this option +might help you. But think twice before using it---there is no +way short of restoring the latest backup to undo this command! + +The next line is an example of a command that you would +@emph{not} like to execute. + +@example +$ cvs admin -o:R_1_02 . +@end example + +The above command will delete all revisions up to, and +including, the revision that corresponds to the tag +R_1_02. But beware! If there are files that have not +changed between R_1_02 and R_1_03 the file will have +@emph{the same} numerical revision number assigned to +the tags R_1_02 and R_1_03. So not only will it be +impossible to retrieve R_1_02; R_1_03 will also have to +be restored from the tapes! + +@appendixsubsubsec Comment leaders +@cindex Comment leader +@cindex Log keyword, selecting comment leader +@cindex Nroff (selecting comment leader) + +If you use the @code{$@asis{}Log$} keyword and you do +not agree with the guess for comment leader that +@sc{cvs} has done, you can enforce your will with +@code{cvs admin -c}. This might be suitable for +@code{nroff} source: + +@example +$ cvs admin -c'.\" ' *.man +$ rm *.man +$ cvs update +@end example + +The two last steps are to make sure that you get the +versions with correct comment leaders in your working +files. + +@c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +@node checkout +@appendixsec checkout---Check out sources for editing +@cindex Checkout (subcommand) +@cindex Co (subcommand) + +@itemize @bullet +@item +Synopsis: checkout [options] modules@dots{} +@item +Requires: repository. +@item +Changes: working directory. +@item +Synonyms: co, get +@end itemize + +Make a working directory containing copies of the +source files specified by @var{modules}. You must execute +@code{checkout} before using most of the other @sc{cvs} +commands, since most of them operate on your working +directory. + +The @var{modules} part of the command are either +symbolic names for some +collection of source directories and files, or paths to +directories or files in the repository. The symbolic +names are defined in the @samp{modules} file. +@xref{modules}. + +Depending on the modules you specify, @code{checkout} may +recursively create directories and populate them with +the appropriate source files. You can then edit these +source files at any time (regardless of whether other +software developers are editing their own copies of the +sources); update them to include new changes applied by +others to the source repository; or commit your work as +a permanent change to the source repository. + +Note that @code{checkout} is used to create +directories. The top-level directory created is always +added to the directory where @code{checkout} is +invoked, and usually has the same name as the specified +module. In the case of a module alias, the created +sub-directory may have a different name, but you can be +sure that it will be a sub-directory, and that +@code{checkout} will show the relative path leading to +each file as it is extracted into your private work +area (unless you specify the @samp{-Q} global option). + +The files created by @code{checkout} are created +read-write, unless the @samp{-r} option to @sc{cvs} +(@pxref{Global options}) is specified, the +@code{CVSREAD} environment variable is specified +(@pxref{Environment variables}), or a watch is in +effect for that file (@pxref{Watches}). + +@c FIXME: misleading--checkout takes a module as +@c argument, and update does not--so -d behavior is not the only +@c difference. +Running @code{checkout} on a directory that was already +built by a prior @code{checkout} is also permitted, and +has the same effect as specifying the @samp{-d} option +to the @code{update} command, that is, any new +directories that have been created in the repository +will appear in your work area. @xref{update}. + +@menu +* checkout options:: checkout options +* checkout examples:: checkout examples +@end menu + +@c . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . +@node checkout options +@appendixsubsec checkout options + +These standard options are supported by @code{checkout} +(@pxref{Common options}, for a complete description of +them): + +@table @code +@item -D @var{date} +Use the most recent revision no later than @var{date}. +This option is sticky, and implies @samp{-P}. See +@ref{Sticky tags}, for more information on sticky tags/dates. + +@item -f +Only useful with the @samp{-D @var{date}} or @samp{-r +@var{tag}} flags. If no matching revision is found, +retrieve the most recent revision (instead of ignoring +the file). + +@item -k @var{kflag} +Process @sc{rcs} keywords according to @var{kflag}. See +co(1). This option is sticky; future updates of +this file in this working directory will use the same +@var{kflag}. The @code{status} command can be viewed +to see the sticky options. @xref{status}. + +@item -l +Local; run only in current working directory. + +@item -n +Do not run any checkout program (as specified +with the @samp{-o} option in the modules file; +@pxref{modules}). + +@item -P +Prune empty directories. + +@item -p +Pipe files to the standard output. + +@item -r @var{tag} +Use revision @var{tag}. This option is sticky, and implies @samp{-P}. +See @ref{Sticky tags}, for more information on sticky tags/dates. +@end table + +In addition to those, you can use these special command +options with @code{checkout}: + +@table @code +@item -A +Reset any sticky tags, dates, or @samp{-k} options. +See @ref{Sticky tags}, for more information on sticky tags/dates. + +@item -c +Copy the module file, sorted, to the standard output, +instead of creating or modifying any files or +directories in your working directory. + +@item -d @var{dir} +Create a directory called @var{dir} for the working +files, instead of using the module name. Unless you +also use @samp{-N}, the paths created under @var{dir} +will be as short as possible. + +@item -j @var{tag} +With two @samp{-j} options, merge changes from the +revision specified with the first @samp{-j} option to +the revision specified with the second @samp{j} option, +into the working directory. + +With one @samp{-j} option, merge changes from the +ancestor revision to the revision specified with the +@samp{-j} option, into the working directory. The +ancestor revision is the common ancestor of the +revision which the working directory is based on, and +the revision specified in the @samp{-j} option. + +In addition, each -j option can contain an optional +date specification which, when used with branches, can +limit the chosen revision to one within a specific +date. An optional date is specified by adding a colon +(:) to the tag: +@samp{-j@var{Symbolic_Tag}:@var{Date_Specifier}}. + +@xref{Merging}. + +@item -N +Only useful together with @samp{-d @var{dir}}. With this +option, @sc{cvs} will not shorten module paths in your +working directory. (Normally, @sc{cvs} shortens paths as +much as possible when you specify an explicit target +directory). + +@item -s +Like @samp{-c}, but include the status of all modules, +and sort it by the status string. @xref{modules}, for +info about the @samp{-s} option that is used inside the +modules file to set the module status. +@end table + +@c . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . +@node checkout examples +@appendixsubsec checkout examples + +Get a copy of the module @samp{tc}: + +@example +$ cvs checkout tc +@end example + +Get a copy of the module @samp{tc} as it looked one day +ago: + +@example +$ cvs checkout -D yesterday tc +@end example + +@c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +@node commit +@appendixsec commit---Check files into the repository +@cindex Commit (subcommand) + +@itemize @bullet +@item +Version 1.3 Synopsis: commit [-lnR] [-m 'log_message' | +-f file] [-r revision] [files@dots{}] +@item +Version 1.3.1 Synopsis: commit [-lnRf] [-m 'log_message' | +-F file] [-r revision] [files@dots{}] +@c -- rename-f-F-- +@item +Requires: working directory, repository. +@item +Changes: repository. +@item +Synonym: ci +@end itemize + +@strong{Warning:} The @samp{-f @var{file}} option will +probably be renamed to @samp{-F @var{file}}, and @samp{-f} +will be given a new behavior in future releases of @sc{cvs}. +@c -- rename-f-F-- + +Use @code{commit} when you want to incorporate changes +from your working source files into the source +repository. + +If you don't specify particular files to commit, all of +the files in your working current directory are +examined. @code{commit} is careful to change in the +repository only those files that you have really +changed. By default (or if you explicitly specify the +@samp{-R} option), files in subdirectories are also +examined and committed if they have changed; you can +use the @samp{-l} option to limit @code{commit} to the +current directory only. + +@code{commit} verifies that the selected files are up +to date with the current revisions in the source +repository; it will notify you, and exit without +committing, if any of the specified files must be made +current first with @code{update} (@pxref{update}). +@code{commit} does not call the @code{update} command +for you, but rather leaves that for you to do when the +time is right. + +When all is well, an editor is invoked to allow you to +enter a log message that will be written to one or more +logging programs (@pxref{modules}, and @pxref{loginfo}) +and placed in the @sc{rcs} history file inside the +repository. This log message can be retrieved with the +@code{log} command; @xref{log}. You can specify the +log message on the command line with the @samp{-m +@var{message}} option, and thus avoid the editor invocation, +or use the @samp{-f @var{file}} option to specify +@c -- rename-f-F-- +that the argument file contains the log message. + +@menu +* commit options:: commit options +* commit examples:: commit examples +@end menu + +@c . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . +@node commit options +@appendixsubsec commit options + +These standard options are supported by @code{commit} +(@pxref{Common options}, for a complete description of +them): + +@table @code +@item -l +Local; run only in current working directory. + +@item -n +Do not run any module program. + +@item -R +Commit directories recursively. This is on by default. + +@item -r @var{revision} +Commit to @var{revision}. @var{revision} must be +either a branch, or a revision on the main trunk that +is higher than any existing revision number. You +cannot commit to a specific revision on a branch. +@end table + +@code{commit} also supports these options: + +@table @code +@item -F @var{file} +This option is present in @sc{cvs} releases 1.3-s3 and +later. Read the log message from @var{file}, instead +of invoking an editor. + +@item -f +@c -- rename-f-F-- +This option is present in @sc{cvs} 1.3-s3 and later releases +of @sc{cvs}. Note that this is not the standard behavior of +the @samp{-f} option as defined in @xref{Common options}. + +Force @sc{cvs} to commit a new revision even if you haven't +made any changes to the file. If the current revision +of @var{file} is 1.7, then the following two commands +are equivalent: + +@example +$ cvs commit -f @var{file} +$ cvs commit -r 1.8 @var{file} +@end example + +@item -f @var{file} +@c -- rename-f-F-- +This option is present in @sc{cvs} releases 1.3, 1.3-s1 and +1.3-s2. Note that this is not the standard behavior of +the @samp{-f} option as defined in @xref{Common options}. + +Read the log message from @var{file}, instead +of invoking an editor. + +@item -m @var{message} +Use @var{message} as the log message, instead of +invoking an editor. +@end table + +@need 2000 +@c . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . +@node commit examples +@appendixsubsec commit examples + +@appendixsubsubsec New major release number + +When you make a major release of your product, you +might want the revision numbers to track your major +release number. You should normally not care about +the revision numbers, but this is a thing that many +people want to do, and it can be done without doing any +harm. + +To bring all your files up to the @sc{rcs} revision 3.0 +(including those that haven't changed), you might do: + +@example +$ cvs commit -r 3.0 +@end example + +Note that it is generally a bad idea to try to make the +@sc{rcs} revision number equal to the current release number +of your product. You should think of the revision +number as an internal number that the @sc{cvs} package +maintains, and that you generally never need to care +much about. Using the @code{tag} and @code{rtag} +commands you can give symbolic names to the releases +instead. @xref{tag} and @xref{rtag}. + +Note that the number you specify with @samp{-r} must be +larger than any existing revision number. That is, if +revision 3.0 exists, you cannot @samp{cvs commit +-r 1.3}. + +@appendixsubsubsec Committing to a branch + +You can commit to a branch revision (one that has an +even number of dots) with the @samp{-r} option. To +create a branch revision, use the @samp{-b} option +of the @code{rtag} or @code{tag} commands (@pxref{tag} +or @pxref{rtag}). Then, either @code{checkout} or +@code{update} can be used to base your sources on the +newly created branch. From that point on, all +@code{commit} changes made within these working sources +will be automatically added to a branch revision, +thereby not disturbing main-line development in any +way. For example, if you had to create a patch to the +1.2 version of the product, even though the 2.0 version +is already under development, you might do: + +@example +$ cvs rtag -b -r FCS1_2 FCS1_2_Patch product_module +$ cvs checkout -r FCS1_2_Patch product_module +$ cd product_module +[[ hack away ]] +$ cvs commit +@end example + +@noindent +This works automatically since the @samp{-r} option is +sticky. + +@appendixsubsubsec Creating the branch after editing + +Say you have been working on some extremely +experimental software, based on whatever revision you +happened to checkout last week. If others in your +group would like to work on this software with you, but +without disturbing main-line development, you could +commit your change to a new branch. Others can then +checkout your experimental stuff and utilize the full +benefit of @sc{cvs} conflict resolution. The scenario might +look like: + +@c FIXME: Should we be recommending tagging the branchpoint? +@example +[[ hacked sources are present ]] +$ cvs tag -b EXPR1 +$ cvs update -r EXPR1 +$ cvs commit +@end example + +The @code{update} command will make the @samp{-r +EXPR1} option sticky on all files. Note that your +changes to the files will never be removed by the +@code{update} command. The @code{commit} will +automatically commit to the correct branch, because the +@samp{-r} is sticky. You could also do like this: + +@c FIXME: Should we be recommending tagging the branchpoint? +@example +[[ hacked sources are present ]] +$ cvs tag -b EXPR1 +$ cvs commit -r EXPR1 +@end example + +@noindent +but then, only those files that were changed by you +will have the @samp{-r EXPR1} sticky flag. If you hack +away, and commit without specifying the @samp{-r EXPR1} +flag, some files may accidentally end up on the main +trunk. + +To work with you on the experimental change, others +would simply do + +@example +$ cvs checkout -r EXPR1 whatever_module +@end example + +@c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +@node diff +@appendixsec diff---Run diffs between revisions +@cindex Diff (subcommand) + +@itemize @bullet +@item +Synopsis: diff [-l] [rcsdiff_options] [[-r rev1 | -D date1] [-r rev2 | -D date2]] [files@dots{}] +@item +Requires: working directory, repository. +@item +Changes: nothing. +@end itemize + +The @code{diff} command is used to compare different +revisions of files. The default action is to compare +your working files with the revisions they were based +on, and report any differences that are found. + +If any file names are given, only those files are +compared. If any directories are given, all files +under them will be compared. + +The exit status will be 0 if no differences were found, +1 if some differences were found, and 2 if any error +occurred. + +@menu +* diff options:: diff options +* diff examples:: diff examples +@end menu + +@c . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . +@node diff options +@appendixsubsec diff options + +These standard options are supported by @code{diff} +(@pxref{Common options}, for a complete description of +them): + +@table @code +@item -D @var{date} +Use the most recent revision no later than @var{date}. +See @samp{-r} for how this affects the comparison. + +@sc{cvs} can be configured to pass the @samp{-D} option +through to @code{rcsdiff} (which in turn passes it on +to @code{diff}. @sc{Gnu} diff uses @samp{-D} as a way to +put @code{cpp}-style @samp{#define} statements around the output +differences. There is no way short of testing to +figure out how @sc{cvs} was configured. In the default +configuration @sc{cvs} will use the @samp{-D @var{date}} option. + +@item -k @var{kflag} +Process @sc{rcs} keywords according to @var{kflag}. See +co(1). + +@item -l +Local; run only in current working directory. + +@item -R +Examine directories recursively. This option is on by +default. + +@item -r @var{tag} +Compare with revision @var{tag}. Zero, one or two +@samp{-r} options can be present. With no @samp{-r} +option, the working file will be compared with the +revision it was based on. With one @samp{-r}, that +revision will be compared to your current working file. +With two @samp{-r} options those two revisions will be +compared (and your working file will not affect the +outcome in any way). + +One or both @samp{-r} options can be replaced by a +@samp{-D @var{date}} option, described above. +@end table + +Any other options that are found are passed through to +@code{rcsdiff}, which in turn passes them to +@code{diff}. The exact meaning of the options depends +on which @code{diff} you are using. The long options +introduced in @sc{gnu} diff 2.0 are not yet supported in +@sc{cvs}. See the documentation for your @code{diff} to see +which options are supported. + +@c -- Document some common useful diff options, such as +@c -u and -c. +@c . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . +@node diff examples +@appendixsubsec diff examples + +The following line produces a Unidiff (@samp{-u} flag) +between revision 1.14 and 1.19 of +@file{backend.c}. Due to the @samp{-kk} flag no +keywords are substituted, so differences that only depend +on keyword substitution are ignored. + +@example +$ cvs diff -kk -u -r 1.14 -r 1.19 backend.c +@end example + +Suppose the experimental branch EXPR1 was based on a +set of files tagged RELEASE_1_0. To see what has +happened on that branch, the following can be used: + +@example +$ cvs diff -r RELEASE_1_0 -r EXPR1 +@end example + +A command like this can be used to produce a context +diff between two releases: + +@example +$ cvs diff -c -r RELEASE_1_0 -r RELEASE_1_1 > diffs +@end example + +If you are maintaining ChangeLogs, a command like the following +just before you commit your changes may help you write +the ChangeLog entry. All local modifications that have +not yet been committed will be printed. + +@example +$ cvs diff -u | less +@end example + +@c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +@node export +@appendixsec export---Export sources from CVS, similar to checkout +@cindex Export (subcommand) + +@itemize @bullet +@item +Synopsis: export [-flNn] [-r rev|-D date] [-k subst] [-d dir] module@dots{} +@item +Requires: repository. +@item +Changes: current directory. +@end itemize + +This command is a variant of @code{checkout}; use it +when you want a copy of the source for module without +the @sc{cvs} administrative directories. For example, you +might use @code{export} to prepare source for shipment +off-site. This command requires that you specify a +date or tag (with @samp{-D} or @samp{-r}), so that you +can count on reproducing the source you ship to others. + +One often would like to use @samp{-kv} with @code{cvs +export}. This causes any @sc{rcs} keywords to be +expanded such that an import done at some other site +will not lose the keyword revision information. But be +aware that doesn't handle an export containing binary +files correctly. Also be aware that after having used +@samp{-kv}, one can no longer use the @code{ident} +command (which is part of the @sc{rcs} suite---see +ident(1)) which looks for @sc{rcs} keyword strings. If +you want to be able to use @code{ident} you must not +use @samp{-kv}. + +@menu +* export options:: export options +@end menu + +@c . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . +@node export options +@appendixsubsec export options + +These standard options are supported by @code{export} +(@pxref{Common options}, for a complete description of +them): + +@table @code +@item -D @var{date} +Use the most recent revision no later than @var{date}. + +@item -f +If no matching revision is found, retrieve the most +recent revision (instead of ignoring the file). + +@item -l +Local; run only in current working directory. + +@item -n +Do not run any checkout program. + +@item -R +Export directories recursively. This is on by default. + +@item -r @var{tag} +Use revision @var{tag}. +@end table + +In addition, these options (that are common to +@code{checkout} and @code{export}) are also supported: + +@table @code +@item -d @var{dir} +Create a directory called @var{dir} for the working +files, instead of using the module name. Unless you +also use @samp{-N}, the paths created under @var{dir} +will be as short as possible. + +@item -k @var{subst} +Set keyword expansion mode (@pxref{Substitution modes}). + +@item -N +Only useful together with @samp{-d @var{dir}}. With this +option, @sc{cvs} will not shorten module paths in your +working directory. (Normally, @sc{cvs} shortens paths as +much as possible when you specify an explicit target +directory.) +@end table + +@ignore +@c . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . +@c @node export examples +@appendixsubsec export examples + +Contributed examples are gratefully accepted. +@c -- Examples here!! +@end ignore + +@c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +@node history +@appendixsec history---Show status of files and users +@cindex History (subcommand) + +@itemize @bullet +@item +Synopsis: history [-report] [-flags] [-options args] [files@dots{}] +@item +Requires: the file @file{$CVSROOT/CVSROOT/history} +@item +Changes: nothing. +@end itemize + +@sc{cvs} can keep a history file that tracks each use of the +@code{checkout}, @code{commit}, @code{rtag}, +@code{update}, and @code{release} commands. You can +use @code{history} to display this information in +various formats. + +Logging must be enabled by creating the file +@file{$CVSROOT/CVSROOT/history}. + +@strong{Warning:} @code{history} uses @samp{-f}, @samp{-l}, +@samp{-n}, and @samp{-p} in ways that conflict with the +normal use inside @sc{cvs} (@pxref{Common options}). + +@menu +* history options:: history options +@end menu + +@c . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . +@node history options +@appendixsubsec history options + +Several options (shown above as @samp{-report}) control what +kind of report is generated: + +@table @code +@item -c +Report on each time commit was used (i.e., each time +the repository was modified). + +@item -e +Everything (all record types); equivalent to specifying +@samp{-xMACFROGWUT}. + +@item -m @var{module} +Report on a particular module. (You can meaningfully +use @samp{-m} more than once on the command line.) + +@item -o +Report on checked-out modules. + +@item -T +Report on all tags. + +@item -x @var{type} +Extract a particular set of record types @var{type} from the @sc{cvs} +history. The types are indicated by single letters, +which you may specify in combination. + +Certain commands have a single record type: + +@table @code +@item F +release +@item O +checkout +@item T +rtag +@end table + +@noindent +One of four record types may result from an update: + +@table @code +@item C +A merge was necessary but collisions were +detected (requiring manual merging). +@item G +A merge was necessary and it succeeded. +@item U +A working file was copied from the repository. +@item W +The working copy of a file was deleted during +update (because it was gone from the repository). +@end table + +@noindent +One of three record types results from commit: + +@table @code +@item A +A file was added for the first time. +@item M +A file was modified. +@item R +A file was removed. +@end table +@end table + +The options shown as @samp{-flags} constrain or expand +the report without requiring option arguments: + +@table @code +@item -a +Show data for all users (the default is to show data +only for the user executing @code{history}). + +@item -l +Show last modification only. + +@item -w +Show only the records for modifications done from the +same working directory where @code{history} is +executing. +@end table + +The options shown as @samp{-options @var{args}} constrain the report +based on an argument: + +@table @code +@item -b @var{str} +Show data back to a record containing the string +@var{str} in either the module name, the file name, or +the repository path. + +@item -D @var{date} +Show data since @var{date}. This is slightly different +from the normal use of @samp{-D @var{date}}, which +selects the newest revision older than @var{date}. + +@item -p @var{repository} +Show data for a particular source repository (you +can specify several @samp{-p} options on the same command +line). + +@item -r @var{rev} +Show records referring to revisions since the revision +or tag named @var{rev} appears in individual @sc{rcs} +files. Each @sc{rcs} file is searched for the revision or +tag. + +@item -t @var{tag} +Show records since tag @var{tag} was last added to the the +history file. This differs from the @samp{-r} flag +above in that it reads only the history file, not the +@sc{rcs} files, and is much faster. + +@item -u @var{name} +Show records for user @var{name}. +@end table + +@ignore +@c . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . +@c @node history examples +@appendixsubsec history examples + +Contributed examples will gratefully be accepted. +@c -- Examples here! +@end ignore + +@c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +@node import +@appendixsec import---Import sources into CVS, using vendor branches +@cindex Import (subcommand) + +@itemize @bullet +@item +Synopsis: import [-options] repository vendortag releasetag@dots{} +@item +Requires: Repository, source distribution directory. +@item +Changes: repository. +@end itemize + +Use @code{import} to incorporate an entire source +distribution from an outside source (e.g., a source +vendor) into your source repository directory. You can +use this command both for initial creation of a +repository, and for wholesale updates to the module +from the outside source. @xref{Tracking sources}, for +a discussion on this subject. + +The @var{repository} argument gives a directory name +(or a path to a directory) under the @sc{cvs} root directory +for repositories; if the directory did not exist, +import creates it. + +When you use import for updates to source that has been +modified in your source repository (since a prior +import), it will notify you of any files that conflict +in the two branches of development; use @samp{checkout +-j} to reconcile the differences, as import instructs +you to do. + +If @sc{cvs} decides a file should be ignored +(@pxref{cvsignore}), it does not import it and prints +@samp{I } followed by the filename + +If the file @file{$CVSROOT/CVSROOT/cvswrappers} exists, +any file whose names match the specifications in that +file will be treated as packages and the appropriate +filtering will be performed on the file/directory +before being imported, @xref{Wrappers}. + +The outside source is saved in a first-level @sc{rcs} +branch, by default 1.1.1. Updates are leaves of this +branch; for example, files from the first imported +collection of source will be revision 1.1.1.1, then +files from the first imported update will be revision +1.1.1.2, and so on. + +At least three arguments are required. +@var{repository} is needed to identify the collection +of source. @var{vendortag} is a tag for the entire +branch (e.g., for 1.1.1). You must also specify at +least one @var{releasetag} to identify the files at +the leaves created each time you execute @code{import}. + +@menu +* import options:: import options +* import examples:: import examples +@end menu + +@c . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . +@node import options +@appendixsubsec import options + +This standard option is supported by @code{import} +(@pxref{Common options}, for a complete description): + +@table @code +@item -m @var{message} +Use @var{message} as log information, instead of +invoking an editor. +@end table + +There are three additional special options. + +@table @code +@item -b @var{branch} +Specify a first-level branch other than 1.1.1. Unless +the @samp{-b @var{branch}} flag is given, revisions will +@emph{always} be made to the branch 1.1.1---even if a +@var{vendortag} that matches another branch is given! +What happens in that case, is that the tag will be +reset to 1.1.1. Warning: This behavior might change +in the future. + +@item -k @var{subst} +Indicate the RCS keyword expansion mode desired. This +setting will apply to all files created during the +import, but not to any files that previously existed in +the repository. See @ref{Substitution modes} for a +list of valid @samp{-k} settings. + +@item -I @var{name} +Specify file names that should be ignored during +import. You can use this option repeatedly. To avoid +ignoring any files at all (even those ignored by +default), specify `-I !'. + +@var{name} can be a file name pattern of the same type +that you can specify in the @file{.cvsignore} file. +@xref{cvsignore}. +@c -- Is this really true? + +@item -W @var{spec} +Specify file names that should be filtered during +import. You can use this option repeatedly. + +@var{spec} can be a file name pattern of the same type +that you can specify in the @file{.cvswrappers} +file. @xref{Wrappers}. +@end table + +@c . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . +@node import examples +@appendixsubsec import examples + +@xref{Tracking sources}, and @xref{From files}. + +@c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +@node log +@appendixsec log---Print out 'rlog' information for files +@cindex Log (subcommand) + +@itemize @bullet +@item +Synopsis: log [-l] rlog-options [files@dots{}] +@item +Requires: repository, working directory. +@item +Changes: nothing. +@item +Synonym: rlog +@end itemize + +Display log information for files. @code{log} calls +the @sc{rcs} utility @code{rlog}, which prints all available +information about the @sc{rcs} history file. This includes +the location of the @sc{rcs} file, the @dfn{head} revision +(the latest revision on the trunk), all symbolic names (tags) +and some other things. For each revision, the revision +number, the author, the number of lines added/deleted and +the log message are printed. All times are displayed in +Coordinated Universal Time (UTC). (Other parts of @sc{cvs} +print times in the local timezone). +@c -- timezone-- + +@menu +* log options:: log options +* log examples:: log examples +@end menu + +@c . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . +@node log options +@appendixsubsec log options + +Only one option is interpreted by @sc{cvs} and not passed on to @code{rlog}: + +@table @code +@item -l +Local; run only in current working directory. (Default +is to run recursively). +@end table + +By default, @code{rlog} prints all information that is +available. All other options (including those that +normally behave differently) are passed through to +@code{rlog} and restrict the output. See rlog(1) for a +complete description of options. This incomplete list +(which is a slightly edited extract from rlog(1)) lists +all options that are useful in conjunction with @sc{cvs}. + +@strong{Please note:} There can be no space between the option +and its argument, since @code{rlog} parses its options +in a different way than @sc{cvs}. + +@table @code +@item -b +Print information about the revisions on the default +branch, normally the highest branch on the trunk. + +@item -d@var{dates} +Print information about revisions with a checkin +date/time in the range given by the +semicolon-separated list of dates. The following table +explains the available range formats: + +@table @code +@item @var{d1}<@var{d2} +@itemx @var{d2}>@var{d1} +Select the revisions that were deposited between +@var{d1} and @var{d2} inclusive. + +@item <@var{d} +@itemx @var{d}> +Select all revisions dated @var{d} or earlier. + +@item @var{d}< +@itemx >@var{d} +Select all revisions dated @var{d} or later. + +@item @var{d} +Select the single, latest revision dated @var{d} or +earlier. +@end table + +The date/time strings @var{d}, @var{d1}, and @var{d2} +are in the free format explained in co(1). Quoting is +normally necessary, especially for < and >. Note that +the separator is a semicolon (;). + +@item -h +Print only the @sc{rcs} pathname, working pathname, head, +default branch, access list, locks, symbolic names, and +suffix. + +@item -N +Do not print the list of tags for this file. This +option can be very useful when your site uses a lot of +tags, so rather than "more"'ing over 3 pages of tag +information, the log information is presented without +tags at all. + +@item -R +Print only the name of the @sc{rcs} history file. + +@item -r@var{revisions} +Print information about revisions given in the +comma-separated list @var{revisions} of revisions and +ranges. The following table explains the available +range formats: + +@table @code +@item @var{rev1}:@var{rev2} +Revisions @var{rev1} to @var{rev2} (which must be on +the same branch). + +@item :@var{rev} +Revisions from the beginning of the branch up to +and including @var{rev}. + +@item @var{rev}: +Revisions starting with @var{rev} to the end of the +branch containing @var{rev}. + +@item @var{branch} +An argument that is a branch means all revisions on +that branch. You can unfortunately not specify a +symbolic branch here. You must specify the numeric +branch number. @xref{Magic branch numbers}, for an +explanation. + +@item @var{branch1}:@var{branch2} +A range of branches means all revisions +on the branches in that range. + +@item @var{branch}. +The latest revision in @var{branch}. +@end table + +A bare @samp{-r} with no revisions means the latest +revision on the default branch, normally the trunk. + +@item -s@var{states} +Print information about revisions whose state +attributes match one of the states given in the +comma-separated list @var{states}. + +@item -t +Print the same as @samp{-h}, plus the descriptive text. + +@item -w@var{logins} +Print information about revisions checked in by users +with login names appearing in the comma-separated list +@var{logins}. If @var{logins} is omitted, the user's +login is assumed. +@end table + +@code{rlog} prints the intersection of the revisions +selected with the options @samp{-d}, @samp{-l}, +@samp{-s}, and @samp{-w}, intersected with the union of +the revisions selected by @samp{-b} and @samp{-r}. + +@c . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . +@node log examples +@appendixsubsec log examples + +Contributed examples are gratefully accepted. + +@c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +@node rdiff +@appendixsec rdiff---'patch' format diffs between releases +@cindex Rdiff (subcommand) + +@itemize @bullet +@item +rdiff [-flags] [-V vn] [-r t|-D d [-r t2|-D d2]] modules@dots{} +@item +Requires: repository. +@item +Changes: nothing. +@item +Synonym: patch +@end itemize + +Builds a Larry Wall format patch(1) file between two +releases, that can be fed directly into the patch +program to bring an old release up-to-date with the new +release. (This is one of the few @sc{cvs} commands that +operates directly from the repository, and doesn't +require a prior checkout.) The diff output is sent to +the standard output device. + +You can specify (using the standard @samp{-r} and +@samp{-D} options) any combination of one or two +revisions or dates. If only one revision or date is +specified, the patch file reflects differences between +that revision or date and the current head revisions in +the @sc{rcs} file. + +Note that if the software release affected is contained +in more than one directory, then it may be necessary to +specify the @samp{-p} option to the patch command when +patching the old sources, so that patch is able to find +the files that are located in other directories. + +@menu +* rdiff options:: rdiff options +* rdiff examples:: rdiff examples +@end menu + +@c . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . +@node rdiff options +@appendixsubsec rdiff options + +These standard options are supported by @code{rdiff} +(@pxref{Common options}, for a complete description of +them): + +@table @code +@item -D @var{date} +Use the most recent revision no later than @var{date}. + +@item -f +If no matching revision is found, retrieve the most +recent revision (instead of ignoring the file). + +@item -l +Local; don't descend subdirectories. + +@item -r @var{tag} +Use revision @var{tag}. +@end table + +In addition to the above, these options are available: + +@table @code +@item -c +Use the context diff format. This is the default format. + +@item -s +Create a summary change report instead of a patch. The +summary includes information about files that were +changed or added between the releases. It is sent to +the standard output device. This is useful for finding +out, for example, which files have changed between two +dates or revisions. + +@item -t +A diff of the top two revisions is sent to the standard +output device. This is most useful for seeing what the +last change to a file was. + +@item -u +Use the unidiff format for the context diffs. +This option is not available if your diff does not +support the unidiff format. Remember that old versions +of the @code{patch} program can't handle the unidiff +format, so if you plan to post this patch to the net +you should probably not use @samp{-u}. + +@item -V @var{vn} +Expand @sc{rcs} keywords according to the rules current in +@sc{rcs} version @var{vn} (the expansion format changed with +@sc{rcs} version 5). +@end table + +@c . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . +@node rdiff examples +@appendixsubsec rdiff examples + +Suppose you receive mail from @t{foo@@bar.com} asking for an +update from release 1.2 to 1.4 of the tc compiler. You +have no such patches on hand, but with @sc{cvs} that can +easily be fixed with a command such as this: + +@example +$ cvs rdiff -c -r FOO1_2 -r FOO1_4 tc | \ +$$ Mail -s 'The patches you asked for' foo@@bar.com +@end example + +Suppose you have made release 1.3, and forked a branch +called @samp{R_1_3fix} for bugfixes. @samp{R_1_3_1} +corresponds to release 1.3.1, which was made some time +ago. Now, you want to see how much development has been +done on the branch. This command can be used: + +@example +$ cvs patch -s -r R_1_3_1 -r R_1_3fix module-name +cvs rdiff: Diffing module-name +File ChangeLog,v changed from revision 1.52.2.5 to 1.52.2.6 +File foo.c,v changed from revision 1.52.2.3 to 1.52.2.4 +File bar.h,v changed from revision 1.29.2.1 to 1.2 +@end example + +@c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +@node release +@appendixsec release---Indicate that a Module is no longer in use +@cindex Release (subcommand) + +@itemize @bullet +@item +release [-d] directories@dots{} +@item +Requires: Working directory. +@item +Changes: Working directory, history log. +@end itemize + +This command is meant to safely cancel the effect of +@samp{cvs checkout}. Since @sc{cvs} doesn't lock files, it +isn't strictly necessary to use this command. You can +always simply delete your working directory, if you +like; but you risk losing changes you may have +forgotten, and you leave no trace in the @sc{cvs} history +file (@pxref{history file}) that you've abandoned your +checkout. + +Use @samp{cvs release} to avoid these problems. This +command checks that no uncommitted changes are +present; that you are executing it from immediately +above a @sc{cvs} working directory; and that the repository +recorded for your files is the same as the repository +defined in the module database. + +If all these conditions are true, @samp{cvs release} +leaves a record of its execution (attesting to your +intentionally abandoning your checkout) in the @sc{cvs} +history log. + +@menu +* release options:: release options +* release output:: release options +* release examples:: release examples +@end menu + +@c . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . +@node release options +@appendixsubsec release options + +The @code{release} command supports one command option: + +@table @code +@item -d +Delete your working copy of the file if the release +succeeds. If this flag is not given your files will +remain in your working directory. + +@strong{Warning:} The @code{release} command uses +@samp{rm -r @file{module}} to delete your file. This +has the very serious side-effect that any directory +that you have created inside your checked-out sources, +and not added to the repository (using the @code{add} +command; @pxref{add}) will be silently deleted---even +if it is non-empty! +@end table + +@c . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . +@node release output +@appendixsubsec release output + +Before @code{release} releases your sources it will +print a one-line message for any file that is not +up-to-date. + +@strong{Warning:} Any new directories that you have +created, but not added to the @sc{cvs} directory hierarchy +with the @code{add} command (@pxref{add}) will be +silently ignored (and deleted, if @samp{-d} is +specified), even if they contain files. + +@table @code +@item U @var{file} +There exists a newer revision of this file in the +repository, and you have not modified your local copy +of the file. + +@item A @var{file} +The file has been added to your private copy of the +sources, but has not yet been committed to the +repository. If you delete your copy of the sources +this file will be lost. + +@item R @var{file} +The file has been removed from your private copy of the +sources, but has not yet been removed from the +repository, since you have not yet committed the +removal. @xref{commit}. + +@item M @var{file} +The file is modified in your working directory. There +might also be a newer revision inside the repository. + +@item ? @var{file} +@var{file} is in your working directory, but does not +correspond to anything in the source repository, and is +not in the list of files for @sc{cvs} to ignore (see the +description of the @samp{-I} option, and +@pxref{cvsignore}). If you remove your working +sources, this file will be lost. + +Note that no warning message like this is printed for +spurious directories that @sc{cvs} encounters. The +directory, and all its contents, are silently ignored. + +@c FIXME -- CVS should be fixed to print "? foo" for +@c such spurious directories +@end table + +@c . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . +@node release examples +@appendixsubsec release examples + +Release the module, and delete your local working copy +of the files. + +@example +$ cd .. # @r{You must stand immediately above the} + # @r{sources when you issue @samp{cvs release}.} +$ cvs release -d tc +You have [0] altered files in this repository. +Are you sure you want to release (and delete) module `tc': y +$ +@end example + +@node rtag +@appendixsec rtag---Add a tag to the RCS file +@cindex Rtag (subcommand) + +@itemize @bullet +@item +rtag [-falnR] [-b] [-d] [-r tag | -Ddate] symbolic_tag modules@dots{} +@item +Requires: repository. +@item +Changes: repository. +@item +Synonym: rfreeze +@end itemize + +You can use this command to assign symbolic tags to +particular, explicitly specified source revisions in +the repository. @code{rtag} works directly on the +repository contents (and requires no prior checkout). +Use @code{tag} instead (@pxref{tag}), to base the +selection of revisions on the contents of your +working directory. + +If you attempt to use a tag name that already exists, +@sc{cvs} will complain and not overwrite that tag. Use +the @samp{-F} option to force the new tag value. + +@menu +* rtag options:: rtag options +@end menu + +@c . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . +@node rtag options +@appendixsubsec rtag options + +These standard options are supported by @code{rtag} +(@pxref{Common options}, for a complete description of +them): + +@table @code +@item -D @var{date} +Tag the most recent revision no later than @var{date}. + +@item -f +Only useful with the @samp{-D @var{date}} or @samp{-r @var{tag}} +flags. If no matching revision is found, use the most +recent revision (instead of ignoring the file). + +@item -F +Overwrite an existing tag of the same name on a +different revision. This option is new in @sc{cvs} +1.4. The old behavior is matched by @samp{cvs tag -F}. + +@item -l +Local; run only in current working directory. + +@item -n +Do not run any tag program that was specified with the +@samp{-t} flag inside the @file{modules} file. +(@pxref{modules}). + +@item -R +Commit directories recursively. This is on by default. + +@item -r @var{tag} +Only tag those files that contain @var{tag}. This can +be used to rename a tag: tag only the files identified +by the old tag, then delete the old tag, leaving the +new tag on exactly the same files as the old tag. +@end table + +In addition to the above common options, these options +are available: + +@table @code +@item -a +Use the @samp{-a} option to have @code{rtag} look in the +@file{Attic} (@pxref{Removing files}) for removed files +that contain the specified tag. The tag is removed from +these files, which makes it convenient to re-use a +symbolic tag as development continues (and files get +removed from the up-coming distribution). + +@item -b +Make the tag a branch tag. @xref{Branches}. + +@item -d +Delete the tag instead of creating it. + +In general, tags (often the symbolic names of software +distributions) should not be removed, but the @samp{-d} +option is available as a means to remove completely +obsolete symbolic names if necessary (as might be the +case for an Alpha release, or if you mistagged a +module). +@end table + +@ignore +@c . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . +@c @node rtag examples +@appendixsubsec rtag examples + +@c -- Examples here! +@end ignore + +@c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +@node status +@appendixsec status---Status info on the revisions +@cindex Status (subcommand) + +@itemize @bullet +@item +status [-lR] [-v] [files@dots{}] +@item +Requires: working directory, repository. +@item +Changes: nothing. +@end itemize + +Display a brief report on the current status of files +with respect to the source repository, including any +sticky tags, dates, or @samp{-k} options. + +You can also use this command to determine the +potential impact of a @samp{cvs update} on your working +source directory---but remember that things might +change in the repository before you run @code{update}. + +@menu +* status options:: status options +@end menu + +@c . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . +@node status options +@appendixsubsec status options + +These standard options are supported by @code{status} +(@pxref{Common options}, for a complete description of +them): + +@table @code +@item -l +Local; run only in current working directory. + +@item -R +Commit directories recursively. This is on by default. +@end table + +There is one additional option: + +@table @code +@item -v +Verbose. In addition to the information normally +displayed, print all symbolic tags, together with the +numerical value of the revision or branch they refer +to. +@end table + +@ignore +@c . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . +@c @node status examples +@appendixsubsec status examples + +@c -- FIXME +@end ignore + +@c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +@node tag +@appendixsec tag---Add a symbolic tag to checked out version of RCS file +@c -- //////// - unnecessary. Also +@c -- in a lot of other +@c -- places. +@cindex Tag (subcommand) + +@itemize @bullet +@item +tag [-lR] [-b] [-d] symbolic_tag [files@dots{}] +@item +Requires: working directory, repository. +@item +Changes: repository. +@item +Synonym: freeze +@end itemize + +Use this command to assign symbolic tags to the nearest +repository versions to your working sources. The tags +are applied immediately to the repository, as with +@code{rtag}, but the versions are supplied implicitly by the +@sc{cvs} records of your working files' history rather than +applied explicitly. + +One use for tags is to record a snapshot of the +current sources when the software freeze date of a +project arrives. As bugs are fixed after the freeze +date, only those changed sources that are to be part of +the release need be re-tagged. + +The symbolic tags are meant to permanently record which +revisions of which files were used in creating a +software distribution. The @code{checkout} and +@code{update} commands allow you to extract an exact +copy of a tagged release at any time in the future, +regardless of whether files have been changed, added, +or removed since the release was tagged. + +This command can also be used to delete a symbolic tag, +or to create a branch. See the options section below. + +If you attempt to use a tag name that already exists, +@sc{cvs} will complain and not overwrite that tag. Use +the @samp{-F} option to force the new tag value. + + +@menu +* tag options:: tag options +@end menu + +@c . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . +@node tag options +@appendixsubsec tag options + +These standard options are supported by @code{tag} +(@pxref{Common options}, for a complete description of +them): + +@table @code +@item -F +Overwrite an existing tag of the same name on a +different revision. This option is new in @sc{cvs} +1.4. The old behavior is matched by @samp{cvs tag -F}. + +@item -l +Local; run only in current working directory. + +@item -R +Commit directories recursively. This is on by default. +@end table + +Two special options are available: + +@table @code +@item -b +The -b option makes the tag a branch tag +(@pxref{Branches}), allowing concurrent, isolated +development. This is most useful for creating a patch +to a previously released software distribution. + +@item -d +Delete a tag. + +If you use @samp{cvs tag -d symbolic_tag}, the symbolic +tag you specify is deleted instead of being added. +Warning: Be very certain of your ground before you +delete a tag; doing this permanently discards some +historical information, which may later turn out to +be valuable. +@end table + +@ignore +@c . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . +@c @node tag examples +@appendixsubsec tag examples + +@c -- FIXME +@end ignore + +@c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +@node update +@appendixsec update---Bring work tree in sync with repository +@cindex Update (subcommand) + +@itemize @bullet +@item +update [-AdflPpR] [-d] [-r tag|-D date] files@dots{} +@item +Requires: repository, working directory. +@item +Changes: working directory. +@end itemize + +After you've run checkout to create your private copy +of source from the common repository, other developers +will continue changing the central source. From time +to time, when it is convenient in your development +process, you can use the @code{update} command from +within your working directory to reconcile your work +with any revisions applied to the source repository +since your last checkout or update. + +@menu +* update options:: update options +* update output:: update output +* update examples:: update examples +@end menu + +@c . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . +@node update options +@appendixsubsec update options + +These standard options are available with @code{update} +(@pxref{Common options}, for a complete description of +them): + +@table @code +@item -D date +Use the most recent revision no later than @var{date}. +This option is sticky, and implies @samp{-P}. +See @ref{Sticky tags}, for more information on sticky tags/dates. + +@item -f +Only useful with the @samp{-D @var{date}} or @samp{-r +@var{tag}} flags. If no matching revision is found, +retrieve the most recent revision (instead of ignoring +the file). + +@item -k @var{kflag} +Process @sc{rcs} keywords according to @var{kflag}. See +co(1). This option is sticky; future updates of +this file in this working directory will use the same +@var{kflag}. The @code{status} command can be viewed +to see the sticky options. @xref{status}. + +@item -l +Local; run only in current working directory. @xref{Recursive behavior}. + +@item -P +Prune empty directories. + +@item -p +Pipe files to the standard output. + +@item -R +Operate recursively. This is on by default. +@xref{Recursive behavior}. + +@item -r tag +Retrieve revision @var{tag}. This option is sticky, +and implies @samp{-P}. +See @ref{Sticky tags}, for more information on sticky tags/dates. +@end table + +@need 800 +These special options are also available with +@code{update}. + +@table @code +@item -A +Reset any sticky tags, dates, or @samp{-k} options. +See @ref{Sticky tags}, for more information on sticky tags/dates. + +@item -d +Create any directories that exist in the repository if +they're missing from the working directory. Normally, +@code{update} acts only on directories and files that +were already enrolled in your working directory. + +This is useful for updating directories that were +created in the repository since the initial checkout; +but it has an unfortunate side effect. If you +deliberately avoided certain directories in the +repository when you created your working directory +(either through use of a module name or by listing +explicitly the files and directories you wanted on the +command line), then updating with @samp{-d} will create +those directories, which may not be what you want. + +@item -I @var{name} +Ignore files whose names match @var{name} (in your +working directory) during the update. You can specify +@samp{-I} more than once on the command line to specify +several files to ignore. Use @samp{-I !} to avoid +ignoring any files at all. @xref{cvsignore}, for other +ways to make @sc{cvs} ignore some files. + +@item -W@var{spec} +Specify file names that should be filtered during +update. You can use this option repeatedly. + +@var{spec} can be a file name pattern of the same type +that you can specify in the @file{.cvswrappers} +file. @xref{Wrappers}. + +@item -j@var{revision} +With two @samp{-j} options, merge changes from the +revision specified with the first @samp{-j} option to +the revision specified with the second @samp{j} option, +into the working directory. + +With one @samp{-j} option, merge changes from the +ancestor revision to the revision specified with the +@samp{-j} option, into the working directory. The +ancestor revision is the common ancestor of the +revision which the working directory is based on, and +the revision specified in the @samp{-j} option. + +In addition, each -j option can contain an optional +date specification which, when used with branches, can +limit the chosen revision to one within a specific +date. An optional date is specified by adding a colon +(:) to the tag: +@samp{-j@var{Symbolic_Tag}:@var{Date_Specifier}}. + +@xref{Merging}. + +@end table + +@c . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . +@node update output +@appendixsubsec update output + +@code{update} keeps you informed of its progress by +printing a line for each file, preceded by one +character indicating the status of the file: + +@table @code +@item U @var{file} +The file was brought up to date with respect to the +repository. This is done for any file that exists in +the repository but not in your source, and for files +that you haven't changed but are not the most recent +versions available in the repository. + +@item A @var{file} +The file has been added to your private copy of the +sources, and will be added to the source repository +when you run @code{commit} on the file. This is a +reminder to you that the file needs to be committed. + +@item R @var{file} +The file has been removed from your private copy of the +sources, and will be removed from the source repository +when you run @code{commit} on the file. This is a +reminder to you that the file needs to be committed. + +@item M @var{file} +The file is modified in your working directory. + +@samp{M} can indicate one of two states for a file +you're working on: either there were no modifications +to the same file in the repository, so that your file +remains as you last saw it; or there were modifications +in the repository as well as in your copy, but they +were merged successfully, without conflict, in your +working directory. + +@sc{cvs} will print some messages if it merges your work, +and a backup copy of your working file (as it looked +before you ran @code{update}) will be made. The exact +name of that file is printed while @code{update} runs. + +@item C @var{file} +A conflict was detected while trying to merge your +changes to @var{file} with changes from the source +repository. @var{file} (the copy in your working +directory) is now the output of the rcsmerge(1) command +on the two revisions; an unmodified copy of your file +is also in your working directory, with the name +@file{.#@var{file}.@var{revision}} where @var{revision} +is the @sc{rcs} revision that your modified file started +from. (Note that some systems automatically purge +files that begin with @file{.#} if they have not been +accessed for a few days. If you intend to keep a copy +of your original file, it is a very good idea to rename +it.) + +@item ? @var{file} +@var{file} is in your working directory, but does not +correspond to anything in the source repository, and is +not in the list of files for @sc{cvs} to ignore (see the +description of the @samp{-I} option, and +@pxref{cvsignore}). + +Note that no warning message like this is printed for +spurious directories that @sc{cvs} encounters. The +directory, and all its contents, are silently ignored. +@end table + +@c . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . +@node update examples +@appendixsubsec update examples + +The following line will display all files which are not +up-to-date without actually change anything in your +working directory. It can be used to check what has +been going on with the project. + +@example +$ cvs -n -q update +@end example + +@c --------------------------------------------------------------------- +@node Administrative files +@appendix Reference manual for the Administrative files +@cindex Administrative files (reference) +@cindex Files, reference manual +@cindex Reference manual (files) +@cindex CVSROOT (file) + +Inside the repository, in the directory +@file{$CVSROOT/CVSROOT}, there are a number of +supportive files for @sc{cvs}. You can use @sc{cvs} in a limited +fashion without any of them, but if they are set up +properly they can help make life easier. + +The most important of these files is the @file{modules} +file, which defines the modules inside the repository. + +@menu +* modules:: Defining modules +* Wrappers:: Treat directories as files +* commit files:: The commit support files +* commitinfo:: Pre-commit checking +* editinfo:: Specifying how log messages are created +* loginfo:: Where should log messages be sent? +* rcsinfo:: Templates for the log messages +* cvsignore:: Ignoring files via cvsignore +* history file:: History information +* Setting up:: Setting up the repository +* Variables:: Various variables are expanded +@end menu + +@c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +@node modules +@appendixsec The modules file +@cindex Modules (admin file) +@cindex Defining modules (reference manual) + +The @file{modules} file records your definitions of +names for collections of source code. @sc{cvs} will +use these definitions if you use @sc{cvs} to update the +modules file (use normal commands like @code{add}, +@code{commit}, etc). + +The @file{modules} file may contain blank lines and +comments (lines beginning with @samp{#}) as well as +module definitions. Long lines can be continued on the +next line by specifying a backslash (@samp{\}) as the +last character on the line. + +A module definition is a single line of the +@file{modules} file, in either of two formats. In both +cases, @var{mname} represents the symbolic module name, +and the remainder of the line is its definition. + +@table @code +@item @var{mname} -a @var{aliases}@dots{} +This represents the simplest way of defining a module +@var{mname}. The @samp{-a} flags the definition as a +simple alias: @sc{cvs} will treat any use of @var{mname} (as +a command argument) as if the list of names +@var{aliases} had been specified instead. +@var{aliases} may contain either other module names or +paths. When you use paths in aliases, @code{checkout} +creates all intermediate directories in the working +directory, just as if the path had been specified +explicitly in the @sc{cvs} arguments. + +@item @var{mname} [ options ] @var{dir} [ @var{files}@dots{} ] [ &@var{module}@dots{} ] +In the simplest case, this form of module definition +reduces to @samp{@var{mname} @var{dir}}. This defines +all the files in directory @var{dir} as module mname. +@var{dir} is a relative path (from @code{$CVSROOT}) to a +directory of source in the source repository. In this +case, on checkout, a single directory called +@var{mname} is created as a working directory; no +intermediate directory levels are used by default, even +if @var{dir} was a path involving several directory +levels. + +By explicitly specifying files in the module definition +after @var{dir}, you can select particular files from +directory @var{dir}. The sample definition for +@samp{modules} is an example of a module defined with a +single file from a particular directory. Here is +another example: + +@example +m4test unsupported/gnu/m4 foreach.m4 forloop.m4 +@end example + +@noindent +With this definition, executing @samp{cvs checkout +m4test} will create a single working directory +@file{m4test} containing the two files listed, which +both come from a common directory several levels deep +in the @sc{cvs} source repository. + +A module definition can refer to other modules by +including @samp{&@var{module}} in its definition. +@code{checkout} creates a subdirectory for each such +module, in your working directory. +@c -- Nope. "in your working directory" is wrong. What +@c -- is right? + +@table @code +@item -d @var{name} +Name the working directory something other than the +module name. + +@cindex Export program +@item -e @var{prog} +Specify a program @var{prog} to run whenever files in a +module are exported. @var{prog} runs with a single +argument, the module name. + +@cindex Checkin program +@item -i @var{prog} +Specify a program @var{prog} to run whenever files in a +module are committed. @var{prog} runs with a single +argument, the full pathname of the affected directory +in a source repository. The @file{commitinfo}, +@file{loginfo}, and @file{editinfo} files provide other +ways to call a program on commit. + +@cindex Checkout program +@item -o @var{prog} +Specify a program @var{prog} to run whenever files in a +module are checked out. @var{prog} runs with a single +argument, the module name. + +@cindex Status of a module +@cindex Module status +@item -s @var{status} +Assign a status to the module. When the module file is +printed with @samp{cvs checkout -s} the modules are +sorted according to primarily module status, and +secondarily according to the module name. This option +has no other meaning. You can use this option for +several things besides status: for instance, list the +person that is responsible for this module. + +@cindex Tag program +@item -t @var{prog} +Specify a program @var{prog} to run whenever files in a +module are tagged with @code{rtag}. @var{prog} runs +with two arguments: the module name and the symbolic +tag specified to @code{rtag}. There is no way to +specify a program to run when @code{tag} is executed. + +@cindex Update program +@item -u @var{prog} +Specify a program @var{prog} to run whenever @samp{cvs +update} is executed from the top-level directory of the +checked-out module. @var{prog} runs with a single +argument, the full path to the source repository for +this module. +@end table +@end table + +@c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +@node Wrappers +@appendixsec The cvswrappers file +@cindex cvswrappers (admin file) +@cindex CVSWRAPPERS, environment variable +@cindex Wrappers + +Wrappers allow you to set a hook which transforms files on +their way in and out of @sc{cvs}. Most or all of the +wrappers features do not work with client/server @sc{cvs}. + +The file @file{cvswrappers} defines the script that will be +run on a file when its name matches a regular +expresion. There are two scripts that can be run on a +file or directory. One script is executed on the file/directory +before being checked into the repository (this is denoted +with the @code{-t} flag) and the other when the file is +checked out of the repository (this is denoted with the +@code{-f} flag) + +The @file{cvswrappers} also has a @samp{-m} option to +specify the merge methodology that should be used when +the file is updated. @code{MERGE} means the usual +@sc{cvs} behavior: try to merge the files (this +generally will not work for binary files). @code{COPY} +means that @code{cvs update} will merely copy one +version over the other, and require the user using +mechanisms outside @sc{cvs}, to insert any necessary +changes. +@c FIXME: which version is copied over which version? +The @samp{-m} wrapper option only affects behavior when +merging is done on update; it does not affect how files +are stored. See @xref{Binary files}, for more on +binary files. + +The basic format of the file @file{cvswrappers} is: + +@example +wildcard [option value][option value]... + +where option is one of +-f from cvs filter value: path tofilter +-t to cvs filter value: path to filter +-m update methodology value: MERGE or COPY + +and value is a single-quote delimited value. +@end example + +@example +*.nib -f 'unwrap %s' -t 'wrap %s %s' -m 'COPY' +*.c -t 'indent %s %s' +@end example + +@noindent +The above example of a @file{cvswrappers} file +states that all files/directories that end with a @code{.nib} +should be filtered with the @file{wrap} program before +checking the file into the repository. The file should +be filtered though the @file{unwrap} program when the +file is checked out of the repository. The +@file{cvswrappers} file also states that a @code{COPY} +methodology should be used when updating the files in +the repository (that is no merging should be performed). + +The last example line says that all files that end with +a @code{*.c} should be filtered with @file{indent} +before being checked into the repository. Unlike the previous +example no filtering of the @code{*.c} file is done when +it is checked out of the repository. +@noindent +The @code{-t} filter is called with two arguments, +the first is the name of the file/directory to filter +and the second is the pathname to where the resulting +filtered file should be placed. + +@noindent +The @code{-f} filter is called with one argument, +which is the name of the file to filter from. The end +result of this filter will be a file in the users directory +that they can work on as they normally would. + +@c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +@node commit files +@appendixsec The commit support files +@cindex Commit files + +The @samp{-i} flag in the @file{modules} file can be +used to run a certain program whenever files are +committed (@pxref{modules}). The files described in +this section provide other, more flexible, ways to run +programs whenever something is committed. + +There are three kind of programs that can be run on +commit. They are specified in files in the repository, +as described below. The following table summarizes the +file names and the purpose of the corresponding +programs. + +@table @file +@item commitinfo +The program is responsible for checking that the commit +is allowed. If it exits with a non-zero exit status +the commit will be aborted. + +@item editinfo +The specified program is used to edit the log message, +and possibly verify that it contains all required +fields. This is most useful in combination with the +@file{rcsinfo} file, which can hold a log message +template (@pxref{rcsinfo}). + +@item loginfo +The specified program is called when the commit is +complete. It receives the log message and some +additional information and can store the log message in +a file, or mail it to appropriate persons, or maybe +post it to a local newsgroup, or@dots{} Your +imagination is the limit! +@end table + +@menu +* syntax:: The common syntax +@end menu + +@c . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . +@node syntax +@appendixsubsec The common syntax +@cindex Info files (syntax) +@cindex Syntax of info files +@cindex Common syntax of info files + +The four files @file{commitinfo}, @file{loginfo}, +@file{rcsinfo} and @file{editinfo} all have a common +format. The purpose of the files are described later +on. The common syntax is described here. + +Each line contains the following: +@itemize @bullet +@item +A regular expression + +@item +A whitespace separator---one or more spaces and/or tabs. + +@item +A file name or command-line template. +@end itemize + +@noindent +Blank lines are ignored. Lines that start with the +character @samp{#} are treated as comments. Long lines +unfortunately can @emph{not} be broken in two parts in +any way. + +The first regular expression that matches the current +directory name in the repository is used. The rest of the line +is used as a file name or command-line as appropriate. + +@c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +@node commitinfo +@appendixsec Commitinfo +@cindex Commitinfo +@cindex Checking commits +@cindex Precommit checking + +The @file{commitinfo} file defines programs to execute +whenever @samp{cvs commit} is about to execute. These +programs are used for pre-commit checking to verify +that the modified, added and removed files are really +ready to be committed. This could be used, for +instance, to verify that the changed files conform to +to your site's standards for coding practice. + +As mentioned earlier, each line in the +@file{commitinfo} file consists of a regular expression +and a command-line template. The template can include +a program name and any number of arguments you wish to +supply to it. The full path to the current source +repository is appended to the template, followed by the +file names of any files involved in the commit (added, +removed, and modified files). + +The first line with a regular expression matching the +relative path to the module will be used. If the +command returns a non-zero exit status the commit will +be aborted. + +@cindex DEFAULT in commitinfo +If the repository name does not match any of the +regular expressions in this file, the @samp{DEFAULT} +line is used, if it is specified. + +@cindex ALL in commitinfo +All occurances of the name @samp{ALL} appearing as a +regular expression are used in addition to the first +matching regular expression or the name @samp{DEFAULT}. + +Note: when @sc{CVS} is accessing a remote repository, +@file{commitinfo} will be run on the @emph{remote} +(i.e., server) side, not the client side (@pxref{Remote +repositories}). + +@c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +@node editinfo +@appendixsec Editinfo +@cindex Editinfo +@cindex Editor, specifying per module +@cindex Per-module editor +@cindex Log messages, editing + +If you want to make sure that all log messages look the +same way, you can use the @file{editinfo} file to +specify a program that is used to edit the log message. +This program could be a custom-made editor that always +enforces a certain style of the log message, or maybe a +simple shell script that calls an editor, and checks +that the entered message contains the required fields. + +If no matching line is found in the @file{editinfo} +file, the editor specified in the environment variable +@code{$CVSEDITOR} is used instead. If that variable is +not set, then the environment variable @code{$EDITOR} +is used instead. If that variable is not +set a precompiled default, normally @code{vi}, will be +used. + +The @file{editinfo} file is often most useful together +with the @file{rcsinfo} file, which can be used to +specify a log message template. + +Each line in the @file{editinfo} file consists of a +regular expression and a command-line template. The +template must include a program name, and can include +any number of arguments. The full path to the current +log message template file is appended to the template. + +One thing that should be noted is that the @samp{ALL} +keyword is not supported. If more than one matching +line is found, the first one is used. This can be +useful for specifying a default edit script in a +module, and then overriding it in a subdirectory. + +@cindex DEFAULT in editinfo +If the repository name does not match any of the +regular expressions in this file, the @samp{DEFAULT} +line is used, if it is specified. + +If the edit script exits with a non-zero exit status, +the commit is aborted. + +Note: when @sc{CVS} is accessing a remote repository, +@file{editinfo} will be run on the @emph{remote} +(i.e., server) side, not the client side (@pxref{Remote +repositories}). + +@menu +* editinfo example:: Editinfo example +@end menu + +@c . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . +@node editinfo example +@appendixsubsec Editinfo example + +The following is a little silly example of a +@file{editinfo} file, together with the corresponding +@file{rcsinfo} file, the log message template and an +editor script. We begin with the log message template. +We want to always record a bug-id number on the first +line of the log message. The rest of log message is +free text. The following template is found in the file +@file{/usr/cvssupport/tc.template}. + +@example +BugId: +@end example + +The script @file{/usr/cvssupport/bugid.edit} is used to +edit the log message. + +@example +#!/bin/sh +# +# bugid.edit filename +# +# Call $EDITOR on FILENAME, and verify that the +# resulting file contains a valid bugid on the first +# line. +if [ "x$EDITOR" = "x" ]; then EDITOR=vi; fi +if [ "x$CVSEDITOR" = "x" ]; then CVSEDITOR=$EDITOR; fi +$CVSEDITOR $1 +until head -1|grep '^BugId:[ ]*[0-9][0-9]*$' < $1 +do echo -n "No BugId found. Edit again? ([y]/n)" + read ans + case $@{ans@} in + n*) exit 1;; + esac + $CVSEDITOR $1 +done +@end example + +The @file{editinfo} file contains this line: + +@example +^tc /usr/cvssupport/bugid.edit +@end example + +The @file{rcsinfo} file contains this line: + +@example +^tc /usr/cvssupport/tc.template +@end example + +@c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +@node loginfo +@appendixsec Loginfo +@cindex Loginfo +@cindex Storing log messages +@cindex Mailing log messages +@cindex Distributing log messages +@cindex Log messages + +The @file{loginfo} file is used to control where +@samp{cvs commit} log information is sent. The first +entry on a line is a regular expression which is tested +against the directory that the change is being made to, +relative to the @code{$CVSROOT}. If a match is found, then +the remainder of the line is a filter program that +should expect log information on its standard input. + +The filter program may use one and only one % modifier +(a la printf). If @samp{%s} is specified in the filter +program, a brief title is included (enclosed in single +quotes) showing the modified file names. + +If the repository name does not match any of the +regular expressions in this file, the @samp{DEFAULT} +line is used, if it is specified. + +All occurances of the name @samp{ALL} appearing as a +regular expression are used in addition to the first +matching regular expression or @samp{DEFAULT}. + +The first matching regular expression is used. + +@xref{commit files}, for a description of the syntax of +the @file{loginfo} file. + +Note: when @sc{CVS} is accessing a remote repository, +@file{loginfo} will be run on the @emph{remote} +(i.e., server) side, not the client side (@pxref{Remote +repositories}). + +@menu +* loginfo example:: Loginfo example +@end menu + +@c . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . +@node loginfo example +@appendixsubsec Loginfo example + +The following @file{loginfo} file, together with the +tiny shell-script below, appends all log messages +to the file @file{$CVSROOT/CVSROOT/commitlog}, +and any commits to the administrative files (inside +the @file{CVSROOT} directory) are also logged in +@file{/usr/adm/cvsroot-log}. +@c and mailed to @t{ceder}. + +@c FIXME: is it a CVS feature or bug that only the +@c first matching line is used? It is documented +@c above, but is it useful? This example (with the +@c mail to ceder put back in) is awkward to write if +@c only the first matching line is used. +@example +ALL /usr/local/bin/cvs-log $CVSROOT/CVSROOT/commitlog +@c ^CVSROOT Mail -s %s ceder +^CVSROOT /usr/local/bin/cvs-log /usr/adm/cvsroot-log +@end example + +The shell-script @file{/usr/local/bin/cvs-log} looks +like this: + +@example +#!/bin/sh +(echo "-----------------------------------------------------------------"; + echo -n $USER" "; + date; + echo; + sed '1s+'$@{CVSROOT@}'++') >> $1 +@end example + +@c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +@node rcsinfo +@appendixsec Rcsinfo +@cindex Rcsinfo +@cindex Form for log message +@cindex Log message template +@cindex Template for log message + +The @file{rcsinfo} file can be used to specify a form to +edit when filling out the commit log. The +@file{rcsinfo} file has a syntax similar to the +@file{editinfo}, @file{commitinfo} and @file{loginfo} +files. @xref{syntax}. Unlike the other files the second +part is @emph{not} a command-line template. Instead, +the part after the regular expression should be a full pathname to +a file containing the log message template. + +If the repository name does not match any of the +regular expressions in this file, the @samp{DEFAULT} +line is used, if it is specified. + +All occurances of the name @samp{ALL} appearing as a +regular expression are used in addition to the first +matching regular expression or @samp{DEFAULT}. + +The log message template will be used as a default log +message. If you specify a log message with @samp{cvs +commit -m @var{message}} or @samp{cvs commit -f +@var{file}} that log message will override the +template. + +@xref{editinfo example}, for an example @file{rcsinfo} +file. + +When @sc{CVS} is accessing a remote repository, +the contents of @file{rcsinfo} at the time a directory +is first checked out will specify a template which does +not then change. If you edit @file{rcsinfo} or its +templates, you may need to check out a new working +directory. +@c Would be nice to fix CVS so this isn't needed. For +@c example, a mechanism analogous to CVS/Entries, where +@c the client keeps track of what version of the template +@c it has. + +@c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +@node cvsignore +@appendixsec Ignoring files via cvsignore +@cindex Cvsignore, global +@cindex Global cvsignore +@cindex Ignoring files +@c -- This chapter should maybe be moved to the +@c tutorial part of the manual? + +There are certain file names that frequently occur +inside your working copy, but that you don't want to +put under @sc{cvs} control. Examples are all the object +files that you get while you compile your sources. +Normally, when you run @samp{cvs update}, it prints a +line for each file it encounters that it doesn't know +about (@pxref{update output}). + +@sc{cvs} has a list of files (or sh(1) file name patterns) +that it should ignore while running @code{update}, +@code{import} and @code{release}. +@c -- Are those the only three commands affected? +This list is constructed in the following way. + +@itemize @bullet +@item +The list is initialized to include certain file name +patterns: names associated with @sc{cvs} +administration, or with other common source control +systems; common names for patch files, object files, +archive files, and editor backup files; and other names +that are usually artifacts of assorted utilities. +Currently, the default list of ignored file name +patterns is: + +@cindex Ignored files +@cindex Automatically ignored files +@example + RCS SCCS CVS CVS.adm + RCSLOG cvslog.* + tags TAGS + .make.state .nse_depinfo + *~ #* .#* ,* _$* *$ + *.old *.bak *.BAK *.orig *.rej .del-* + *.a *.olb *.o *.obj *.so *.exe + *.Z *.elc *.ln + core +@end example + +@item +The per-repository list in +@file{$CVSROOT/CVSROOT/cvsignore} is appended to +the list, if that file exists. + +@item +The per-user list in @file{.cvsignore} in your home +directory is appended to the list, if it exists. + +@item +Any entries in the environment variable +@code{$CVSIGNORE} is appended to the list. + +@item +Any @samp{-I} options given to @sc{cvs} is appended. + +@item +As @sc{cvs} traverses through your directories, the contents +of any @file{.cvsignore} will be appended to the list. +The patterns found in @file{.cvsignore} are only valid +for the directory that contains them, not for +any sub-directories. +@end itemize + +In any of the 5 places listed above, a single +exclamation mark (@samp{!}) clears the ignore list. +This can be used if you want to store any file which +normally is ignored by @sc{cvs}. + +@c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +@node history file +@appendixsec The history file +@cindex History file +@cindex Log information, saving + +The file @file{$CVSROOT/CVSROOT/history} is used +to log information for the @code{history} command +(@pxref{history}). This file must be created to turn +on logging. This is done automatically if the +@code{cvs init} command is used to set up the +repository (@pxref{Setting up}). + +The file format of the @file{history} file is +documented only in comments in the @sc{cvs} source +code, but generally programs should use the @code{cvs +history} command to access it anyway, in case the +format changes with future releases of @sc{cvs}. + +@c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +@node Setting up +@appendixsec Setting up the repository +@cindex Repository, setting up +@cindex Creating a repository +@cindex Setting up a repository + +To set up a @sc{cvs} repository, choose a directory +with ample disk space available for the revision +history of the source files. It should be accessable +(directly or via a networked file system) from all +machines which want to use @sc{cvs} in server or local +mode; the client machines need not have any access to +it other than via the @sc{cvs} protocol. + +To create a repository, run the @code{cvs init} +command. It will set up an empty repository in the +@sc{cvs} root specified in the usual way +(@pxref{Repository}). For example, + +@example +cvs -d /usr/local/cvsroot init +@end example + +@code{cvs init} is careful to never overwrite any +existing files in the repository, so no harm is done if +you run @code{cvs init} on an already set-up +repository. + +@code{cvs init} will enable history logging; if you +don't want that, remove the history file after running +@code{cvs init}. @xref{history file}. + +@node Variables +@appendixsec Expansions in administrative files + +Sometimes in writing an administrative file, you might +want the file to be able to know various things based +on environment @sc{cvs} is running in. There are +several mechanisms to do that. + +To find the home directory of the user running @sc{cvs} +(from the @code{HOME} environment variable), use +@samp{~} followed by @samp{/} or the end of the line. +Likewise for the home directory of @var{user}, use +@samp{~@var{user}}. These variables are expanded on +the server machine, and don't get any resonable +expansion if pserver (@pxref{Password authenticated}) +is in used; therefore user variables (see below) may be +a better choice to customize behavior based on the user +running @sc{cvs}. +@c Based on these limitations, should we deprecate ~? +@c What is it good for? Are people using it? + +One may want to know about various pieces of +information internal to @sc{cvs}. A @sc{cvs} internal +variable has the syntax @code{$@{@var{variable}@}}, +where @var{variable} starts with a letter and consists +of alphanumberic characters and @samp{_}. If the +character following @var{variable} is a +non-alphanumeric character other than @samp{_}, the +@samp{@{} and @samp{@}} can be omitted. The @sc{cvs} +internal variables are: + +@table @code +@item CVSROOT +This is the value of the @sc{cvs} root in use. +@xref{Repository}, for a description of the various +ways to specify this. + +@item RCSBIN +This is the value @sc{cvs} is using for where to find +@sc{rcs} binaries. @xref{Global options}, for a +description of how to specify this. + +@item CVSEDITOR +@itemx VISUAL +@itemx EDITOR +These all expand to the same value, which is the editor +that @sc{cvs} is using. @xref{Global options}, for how +to specify this. + +@item USER +Username of the user running @sc{cvs} (on the @sc{cvs} +server machine). +@end table + +If you want to pass a value to the administrative files +which the user that is running @sc{cvs} can specify, +use a user variable. To expand a user variable, the +administrative file contains +@code{$@{=@var{variable}@}}. To set a user variable, +specify the global option @samp{-s} to @sc{cvs}, with +argument @code{@var{variable}=@var{value}}. It may be +particularly useful to specify this option via +@file{.cvsrc} (@pxref{~/.cvsrc}). + +For example, if you want the administrative file to +refer to a test directory you might create a user +variable @code{TESTDIR}. Then if @sc{cvs} is invoked +as @code{cvs -s TESTDIR=/work/local/tests}, and the +administrative file contains @code{sh +$@{=TESTDIR@}/runtests}, then that string is expanded +to @code{sh /work/local/tests/runtests}. + +All other strings containing @samp{$} are reserved; +there is no way to quote a @samp{$} character so that +@samp{$} represents itself. + +@c --------------------------------------------------------------------- +@node Environment variables +@appendix All environment variables which affect CVS +@cindex Environment variables +@cindex Reference manual for variables + +This is a complete list of all environment variables +that affect @sc{cvs}. + +@table @code +@cindex CVSIGNORE +@item $CVSIGNORE +A whitespace-separated list of file name patterns that +@sc{cvs} should ignore. @xref{cvsignore}. + +@cindex CVSWRAPPERS +@item $CVSWRAPPERS +A whitespace-separated list of file name patterns that +@sc{cvs} should treat as wrappers. @xref{Wrappers}. + +@cindex CVSREAD +@item $CVSREAD +If this is set, @code{checkout} and @code{update} will +try hard to make the files in your working directory +read-only. When this is not set, the default behavior +is to permit modification of your working files. + +@cindex CVSROOT +@item $CVSROOT +Should contain the full pathname to the root of the @sc{cvs} +source repository (where the @sc{rcs} history files are +kept). This information must be available to @sc{cvs} for +most commands to execute; if @code{$CVSROOT} is not set, +or if you wish to override it for one invocation, you +can supply it on the command line: @samp{cvs -d cvsroot +cvs_command@dots{}} Once you have checked out a working +directory, @sc{cvs} stores the appropriate root (in +the file @file{CVS/Root}), so normally you only need to +worry about this when initially checking out a working +directory. + +@cindex EDITOR +@cindex CVSEDITOR +@item $EDITOR +@itemx $CVSEDITOR +Specifies the program to use for recording log messages +during commit. If not set, the default is +@samp{/usr/ucb/vi}. @code{$CVSEDITOR} overrides +@code{$EDITOR}. @code{$CVSEDITOR} does not exist in +@sc{cvs} 1.3, but the next release will probably +include it. + +@cindex PATH +@item $PATH +If @code{$RCSBIN} is not set, and no path is compiled +into @sc{cvs}, it will use @code{$PATH} to try to find all +programs it uses. + +@cindex RCSBIN +@item $RCSBIN +Specifies the full pathname of the location of @sc{rcs} programs, +such as co(1) and ci(1). If not set, a compiled-in +value is used, or your @code{$PATH} is searched. + +@cindex HOME +@item $HOME +@cindex HOMEPATH +@item $HOMEPATH +Used to locate the directory where the @file{.cvsrc} +file is searched (@code{$HOMEPATH} is used for Windows-NT). +@pxref{~/.cvsrc} + +@cindex CVS_RSH +@item $CVS_RSH +Used in client-server mode when accessing a remote +repository using @sc{rsh}. The default value is +@code{rsh}. You can set it to use another program for +accssing the remote server (e.g. for HP-UX 9, you +should set it to @code{remsh} because @code{rsh} +invokes the restricted shell). @pxref{Connecting via +rsh} + +@item $CVS_SERVER +Used in client-server mode when accessing a remote +repository using @sc{rsh}. It specifies the name of +the program to start on the server side when accessing +a remote repository using @sc{rsh}. The default value +is @code{cvs}. @pxref{Connecting via rsh} + +@item $CVS_PASSFILE +Used in client-server mode when accessing the @code{cvs +login server}. Default value is @file{$HOME/.cvspass}. +@pxref{Password authentication client} + +@item $CVS_PASSWORD +Used in client-server mode when accessing the @code{cvs +login server}. +@pxref{Password authentication client} + +@item $CVS_CLIENT_PORT +Used in client-server mode when accessing the server +via Kerberos. +@pxref{Kerberos authenticated} + +@cindex CVS_RCMD_PORT +@item $CVS_RCMD_PORT +Used in client-server mode. If set, specifies the port +number to be used when accessing the @sc{rcmd} demon on +the server side. (Currently not used for Unix clients). + +@cindex CVS_CLIENT_LOG +@item $CVS_CLIENT_LOG +Used for debugging only in client-server +mode. If set, everything send to the server is logged +into @file{@code{$CVS_CLIENT_LOG}.in} and everything +send from the server is logged into +@file{@code{$CVS_CLIENT_LOG}.out}. + +@cindex CVS_SERVER_SLEEP +@item $CVS_SERVER_SLEEP +Used only for debugging the server side in +client-server mode. If set, delays the start of the +server child process the the specified amount of +seconds so that you can attach to it with a debugger. + +@cindex CVS_IGNORE_REMOTE_ROOT +@item $CVS_IGNORE_REMOTE_ROOT +(What is the purpose of this variable?) + +@cindex COMSPEC +@item $COMSPEC +Used under OS/2 only. It specifies the name of the +command interpreter and defaults to @sc{cmd.exe}. + + +@end table + +@sc{cvs} is a front-end to @sc{rcs}. The following environment +variables affect @sc{rcs}. Note that if you are using +the client/server @sc{cvs}, these variables need to be +set on the server side (which may or not may be +possible depending on how you are connecting). There +is probably not any need to set any of them, however. + +@table @code +@cindex LOGNAME +@item $LOGNAME +@cindex USER +@itemx $USER +If set, they affect who @sc{rcs} thinks you are. If you +have trouble checking in files it might be because your +login name differs from the setting of e.g. +@code{$LOGNAME}. + +@cindex RCSINIT +@item $RCSINIT +Options prepended to the argument list, separated by +spaces. A backslash escapes spaces within an option. +The @code{$RCSINIT} options are prepended to the +argument lists of most @sc{rcs} commands. + +@cindex TMPDIR +@item $TMPDIR +@cindex TMP +@itemx $TMP +@cindex TEMP +@itemx $TEMP +Name of the temporary directory. The environment +variables are inspected in the order they appear above +and the first value found is taken; if none of them are +set, a host-dependent default is used, typically +@file{/tmp}. +@end table + +@c --------------------------------------------------------------------- +@node Troubleshooting +@appendix Troubleshooting + +@menu +* Magic branch numbers:: Magic branch numbers +@end menu + +@ignore +@c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +@c @node Bad administrative files +@appendixsec Bad administrative files + +@c -- Give hints on how to fix them +@end ignore + +@c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +@node Magic branch numbers +@appendixsec Magic branch numbers + +Externally, branch numbers consist of an odd number of +dot-separated decimal integers. @xref{Revision +numbers}. That is not the whole truth, however. For +efficiency reasons @sc{cvs} sometimes inserts an extra 0 +in the second rightmost position (1.2.3 becomes +1.2.0.3, 8.9.10.11.12 becomes 8.9.10.11.0.12 and so +on). + +@sc{cvs} does a pretty good job at hiding these so +called magic branches, but in at least four places the +hiding is incomplete. + +@itemize @bullet +@item +The magic branch can appear in the output from +@code{cvs status} in vanilla @sc{cvs} 1.3. This is +fixed in @sc{cvs} 1.3-s2. + +@item +The magic branch number appears in the output from +@code{cvs log}. This is much harder to fix, since +@code{cvs log} runs @code{rlog} (which is part of the +@sc{rcs} distribution), and modifying @code{rlog} to +know about magic branches would probably break someone's +habits (if they use branch 0 for their own purposes). + +@item +You cannot specify a symbolic branch name to @code{cvs log}. + +@item +You cannot specify a symbolic branch name to @code{cvs +admin}. + +@end itemize + +You can use the @code{admin} command to reassign a +symbolic name to a branch the way @sc{rcs} expects it +to be. If @code{R4patches} is assigned to the branch +1.4.2 (magic branch number 1.4.0.2) in file +@file{numbers.c} you can do this: + +@example +$ cvs admin -NR4patches:1.4.2 numbers.c +@end example + +It only works if at least one revision is already +committed on the branch. Be very careful so that you +do not assign the tag to the wrong number. (There is +no way to see how the tag was assigned yesterday). + +@c --------------------------------------------------------------------- +@node Copying +@appendix GNU GENERAL PUBLIC LICENSE +@c @include gpl.texinfo + +@c --------------------------------------------------------------------- +@node Index +@unnumbered Index +@cindex Index + +@printindex cp + +@summarycontents + +@contents + +@bye + +Local Variables: +fill-column: 55 +End: diff --git a/contrib/cvs/doc/cvsclient.texi b/contrib/cvs/doc/cvsclient.texi new file mode 100644 index 0000000..0d61ac1 --- /dev/null +++ b/contrib/cvs/doc/cvsclient.texi @@ -0,0 +1,824 @@ +\input texinfo @c -*- texinfo -*- + +@setfilename cvsclient.info +@include CVSvn.texi + +@node Top +@top CVS Client/Server + +This document describes the client/server protocol used by CVS. It does +not describe how to use or administer client/server CVS; see the regular +CVS manual for that. This is version @value{CVSVN} of the protocol +specification---@xref{Introduction} for more on what this version number +means. + +@menu +* Introduction:: What is CVS and what is the client/server protocol for? +* Goals:: Basic design decisions, requirements, scope, etc. +* Notes:: Notes on the current implementation +* Protocol Notes:: Possible enhancements, limitations, etc. of the protocol +* Connection and Authentication:: Various ways to connect to the server +* Protocol:: Complete description of the protocol +@end menu + +@node Introduction +@chapter Introduction + +CVS is a version control system (with some additional configuration +management functionality). It maintains a central @dfn{repository} +which stores files (often source code), including past versions, +information about who modified them and when, and so on. People who +wish to look at or modify those files, known as @dfn{developers}, use +CVS to @dfn{check out} a @dfn{working directory} from the repository, to +@dfn{check in} new versions of files to the repository, and other +operations such as viewing the modification history of a file. If +developers are connected to the repository by a network, particularly a +slow or flaky one, the most efficient way to use the network is with the +CVS-specific protocol described in this document. + +Developers, using the machine on which they store their working +directory, run the CVS @dfn{client} program. To perform operations +which cannot be done locally, it connects to the CVS @dfn{server} +program, which maintains the repository. For more information on how +to connect see @ref{Connection and Authentication}. + +This document describes the CVS protocol. Unfortunately, it does not +yet completely document one aspect of the protocol---the detailed +operation of each CVS command and option---and one must look at the CVS +user documentation, @file{cvs.texinfo}, for that information. The +protocol is non-proprietary (anyone who wants to is encouraged to +implement it) and an implementation, known as CVS, is available under +the GNU Public License. The CVS distribution, containing this +implementation, @file{cvs.texinfo}, and a copy (possibly more or less up +to date than what you are reading now) of this document, +@file{cvsclient.texi}, can be found at the usual GNU FTP sites, with a +filename such as @file{cvs-@var{version}.tar.gz}. + +This is version @value{CVSVN} of the protocol specification. This +version number is intended only to aid in distinguishing different +versions of this specification. Although the specification is currently +maintained in conjunction with the CVS implementation, and carries the +same version number, it also intends to document what is involved with +interoperating with other implementations (such as other versions of +CVS); see @xref{Requirements}. This version number should not be used +by clients or servers to determine what variant of the protocol to +speak; they should instead use the @code{valid-requests} and +@code{Valid-responses} mechanism (@pxref{Protocol}), which is more +flexible. + +@node Goals +@chapter Goals + +@itemize @bullet +@item +Do not assume any access to the repository other than via this protocol. +It does not depend on NFS, rdist, etc. + +@item +Providing a reliable transport is outside this protocol. It is expected +that it runs over TCP, UUCP, etc. + +@item +Security and authentication are handled outside this protocol (but see +below about @samp{cvs kserver}). + +@item +This might be a first step towards adding transactions to CVS (i.e. a +set of operations is either executed atomically or none of them is +executed), improving the locking, or other features. The current server +implementation is a long way from being able to do any of these +things. The protocol, however, is not known to contain any defects +which would preclude them. + +@item +The server never has to have any CVS locks in place while it is waiting +for communication with the client. This makes things robust in the face +of flaky networks. + +@item +Data is transferred in large chunks, which is necessary for good +performance. In fact, currently the client uploads all the data +(without waiting for server responses), and then waits for one server +response (which consists of a massive download of all the data). There +may be cases in which it is better to have a richer interraction, but +the need for the server to release all locks whenever it waits for the +client makes it complicated. +@end itemize + +@node Notes +@chapter Notes on the Current Implementation + +The client is built in to the normal @code{cvs} program, triggered by a +@code{CVSROOT} variable containing a colon, for example +@code{cygnus.com:/rel/cvsfiles}. + +The client stores what is stored in checked-out directories (including +@file{CVS}). The way these are stored is totally compatible with +standard CVS. The server requires no storage other than the repository, +which also is totally compatible with standard CVS. + +The server is started by @code{cvs server}. There is no particularly +compelling reason for this rather than making it a separate program +which shares a lot of sources with cvs. + +The server can also be started by @code{cvs kserver}, in which case it +does an initial Kerberos authentication on stdin. If the authentication +succeeds, it subsequently runs identically to @code{cvs server}. + +The current server implementation can use up huge amounts of memory +when transmitting a lot of data over a slow link (i.e. the network is +slower than the server can generate the data). There is some +experimental code (see @code{SERVER_FLOWCONTROL} in options.h) which +should help significantly. + +@node Protocol Notes +@chapter Notes on the Protocol + +A number of enhancements are possible: + +@itemize @bullet +@item +The @code{Modified} request could be speeded up by sending diffs rather +than entire files. The client would need some way to keep the version +of the file which was originally checked out, which would double client +disk space requirements or require coordination with editors (e.g. maybe +it could use emacs numbered backups). This would also allow local +operation of @code{cvs diff} without arguments. + +@item +Have the client keep a copy of some part of the repository. This allows +all of @code{cvs diff} and large parts of @code{cvs update} and +@code{cvs ci} to be local. The local copy could be made consistent with +the master copy at night (but if the master copy has been updated since +the latest nightly re-sync, then it would read what it needs to from the +master). + +@item +Provide encryption using kerberos. + +@item +The current procedure for @code{cvs update} is highly sub-optimal if +there are many modified files. One possible alternative would be to +have the client send a first request without the contents of every +modified file, then have the server tell it what files it needs. Note +the server needs to do the what-needs-to-be-updated check twice (or +more, if changes in the repository mean it has to ask the client for +more files), because it can't keep locks open while waiting for the +network. Perhaps this whole thing is irrelevant if client-side +repositories are implemented, and the rcsmerge is done by the client. +@end itemize + +@node Connection and Authentication +@chapter How to Connect to and Authenticate Oneself to the CVS server + +Connection and authentication occurs before the CVS protocol itself is +started. There are several ways to connect. + +@table @asis +@item rsh +If the client has a way to execute commands on the server, and provide +input to the commands and output from them, then it can connect that +way. This could be the usual rsh (port 514) protocol, Kerberos rsh, +SSH, or any similar mechanism. The client may allow the user to specify +the name of the server program; the default is @code{cvs}. It is +invoked with one argument, @code{server}. Once it invokes the server, +the client proceeds to start the cvs protocol. + +@item kserver +The kerberized server listens on a port (in the current implementation, +by having inetd call "cvs kserver") which defaults to 1999. The client +connects, sends the usual kerberos authentication information, and then +starts the cvs protocol. Note: port 1999 is officially registered for +another use, and in any event one cannot register more than one port for +CVS, so the kerberized client and server should be changed to use port +2401 (see below), and send a different string in place of @samp{BEGIN +AUTH REQUEST} to identify the authentication method in use. However, +noone has yet gotten around to implementing this. + +@item pserver +The password authenticated server listens on a port (in the current +implementation, by having inetd call "cvs pserver") which defaults to +2401 (this port is officially registered). The client +connects, sends the string @samp{BEGIN AUTH REQUEST}, a linefeed, the +cvs root, a linefeed, the username, a linefeed, the password trivially +encoded (see scramble.c in the cvs sources), a linefeed, the string +@samp{END AUTH REQUEST}, and a linefeed. The server responds with +@samp{I LOVE YOU} and a linefeed if the authentication is successful or +@samp{I HATE YOU} and a linefeed if the authentication fails. After +receiving @samp{I LOVE YOU}, the client proceeds with the cvs protocol. +If the client wishes to merely authenticate without starting the cvs +protocol, the procedure is the same, except @samp{BEGIN AUTH REQUEST} is +replaced with @samp{BEGIN VERIFICATION REQUEST}, @samp{END AUTH REQUEST} +is replaced with @samp{END VERIFICATION REQUEST}, and upon receipt of +@samp{I LOVE YOU} the connection is closed rather than continuing. +@end table + +@node Protocol +@chapter The CVS client/server protocol + +In the following, @samp{\n} refers to a linefeed and @samp{\t} refers +to a horizontal tab. + +@menu +* Entries Lines:: +* Modes:: +* Filenames:: Conventions regarding filenames +* Requests:: +* Responses:: +* Example:: +* Requirements:: +@end menu + +@node Entries Lines +@section Entries Lines + +Entries lines are transmitted as: + +@example +/ @var{name} / @var{version} / @var{conflict} / @var{options} / @var{tag_or_date} +@end example + +@var{tag_or_date} is either @samp{T} @var{tag} or @samp{D} @var{date} +or empty. If it is followed by a slash, anything after the slash +shall be silently ignored. + +@var{version} can be empty, or start with @samp{0} or @samp{-}, for no +user file, new user file, or user file to be removed, respectively. + +@var{conflict}, if it starts with @samp{+}, indicates that the file had +conflicts in it. The rest of @var{conflict} is @samp{=} if the +timestamp matches the file, or anything else if it doesn't. If +@var{conflict} does not start with a @samp{+}, it is silently ignored. + +@node Modes +@section Modes + +A mode is any number of repetitions of + +@example +@var{mode-type} = @var{data} +@end example + +separated by @samp{,}. + +@var{mode-type} is an identifier composed of alphanumeric characters. +Currently specified: @samp{u} for user, @samp{g} for group, @samp{o} +for other (see below for discussion of whether these have their POSIX +meaning or are more loose). Unrecognized values of @var{mode-type} +are silently ignored. + +@var{data} consists of any data not containing @samp{,}, @samp{\0} or +@samp{\n}. For @samp{u}, @samp{g}, and @samp{o} mode types, data +consists of alphanumeric characters, where @samp{r} means read, @samp{w} +means write, @samp{x} means execute, and unrecognized letters are +silently ignored. + +The two most obvious ways in which the mode matters are: (1) is it +writeable? This is used by the developer communication features, and +is implemented even on OS/2 (and could be implemented on DOS), whose +notion of mode is limited to a readonly bit. (2) is it executable? +Unix CVS users need CVS to store this setting (for shell scripts and +the like). The current CVS implementation on unix does a little bit +more than just maintain these two settings, but it doesn't really have +a nice general facility to store or version control the mode, even on +unix, much less across operating systems with diverse protection +features. So all the ins and outs of what the mode means across +operating systems haven't really been worked out (e.g. should the VMS +port use ACLs to get POSIX semantics for groups?). + +@node Filenames +@section Conventions regarding transmission of file names + +In most contexts, @samp{/} is used to separate directory and file +names in filenames, and any use of other conventions (for example, +that the user might type on the command line) is converted to that +form. The only exceptions might be a few cases in which the server +provides a magic cookie which the client then repeats verbatim, but as +the server has not yet been ported beyond unix, the two rules provide +the same answer (and what to do if future server ports are operating +on a repository like e:/foo or CVS_ROOT:[FOO.BAR] has not been +carefully thought out). + +@node Requests +@section Requests + +File contents (noted below as @var{file transmission}) can be sent in +one of two forms. The simpler form is a number of bytes, followed by a +newline, followed by the specified number of bytes of file contents. +These are the entire contents of the specified file. Second, if both +client and server support @samp{gzip-file-contents}, a @samp{z} may +precede the length, and the `file contents' sent are actually compressed +with @samp{gzip}. The length specified is that of the compressed +version of the file. + +In neither case are the file content followed by any additional data. +The transmission of a file will end with a newline iff that file (or its +compressed form) ends with a newline. + +@table @code +@item Root @var{pathname} \n +Response expected: no. Tell the server which @code{CVSROOT} to use. +@var{pathname} must already exist; if creating a new root, use the +@code{init} request, not @code{Root}. @var{pathname} does not include +the hostname of the server, how to access the server, etc.; by the time +the CVS protocol is in use, connection, authentication, etc., are +already taken care of. + +@item Valid-responses @var{request-list} \n +Response expected: no. +Tell the server what responses the client will accept. +request-list is a space separated list of tokens. + +@item valid-requests \n +Response expected: yes. +Ask the server to send back a @code{Valid-requests} response. + +@item Repository @var{repository} \n +Response expected: no. Tell the server what repository to use. This +should be a directory name from a previous server response. Note that +this both gives a default for @code{Entry } and @code{Modified } and +also for @code{ci} and the other commands; normal usage is to send a +@code{Repository } for each directory in which there will be an +@code{Entry } or @code{Modified }, and then a final @code{Repository } +for the original directory, then the command. + +@item Directory @var{local-directory} \n +Additional data: @var{repository} \n. This is like @code{Repository}, +but the local name of the directory may differ from the repository name. +If the client uses this request, it affects the way the server returns +pathnames; see @ref{Responses}. @var{local-directory} is relative to +the top level at which the command is occurring (i.e. the last +@code{Directory} or @code{Repository} which is sent before the command). + +@item Max-dotdot @var{level} \n +Tell the server that @var{level} levels of directories above the +directory which @code{Directory} requests are relative to will be +needed. For example, if the client is planning to use a +@code{Directory} request for @file{../../foo}, it must send a +@code{Max-dotdot} request with a @var{level} of at least 2. +@code{Max-dotdot} must be sent before the first @code{Directory} +request. + +@item Static-directory \n +Response expected: no. Tell the server that the directory most recently +specified with @code{Repository} or @code{Directory} should not have +additional files checked out unless explicitly requested. The client +sends this if the @code{Entries.Static} flag is set, which is controlled +by the @code{Set-static-directory} and @code{Clear-static-directory} +responses. + +@item Sticky @var{tagspec} \n +Response expected: no. Tell the server that the directory most recently +specified with @code{Repository} has a sticky tag or date @var{tagspec}. +The first character of @var{tagspec} is @samp{T} for a tag, or @samp{D} +for a date. The remainder of @var{tagspec} contains the actual tag or +date. + +@item Checkin-prog @var{program} \n +Response expected: no. Tell the server that the directory most recently +specified with @code{Directory} has a checkin program @var{program}. +Such a program would have been previously set with the +@code{Set-checkin-prog} response. + +@item Update-prog @var{program} \n +Response expected: no. Tell the server that the directory most recently +specified with @code{Directory} has an update program @var{program}. +Such a program would have been previously set with the +@code{Set-update-prog} response. + +@item Entry @var{entry-line} \n +Response expected: no. Tell the server what version of a file is on the +local machine. The name in @var{entry-line} is a name relative to the +directory most recently specified with @code{Repository}. If the user +is operating on only some files in a directory, @code{Entry} requests +for only those files need be included. If an @code{Entry} request is +sent without @code{Modified}, @code{Unchanged}, or @code{Lost} for that +file the meaning depends on whether @code{UseUnchanged} has been sent; +if it has been it means the file is lost, if not it means the file is +unchanged. + +@item Modified @var{filename} \n +Response expected: no. Additional data: mode, \n, file transmission. +Send the server a copy of one locally modified file. @var{filename} is +relative to the most recent repository sent with @code{Repository}. If +the user is operating on only some files in a directory, only those +files need to be included. This can also be sent without @code{Entry}, +if there is no entry for the file. + +@item Lost @var{filename} \n +Response expected: no. Tell the server that @var{filename} no longer +exists. The name is relative to the most recent repository sent with +@code{Repository}. This is used for any case in which @code{Entry} is +being sent but the file no longer exists. If the client has issued the +@code{UseUnchanged} request, then this request is not used. + +@item Unchanged @var{filename} \n +Response expected: no. Tell the server that @var{filename} has not been +modified in the checked out directory. The name is relative to the most +recent repository sent with @code{Repository}. This request can only be +issued if @code{UseUnchanged} has been sent. + +@item UseUnchanged \n +Response expected: no. Tell the server that the client will be +indicating unmodified files with @code{Unchanged}, and that files for +which no information is sent are nonexistent on the client side, not +unchanged. This is necessary for correct behavior since only the server +knows what possible files may exist, and thus what files are +nonexistent. + +@item Notify @var{filename} \n +Tell the server that a @code{edit} or @code{unedit} command has taken +place. The server needs to send a @code{Notified} response, but such +response is deferred until the next time that the server is sending +responses. Response expected: no. Additional data: +@example +@var{notification-type} \t @var{time} \t @var{clienthost} \t +@var{working-dir} \t @var{watches} \n +@end example +where @var{notification-type} is @samp{E} for edit or @samp{U} for +unedit, @var{time} is the time at which the edit or unedit took place, +@var{clienthost} is the name of the host on which the edit or unedit +took place, and @var{working-dir} is the pathname of the working +directory where the edit or unedit took place. @var{watches} are the +temporary watches to set; if it is followed by \t then the tab and the +rest of the line are ignored. + +@item Questionable @var{filename} \n +Response expected: no. Additional data: no. Tell the server to check +whether @var{filename} should be ignored, and if not, next time the +server sends responses, send (in a @code{M} response) @samp{?} followed +by the directory and filename. + +@item Case \n +Tell the server that filenames should be matched against ignore patterns +in a case-insensitive fashion. Note that this does not apply to other +comparisons---for example the filenames given in @code{Entry} and +@code{Modified} requests for the same file must match in case regardless +of whether the @code{Case} request is sent. + +@item Argument @var{text} \n +Response expected: no. +Save argument for use in a subsequent command. Arguments +accumulate until an argument-using command is given, at which point +they are forgotten. + +@item Argumentx @var{text} \n +Response expected: no. Append \n followed by text to the current +argument being saved. + +@item Global_option @var{option} \n +Transmit one of the global options @samp{-q}, @samp{-Q}, @samp{-l}, +@samp{-t}, @samp{-r}, or @samp{-n}. @var{option} must be one of those +strings, no variations (such as combining of options) are allowed. For +graceful handling of @code{valid-requests}, it is probably better to +make new global options separate requests, rather than trying to add +them to this request. + +@item Set @var{variable}=@var{value} \n +Set a user variable @var{variable} to @var{value}. + +@item expand-modules \n +Response expected: yes. Expand the modules which are specified in the +arguments. Returns the data in @code{Module-expansion} responses. Note +that the server can assume that this is checkout or export, not rtag or +rdiff; the latter do not access the working directory and thus have no +need to expand modules on the client side. + +@item co \n +@itemx ci \n +@itemx diff \n +@itemx tag \n +@itemx status \n +@itemx log \n +@itemx add \n +@itemx remove \n +@itemx rdiff \n +@itemx rtag \n +@itemx admin \n +@itemx export \n +@itemx history \n +@itemx watchers \n +@itemx editors \n +@itemx annotate \n +Response expected: yes. Actually do a cvs command. This uses any +previous @code{Argument}, @code{Repository}, @code{Entry}, +@code{Modified}, or @code{Lost} requests, if they have been sent. The +last @code{Repository} sent specifies the working directory at the time +of the operation. No provision is made for any input from the user. +This means that @code{ci} must use a @code{-m} argument if it wants to +specify a log message. + +@itemx init @var{root-name} \n +Response expected: yes. If it doesn't already exist, create a @sc{cvs} +repository @var{root-name}. The @code{Root} request need not have been +previously sent. + +@itemx update \n +Response expected: yes. Actually do a @code{cvs update} command. This +uses any previous @code{Argument}, @code{Repository}, @code{Entry}, +@code{Modified}, or @code{Lost} requests, if they have been sent. The +last @code{Repository} sent specifies the working directory at the time +of the operation. The @code{-I} option is not used--files which the +client can decide whether to ignore are not mentioned and the client +sends the @code{Questionable} request for others. + +@item import \n +Response expected: yes. Actually do a @code{cvs import} command. This +uses any previous @code{Argument}, @code{Repository}, @code{Entry}, +@code{Modified}, or @code{Lost} requests, if they have been sent. The +last @code{Repository} sent specifies the working directory at the time +of the operation. The files to be imported are sent in @code{Modified} +requests (files which the client knows should be ignored are not sent; +the server must still process the CVSROOT/cvsignore file unless -I ! is +sent). A log message must have been specified with a @code{-m} +argument. + +@item watch-on \n +@itemx watch-off \n +@itemx watch-add \n +@itemx watch-remove \n +Response expected: yes. Actually do the @code{cvs watch on}, @code{cvs +watch off}, @code{cvs watch add}, and @code{cvs watch remove} commands, +respectively. This uses any previous @code{Argument}, +@code{Repository}, @code{Entry}, @code{Modified}, or @code{Lost} +requests, if they have been sent. The last @code{Repository} sent +specifies the working directory at the time of the operation. + +@item release \n +Response expected: yes. Note that a @code{cvs release} command has +taken place and update the history file accordingly. + +@item noop \n +Response expected: yes. This request is a null command in the sense +that it doesn't do anything, but merely (as with any other requests +expecting a response) sends back any responses pertaining to pending +errors, pending @code{Notified} responses, etc. + +@item update-patches \n +This request does not actually do anything. It is used as a signal that +the server is able to generate patches when given an @code{update} +request. The client must issue the @code{-u} argument to @code{update} +in order to receive patches. + +@item gzip-file-contents @var{level} \n +This request asks the server to filter files it sends to the client +through the @samp{gzip} program, using the specified level of +compression. If this request is not made, the server must not do any +compression. + +This is only a hint to the server. It may still decide (for example, in +the case of very small files, or files that already appear to be +compressed) not to do the compression. Compression is indicated by a +@samp{z} preceding the file length. + +Availability of this request in the server indicates to the client that +it may compress files sent to the server, regardless of whether the +client actually uses this request. + +@item @var{other-request} @var{text} \n +Response expected: yes. +Any unrecognized request expects a response, and does not +contain any additional data. The response will normally be something like +@samp{error unrecognized request}, but it could be a different error if +a previous command which doesn't expect a response produced an error. +@end table + +When the client is done, it drops the connection. + +@node Responses +@section Responses + +After a command which expects a response, the server sends however many +of the following responses are appropriate. Pathnames are of the actual +files operated on (i.e. they do not contain @samp{,v} endings), and are +suitable for use in a subsequent @code{Repository} request. However, if +the client has used the @code{Directory} request, then it is instead a +local directory name relative to the directory in which the command was +given (i.e. the last @code{Directory} before the command). Then a +newline and a repository name (the pathname which is sent if +@code{Directory} is not used). Then the slash and the filename. For +example, for a file @file{i386.mh} which is in the local directory +@file{gas.clean/config} and for which the repository is +@file{/rel/cvsfiles/devo/gas/config}: + +@example +gas.clean/config/ +/rel/cvsfiles/devo/gas/config/i386.mh +@end example + +Any response always ends with @samp{error} or @samp{ok}. This indicates +that the response is over. + +@table @code +@item Valid-requests @var{request-list} \n +Indicate what requests the server will accept. @var{request-list} +is a space separated list of tokens. If the server supports sending +patches, it will include @samp{update-patches} in this list. The +@samp{update-patches} request does not actually do anything. + +@item Checked-in @var{pathname} \n +Additional data: New Entries line, \n. This means a file @var{pathname} +has been successfully operated on (checked in, added, etc.). name in +the Entries line is the same as the last component of @var{pathname}. + +@item New-entry @var{pathname} \n +Additional data: New Entries line, \n. Like @code{Checked-in}, but the +file is not up to date. + +@item Updated @var{pathname} \n +Additional data: New Entries line, \n, mode, \n, file transmission. A +new copy of the file is enclosed. This is used for a new revision of an +existing file, or for a new file, or for any other case in which the +local (client-side) copy of the file needs to be updated, and after +being updated it will be up to date. If any directory in pathname does +not exist, create it. + +@item Merged @var{pathname} \n +This is just like @code{Updated} and takes the same additional data, +with the one difference that after the new copy of the file is enclosed, +it will still not be up to date. Used for the results of a merge, with +or without conflicts. + +@item Patched @var{pathname} \n +This is just like @code{Updated} and takes the same additional data, +with the one difference that instead of sending a new copy of the file, +the server sends a patch produced by @samp{diff -u}. This client must +apply this patch, using the @samp{patch} program, to the existing file. +This will only be used when the client has an exact copy of an earlier +revision of a file. This response is only used if the @code{update} +command is given the @samp{-u} argument. + +@item Mode @var{mode} \n +This @var{mode} applies to the next file mentioned in +@code{Checked-in}. It does not apply to any request which follows a +@code{Checked-in}, @code{New-entry}, @code{Updated}, @code{Merged}, or +@code{Patched} response. + +@item Checksum @var{checksum}\n +The @var{checksum} applies to the next file sent over via +@code{Updated}, @code{Merged}, or @code{Patched}. In the case of +@code{Patched}, the checksum applies to the file after being patched, +not to the patch itself. The client should compute the checksum itself, +after receiving the file or patch, and signal an error if the checksums +do not match. The checksum is the 128 bit MD5 checksum represented as +32 hex digits. This response is optional, and is only used if the +client supports it (as judged by the @code{Valid-responses} request). + +@item Copy-file @var{pathname} \n +Additional data: @var{newname} \n. Copy file @var{pathname} to +@var{newname} in the same directory where it already is. This does not +affect @code{CVS/Entries}. + +@item Removed @var{pathname} \n +The file has been removed from the repository (this is the case where +cvs prints @samp{file foobar.c is no longer pertinent}). + +@item Remove-entry @var{pathname} \n +The file needs its entry removed from @code{CVS/Entries}, but the file +itself is already gone (this happens in response to a @code{ci} request +which involves committing the removal of a file). + +@item Set-static-directory @var{pathname} \n +This instructs the client to set the @code{Entries.Static} flag, which +it should then send back to the server in a @code{Static-directory} +request whenever the directory is operated on. @var{pathname} ends in a +slash; its purpose is to specify a directory, not a file within a +directory. + +@item Clear-static-directory @var{pathname} \n +Like @code{Set-static-directory}, but clear, not set, the flag. + +@item Set-sticky @var{pathname} \n +Additional data: @var{tagspec} \n. Tell the client to set a sticky tag +or date, which should be supplied with the @code{Sticky} request for +future operations. @var{pathname} ends in a slash; its purpose is to +specify a directory, not a file within a directory. The first character +of @var{tagspec} is @samp{T} for a tag, or @samp{D} for a date. The +remainder of @var{tagspec} contains the actual tag or date. + +@item Clear-sticky @var{pathname} \n +Clear any sticky tag or date set by @code{Set-sticky}. + +@item Template @var{pathname} \n +Additional data: file transmission (note: compressed file transmissions +are not supported). @var{pathname} ends in a slash; its purpose is to +specify a directory, not a file within a directory. Tell the client to +store the file transmission as the template log message, and then use +that template in the future when prompting the user for a log message. + +@item Set-checkin-prog @var{dir} \n +Additional data: @var{prog} \n. Tell the client to set a checkin +program, which should be supplied with the @code{Checkin-prog} request +for future operations. + +@item Set-update-prog @var{dir} \n +Additional data: @var{prog} \n. Tell the client to set an update +program, which should be supplied with the @code{Update-prog} request +for future operations. + +@item Notified @var{pathname} \n +Indicate to the client that the notification for @var{pathname} has been +done. There should be one such response for every @code{Notify} +request; if there are several @code{Notify} requests for a single file, +the requests should be processed in order; the first @code{Notified} +response pertains to the first @code{Notify} request, etc. + +@item Module-expansion @var{pathname} \n Return a file or directory +which is included in a particular module. @var{pathname} is relative +to cvsroot, unlike most pathnames in responses. @var{pathname} should +be used to look and see whether some or all of the module exists on +the client side; it is not necessarily suitable for passing as an +argument to a @code{co} request (for example, if the modules file +contains the @samp{-d} option, it will be the directory specified with +@samp{-d}, not the name of the module). + +@item M @var{text} \n +A one-line message for the user. + +@item E @var{text} \n +Same as @code{M} but send to stderr not stdout. + +@item error @var{errno-code} @samp{ } @var{text} \n +The command completed with an error. @var{errno-code} is a symbolic +error code (e.g. @code{ENOENT}); if the server doesn't support this +feature, or if it's not appropriate for this particular message, it just +omits the errno-code (in that case there are two spaces after +@samp{error}). Text is an error message such as that provided by +strerror(), or any other message the server wants to use. + +@item ok \n +The command completed successfully. +@end table + +@node Example +@section Example + +Lines beginning with @samp{c>} are sent by the client; lines beginning +with @samp{s>} are sent by the server; lines beginning with @samp{#} are +not part of the actual exchange. + +@example +c> Root /rel/cvsfiles +# In actual practice the lists of valid responses and requests would +# be longer +c> Valid-responses Updated Checked-in M ok error +c> valid-requests +s> Valid-requests Root co Modified Entry Repository ci Argument Argumentx +s> ok +# cvs co devo/foo +c> Argument devo/foo +c> co +s> Updated /rel/cvsfiles/devo/foo/foo.c +s> /foo.c/1.4/Mon Apr 19 15:36:47 1993 Mon Apr 19 15:36:47 1993// +s> 26 +s> int mein () @{ abort (); @} +s> Updated /rel/cvsfiles/devo/foo/Makefile +s> /Makefile/1.2/Mon Apr 19 15:36:47 1993 Mon Apr 19 15:36:47 1993// +s> 28 +s> foo: foo.c +s> $(CC) -o foo $< +s> ok +# In actual practice the next part would be a separate connection. +# Here it is shown as part of the same one. +c> Repository /rel/cvsfiles/devo/foo +# foo.c relative to devo/foo just set as Repository. +c> Entry /foo.c/1.4/Mon Apr 19 15:36:47 1993 Mon Apr 19 15:36:47 1993// +c> Entry /Makefile/1.2/Mon Apr 19 15:36:47 1993 Mon Apr 19 15:36:47 1993// +c> Modified foo.c +c> 26 +c> int main () @{ abort (); @} +# cvs ci -m <log message> foo.c +c> Argument -m +c> Argument Well, you see, it took me hours and hours to find this typo and I +c> Argumentx searched and searched and eventually had to ask John for help. +c> Argument foo.c +c> ci +s> Checked-in /rel/cvsfiles/devo/foo/foo.c +s> /foo.c/1.5/ Mon Apr 19 15:54:22 CDT 1993// +s> M Checking in foo.c; +s> M /cygint/rel/cvsfiles/devo/foo/foo.c,v <-- foo.c +s> M new revision: 1.5; previous revision: 1.4 +s> M done +s> ok +@end example + +@node Requirements +@section Required versus optional parts of the protocol + +The following are part of every known implementation of the CVS +protocol and it is considered reasonable behavior to completely fail +to work if you are connected with an implementation which attempts to +not support them. Requests: Root, Valid-responses, valid-requests, +Repository, Entry, Modified, Argument, Argumentx, ci, co, update. +Responses: ok, error, Valid-requests, Checked-in, Updated, Merged, +Removed, M, E. + +Failure to support the Directory, UseUnchanged, and Unchanged requests +is deprecated. CVS 1.5 and later have supported these requests and in +the future it will be considered reasonable behavior to completely +fail to work with an implementation which attempts to not support +them. Support for the Repository and Lost requests is deprecated; CVS +clients 1.5 and later will not use them if communicating with a server +which supports Directory and UseUnchanged. + +@bye diff --git a/contrib/cvs/install-sh b/contrib/cvs/install-sh new file mode 100755 index 0000000..ab74c88 --- /dev/null +++ b/contrib/cvs/install-sh @@ -0,0 +1,238 @@ +#!/bin/sh +# +# install - install a program, script, or datafile +# This comes from X11R5. +# +# Calling this script install-sh is preferred over install.sh, to prevent +# `make' implicit rules from creating a file called install from it +# when there is no Makefile. +# +# This script is compatible with the BSD install script, but was written +# from scratch. +# + + +# set DOITPROG to echo to test this script + +# Don't use :- since 4.3BSD and earlier shells don't like it. +doit="${DOITPROG-}" + + +# put in absolute paths if you don't have them in your path; or use env. vars. + +mvprog="${MVPROG-mv}" +cpprog="${CPPROG-cp}" +chmodprog="${CHMODPROG-chmod}" +chownprog="${CHOWNPROG-chown}" +chgrpprog="${CHGRPPROG-chgrp}" +stripprog="${STRIPPROG-strip}" +rmprog="${RMPROG-rm}" +mkdirprog="${MKDIRPROG-mkdir}" + +tranformbasename="" +transform_arg="" +instcmd="$mvprog" +chmodcmd="$chmodprog 0755" +chowncmd="" +chgrpcmd="" +stripcmd="" +rmcmd="$rmprog -f" +mvcmd="$mvprog" +src="" +dst="" +dir_arg="" + +while [ x"$1" != x ]; do + case $1 in + -c) instcmd="$cpprog" + shift + continue;; + + -d) dir_arg=true + shift + continue;; + + -m) chmodcmd="$chmodprog $2" + shift + shift + continue;; + + -o) chowncmd="$chownprog $2" + shift + shift + continue;; + + -g) chgrpcmd="$chgrpprog $2" + shift + shift + continue;; + + -s) stripcmd="$stripprog" + shift + continue;; + + -t=*) transformarg=`echo $1 | sed 's/-t=//'` + shift + continue;; + + -b=*) transformbasename=`echo $1 | sed 's/-b=//'` + shift + continue;; + + *) if [ x"$src" = x ] + then + src=$1 + else + # this colon is to work around a 386BSD /bin/sh bug + : + dst=$1 + fi + shift + continue;; + esac +done + +if [ x"$src" = x ] +then + echo "install: no input file specified" + exit 1 +else + true +fi + +if [ x"$dir_arg" != x ]; then + dst=$src + src="" + + if [ -d $dst ]; then + instcmd=: + else + instcmd=mkdir + fi +else + +# Waiting for this to be detected by the "$instcmd $src $dsttmp" command +# might cause directories to be created, which would be especially bad +# if $src (and thus $dsttmp) contains '*'. + + if [ -f $src -o -d $src ] + then + true + else + echo "install: $src does not exist" + exit 1 + fi + + if [ x"$dst" = x ] + then + echo "install: no destination specified" + exit 1 + else + true + fi + +# If destination is a directory, append the input filename; if your system +# does not like double slashes in filenames, you may need to add some logic + + if [ -d $dst ] + then + dst="$dst"/`basename $src` + else + true + fi +fi + +## this sed command emulates the dirname command +dstdir=`echo $dst | sed -e 's,[^/]*$,,;s,/$,,;s,^$,.,'` + +# Make sure that the destination directory exists. +# this part is taken from Noah Friedman's mkinstalldirs script + +# Skip lots of stat calls in the usual case. +if [ ! -d "$dstdir" ]; then +defaultIFS=' +' +IFS="${IFS-${defaultIFS}}" + +oIFS="${IFS}" +# Some sh's can't handle IFS=/ for some reason. +IFS='%' +set - `echo ${dstdir} | sed -e 's@/@%@g' -e 's@^%@/@'` +IFS="${oIFS}" + +pathcomp='' + +while [ $# -ne 0 ] ; do + pathcomp="${pathcomp}${1}" + shift + + if [ ! -d "${pathcomp}" ] ; + then + $mkdirprog "${pathcomp}" + else + true + fi + + pathcomp="${pathcomp}/" +done +fi + +if [ x"$dir_arg" != x ] +then + $doit $instcmd $dst && + + if [ x"$chowncmd" != x ]; then $doit $chowncmd $dst; else true ; fi && + if [ x"$chgrpcmd" != x ]; then $doit $chgrpcmd $dst; else true ; fi && + if [ x"$stripcmd" != x ]; then $doit $stripcmd $dst; else true ; fi && + if [ x"$chmodcmd" != x ]; then $doit $chmodcmd $dst; else true ; fi +else + +# If we're going to rename the final executable, determine the name now. + + if [ x"$transformarg" = x ] + then + dstfile=`basename $dst` + else + dstfile=`basename $dst $transformbasename | + sed $transformarg`$transformbasename + fi + +# don't allow the sed command to completely eliminate the filename + + if [ x"$dstfile" = x ] + then + dstfile=`basename $dst` + else + true + fi + +# Make a temp file name in the proper directory. + + dsttmp=$dstdir/#inst.$$# + +# Move or copy the file name to the temp name + + $doit $instcmd $src $dsttmp && + + trap "rm -f ${dsttmp}" 0 && + +# and set any options; do chmod last to preserve setuid bits + +# If any of these fail, we abort the whole thing. If we want to +# ignore errors from any of these, just make sure not to ignore +# errors from the above "$doit $instcmd $src $dsttmp" command. + + if [ x"$chowncmd" != x ]; then $doit $chowncmd $dsttmp; else true;fi && + if [ x"$chgrpcmd" != x ]; then $doit $chgrpcmd $dsttmp; else true;fi && + if [ x"$stripcmd" != x ]; then $doit $stripcmd $dsttmp; else true;fi && + if [ x"$chmodcmd" != x ]; then $doit $chmodcmd $dsttmp; else true;fi && + +# Now rename the file to the real destination. + + $doit $rmcmd -f $dstdir/$dstfile && + $doit $mvcmd $dsttmp $dstdir/$dstfile + +fi && + + +exit 0 diff --git a/contrib/cvs/lib/ChangeLog b/contrib/cvs/lib/ChangeLog new file mode 100644 index 0000000..900d6d4 --- /dev/null +++ b/contrib/cvs/lib/ChangeLog @@ -0,0 +1,448 @@ +Thu Apr 25 18:26:34 1996 Jim Kingdon <kingdon@harvey.cyclic.com> + + * getdate.y (get_date): Set Start from nowtime, not now->time, + which may not be set. + * getdate.c: Regenerated. + +Wed Apr 10 17:55:02 1996 Jim Kingdon <kingdon@harvey.cyclic.com> + + * getdate.y (get_date): Use a time_t variable rather than a field + in a struct timeb. Works around Solaris compiler bug. Sure, it + is a compiler bug, but the workaround is completely painless. + * getdate.c: Regenerated. + +Fri Mar 22 11:17:05 1996 Jim Kingdon <kingdon@harvey.cyclic.com> + + * system.h: If EXIT_FAILURE is not defined by stdlib.h, define it + ourself. + +Thu Mar 14 16:27:53 1996 Jim Kingdon <kingdon@harvey.cyclic.com> + + * system.h: Remove alloca cruft. + +Wed Feb 28 03:16:48 1996 Benjamin J. Lee <benjamin@cyclic.com> + + * build_lib.com: Changed definition of symbol CC to search + for include files in [-.VMS] so VMS config.h can be picked + up without copying. + +Tue Feb 27 21:26:34 1996 Benjamin J. Lee <benjamin@cyclic.com> + + * build_lib.com: Added. DCL File to build contents of [.lib] + +Tue Feb 27 21:18:38 1996 Benjamin J. Lee <benjamin@cyclic.com> + + * system.h: added an existence_error macro check for EVMSERR + necessary for happiness under VMS + +Thu Feb 22 22:30:04 1996 Jim Kingdon <kingdon@harvey.cyclic.com> + + * Makefile.in (OBJECTS): Remove @ALLOCA@ + (SOURCES): Remove alloca.c + * alloca.c: Removed. + * regex.c (REGEX_MALLOC): Define. + +Thu Feb 15 14:00:00 Jim Kingdon <kingdon@cyclic.com> + + * vasprintf.c: Declare abs(). + +Wed Feb 14 14:48:31 1996 Jim Kingdon <kingdon@harvey.cyclic.com> + + * vasprintf.c (int_vasprintf): Don't cast arguments to memcpy. + * vasprintf.c, strtoul.c: Don't include ansidecl.h. Do include + config.h if HAVE_CONFIG_H (for const). + * strtoul.c: Change CONST to const. + +Tue Feb 13 20:04:39 1996 Jim Kingdon <kingdon@harvey.cyclic.com> + + * strtoul.c: Added (needed by vasprintf.c, and missing on SunOS4). + * Makefile.in (SOURCES): Add strtoul.c. + +Mon Feb 12 10:04:46 1996 Jim Kingdon <kingdon@harvey.cyclic.com> + + * vasprintf.c: Added (same contents as before). + * Makefile.in (SOURCES): Add vasprintf.c. + +Thu Feb 1 14:33:17 1996 Karl Fogel <kfogel@floss.red-bean.com> + + * Makefile.in (xlint): new rule; does nothing, as I'm not sure + running lint is actually advisable in here, but the top-level + Makefile thinks it can `make xlint' here. + +Thu Feb 1 15:07:42 1996 Jim Kingdon <kingdon@harvey.cyclic.com> + + * getopt.c: Remove rcsid. + +Tue Jan 30 18:20:27 1996 Jim Kingdon <kingdon@harvey.cyclic.com> + + * getline.c: Don't define NDEBUG. + (getstr): Rewrite assertions in a way which should stay clear of + signed/unsigned problems and compiler warnings thereof. + +Thu Jan 25 00:14:06 1996 Jim Kingdon <kingdon@beezley.cyclic.com> + + * yesno.c (yesno): fflush stdout as well as stderr. + +Wed Jan 3 18:16:50 1996 Jim Kingdon <kingdon@harvey.cyclic.com> + + * sighandle.c (SIG_register): Use memset not bzero. + * system.h: Remove defines for index, rindex, bcmp, and bzero. + All the calls to those functions are gone from CVS. + +Tue Jan 2 13:00:00 1996 Jim Kingdon <kingdon@peary.cyclic.com> + + Visual C++ lint: + * sighandle.c: Prototype SIG_handle and SIG_defaults. + Use SIG_ERR where appropriate. + +Mon Dec 18 10:15:05 1995 Jim Kingdon <kingdon@harvey.cyclic.com> + + * rename.c: Check ENOENT rather than existence_error. The latter + is undefined in this file, and including system.h is said to cause + (unspecified) problems. + +Sun Dec 17 23:58:06 1995 Jim Kingdon <kingdon@harvey.cyclic.com> + + * vasprintf.c: Removed (it is no longer used). + * Makefile.in (SOURCES): Remove vasprintf.c. + +Sat Dec 16 17:18:33 1995 Jim Kingdon <kingdon@harvey.cyclic.com> + + * vasprintf.c: Added. + * Makefile.in (SOURCES): Add vasprintf.c + +Mon Dec 4 10:54:04 1995 Jim Kingdon <kingdon@harvey.cyclic.com> + + * getdate.c: Remove #line directives. I know, this is a kludge, + but Visual C++ 2.1 seems to require it (why, I have no idea. It + has no trouble with the #line directives in getdate in CVS 1.6). + +Sat Nov 18 16:20:37 1995 Karl Fogel <kfogel@floss.cyclic.com> + + * rename.c: same. + + * mkdir.c: Use new macro `existence_error', instead of comparing + errno to ENOENT directly. + + * system.h (existence_error): new macro, tries to portably ask if + errno represents a file-not-exist error. + +Fri Nov 17 20:08:58 1995 Karl Fogel <kfogel@floss.cyclic.com> + + * system.h (NEED_DECOY_PERMISSIONS): moved this section to where + it belongs, duh. + + * getdate.c: if STDC_HEADERS, then just include <stdlib.h> instead + of declaring malloc() and realloc() to be char *. + + * system.h: ifdef NEED_DECOY_PERMISSIONS, then define the S_I* + permission masks for USR, GRP, and OTH in terms of the simpler + OS/2 masks. + +Wed Nov 15 15:36:03 1995 Karl Fogel <kfogel@floss.cyclic.com> + + * system.h: ifdef USE_OWN_TCPIP_H, then include "tcpip.h". Only + OS/2 does this right now. + +Tue Nov 14 18:44:57 1995 Greg A. Woods <woods@most.weird.com> + + * getdate.c: OK, this one is from SunOS-4.1 yacc and may be more + portable -- at least it compiles silently here! ;-) + +Mon Nov 13 03:53:45 1995 Karl Fogel <kfogel@floss.cyclic.com> + + * fnmatch.c: conform to 80 column standard (yes, I'm a pedant). + +Wed Nov 8 11:10:59 1995 Karl Fogel <kfogel@floss.cyclic.com> + + * system.h (STAT_MACROS): ifdef S_IFMT, then use it as before; but + if it's not defined, then just do a single mask and assume + acceptance any of non-zero result. Norbert, I trust you'll let me + know if this is unsatisfactory. :-) + Ifdef HAVE_SYS_UTIME_H, then include <sys/utime.h>. Only OS/2 + defines this right now. + +Wed Nov 8 13:18:51 1995 Norbert Kiesel <nk@col.sw-ley.de> + + * valloc.c: omit malloc declaration (it's already in system.h + which is included and conflicts with <stdlib.h> on some + systems). + +Tue Nov 7 19:38:48 1995 Norbert Kiesel <nk@col.sw-ley.de> + + * system.h (STAT_MACROS_BROKEN): undo previous change, because + else all regular files will be identified as links (the mask for + links is S_IFREG|S_IFCHR). + +Mon Nov 6 19:20:56 1995 Karl Fogel <kfogel@floss.cyclic.com> + + * system.h (STAT_MACROS_BROKEN): in defining the S_IF* macros, + don't fold to 1 or 0 by first masking with S_IFMT; not all + systems have that macro, and anyway it's only necessary that we + return non-zero. + +Fri Oct 27 13:43:35 1995 Karl Fogel <kfogel@floss.cyclic.com> + + * save-cwd.c: use __PROTO instead of __P (see below). + + * getline.h (__PROTO): same as below. + + * save-cwd.h (__PROTO): replaces __P. New name, so don't ask if + already defined. The conflict was that OS/2 w/ IBM C/C++ uses + `__P' for something else, in <ctype.h> of all places. + + * system.h: do nothing about alloca ifdef ALLOCA_IN_STDLIB (see + ../src/ChangeLog). + +Tue Oct 24 13:01:25 1995 Norbert Kiesel <nk@col.sw-ley.de> + + * wait.h: include sys/resource.h if available. This is needed at + least under AIX-3.2 where <sys/wait.h> doesn't include it. + +Mon Oct 23 17:39:11 1995 Norbert Kiesel <nk@col.sw-ley.de> + + * valloc.c (valloc): change parameter definition + +Sun Oct 22 14:15:44 1995 Jim Meyering (meyering@comco.com) + + * getline.c, getline.h: New files. + * Makefile.in (SOURCES, OBJECTS, HEADERS): Add getline.c, getline.o, + and getline.h, respectively. + +Tue Oct 10 18:01:50 1995 Karl Fogel <kfogel@totoro.cyclic.com> + + * Makefile.in (cvs_srcdir): define cvs_srcdir to be ../src, then + include it with -I so save_cwd.c can find error.h (for example). + +Sun Oct 8 12:27:57 1995 Peter Wemm <peter@haywire.DIALix.COM> + + * system.h: define POSIX_SIGNALS or BSD_SIGNALS if configure has + located all the necessary functions for each "type". + * sighandle.c: detect/use POSIX/BSD reliable signals (especially + for blocking signals in critical sections). Helps prevent stray + locks on interruption. + +Mon Oct 2 18:11:23 1995 Jim Blandy <jimb@totoro.cyclic.com> + + * system.h: Doc fix. + +Mon Oct 2 18:10:35 1995 Larry Jones <larry.jones@sdrc.com> + + * regex.c: compile 4.2 BSD compatible functions even when + _POSIX_SOURCE is defined since we need them and we wouldn't be + compiling this file unless they don't exist. + +Mon Oct 2 10:32:20 1995 Michael Finken <finken@conware.de> + + * strstr.c (strstr): new file and func. + + * Makefile.in (SOURCES): added strstr.c. + +Sun Oct 1 21:03:40 1995 Karl Fogel <kfogel@totoro.cyclic.com> + + * regex.c: reverted below change. + +Thu Sep 28 13:37:04 1995 Larry Jones <larry.jones@sdrc.com> + + * regexp.c: check for ISC. + +Thu Sep 7 19:18:00 1995 Jim Blandy <jimb@cyclic.com> + + * save-cwd.c: #include <direct.h> and <io.h>, on systems that + have them. + + * getopt.c (_getopt_internal): Cast the return value of strlen, + which is unsigned, before comparing it with the difference between + two pointers, which is unsigned. + +Thu Aug 31 11:31:42 1995 Jim Blandy <jimb@totoro.cyclic.com> + + * getdate.y [STDC_HEADERS]: #include <stdlib.h>, for abort. + [HAVE_ALLOCA_H]: #include <alloca.h>, for alloca on Windows NT. + +Wed Aug 30 18:48:44 1995 Jim Blandy <jimb@totoro.cyclic.com> + + * system.h [HAVE_IO_H]: #include <io.h>, for Windows NT. + [HAVE_DIRECT_H]: #include <direct.h>, for Windows NT. + (CVS_MKDIR, FOLD_FN_CHAR, fnfold, fncmp, ISDIRSEP, OPEN_BINARY, + FOPEN_BINARY_READ, FOPEN_BINARY_WRITE): New macros/functions, for + use in system-sensitive code. + + * regex.c (re_set_registers): start and end are pointers, not + integers. Cast the initializing value appropriately. + + * getopt.c [HAVE_STRING_H]: #include <string.h>, to avoid + warnings. + + * fnmatch.c (FOLD_FN_CHAR): Give this a dummy #definition if + config.h didn't #define it. + (fnmatch): Pass filename characters through FOLD_FN_CHAR before + comparing them. + + * argmatch.c: #include <sys/types.h>. + (argmatch): Declare arglen to be a size_t, rather than an int, + to avoid signed/unsigned comparison "problems". + + * .cvsignore: Remove getdate.c from this file. We want to + distribute it, for systems that don't have a Yacc-equivalent + installed (like Windows NT). + +Sat Aug 19 22:00:51 1995 Jim Blandy <jimb@totoro.cyclic.com> + + * error.c: Don't #define CVS_SUPPORT here. config.h takes care of + that for us. + [CVS_SUPPORT] (error_use_protocol): New variable, with apology. + (error): If error_use_protocol is set, report errors using the + client/server protocol. + * error.h [CVS_SUPPORT]: Extern decl for error_use_protocol. + +Fri Aug 4 00:01:24 1995 Jim Meyering (meyering@comco.com) + + * xgetwd.c: Don't declare free. A K&R style declaration gets + a conflict on some Sun systems when compiling with acc. + + * save-cwd.c: New file. + * save-cwd.h: New file. + * Makefile.in (SOURCES): Add save-cwd.c + (OBJECTS): Add save-cwd.o. + (HEADERS): Add save-cwd.h. + +Thu Aug 3 00:55:54 1995 Jim Meyering (meyering@comco.com) + + * error.h: New file. + * Makefile.in (HEADERS): Add error.h. + +Sat Jul 29 15:53:55 1995 James Kingdon <kingdon@harvey.cyclic.com> + + * Makefile.in (SOURCES): Add getdate.c. + +Thu Jul 27 09:11:41 1995 Robert Lipe <robertl@rjlhome.arnet.com> + + * system.h: Check for PATHSIZE before falling back to _POSIX_PATH_MAX. + +Thu Jul 20 12:38:03 1995 James Kingdon <kingdon@harvey.cyclic.com> + + * error.c: Instead of calling cvs functions to clean up, allow cvs + to register a callback via error_set_cleanup. Avoids hassles with + include files and SERVER_SUPPORT and so on. + +Tue Jul 18 21:18:00 1995 Jim Blandy <jimb@cyclic.com> + + * system.h: Include <sys/param.h> only if HAVE_SYS_PARAM_H + is #defined. We've added a test to configure.in to #define this + on most systems. + +Thu Jul 13 11:22:21 1995 Jim Meyering (meyering@comco.com) + + * xgetwd.c: New file. + * Makefile.in (SOURCES): Add xgetwd.c + (OBJECTS): Add xgetwd.o. + +Wed Jul 12 09:18:49 1995 Jim Meyering (meyering@comco.com) + + * Makefile.in (OBJECTS): Remove fnmatch.o. Now configure adds it + to LIBOBJS when necessary. + +Fri Jun 30 16:27:18 1995 James Kingdon <kingdon@harvey.cyclic.com> + + * rename.c (rename): If MVDIR is not defined, just give an error + on attempt to rename a directory. + +Thu Jun 29 00:46:31 1995 James Kingdon <kingdon@harvey.cyclic.com> + + * system.h: Check HAVE_SYS_TIMEB_H not non-existent HAVE_TIMEB_H. + + * system.h: Don't define alloca if it is already defined. + +Wed Jun 28 15:24:51 1995 James Kingdon <kingdon@harvey.cyclic.com> + + * system.h: If NeXT, define utimbuf ourself. + +Mon May 29 22:32:40 1995 J.T. Conklin <jtc@rtl.cygnus.com> + + * system.h: Handle time and directory headers as recommended in + the autoconf manual. + Undefine the S_FOO() macros if STAT_MACROS_BROKEN is set. + Don't define mode_t, as it is handled by config.h. + +Sat May 27 08:46:00 1995 Jim Meyering (meyering@comco.com) + + * Makefile.in (Makefile): Regenerate only Makefile in current + directory when Makefile.in is out of date. Depend on ../config.status. + +Fri Apr 28 22:49:25 1995 Jim Blandy <jimb@totoro.bio.indiana.edu> + + * Makefile.in (SOURCES, OBJECTS): Updated. + (HEADERS): New variable. + (DISTFILES): Updated. + (dist-dir): Renamed from dist; changed to work with DISTDIR + variable passed from parent. + +Wed Feb 8 06:37:53 1995 Roland McGrath <roland@churchy.gnu.ai.mit.edu> + + * system.h (S_IRUSR et al): Define if not already defined. + + * waitpid.c [HAVE_CONFIG_H]: Include "config.h". + (ualloc): Return OLDPTR rather than running off the end. + +Mon Aug 22 22:48:19 1994 Ken Raeburn (raeburn@kr-pc.cygnus.com) + + * error.c (strerror): Replaced conditional static definition + (always used, since the condition variable was never set) with an + extern declaration, since it's provided by libc or strerror.c. + +Wed Aug 10 14:54:25 1994 Ken Raeburn (raeburn@cujo.cygnus.com) + + * Makefile.in (SOURCES): Add waitpid.c. + * waitpid.c: New file. + +Tue Aug 9 16:00:12 1994 Ken Raeburn (raeburn@cujo.cygnus.com) + + * md5.h (uint32): If SIZEOF_LONG isn't 4, don't define this to be + "unsigned long"; try SIZEOF_INT and "unsigned int", otherwise + complain. + + * md5.c: Include config.h. + (const): Don't bother defining here, config.h should take care of + it. + + * valloc.c (malloc): Declare. + +Fri Jul 15 12:57:20 1994 Ian Lance Taylor (ian@sanguine.cygnus.com) + + * getopt.c: Do not include <stdlib.h> unless __GNU_LIBRARY__ is + defined. On Irix 5.2, <stdlib.h> includes <getopt.h>, which + causes a multiple definition of struct option. + +Fri Jul 8 10:04:59 1994 Jim Kingdon (kingdon@lioth.cygnus.com) + + * md5.h, md5.c: Remove ANSI-isms. + +Thu Jul 7 20:24:18 1994 Ian Lance Taylor (ian@sanguine.cygnus.com) + + * md5.h, md5.c: New files. + * Makefile.in (SOURCES): Add md5.c. + (OBJECTS): Add md5.o. + (DISTFILES): Add md5.h. + (md5.o): New target; depend upon md5.h. + +Fri May 27 18:15:34 1994 Ian Lance Taylor (ian@tweedledumb.cygnus.com) + + * valloc.c: New file. + +Tue May 17 08:18:26 1994 Jim Kingdon (kingdon@lioth.cygnus.com) + + * error.c (error, fperror): If server_active, call server_cleanup + as well as Lock_Cleanup. + +Thu Jan 6 13:45:04 1994 Ken Raeburn (raeburn@cujo.cygnus.com) + + * system.h: Fix Dec 27 change to work correctly. Makes Sep 9 + change unnecessary, so backed that one out. Never define PATH_MAX + in terms of pathconf, because that doesn't produce a constant, and + PATH_MAX is used to set array sizes. + +Mon Dec 27 14:22:07 1993 Mark Eichin (eichin@cygnus.com) + + * system.h: don't touch PATH_MAX or MAXPATHLEN if *both* of them + are already defined, as one may be defined in terms of the other. diff --git a/contrib/cvs/lib/ChangeLog.fsf b/contrib/cvs/lib/ChangeLog.fsf new file mode 100644 index 0000000..176d791 --- /dev/null +++ b/contrib/cvs/lib/ChangeLog.fsf @@ -0,0 +1,90 @@ +Thu Sep 15 00:18:26 1994 david d `zoo' zuhn <zoo@monad.armadillo.com> + + * system.h: remove a bunch of "extern int " declarations of system + functions (could conflict with vendor header files, and didn't + do anything *too* useful to begin with). + + * Makefile.in: update getdate.y message (now has 10 s/r conflicts) + +Wed Sep 14 22:12:21 1994 david d `zoo' zuhn <zoo@monad.armadillo.com> + + * strerror.c: more complete, from the Cygnus libiberty package + + * error.c (strerror): removed, functionality is in strerror.c + + * cvs.h: remove duplicate prototype for Reader_Lock + * history.c: printf argument mismatch + (Both fixes thanks to J.T. Conklin (jtc@cygnus.com) + +Sat Jul 30 13:50:11 1994 david d `zoo' zuhn (zoo@monad.armadillo.com) + + * getopt1.c, getopt.c, getopt.h, getdate.y: latest versions from FSF + +Wed Jul 13 22:11:17 1994 david d `zoo' zuhn (zoo@monad.armadillo.com) + + * system.h: don't set PATH_MAX to pathconf(), since PATH_MAX is + used to size arrays. (thanks to kingdon@cygnus.com) + + * getopt1.c: remove #ifdef __STDC__ around const usages (which + isn't correct and weren't complete) + +Wed Apr 20 14:57:16 1994 Ian Lance Taylor (ian@tweedledumb.cygnus.com) + + * getopt.h: Prevent multiple inclusion. + +Tue Jan 25 17:34:42 1994 david d zuhn (zoo@monad.armadillo.com) + + * Makefile.in: make sure that no blank lines are in the $(OBJECTS) + list (from Brad Figg) + +Mon Jan 24 12:27:13 1994 david d zuhn (zoo@monad.armadillo.com) + + * system.h: remove alloca checks (added to src/cvs.h); revamped + the MAXPATHLEN and PATH_MAX tests (from Brad Figg + <bradf@wv.MENTORG.COM>); handle index,rindex,bcmp,bzero better + (don't redefine if already defined); added S_IWRITE, S_IWGRP, + S_IWOTH definitions (header file reorganization) + + * strippath.c: use strchr, not index + + * getopt1.c: match prototypes when __STDC__ compiler (lint fixes) + + * getdate.c: alloca checks for when using bison + + * Makefile.in: added CC and YACC definitions; use YACC not BISON; + better getdate.c tests (also from Brad Figg) + +Sat Dec 18 00:55:43 1993 david d zuhn (zoo@monad.armadillo.com) + + * Makefile.in (VPATH): don't use $(srcdir), but @srcdir@ instead + + * memmove.c: new file, implements memmove in terms of bcopy + + * wait.h: include <sys/wait.h> if HAVE_SYS_WAIT_H, not if POSIX + +Thu Sep 9 18:02:11 1993 david d `zoo' zuhn (zoo@rtl.cygnus.com) + + * system.h: only #undef PATH_MAX if not on an Alpha. The #undef + causes problems with the Alpha C compiler. + +Thu Apr 8 12:39:56 1993 Ian Lance Taylor (ian@cygnus.com) + + * system.h: Removed several incorrect declarations which fail + on Solaris. + +Wed Jan 20 17:57:24 1993 K. Richard Pixley (rich@rtl.cygnus.com) + + * system.h: add externs for sun4 so that gcc -Wall becomes useful + again. + +Wed Feb 26 18:04:40 1992 K. Richard Pixley (rich@cygnus.com) + + * Makefile.in, configure.in: removed traces of namesubdir, + -subdirs, $(subdir), $(unsubdir), some rcs triggers. Forced + copyrights to '92, changed some from Cygnus to FSF. + +Sat Dec 28 02:42:06 1991 K. Richard Pixley (rich at cygnus.com) + + * mkdir.c, rename.c: change fork() to vfork(). + + diff --git a/contrib/cvs/lib/Makefile.in b/contrib/cvs/lib/Makefile.in new file mode 100644 index 0000000..9fb93f3 --- /dev/null +++ b/contrib/cvs/lib/Makefile.in @@ -0,0 +1,164 @@ +# Makefile for library files used by GNU CVS. +# Do not use this makefile directly, but only from `../Makefile'. +# Copyright (C) 1986, 1988-1994 Free Software Foundation, Inc. + +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2, or (at your option) +# any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +# $CVSid: @(#)Makefile.in 1.21 94/09/24 $ +srcdir = @srcdir@ +top_srcdir = @top_srcdir@ +cvs_srcdir = @top_srcdir@/src +VPATH = @srcdir@ + +SHELL = /bin/sh + +prefix = @prefix@ +exec_prefix = @exec_prefix@ + +SOURCES = \ + argmatch.c \ + dup2.c \ + fnmatch.c \ + ftruncate.c \ + getdate.c \ + getdate.y \ + getline.c \ + getopt.c \ + getopt1.c \ + getwd.c \ + hostname.c \ + md5.c \ + memmove.c \ + mkdir.c \ + regex.c \ + rename.c \ + savecwd.c \ + sighandle.c \ + strdup.c \ + strstr.c \ + strerror.c \ + strippath.c \ + stripslash.c \ + strtoul.c \ + valloc.c \ + vasprintf.c \ + waitpid.c \ + xgetwd.c \ + yesno.c + +HEADERS = getline.h getopt.h fnmatch.h regex.h system.h wait.h md5.h savecwd.h + +OBJECTS = \ + @LIBOBJS@ \ + argmatch.o \ + getline.o \ + getopt.o \ + getopt1.o \ + md5.o \ + savecwd.o \ + sighandle.o \ + strippath.o \ + stripslash.o \ + xgetwd.o \ + yesno.o \ + getdate.o + +DISTFILES = \ + .cvsignore ChangeLog ChangeLog.fsf Makefile.in \ + ${SOURCES} ${HEADERS} + +DEFS = @DEFS@ +RANLIB = @RANLIB@ + +CC = @CC@ +CFLAGS = -g +CPPFLAGS= + +YACC = @YACC@ + +.c.o: + $(CC) $(CPPFLAGS) -I.. -I$(srcdir) -I$(cvs_srcdir) \ + $(DEFS) $(CFLAGS) -c $< + +all: libcvs.a +.PHONY: all + +install: all +.PHONY: install + +tags: $(DISTFILES) + ctags `for i in $(DISTFILES); do echo $(srcdir)/$$i; done` + +TAGS: $(DISTFILES) + etags `for i in $(DISTFILES); do echo $(srcdir)/$$i; done` + +ls: + @echo $(DISTFILES) +.PHONY: ls + +clean: + rm -f *.a *.o +.PHONY: clean + +distclean: clean + rm -f tags TAGS Makefile +.PHONY: distclean + +realclean: distclean + rm -f *.tab.c getdate.c +.PHONY: realclean + +dist-dir: + mkdir ${DISTDIR} + for i in ${DISTFILES}; do \ + ln $(srcdir)/$${i} ${DISTDIR}; \ + done +.PHONY: dist-dir + +libcvs.a: $(OBJECTS) + $(AR) cr $@ $(OBJECTS) + -$(RANLIB) $@ + +getdate.c: getdate.y + @echo expect 10 shift/reduce conflicts + $(YACC) $(srcdir)/getdate.y + -@if test -f y.tab.c; then \ + mv y.tab.c getdate.c ;\ + else \ + if test -f getdate.tab.c ; then \ + mv getdate.tab.c getdate.c ; \ + else \ + echo '*** Unable to create getdate.c' ;\ + fi ;\ + fi + +fnmatch.o: fnmatch.h +getopt1.o: getopt.h +regex.o: regex.h +getwd.o: system.h +md5.o: md5.h + +xlint: + @echo xlint does nothing + +subdir = lib +Makefile: ../config.status Makefile.in + cd .. && CONFIG_FILES=$(subdir)/$@ CONFIG_HEADERS= ./config.status + +#../config.status: ../configure +# cd .. ; $(SHELL) config.status --recheck + +#../configure: ../configure.in +# cd $(top_srcdir) ; autoconf diff --git a/contrib/cvs/lib/argmatch.c b/contrib/cvs/lib/argmatch.c new file mode 100644 index 0000000..cc360ee --- /dev/null +++ b/contrib/cvs/lib/argmatch.c @@ -0,0 +1,89 @@ +/* argmatch.c -- find a match for a string in an array + Copyright (C) 1990 Free Software Foundation, Inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2, or (at your option) + any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ + +/* Written by David MacKenzie <djm@ai.mit.edu> */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <sys/types.h> + +#include <stdio.h> +#ifdef STDC_HEADERS +#include <string.h> +#endif + +extern char *program_name; + +/* If ARG is an unambiguous match for an element of the + null-terminated array OPTLIST, return the index in OPTLIST + of the matched element, else -1 if it does not match any element + or -2 if it is ambiguous (is a prefix of more than one element). */ + +int +argmatch (arg, optlist) + char *arg; + char **optlist; +{ + int i; /* Temporary index in OPTLIST. */ + size_t arglen; /* Length of ARG. */ + int matchind = -1; /* Index of first nonexact match. */ + int ambiguous = 0; /* If nonzero, multiple nonexact match(es). */ + + arglen = strlen (arg); + + /* Test all elements for either exact match or abbreviated matches. */ + for (i = 0; optlist[i]; i++) + { + if (!strncmp (optlist[i], arg, arglen)) + { + if (strlen (optlist[i]) == arglen) + /* Exact match found. */ + return i; + else if (matchind == -1) + /* First nonexact match found. */ + matchind = i; + else + /* Second nonexact match found. */ + ambiguous = 1; + } + } + if (ambiguous) + return -2; + else + return matchind; +} + +/* Error reporting for argmatch. + KIND is a description of the type of entity that was being matched. + VALUE is the invalid value that was given. + PROBLEM is the return value from argmatch. */ + +void +invalid_arg (kind, value, problem) + char *kind; + char *value; + int problem; +{ + fprintf (stderr, "%s: ", program_name); + if (problem == -1) + fprintf (stderr, "invalid"); + else /* Assume -2. */ + fprintf (stderr, "ambiguous"); + fprintf (stderr, " %s `%s'\n", kind, value); +} diff --git a/contrib/cvs/lib/dup2.c b/contrib/cvs/lib/dup2.c new file mode 100644 index 0000000..1974383 --- /dev/null +++ b/contrib/cvs/lib/dup2.c @@ -0,0 +1,40 @@ +/* + dup2 -- 7th Edition UNIX system call emulation for UNIX System V + + last edit: 11-Feb-1987 D A Gwyn +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <errno.h> +#include <fcntl.h> + +extern int close(), fcntl(); + +int +dup2( oldfd, newfd ) + int oldfd; /* already-open file descriptor */ + int newfd; /* desired duplicate descriptor */ +{ + register int ret; /* for fcntl() return value */ + register int save; /* for saving entry errno */ + + if ( oldfd == newfd ) + return oldfd; /* be careful not to close() */ + + save = errno; /* save entry errno */ + (void) close( newfd ); /* in case newfd is open */ + /* (may have just clobbered the original errno value) */ + + ret = fcntl( oldfd, F_DUPFD, newfd ); /* dupe it */ + + if ( ret >= 0 ) + errno = save; /* restore entry errno */ + else /* fcntl() returned error */ + if ( errno == EINVAL ) + errno = EBADF; /* we think of everything */ + + return ret; /* return file descriptor */ +} diff --git a/contrib/cvs/lib/fnmatch.c b/contrib/cvs/lib/fnmatch.c new file mode 100644 index 0000000..9cb847e --- /dev/null +++ b/contrib/cvs/lib/fnmatch.c @@ -0,0 +1,193 @@ +/* Copyright (C) 1992 Free Software Foundation, Inc. +This file is part of the GNU C Library. + +The GNU C Library is free software; you can redistribute it and/or +modify it under the terms of the GNU Library General Public License as +published by the Free Software Foundation; either version 2 of the +License, or (at your option) any later version. + +The GNU C Library 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 +Library General Public License for more details. + +You should have received a copy of the GNU Library General Public +License along with the GNU C Library; see the file COPYING.LIB. If +not, write to the Free Software Foundation, Inc., 675 Mass Ave, +Cambridge, MA 02139, USA. */ + +/* Modified slightly by Brian Berliner <berliner@sun.com> and + Jim Blandy <jimb@cyclic.com> for CVS use */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +/* Some file systems are case-insensitive. If FOLD_FN_CHAR is + #defined, it maps the character C onto its "canonical" form. In a + case-insensitive system, it would map all alphanumeric characters + to lower case. Under Windows NT, / and \ are both path component + separators, so FOLD_FN_CHAR would map them both to /. */ +#ifndef FOLD_FN_CHAR +#define FOLD_FN_CHAR(c) (c) +#endif + +/* IGNORE(@ */ +/* #include <ansidecl.h> */ +/* @) */ +#include <errno.h> +#include <fnmatch.h> + +#if !defined(__GNU_LIBRARY__) && !defined(STDC_HEADERS) +extern int errno; +#endif + +/* Match STRING against the filename pattern PATTERN, returning zero if + it matches, nonzero if not. */ +int +#if __STDC__ +fnmatch (const char *pattern, const char *string, int flags) +#else +fnmatch (pattern, string, flags) + char *pattern; + char *string; + int flags; +#endif +{ + register const char *p = pattern, *n = string; + register char c; + + if ((flags & ~__FNM_FLAGS) != 0) + { + errno = EINVAL; + return -1; + } + + while ((c = *p++) != '\0') + { + switch (c) + { + case '?': + if (*n == '\0') + return FNM_NOMATCH; + else if ((flags & FNM_PATHNAME) && *n == '/') + return FNM_NOMATCH; + else if ((flags & FNM_PERIOD) && *n == '.' && + (n == string || ((flags & FNM_PATHNAME) && n[-1] == '/'))) + return FNM_NOMATCH; + break; + + case '\\': + if (!(flags & FNM_NOESCAPE)) + c = *p++; + if (*n != c) + return FNM_NOMATCH; + break; + + case '*': + if ((flags & FNM_PERIOD) && *n == '.' && + (n == string || ((flags & FNM_PATHNAME) && n[-1] == '/'))) + return FNM_NOMATCH; + + for (c = *p++; c == '?' || c == '*'; c = *p++, ++n) + if (((flags & FNM_PATHNAME) && *n == '/') || + (c == '?' && *n == '\0')) + return FNM_NOMATCH; + + if (c == '\0') + return 0; + + { + char c1 = (!(flags & FNM_NOESCAPE) && c == '\\') ? *p : c; + for (--p; *n != '\0'; ++n) + if ((c == '[' || *n == c1) && + fnmatch(p, n, flags & ~FNM_PERIOD) == 0) + return 0; + return FNM_NOMATCH; + } + + case '[': + { + /* Nonzero if the sense of the character class is inverted. */ + register int not; + + if (*n == '\0') + return FNM_NOMATCH; + + if ((flags & FNM_PERIOD) && *n == '.' && + (n == string || ((flags & FNM_PATHNAME) && n[-1] == '/'))) + return FNM_NOMATCH; + + not = (*p == '!' || *p == '^'); + if (not) + ++p; + + c = *p++; + for (;;) + { + register char cstart = c, cend = c; + + if (!(flags & FNM_NOESCAPE) && c == '\\') + cstart = cend = *p++; + + if (c == '\0') + /* [ (unterminated) loses. */ + return FNM_NOMATCH; + + c = *p++; + + if ((flags & FNM_PATHNAME) && c == '/') + /* [/] can never match. */ + return FNM_NOMATCH; + + if (c == '-' && *p != ']') + { + cend = *p++; + if (!(flags & FNM_NOESCAPE) && cend == '\\') + cend = *p++; + if (cend == '\0') + return FNM_NOMATCH; + c = *p++; + } + + if (*n >= cstart && *n <= cend) + goto matched; + + if (c == ']') + break; + } + if (!not) + return FNM_NOMATCH; + break; + + matched:; + /* Skip the rest of the [...] that already matched. */ + while (c != ']') + { + if (c == '\0') + /* [... (unterminated) loses. */ + return FNM_NOMATCH; + + c = *p++; + if (!(flags & FNM_NOESCAPE) && c == '\\') + /* 1003.2d11 is unclear if this is right. %%% */ + ++p; + } + if (not) + return FNM_NOMATCH; + } + break; + + default: + if (FOLD_FN_CHAR (c) != FOLD_FN_CHAR (*n)) + return FNM_NOMATCH; + } + + ++n; + } + + if (*n == '\0') + return 0; + + return FNM_NOMATCH; +} diff --git a/contrib/cvs/lib/fnmatch.h b/contrib/cvs/lib/fnmatch.h new file mode 100644 index 0000000..a1e4f87 --- /dev/null +++ b/contrib/cvs/lib/fnmatch.h @@ -0,0 +1,45 @@ +/* Copyright (C) 1992 Free Software Foundation, Inc. +This file is part of the GNU C Library. + +The GNU C Library is free software; you can redistribute it and/or +modify it under the terms of the GNU Library General Public License as +published by the Free Software Foundation; either version 2 of the +License, or (at your option) any later version. + +The GNU C Library 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 +Library General Public License for more details. + +You should have received a copy of the GNU Library General Public +License along with the GNU C Library; see the file COPYING.LIB. If +not, write to the Free Software Foundation, Inc., 675 Mass Ave, +Cambridge, MA 02139, USA. */ + +#ifndef _FNMATCH_H + +#define _FNMATCH_H 1 + +/* Bits set in the FLAGS argument to `fnmatch'. */ +#undef FNM_PATHNAME +#define FNM_PATHNAME (1 << 0)/* No wildcard can ever match `/'. */ +#undef FNM_NOESCAPE +#define FNM_NOESCAPE (1 << 1)/* Backslashes don't quote special chars. */ +#undef FNM_PERIOD +#define FNM_PERIOD (1 << 2)/* Leading `.' is matched only explicitly. */ +#undef __FNM_FLAGS +#define __FNM_FLAGS (FNM_PATHNAME|FNM_NOESCAPE|FNM_PERIOD) + +/* Value returned by `fnmatch' if STRING does not match PATTERN. */ +#undef FNM_NOMATCH +#define FNM_NOMATCH 1 + +/* Match STRING against the filename pattern PATTERN, + returning zero if it matches, FNM_NOMATCH if not. */ +#if __STDC__ +extern int fnmatch (const char *pattern, const char *string, int flags); +#else +extern int fnmatch (); +#endif + +#endif /* fnmatch.h */ diff --git a/contrib/cvs/lib/ftruncate.c b/contrib/cvs/lib/ftruncate.c new file mode 100644 index 0000000..13f20a3 --- /dev/null +++ b/contrib/cvs/lib/ftruncate.c @@ -0,0 +1,76 @@ +/* ftruncate emulations that work on some System V's. + This file is in the public domain. */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <sys/types.h> +#include <fcntl.h> + +#ifdef F_CHSIZE +int +ftruncate (fd, length) + int fd; + off_t length; +{ + return fcntl (fd, F_CHSIZE, length); +} +#else +#ifdef F_FREESP +/* The following function was written by + kucharsk@Solbourne.com (William Kucharski) */ + +#include <sys/stat.h> +#include <errno.h> +#include <unistd.h> + +int +ftruncate (fd, length) + int fd; + off_t length; +{ + struct flock fl; + struct stat filebuf; + + if (fstat (fd, &filebuf) < 0) + return -1; + + if (filebuf.st_size < length) + { + /* Extend file length. */ + if (lseek (fd, (length - 1), SEEK_SET) < 0) + return -1; + + /* Write a "0" byte. */ + if (write (fd, "", 1) != 1) + return -1; + } + else + { + /* Truncate length. */ + fl.l_whence = 0; + fl.l_len = 0; + fl.l_start = length; + fl.l_type = F_WRLCK; /* Write lock on file space. */ + + /* This relies on the UNDOCUMENTED F_FREESP argument to + fcntl, which truncates the file so that it ends at the + position indicated by fl.l_start. + Will minor miracles never cease? */ + if (fcntl (fd, F_FREESP, &fl) < 0) + return -1; + } + + return 0; +} +#else +int +ftruncate (fd, length) + int fd; + off_t length; +{ + return chsize (fd, length); +} +#endif +#endif diff --git a/contrib/cvs/lib/getdate.y b/contrib/cvs/lib/getdate.y new file mode 100644 index 0000000..5787142 --- /dev/null +++ b/contrib/cvs/lib/getdate.y @@ -0,0 +1,1001 @@ +%{ +/* +** Originally written by Steven M. Bellovin <smb@research.att.com> while +** at the University of North Carolina at Chapel Hill. Later tweaked by +** a couple of people on Usenet. Completely overhauled by Rich $alz +** <rsalz@bbn.com> and Jim Berets <jberets@bbn.com> in August, 1990; +** send any email to Rich. +** +** This grammar has 10 shift/reduce conflicts. +** +** This code is in the public domain and has no copyright. +*/ +/* SUPPRESS 287 on yaccpar_sccsid *//* Unused static variable */ +/* SUPPRESS 288 on yyerrlab *//* Label unused */ + +#ifdef HAVE_CONFIG_H +#if defined (emacs) || defined (CONFIG_BROKETS) +#include <config.h> +#else +#include "config.h" +#endif +#endif + +/* Since the code of getdate.y is not included in the Emacs executable + itself, there is no need to #define static in this file. Even if + the code were included in the Emacs executable, it probably + wouldn't do any harm to #undef it here; this will only cause + problems if we try to write to a static variable, which I don't + think this code needs to do. */ +#ifdef emacs +#undef static +#endif + +#include <stdio.h> +#include <ctype.h> + +/* The code at the top of get_date which figures out the offset of the + current time zone checks various CPP symbols to see if special + tricks are need, but defaults to using the gettimeofday system call. + Include <sys/time.h> if that will be used. */ + +#if defined(vms) + +#include <types.h> +#include <time.h> + +#else + +#include <sys/types.h> + +#ifdef TIME_WITH_SYS_TIME +#include <sys/time.h> +#include <time.h> +#else +#ifdef HAVE_SYS_TIME_H +#include <sys/time.h> +#else +#include <time.h> +#endif +#endif + +#ifdef timezone +#undef timezone /* needed for sgi */ +#endif + +#if defined(HAVE_SYS_TIMEB_H) +#include <sys/timeb.h> +#else +/* +** We use the obsolete `struct timeb' as part of our interface! +** Since the system doesn't have it, we define it here; +** our callers must do likewise. +*/ +struct timeb { + time_t time; /* Seconds since the epoch */ + unsigned short millitm; /* Field not used */ + short timezone; /* Minutes west of GMT */ + short dstflag; /* Field not used */ +}; +#endif /* defined(HAVE_SYS_TIMEB_H) */ + +#endif /* defined(vms) */ + +#if defined (STDC_HEADERS) || defined (USG) +#include <string.h> +#endif + +/* Some old versions of bison generate parsers that use bcopy. + That loses on systems that don't provide the function, so we have + to redefine it here. */ +#if !defined (HAVE_BCOPY) && defined (HAVE_MEMCPY) && !defined (bcopy) +#define bcopy(from, to, len) memcpy ((to), (from), (len)) +#endif + +#if defined (STDC_HEADERS) +#include <stdlib.h> +#endif + +#if defined (HAVE_ALLOCA_H) +#include <alloca.h> +#endif + +extern struct tm *gmtime(); +extern struct tm *localtime(); + +#define yyparse getdate_yyparse +#define yylex getdate_yylex +#define yyerror getdate_yyerror + +#if !defined(lint) && !defined(SABER) +static char RCS[] = "$CVSid: @(#)getdate.y 1.11 94/09/21 $"; +#endif /* !defined(lint) && !defined(SABER) */ + +static int yylex (); +static int yyerror (); + +#define EPOCH 1970 +#define HOUR(x) ((time_t)(x) * 60) +#define SECSPERDAY (24L * 60L * 60L) + + +/* +** An entry in the lexical lookup table. +*/ +typedef struct _TABLE { + char *name; + int type; + time_t value; +} TABLE; + + +/* +** Daylight-savings mode: on, off, or not yet known. +*/ +typedef enum _DSTMODE { + DSTon, DSToff, DSTmaybe +} DSTMODE; + +/* +** Meridian: am, pm, or 24-hour style. +*/ +typedef enum _MERIDIAN { + MERam, MERpm, MER24 +} MERIDIAN; + + +/* +** Global variables. We could get rid of most of these by using a good +** union as the yacc stack. (This routine was originally written before +** yacc had the %union construct.) Maybe someday; right now we only use +** the %union very rarely. +*/ +static char *yyInput; +static DSTMODE yyDSTmode; +static time_t yyDayOrdinal; +static time_t yyDayNumber; +static int yyHaveDate; +static int yyHaveDay; +static int yyHaveRel; +static int yyHaveTime; +static int yyHaveZone; +static time_t yyTimezone; +static time_t yyDay; +static time_t yyHour; +static time_t yyMinutes; +static time_t yyMonth; +static time_t yySeconds; +static time_t yyYear; +static MERIDIAN yyMeridian; +static time_t yyRelMonth; +static time_t yyRelSeconds; + +%} + +%union { + time_t Number; + enum _MERIDIAN Meridian; +} + +%token tAGO tDAY tDAYZONE tID tMERIDIAN tMINUTE_UNIT tMONTH tMONTH_UNIT +%token tSEC_UNIT tSNUMBER tUNUMBER tZONE tDST + +%type <Number> tDAY tDAYZONE tMINUTE_UNIT tMONTH tMONTH_UNIT +%type <Number> tSEC_UNIT tSNUMBER tUNUMBER tZONE +%type <Meridian> tMERIDIAN o_merid + +%% + +spec : /* NULL */ + | spec item + ; + +item : time { + yyHaveTime++; + } + | zone { + yyHaveZone++; + } + | date { + yyHaveDate++; + } + | day { + yyHaveDay++; + } + | rel { + yyHaveRel++; + } + | number + ; + +time : tUNUMBER tMERIDIAN { + yyHour = $1; + yyMinutes = 0; + yySeconds = 0; + yyMeridian = $2; + } + | tUNUMBER ':' tUNUMBER o_merid { + yyHour = $1; + yyMinutes = $3; + yySeconds = 0; + yyMeridian = $4; + } + | tUNUMBER ':' tUNUMBER tSNUMBER { + yyHour = $1; + yyMinutes = $3; + yyMeridian = MER24; + yyDSTmode = DSToff; + yyTimezone = - ($4 % 100 + ($4 / 100) * 60); + } + | tUNUMBER ':' tUNUMBER ':' tUNUMBER o_merid { + yyHour = $1; + yyMinutes = $3; + yySeconds = $5; + yyMeridian = $6; + } + | tUNUMBER ':' tUNUMBER ':' tUNUMBER tSNUMBER { + yyHour = $1; + yyMinutes = $3; + yySeconds = $5; + yyMeridian = MER24; + yyDSTmode = DSToff; + yyTimezone = - ($6 % 100 + ($6 / 100) * 60); + } + ; + +zone : tZONE { + yyTimezone = $1; + yyDSTmode = DSToff; + } + | tDAYZONE { + yyTimezone = $1; + yyDSTmode = DSTon; + } + | + tZONE tDST { + yyTimezone = $1; + yyDSTmode = DSTon; + } + ; + +day : tDAY { + yyDayOrdinal = 1; + yyDayNumber = $1; + } + | tDAY ',' { + yyDayOrdinal = 1; + yyDayNumber = $1; + } + | tUNUMBER tDAY { + yyDayOrdinal = $1; + yyDayNumber = $2; + } + ; + +date : tUNUMBER '/' tUNUMBER { + yyMonth = $1; + yyDay = $3; + } + | tUNUMBER '/' tUNUMBER '/' tUNUMBER { + yyMonth = $1; + yyDay = $3; + yyYear = $5; + } + | tUNUMBER tSNUMBER tSNUMBER { + /* ISO 8601 format. yyyy-mm-dd. */ + yyYear = $1; + yyMonth = -$2; + yyDay = -$3; + } + | tUNUMBER tMONTH tSNUMBER { + /* e.g. 17-JUN-1992. */ + yyDay = $1; + yyMonth = $2; + yyYear = -$3; + } + | tMONTH tUNUMBER { + yyMonth = $1; + yyDay = $2; + } + | tMONTH tUNUMBER ',' tUNUMBER { + yyMonth = $1; + yyDay = $2; + yyYear = $4; + } + | tUNUMBER tMONTH { + yyMonth = $2; + yyDay = $1; + } + | tUNUMBER tMONTH tUNUMBER { + yyMonth = $2; + yyDay = $1; + yyYear = $3; + } + ; + +rel : relunit tAGO { + yyRelSeconds = -yyRelSeconds; + yyRelMonth = -yyRelMonth; + } + | relunit + ; + +relunit : tUNUMBER tMINUTE_UNIT { + yyRelSeconds += $1 * $2 * 60L; + } + | tSNUMBER tMINUTE_UNIT { + yyRelSeconds += $1 * $2 * 60L; + } + | tMINUTE_UNIT { + yyRelSeconds += $1 * 60L; + } + | tSNUMBER tSEC_UNIT { + yyRelSeconds += $1; + } + | tUNUMBER tSEC_UNIT { + yyRelSeconds += $1; + } + | tSEC_UNIT { + yyRelSeconds++; + } + | tSNUMBER tMONTH_UNIT { + yyRelMonth += $1 * $2; + } + | tUNUMBER tMONTH_UNIT { + yyRelMonth += $1 * $2; + } + | tMONTH_UNIT { + yyRelMonth += $1; + } + ; + +number : tUNUMBER { + if (yyHaveTime && yyHaveDate && !yyHaveRel) + yyYear = $1; + else { + if($1>10000) { + yyHaveDate++; + yyDay= ($1)%100; + yyMonth= ($1/100)%100; + yyYear = $1/10000; + } + else { + yyHaveTime++; + if ($1 < 100) { + yyHour = $1; + yyMinutes = 0; + } + else { + yyHour = $1 / 100; + yyMinutes = $1 % 100; + } + yySeconds = 0; + yyMeridian = MER24; + } + } + } + ; + +o_merid : /* NULL */ { + $$ = MER24; + } + | tMERIDIAN { + $$ = $1; + } + ; + +%% + +/* Month and day table. */ +static TABLE const MonthDayTable[] = { + { "january", tMONTH, 1 }, + { "february", tMONTH, 2 }, + { "march", tMONTH, 3 }, + { "april", tMONTH, 4 }, + { "may", tMONTH, 5 }, + { "june", tMONTH, 6 }, + { "july", tMONTH, 7 }, + { "august", tMONTH, 8 }, + { "september", tMONTH, 9 }, + { "sept", tMONTH, 9 }, + { "october", tMONTH, 10 }, + { "november", tMONTH, 11 }, + { "december", tMONTH, 12 }, + { "sunday", tDAY, 0 }, + { "monday", tDAY, 1 }, + { "tuesday", tDAY, 2 }, + { "tues", tDAY, 2 }, + { "wednesday", tDAY, 3 }, + { "wednes", tDAY, 3 }, + { "thursday", tDAY, 4 }, + { "thur", tDAY, 4 }, + { "thurs", tDAY, 4 }, + { "friday", tDAY, 5 }, + { "saturday", tDAY, 6 }, + { NULL } +}; + +/* Time units table. */ +static TABLE const UnitsTable[] = { + { "year", tMONTH_UNIT, 12 }, + { "month", tMONTH_UNIT, 1 }, + { "fortnight", tMINUTE_UNIT, 14 * 24 * 60 }, + { "week", tMINUTE_UNIT, 7 * 24 * 60 }, + { "day", tMINUTE_UNIT, 1 * 24 * 60 }, + { "hour", tMINUTE_UNIT, 60 }, + { "minute", tMINUTE_UNIT, 1 }, + { "min", tMINUTE_UNIT, 1 }, + { "second", tSEC_UNIT, 1 }, + { "sec", tSEC_UNIT, 1 }, + { NULL } +}; + +/* Assorted relative-time words. */ +static TABLE const OtherTable[] = { + { "tomorrow", tMINUTE_UNIT, 1 * 24 * 60 }, + { "yesterday", tMINUTE_UNIT, -1 * 24 * 60 }, + { "today", tMINUTE_UNIT, 0 }, + { "now", tMINUTE_UNIT, 0 }, + { "last", tUNUMBER, -1 }, + { "this", tMINUTE_UNIT, 0 }, + { "next", tUNUMBER, 2 }, + { "first", tUNUMBER, 1 }, +/* { "second", tUNUMBER, 2 }, */ + { "third", tUNUMBER, 3 }, + { "fourth", tUNUMBER, 4 }, + { "fifth", tUNUMBER, 5 }, + { "sixth", tUNUMBER, 6 }, + { "seventh", tUNUMBER, 7 }, + { "eighth", tUNUMBER, 8 }, + { "ninth", tUNUMBER, 9 }, + { "tenth", tUNUMBER, 10 }, + { "eleventh", tUNUMBER, 11 }, + { "twelfth", tUNUMBER, 12 }, + { "ago", tAGO, 1 }, + { NULL } +}; + +/* The timezone table. */ +/* Some of these are commented out because a time_t can't store a float. */ +static TABLE const TimezoneTable[] = { + { "gmt", tZONE, HOUR( 0) }, /* Greenwich Mean */ + { "ut", tZONE, HOUR( 0) }, /* Universal (Coordinated) */ + { "utc", tZONE, HOUR( 0) }, + { "wet", tZONE, HOUR( 0) }, /* Western European */ + { "bst", tDAYZONE, HOUR( 0) }, /* British Summer */ + { "wat", tZONE, HOUR( 1) }, /* West Africa */ + { "at", tZONE, HOUR( 2) }, /* Azores */ +#if 0 + /* For completeness. BST is also British Summer, and GST is + * also Guam Standard. */ + { "bst", tZONE, HOUR( 3) }, /* Brazil Standard */ + { "gst", tZONE, HOUR( 3) }, /* Greenland Standard */ +#endif +#if 0 + { "nft", tZONE, HOUR(3.5) }, /* Newfoundland */ + { "nst", tZONE, HOUR(3.5) }, /* Newfoundland Standard */ + { "ndt", tDAYZONE, HOUR(3.5) }, /* Newfoundland Daylight */ +#endif + { "ast", tZONE, HOUR( 4) }, /* Atlantic Standard */ + { "adt", tDAYZONE, HOUR( 4) }, /* Atlantic Daylight */ + { "est", tZONE, HOUR( 5) }, /* Eastern Standard */ + { "edt", tDAYZONE, HOUR( 5) }, /* Eastern Daylight */ + { "cst", tZONE, HOUR( 6) }, /* Central Standard */ + { "cdt", tDAYZONE, HOUR( 6) }, /* Central Daylight */ + { "mst", tZONE, HOUR( 7) }, /* Mountain Standard */ + { "mdt", tDAYZONE, HOUR( 7) }, /* Mountain Daylight */ + { "pst", tZONE, HOUR( 8) }, /* Pacific Standard */ + { "pdt", tDAYZONE, HOUR( 8) }, /* Pacific Daylight */ + { "yst", tZONE, HOUR( 9) }, /* Yukon Standard */ + { "ydt", tDAYZONE, HOUR( 9) }, /* Yukon Daylight */ + { "hst", tZONE, HOUR(10) }, /* Hawaii Standard */ + { "hdt", tDAYZONE, HOUR(10) }, /* Hawaii Daylight */ + { "cat", tZONE, HOUR(10) }, /* Central Alaska */ + { "ahst", tZONE, HOUR(10) }, /* Alaska-Hawaii Standard */ + { "nt", tZONE, HOUR(11) }, /* Nome */ + { "idlw", tZONE, HOUR(12) }, /* International Date Line West */ + { "cet", tZONE, -HOUR(1) }, /* Central European */ + { "met", tZONE, -HOUR(1) }, /* Middle European */ + { "mewt", tZONE, -HOUR(1) }, /* Middle European Winter */ + { "mest", tDAYZONE, -HOUR(1) }, /* Middle European Summer */ + { "swt", tZONE, -HOUR(1) }, /* Swedish Winter */ + { "sst", tDAYZONE, -HOUR(1) }, /* Swedish Summer */ + { "fwt", tZONE, -HOUR(1) }, /* French Winter */ + { "fst", tDAYZONE, -HOUR(1) }, /* French Summer */ + { "eet", tZONE, -HOUR(2) }, /* Eastern Europe, USSR Zone 1 */ + { "bt", tZONE, -HOUR(3) }, /* Baghdad, USSR Zone 2 */ +#if 0 + { "it", tZONE, -HOUR(3.5) },/* Iran */ +#endif + { "zp4", tZONE, -HOUR(4) }, /* USSR Zone 3 */ + { "zp5", tZONE, -HOUR(5) }, /* USSR Zone 4 */ +#if 0 + { "ist", tZONE, -HOUR(5.5) },/* Indian Standard */ +#endif + { "zp6", tZONE, -HOUR(6) }, /* USSR Zone 5 */ +#if 0 + /* For completeness. NST is also Newfoundland Stanard, and SST is + * also Swedish Summer. */ + { "nst", tZONE, -HOUR(6.5) },/* North Sumatra */ + { "sst", tZONE, -HOUR(7) }, /* South Sumatra, USSR Zone 6 */ +#endif /* 0 */ + { "wast", tZONE, -HOUR(7) }, /* West Australian Standard */ + { "wadt", tDAYZONE, -HOUR(7) }, /* West Australian Daylight */ +#if 0 + { "jt", tZONE, -HOUR(7.5) },/* Java (3pm in Cronusland!) */ +#endif + { "cct", tZONE, -HOUR(8) }, /* China Coast, USSR Zone 7 */ + { "jst", tZONE, -HOUR(9) }, /* Japan Standard, USSR Zone 8 */ +#if 0 + { "cast", tZONE, -HOUR(9.5) },/* Central Australian Standard */ + { "cadt", tDAYZONE, -HOUR(9.5) },/* Central Australian Daylight */ +#endif + { "east", tZONE, -HOUR(10) }, /* Eastern Australian Standard */ + { "eadt", tDAYZONE, -HOUR(10) }, /* Eastern Australian Daylight */ + { "gst", tZONE, -HOUR(10) }, /* Guam Standard, USSR Zone 9 */ + { "nzt", tZONE, -HOUR(12) }, /* New Zealand */ + { "nzst", tZONE, -HOUR(12) }, /* New Zealand Standard */ + { "nzdt", tDAYZONE, -HOUR(12) }, /* New Zealand Daylight */ + { "idle", tZONE, -HOUR(12) }, /* International Date Line East */ + { NULL } +}; + +/* Military timezone table. */ +static TABLE const MilitaryTable[] = { + { "a", tZONE, HOUR( 1) }, + { "b", tZONE, HOUR( 2) }, + { "c", tZONE, HOUR( 3) }, + { "d", tZONE, HOUR( 4) }, + { "e", tZONE, HOUR( 5) }, + { "f", tZONE, HOUR( 6) }, + { "g", tZONE, HOUR( 7) }, + { "h", tZONE, HOUR( 8) }, + { "i", tZONE, HOUR( 9) }, + { "k", tZONE, HOUR( 10) }, + { "l", tZONE, HOUR( 11) }, + { "m", tZONE, HOUR( 12) }, + { "n", tZONE, HOUR(- 1) }, + { "o", tZONE, HOUR(- 2) }, + { "p", tZONE, HOUR(- 3) }, + { "q", tZONE, HOUR(- 4) }, + { "r", tZONE, HOUR(- 5) }, + { "s", tZONE, HOUR(- 6) }, + { "t", tZONE, HOUR(- 7) }, + { "u", tZONE, HOUR(- 8) }, + { "v", tZONE, HOUR(- 9) }, + { "w", tZONE, HOUR(-10) }, + { "x", tZONE, HOUR(-11) }, + { "y", tZONE, HOUR(-12) }, + { "z", tZONE, HOUR( 0) }, + { NULL } +}; + + + + +/* ARGSUSED */ +static int +yyerror(s) + char *s; +{ + return 0; +} + + +static time_t +ToSeconds(Hours, Minutes, Seconds, Meridian) + time_t Hours; + time_t Minutes; + time_t Seconds; + MERIDIAN Meridian; +{ + if (Minutes < 0 || Minutes > 59 || Seconds < 0 || Seconds > 59) + return -1; + switch (Meridian) { + case MER24: + if (Hours < 0 || Hours > 23) + return -1; + return (Hours * 60L + Minutes) * 60L + Seconds; + case MERam: + if (Hours < 1 || Hours > 12) + return -1; + return (Hours * 60L + Minutes) * 60L + Seconds; + case MERpm: + if (Hours < 1 || Hours > 12) + return -1; + return ((Hours + 12) * 60L + Minutes) * 60L + Seconds; + default: + abort (); + } + /* NOTREACHED */ +} + + +static time_t +Convert(Month, Day, Year, Hours, Minutes, Seconds, Meridian, DSTmode) + time_t Month; + time_t Day; + time_t Year; + time_t Hours; + time_t Minutes; + time_t Seconds; + MERIDIAN Meridian; + DSTMODE DSTmode; +{ + static int DaysInMonth[12] = { + 31, 0, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 + }; + time_t tod; + time_t Julian; + int i; + + if (Year < 0) + Year = -Year; + if (Year < 100) + Year += 1900; + DaysInMonth[1] = Year % 4 == 0 && (Year % 100 != 0 || Year % 400 == 0) + ? 29 : 28; + if (Year < EPOCH || Year > 1999 + || Month < 1 || Month > 12 + /* Lint fluff: "conversion from long may lose accuracy" */ + || Day < 1 || Day > DaysInMonth[(int)--Month]) + return -1; + + for (Julian = Day - 1, i = 0; i < Month; i++) + Julian += DaysInMonth[i]; + for (i = EPOCH; i < Year; i++) + Julian += 365 + (i % 4 == 0); + Julian *= SECSPERDAY; + Julian += yyTimezone * 60L; + if ((tod = ToSeconds(Hours, Minutes, Seconds, Meridian)) < 0) + return -1; + Julian += tod; + if (DSTmode == DSTon + || (DSTmode == DSTmaybe && localtime(&Julian)->tm_isdst)) + Julian -= 60 * 60; + return Julian; +} + + +static time_t +DSTcorrect(Start, Future) + time_t Start; + time_t Future; +{ + time_t StartDay; + time_t FutureDay; + + StartDay = (localtime(&Start)->tm_hour + 1) % 24; + FutureDay = (localtime(&Future)->tm_hour + 1) % 24; + return (Future - Start) + (StartDay - FutureDay) * 60L * 60L; +} + + +static time_t +RelativeDate(Start, DayOrdinal, DayNumber) + time_t Start; + time_t DayOrdinal; + time_t DayNumber; +{ + struct tm *tm; + time_t now; + + now = Start; + tm = localtime(&now); + now += SECSPERDAY * ((DayNumber - tm->tm_wday + 7) % 7); + now += 7 * SECSPERDAY * (DayOrdinal <= 0 ? DayOrdinal : DayOrdinal - 1); + return DSTcorrect(Start, now); +} + + +static time_t +RelativeMonth(Start, RelMonth) + time_t Start; + time_t RelMonth; +{ + struct tm *tm; + time_t Month; + time_t Year; + + if (RelMonth == 0) + return 0; + tm = localtime(&Start); + Month = 12 * tm->tm_year + tm->tm_mon + RelMonth; + Year = Month / 12; + Month = Month % 12 + 1; + return DSTcorrect(Start, + Convert(Month, (time_t)tm->tm_mday, Year, + (time_t)tm->tm_hour, (time_t)tm->tm_min, (time_t)tm->tm_sec, + MER24, DSTmaybe)); +} + + +static int +LookupWord(buff) + char *buff; +{ + register char *p; + register char *q; + register const TABLE *tp; + int i; + int abbrev; + + /* Make it lowercase. */ + for (p = buff; *p; p++) + if (isupper(*p)) + *p = tolower(*p); + + if (strcmp(buff, "am") == 0 || strcmp(buff, "a.m.") == 0) { + yylval.Meridian = MERam; + return tMERIDIAN; + } + if (strcmp(buff, "pm") == 0 || strcmp(buff, "p.m.") == 0) { + yylval.Meridian = MERpm; + return tMERIDIAN; + } + + /* See if we have an abbreviation for a month. */ + if (strlen(buff) == 3) + abbrev = 1; + else if (strlen(buff) == 4 && buff[3] == '.') { + abbrev = 1; + buff[3] = '\0'; + } + else + abbrev = 0; + + for (tp = MonthDayTable; tp->name; tp++) { + if (abbrev) { + if (strncmp(buff, tp->name, 3) == 0) { + yylval.Number = tp->value; + return tp->type; + } + } + else if (strcmp(buff, tp->name) == 0) { + yylval.Number = tp->value; + return tp->type; + } + } + + for (tp = TimezoneTable; tp->name; tp++) + if (strcmp(buff, tp->name) == 0) { + yylval.Number = tp->value; + return tp->type; + } + + if (strcmp(buff, "dst") == 0) + return tDST; + + for (tp = UnitsTable; tp->name; tp++) + if (strcmp(buff, tp->name) == 0) { + yylval.Number = tp->value; + return tp->type; + } + + /* Strip off any plural and try the units table again. */ + i = strlen(buff) - 1; + if (buff[i] == 's') { + buff[i] = '\0'; + for (tp = UnitsTable; tp->name; tp++) + if (strcmp(buff, tp->name) == 0) { + yylval.Number = tp->value; + return tp->type; + } + buff[i] = 's'; /* Put back for "this" in OtherTable. */ + } + + for (tp = OtherTable; tp->name; tp++) + if (strcmp(buff, tp->name) == 0) { + yylval.Number = tp->value; + return tp->type; + } + + /* Military timezones. */ + if (buff[1] == '\0' && isalpha(*buff)) { + for (tp = MilitaryTable; tp->name; tp++) + if (strcmp(buff, tp->name) == 0) { + yylval.Number = tp->value; + return tp->type; + } + } + + /* Drop out any periods and try the timezone table again. */ + for (i = 0, p = q = buff; *q; q++) + if (*q != '.') + *p++ = *q; + else + i++; + *p = '\0'; + if (i) + for (tp = TimezoneTable; tp->name; tp++) + if (strcmp(buff, tp->name) == 0) { + yylval.Number = tp->value; + return tp->type; + } + + return tID; +} + + +static int +yylex() +{ + register char c; + register char *p; + char buff[20]; + int Count; + int sign; + + for ( ; ; ) { + while (isspace(*yyInput)) + yyInput++; + + if (isdigit(c = *yyInput) || c == '-' || c == '+') { + if (c == '-' || c == '+') { + sign = c == '-' ? -1 : 1; + if (!isdigit(*++yyInput)) + /* skip the '-' sign */ + continue; + } + else + sign = 0; + for (yylval.Number = 0; isdigit(c = *yyInput++); ) + yylval.Number = 10 * yylval.Number + c - '0'; + yyInput--; + if (sign < 0) + yylval.Number = -yylval.Number; + return sign ? tSNUMBER : tUNUMBER; + } + if (isalpha(c)) { + for (p = buff; isalpha(c = *yyInput++) || c == '.'; ) + if (p < &buff[sizeof buff - 1]) + *p++ = c; + *p = '\0'; + yyInput--; + return LookupWord(buff); + } + if (c != '(') + return *yyInput++; + Count = 0; + do { + c = *yyInput++; + if (c == '\0') + return c; + if (c == '(') + Count++; + else if (c == ')') + Count--; + } while (Count > 0); + } +} + +#define TM_YEAR_ORIGIN 1900 + +/* Yield A - B, measured in seconds. */ +static long +difftm (a, b) + struct tm *a, *b; +{ + int ay = a->tm_year + (TM_YEAR_ORIGIN - 1); + int by = b->tm_year + (TM_YEAR_ORIGIN - 1); + int days = ( + /* difference in day of year */ + a->tm_yday - b->tm_yday + /* + intervening leap days */ + + ((ay >> 2) - (by >> 2)) + - (ay/100 - by/100) + + ((ay/100 >> 2) - (by/100 >> 2)) + /* + difference in years * 365 */ + + (long)(ay-by) * 365 + ); + return (60*(60*(24*days + (a->tm_hour - b->tm_hour)) + + (a->tm_min - b->tm_min)) + + (a->tm_sec - b->tm_sec)); +} + +time_t +get_date(p, now) + char *p; + struct timeb *now; +{ + struct tm *tm, gmt; + struct timeb ftz; + time_t Start; + time_t tod; + time_t nowtime; + + yyInput = p; + if (now == NULL) { + now = &ftz; + (void)time (&nowtime); + + if (! (tm = gmtime (&nowtime))) + return -1; + gmt = *tm; /* Make a copy, in case localtime modifies *tm. */ + + if (! (tm = localtime (&nowtime))) + return -1; + + ftz.timezone = difftm (&gmt, tm) / 60; + if(tm->tm_isdst) + ftz.timezone += 60; + } + else + { + nowtime = now->time; + } + + tm = localtime(&nowtime); + yyYear = tm->tm_year; + yyMonth = tm->tm_mon + 1; + yyDay = tm->tm_mday; + yyTimezone = now->timezone; + yyDSTmode = DSTmaybe; + yyHour = 0; + yyMinutes = 0; + yySeconds = 0; + yyMeridian = MER24; + yyRelSeconds = 0; + yyRelMonth = 0; + yyHaveDate = 0; + yyHaveDay = 0; + yyHaveRel = 0; + yyHaveTime = 0; + yyHaveZone = 0; + + if (yyparse() + || yyHaveTime > 1 || yyHaveZone > 1 || yyHaveDate > 1 || yyHaveDay > 1) + return -1; + + if (yyHaveDate || yyHaveTime || yyHaveDay) { + Start = Convert(yyMonth, yyDay, yyYear, yyHour, yyMinutes, yySeconds, + yyMeridian, yyDSTmode); + if (Start < 0) + return -1; + } + else { + Start = nowtime; + if (!yyHaveRel) + Start -= ((tm->tm_hour * 60L + tm->tm_min) * 60L) + tm->tm_sec; + } + + Start += yyRelSeconds; + Start += RelativeMonth(Start, yyRelMonth); + + if (yyHaveDay && !yyHaveDate) { + tod = RelativeDate(Start, yyDayOrdinal, yyDayNumber); + Start += tod; + } + + /* Have to do *something* with a legitimate -1 so it's distinguishable + * from the error return value. (Alternately could set errno on error.) */ + return Start == -1 ? 0 : Start; +} + + +#if defined(TEST) + +/* ARGSUSED */ +int +main(ac, av) + int ac; + char *av[]; +{ + char buff[128]; + time_t d; + + (void)printf("Enter date, or blank line to exit.\n\t> "); + (void)fflush(stdout); + while (gets(buff) && buff[0]) { + d = get_date(buff, (struct timeb *)NULL); + if (d == -1) + (void)printf("Bad format - couldn't convert.\n"); + else + (void)printf("%s", ctime(&d)); + (void)printf("\t> "); + (void)fflush(stdout); + } + exit(0); + /* NOTREACHED */ +} +#endif /* defined(TEST) */ diff --git a/contrib/cvs/lib/getline.c b/contrib/cvs/lib/getline.c new file mode 100644 index 0000000..a7ab97b --- /dev/null +++ b/contrib/cvs/lib/getline.c @@ -0,0 +1,125 @@ +/* getline.c -- Replacement for GNU C library function getline + +Copyright (C) 1993 Free Software Foundation, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the +License, 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. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ + +/* Written by Jan Brittenson, bson@gnu.ai.mit.edu. */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <sys/types.h> +#include <stdio.h> +#include <assert.h> + +#if STDC_HEADERS +#include <stdlib.h> +#else +char *malloc (), *realloc (); +#endif + +/* Always add at least this many bytes when extending the buffer. */ +#define MIN_CHUNK 64 + +/* Read up to (and including) a TERMINATOR from STREAM into *LINEPTR + + OFFSET (and null-terminate it). *LINEPTR is a pointer returned from + malloc (or NULL), pointing to *N characters of space. It is realloc'd + as necessary. Return the number of characters read (not including the + null terminator), or -1 on error or EOF. */ + +int +getstr (lineptr, n, stream, terminator, offset) + char **lineptr; + size_t *n; + FILE *stream; + char terminator; + int offset; +{ + int nchars_avail; /* Allocated but unused chars in *LINEPTR. */ + char *read_pos; /* Where we're reading into *LINEPTR. */ + int ret; + + if (!lineptr || !n || !stream) + return -1; + + if (!*lineptr) + { + *n = MIN_CHUNK; + *lineptr = malloc (*n); + if (!*lineptr) + return -1; + } + + nchars_avail = *n - offset; + read_pos = *lineptr + offset; + + for (;;) + { + register int c = getc (stream); + + /* We always want at least one char left in the buffer, since we + always (unless we get an error while reading the first char) + NUL-terminate the line buffer. */ + + assert((*lineptr + *n) == (read_pos + nchars_avail)); + if (nchars_avail < 2) + { + if (*n > MIN_CHUNK) + *n *= 2; + else + *n += MIN_CHUNK; + + nchars_avail = *n + *lineptr - read_pos; + *lineptr = realloc (*lineptr, *n); + if (!*lineptr) + return -1; + read_pos = *n - nchars_avail + *lineptr; + assert((*lineptr + *n) == (read_pos + nchars_avail)); + } + + if (c == EOF || ferror (stream)) + { + /* Return partial line, if any. */ + if (read_pos == *lineptr) + return -1; + else + break; + } + + *read_pos++ = c; + nchars_avail--; + + if (c == terminator) + /* Return the line. */ + break; + } + + /* Done - NUL terminate and return the number of chars read. */ + *read_pos = '\0'; + + ret = read_pos - (*lineptr + offset); + return ret; +} + +int +getline (lineptr, n, stream) + char **lineptr; + size_t *n; + FILE *stream; +{ + return getstr (lineptr, n, stream, '\n', 0); +} diff --git a/contrib/cvs/lib/getline.h b/contrib/cvs/lib/getline.h new file mode 100644 index 0000000..30bcc25 --- /dev/null +++ b/contrib/cvs/lib/getline.h @@ -0,0 +1,15 @@ +#ifndef _getline_h_ +#define _getline_h_ 1 + +#include <stdio.h> + +#if defined (__GNUC__) || (defined (__STDC__) && __STDC__) +#define __PROTO(args) args +#else +#define __PROTO(args) () +#endif /* GCC. */ + +int + getline __PROTO ((char **_lineptr, size_t *_n, FILE *_stream)); + +#endif /* _getline_h_ */ diff --git a/contrib/cvs/lib/getopt.c b/contrib/cvs/lib/getopt.c new file mode 100644 index 0000000..137e66b --- /dev/null +++ b/contrib/cvs/lib/getopt.c @@ -0,0 +1,759 @@ +/* Getopt for GNU. + NOTE: getopt is now part of the C library, so if you don't know what + "Keep this file name-space clean" means, talk to roland@gnu.ai.mit.edu + before changing it! + + Copyright (C) 1987, 88, 89, 90, 91, 92, 93, 94 + Free Software Foundation, Inc. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; either version 2, or (at your option) any + later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. */ + +/* This tells Alpha OSF/1 not to define a getopt prototype in <stdio.h>. + Ditto for AIX 3.2 and <stdlib.h>. */ +#ifndef _NO_PROTO +#define _NO_PROTO +#endif + +#ifdef HAVE_CONFIG_H +#if defined (emacs) || defined (CONFIG_BROKETS) +/* We use <config.h> instead of "config.h" so that a compilation + using -I. -I$srcdir will use ./config.h rather than $srcdir/config.h + (which it would do because it found this file in $srcdir). */ +#include <config.h> +#else +#include "config.h" +#endif +#endif + +#ifndef __STDC__ +/* This is a separate conditional since some stdc systems + reject `defined (const)'. */ +#ifndef const +#define const +#endif +#endif + +#include <stdio.h> + +#ifdef HAVE_STRING_H +#include <string.h> +#endif + +/* Comment out all this code if we are using the GNU C Library, and are not + actually compiling the library itself. This code is part of the GNU C + Library, but also included in many other GNU distributions. Compiling + and linking in this code is a waste when using the GNU C library + (especially if it is a shared library). Rather than having every GNU + program understand `configure --with-gnu-libc' and omit the object files, + it is simpler to just do this in the source for each such file. */ + +#if defined (_LIBC) || !defined (__GNU_LIBRARY__) + + +/* This needs to come after some library #include + to get __GNU_LIBRARY__ defined. */ +#ifdef __GNU_LIBRARY__ +/* Don't include stdlib.h for non-GNU C libraries because some of them + contain conflicting prototypes for getopt. */ +#include <stdlib.h> +#endif /* GNU C library. */ + +/* This version of `getopt' appears to the caller like standard Unix `getopt' + but it behaves differently for the user, since it allows the user + to intersperse the options with the other arguments. + + As `getopt' works, it permutes the elements of ARGV so that, + when it is done, all the options precede everything else. Thus + all application programs are extended to handle flexible argument order. + + Setting the environment variable POSIXLY_CORRECT disables permutation. + Then the behavior is completely standard. + + GNU application programs can use a third alternative mode in which + they can distinguish the relative order of options and other arguments. */ + +#include "getopt.h" + +/* For communication from `getopt' to the caller. + When `getopt' finds an option that takes an argument, + the argument value is returned here. + Also, when `ordering' is RETURN_IN_ORDER, + each non-option ARGV-element is returned here. */ + +char *optarg = NULL; + +/* Index in ARGV of the next element to be scanned. + This is used for communication to and from the caller + and for communication between successive calls to `getopt'. + + On entry to `getopt', zero means this is the first call; initialize. + + When `getopt' returns EOF, this is the index of the first of the + non-option elements that the caller should itself scan. + + Otherwise, `optind' communicates from one call to the next + how much of ARGV has been scanned so far. */ + +/* XXX 1003.2 says this must be 1 before any call. */ +int optind = 0; + +/* The next char to be scanned in the option-element + in which the last option character we returned was found. + This allows us to pick up the scan where we left off. + + If this is zero, or a null string, it means resume the scan + by advancing to the next ARGV-element. */ + +static char *nextchar; + +/* Callers store zero here to inhibit the error message + for unrecognized options. */ + +int opterr = 1; + +/* Set to an option character which was unrecognized. + This must be initialized on some systems to avoid linking in the + system's own getopt implementation. */ + +int optopt = '?'; + +/* Describe how to deal with options that follow non-option ARGV-elements. + + If the caller did not specify anything, + the default is REQUIRE_ORDER if the environment variable + POSIXLY_CORRECT is defined, PERMUTE otherwise. + + REQUIRE_ORDER means don't recognize them as options; + stop option processing when the first non-option is seen. + This is what Unix does. + This mode of operation is selected by either setting the environment + variable POSIXLY_CORRECT, or using `+' as the first character + of the list of option characters. + + PERMUTE is the default. We permute the contents of ARGV as we scan, + so that eventually all the non-options are at the end. This allows options + to be given in any order, even with programs that were not written to + expect this. + + RETURN_IN_ORDER is an option available to programs that were written + to expect options and other ARGV-elements in any order and that care about + the ordering of the two. We describe each non-option ARGV-element + as if it were the argument of an option with character code 1. + Using `-' as the first character of the list of option characters + selects this mode of operation. + + The special argument `--' forces an end of option-scanning regardless + of the value of `ordering'. In the case of RETURN_IN_ORDER, only + `--' can cause `getopt' to return EOF with `optind' != ARGC. */ + +static enum +{ + REQUIRE_ORDER, PERMUTE, RETURN_IN_ORDER +} ordering; + +/* Value of POSIXLY_CORRECT environment variable. */ +static char *posixly_correct; + +#ifdef __GNU_LIBRARY__ +/* We want to avoid inclusion of string.h with non-GNU libraries + because there are many ways it can cause trouble. + On some systems, it contains special magic macros that don't work + in GCC. */ +#include <string.h> +#define my_index strchr +#else + +/* Avoid depending on library functions or files + whose names are inconsistent. */ + +char *getenv (); + +static char * +my_index (str, chr) + const char *str; + int chr; +{ + while (*str) + { + if (*str == chr) + return (char *) str; + str++; + } + return 0; +} + +/* If using GCC, we can safely declare strlen this way. + If not using GCC, it is ok not to declare it. */ +#ifdef __GNUC__ +/* Note that Motorola Delta 68k R3V7 comes with GCC but not stddef.h. + That was relevant to code that was here before. */ +#ifndef __STDC__ +/* gcc with -traditional declares the built-in strlen to return int, + and has done so at least since version 2.4.5. -- rms. */ +extern int strlen (const char *); +#endif /* not __STDC__ */ +#endif /* __GNUC__ */ + +#endif /* not __GNU_LIBRARY__ */ + +/* Handle permutation of arguments. */ + +/* Describe the part of ARGV that contains non-options that have + been skipped. `first_nonopt' is the index in ARGV of the first of them; + `last_nonopt' is the index after the last of them. */ + +static int first_nonopt; +static int last_nonopt; + +/* Exchange two adjacent subsequences of ARGV. + One subsequence is elements [first_nonopt,last_nonopt) + which contains all the non-options that have been skipped so far. + The other is elements [last_nonopt,optind), which contains all + the options processed since those non-options were skipped. + + `first_nonopt' and `last_nonopt' are relocated so that they describe + the new indices of the non-options in ARGV after they are moved. */ + +static void +exchange (argv) + char **argv; +{ + int bottom = first_nonopt; + int middle = last_nonopt; + int top = optind; + char *tem; + + /* Exchange the shorter segment with the far end of the longer segment. + That puts the shorter segment into the right place. + It leaves the longer segment in the right place overall, + but it consists of two parts that need to be swapped next. */ + + while (top > middle && middle > bottom) + { + if (top - middle > middle - bottom) + { + /* Bottom segment is the short one. */ + int len = middle - bottom; + register int i; + + /* Swap it with the top part of the top segment. */ + for (i = 0; i < len; i++) + { + tem = argv[bottom + i]; + argv[bottom + i] = argv[top - (middle - bottom) + i]; + argv[top - (middle - bottom) + i] = tem; + } + /* Exclude the moved bottom segment from further swapping. */ + top -= len; + } + else + { + /* Top segment is the short one. */ + int len = top - middle; + register int i; + + /* Swap it with the bottom part of the bottom segment. */ + for (i = 0; i < len; i++) + { + tem = argv[bottom + i]; + argv[bottom + i] = argv[middle + i]; + argv[middle + i] = tem; + } + /* Exclude the moved top segment from further swapping. */ + bottom += len; + } + } + + /* Update records for the slots the non-options now occupy. */ + + first_nonopt += (optind - last_nonopt); + last_nonopt = optind; +} + +/* Initialize the internal data when the first call is made. */ + +static const char * +_getopt_initialize (optstring) + const char *optstring; +{ + /* Start processing options with ARGV-element 1 (since ARGV-element 0 + is the program name); the sequence of previously skipped + non-option ARGV-elements is empty. */ + + first_nonopt = last_nonopt = optind = 1; + + nextchar = NULL; + + posixly_correct = getenv ("POSIXLY_CORRECT"); + + /* Determine how to handle the ordering of options and nonoptions. */ + + if (optstring[0] == '-') + { + ordering = RETURN_IN_ORDER; + ++optstring; + } + else if (optstring[0] == '+') + { + ordering = REQUIRE_ORDER; + ++optstring; + } + else if (posixly_correct != NULL) + ordering = REQUIRE_ORDER; + else + ordering = PERMUTE; + + return optstring; +} + +/* Scan elements of ARGV (whose length is ARGC) for option characters + given in OPTSTRING. + + If an element of ARGV starts with '-', and is not exactly "-" or "--", + then it is an option element. The characters of this element + (aside from the initial '-') are option characters. If `getopt' + is called repeatedly, it returns successively each of the option characters + from each of the option elements. + + If `getopt' finds another option character, it returns that character, + updating `optind' and `nextchar' so that the next call to `getopt' can + resume the scan with the following option character or ARGV-element. + + If there are no more option characters, `getopt' returns `EOF'. + Then `optind' is the index in ARGV of the first ARGV-element + that is not an option. (The ARGV-elements have been permuted + so that those that are not options now come last.) + + OPTSTRING is a string containing the legitimate option characters. + If an option character is seen that is not listed in OPTSTRING, + return '?' after printing an error message. If you set `opterr' to + zero, the error message is suppressed but we still return '?'. + + If a char in OPTSTRING is followed by a colon, that means it wants an arg, + so the following text in the same ARGV-element, or the text of the following + ARGV-element, is returned in `optarg'. Two colons mean an option that + wants an optional arg; if there is text in the current ARGV-element, + it is returned in `optarg', otherwise `optarg' is set to zero. + + If OPTSTRING starts with `-' or `+', it requests different methods of + handling the non-option ARGV-elements. + See the comments about RETURN_IN_ORDER and REQUIRE_ORDER, above. + + Long-named options begin with `--' instead of `-'. + Their names may be abbreviated as long as the abbreviation is unique + or is an exact match for some defined option. If they have an + argument, it follows the option name in the same ARGV-element, separated + from the option name by a `=', or else the in next ARGV-element. + When `getopt' finds a long-named option, it returns 0 if that option's + `flag' field is nonzero, the value of the option's `val' field + if the `flag' field is zero. + + The elements of ARGV aren't really const, because we permute them. + But we pretend they're const in the prototype to be compatible + with other systems. + + LONGOPTS is a vector of `struct option' terminated by an + element containing a name which is zero. + + LONGIND returns the index in LONGOPT of the long-named option found. + It is only valid when a long-named option has been found by the most + recent call. + + If LONG_ONLY is nonzero, '-' as well as '--' can introduce + long-named options. */ + +int +_getopt_internal (argc, argv, optstring, longopts, longind, long_only) + int argc; + char *const *argv; + const char *optstring; + const struct option *longopts; + int *longind; + int long_only; +{ + optarg = NULL; + + if (optind == 0) + optstring = _getopt_initialize (optstring); + + if (nextchar == NULL || *nextchar == '\0') + { + /* Advance to the next ARGV-element. */ + + if (ordering == PERMUTE) + { + /* If we have just processed some options following some non-options, + exchange them so that the options come first. */ + + if (first_nonopt != last_nonopt && last_nonopt != optind) + exchange ((char **) argv); + else if (last_nonopt != optind) + first_nonopt = optind; + + /* Skip any additional non-options + and extend the range of non-options previously skipped. */ + + while (optind < argc + && (argv[optind][0] != '-' || argv[optind][1] == '\0')) + optind++; + last_nonopt = optind; + } + + /* The special ARGV-element `--' means premature end of options. + Skip it like a null option, + then exchange with previous non-options as if it were an option, + then skip everything else like a non-option. */ + + if (optind != argc && !strcmp (argv[optind], "--")) + { + optind++; + + if (first_nonopt != last_nonopt && last_nonopt != optind) + exchange ((char **) argv); + else if (first_nonopt == last_nonopt) + first_nonopt = optind; + last_nonopt = argc; + + optind = argc; + } + + /* If we have done all the ARGV-elements, stop the scan + and back over any non-options that we skipped and permuted. */ + + if (optind == argc) + { + /* Set the next-arg-index to point at the non-options + that we previously skipped, so the caller will digest them. */ + if (first_nonopt != last_nonopt) + optind = first_nonopt; + return EOF; + } + + /* If we have come to a non-option and did not permute it, + either stop the scan or describe it to the caller and pass it by. */ + + if ((argv[optind][0] != '-' || argv[optind][1] == '\0')) + { + if (ordering == REQUIRE_ORDER) + return EOF; + optarg = argv[optind++]; + return 1; + } + + /* We have found another option-ARGV-element. + Skip the initial punctuation. */ + + nextchar = (argv[optind] + 1 + + (longopts != NULL && argv[optind][1] == '-')); + } + + /* Decode the current option-ARGV-element. */ + + /* Check whether the ARGV-element is a long option. + + If long_only and the ARGV-element has the form "-f", where f is + a valid short option, don't consider it an abbreviated form of + a long option that starts with f. Otherwise there would be no + way to give the -f short option. + + On the other hand, if there's a long option "fubar" and + the ARGV-element is "-fu", do consider that an abbreviation of + the long option, just like "--fu", and not "-f" with arg "u". + + This distinction seems to be the most useful approach. */ + + if (longopts != NULL + && (argv[optind][1] == '-' + || (long_only && (argv[optind][2] || !my_index (optstring, argv[optind][1]))))) + { + char *nameend; + const struct option *p; + const struct option *pfound = NULL; + int exact = 0; + int ambig = 0; + int indfound; + int option_index; + + for (nameend = nextchar; *nameend && *nameend != '='; nameend++) + /* Do nothing. */ ; + + /* Test all long options for either exact match + or abbreviated matches. */ + for (p = longopts, option_index = 0; p->name; p++, option_index++) + if (!strncmp (p->name, nextchar, nameend - nextchar)) + { + if (nameend - nextchar == (int) strlen (p->name)) + { + /* Exact match found. */ + pfound = p; + indfound = option_index; + exact = 1; + break; + } + else if (pfound == NULL) + { + /* First nonexact match found. */ + pfound = p; + indfound = option_index; + } + else + /* Second or later nonexact match found. */ + ambig = 1; + } + + if (ambig && !exact) + { + if (opterr) + fprintf (stderr, "%s: option `%s' is ambiguous\n", + argv[0], argv[optind]); + nextchar += strlen (nextchar); + optind++; + return '?'; + } + + if (pfound != NULL) + { + option_index = indfound; + optind++; + if (*nameend) + { + /* Don't test has_arg with >, because some C compilers don't + allow it to be used on enums. */ + if (pfound->has_arg) + optarg = nameend + 1; + else + { + if (opterr) + { + if (argv[optind - 1][1] == '-') + /* --option */ + fprintf (stderr, + "%s: option `--%s' doesn't allow an argument\n", + argv[0], pfound->name); + else + /* +option or -option */ + fprintf (stderr, + "%s: option `%c%s' doesn't allow an argument\n", + argv[0], argv[optind - 1][0], pfound->name); + } + nextchar += strlen (nextchar); + return '?'; + } + } + else if (pfound->has_arg == 1) + { + if (optind < argc) + optarg = argv[optind++]; + else + { + if (opterr) + fprintf (stderr, "%s: option `%s' requires an argument\n", + argv[0], argv[optind - 1]); + nextchar += strlen (nextchar); + return optstring[0] == ':' ? ':' : '?'; + } + } + nextchar += strlen (nextchar); + if (longind != NULL) + *longind = option_index; + if (pfound->flag) + { + *(pfound->flag) = pfound->val; + return 0; + } + return pfound->val; + } + + /* Can't find it as a long option. If this is not getopt_long_only, + or the option starts with '--' or is not a valid short + option, then it's an error. + Otherwise interpret it as a short option. */ + if (!long_only || argv[optind][1] == '-' + || my_index (optstring, *nextchar) == NULL) + { + if (opterr) + { + if (argv[optind][1] == '-') + /* --option */ + fprintf (stderr, "%s: unrecognized option `--%s'\n", + argv[0], nextchar); + else + /* +option or -option */ + fprintf (stderr, "%s: unrecognized option `%c%s'\n", + argv[0], argv[optind][0], nextchar); + } + nextchar = (char *) ""; + optind++; + return '?'; + } + } + + /* Look at and handle the next short option-character. */ + + { + char c = *nextchar++; + char *temp = my_index (optstring, c); + + /* Increment `optind' when we start to process its last character. */ + if (*nextchar == '\0') + ++optind; + + if (temp == NULL || c == ':') + { + if (opterr) + { + if (posixly_correct) + /* 1003.2 specifies the format of this message. */ + fprintf (stderr, "%s: illegal option -- %c\n", argv[0], c); + else + fprintf (stderr, "%s: invalid option -- %c\n", argv[0], c); + } + optopt = c; + return '?'; + } + if (temp[1] == ':') + { + if (temp[2] == ':') + { + /* This is an option that accepts an argument optionally. */ + if (*nextchar != '\0') + { + optarg = nextchar; + optind++; + } + else + optarg = NULL; + nextchar = NULL; + } + else + { + /* This is an option that requires an argument. */ + if (*nextchar != '\0') + { + optarg = nextchar; + /* If we end this ARGV-element by taking the rest as an arg, + we must advance to the next element now. */ + optind++; + } + else if (optind == argc) + { + if (opterr) + { + /* 1003.2 specifies the format of this message. */ + fprintf (stderr, "%s: option requires an argument -- %c\n", + argv[0], c); + } + optopt = c; + if (optstring[0] == ':') + c = ':'; + else + c = '?'; + } + else + /* We already incremented `optind' once; + increment it again when taking next ARGV-elt as argument. */ + optarg = argv[optind++]; + nextchar = NULL; + } + } + return c; + } +} + +int +getopt (argc, argv, optstring) + int argc; + char *const *argv; + const char *optstring; +{ + return _getopt_internal (argc, argv, optstring, + (const struct option *) 0, + (int *) 0, + 0); +} + +#endif /* _LIBC or not __GNU_LIBRARY__. */ + +#ifdef TEST + +/* Compile with -DTEST to make an executable for use in testing + the above definition of `getopt'. */ + +int +main (argc, argv) + int argc; + char **argv; +{ + int c; + int digit_optind = 0; + + while (1) + { + int this_option_optind = optind ? optind : 1; + + c = getopt (argc, argv, "abc:d:0123456789"); + if (c == EOF) + break; + + switch (c) + { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + if (digit_optind != 0 && digit_optind != this_option_optind) + printf ("digits occur in two different argv-elements.\n"); + digit_optind = this_option_optind; + printf ("option %c\n", c); + break; + + case 'a': + printf ("option a\n"); + break; + + case 'b': + printf ("option b\n"); + break; + + case 'c': + printf ("option c with value `%s'\n", optarg); + break; + + case '?': + break; + + default: + printf ("?? getopt returned character code 0%o ??\n", c); + } + } + + if (optind < argc) + { + printf ("non-option ARGV-elements: "); + while (optind < argc) + printf ("%s ", argv[optind++]); + printf ("\n"); + } + + exit (0); +} + +#endif /* TEST */ diff --git a/contrib/cvs/lib/getopt.h b/contrib/cvs/lib/getopt.h new file mode 100644 index 0000000..f644aa1 --- /dev/null +++ b/contrib/cvs/lib/getopt.h @@ -0,0 +1,131 @@ +/* Declarations for getopt. + Copyright (C) 1989, 1990, 1991, 1992, 1993 Free Software Foundation, Inc. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; either version 2, or (at your option) any + later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. */ + +/* $CVSid: @(#)getopt.h 1.7 94/09/21 $ */ + +#ifndef _GETOPT_H +#define _GETOPT_H 1 + +#ifdef __cplusplus +extern "C" { +#endif + +/* For communication from `getopt' to the caller. + When `getopt' finds an option that takes an argument, + the argument value is returned here. + Also, when `ordering' is RETURN_IN_ORDER, + each non-option ARGV-element is returned here. */ + +extern char *optarg; + +/* Index in ARGV of the next element to be scanned. + This is used for communication to and from the caller + and for communication between successive calls to `getopt'. + + On entry to `getopt', zero means this is the first call; initialize. + + When `getopt' returns EOF, this is the index of the first of the + non-option elements that the caller should itself scan. + + Otherwise, `optind' communicates from one call to the next + how much of ARGV has been scanned so far. */ + +extern int optind; + +/* Callers store zero here to inhibit the error message `getopt' prints + for unrecognized options. */ + +extern int opterr; + +/* Set to an option character which was unrecognized. */ + +extern int optopt; + +/* Describe the long-named options requested by the application. + The LONG_OPTIONS argument to getopt_long or getopt_long_only is a vector + of `struct option' terminated by an element containing a name which is + zero. + + The field `has_arg' is: + no_argument (or 0) if the option does not take an argument, + required_argument (or 1) if the option requires an argument, + optional_argument (or 2) if the option takes an optional argument. + + If the field `flag' is not NULL, it points to a variable that is set + to the value given in the field `val' when the option is found, but + left unchanged if the option is not found. + + To have a long-named option do something other than set an `int' to + a compiled-in constant, such as set a value from `optarg', set the + option's `flag' field to zero and its `val' field to a nonzero + value (the equivalent single-letter option character, if there is + one). For long options that have a zero `flag' field, `getopt' + returns the contents of the `val' field. */ + +struct option +{ +#if __STDC__ + const char *name; +#else + char *name; +#endif + /* has_arg can't be an enum because some compilers complain about + type mismatches in all the code that assumes it is an int. */ + int has_arg; + int *flag; + int val; +}; + +/* Names for the values of the `has_arg' field of `struct option'. */ + +#define no_argument 0 +#define required_argument 1 +#define optional_argument 2 + +#if __STDC__ +#if defined(__GNU_LIBRARY__) +/* Many other libraries have conflicting prototypes for getopt, with + differences in the consts, in stdlib.h. To avoid compilation + errors, only prototype getopt for the GNU C library. */ +extern int getopt (int argc, char *const *argv, const char *shortopts); +#else /* not __GNU_LIBRARY__ */ +extern int getopt (); +#endif /* not __GNU_LIBRARY__ */ +extern int getopt_long (int argc, char *const *argv, const char *shortopts, + const struct option *longopts, int *longind); +extern int getopt_long_only (int argc, char *const *argv, + const char *shortopts, + const struct option *longopts, int *longind); + +/* Internal only. Users should not call this directly. */ +extern int _getopt_internal (int argc, char *const *argv, + const char *shortopts, + const struct option *longopts, int *longind, + int long_only); +#else /* not __STDC__ */ +extern int getopt (); +extern int getopt_long (); +extern int getopt_long_only (); + +extern int _getopt_internal (); +#endif /* not __STDC__ */ + +#ifdef __cplusplus +} +#endif + +#endif /* _GETOPT_H */ diff --git a/contrib/cvs/lib/getopt1.c b/contrib/cvs/lib/getopt1.c new file mode 100644 index 0000000..f784b57 --- /dev/null +++ b/contrib/cvs/lib/getopt1.c @@ -0,0 +1,187 @@ +/* getopt_long and getopt_long_only entry points for GNU getopt. + Copyright (C) 1987, 88, 89, 90, 91, 92, 1993 + Free Software Foundation, Inc. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; either version 2, or (at your option) any + later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. */ + +#ifdef HAVE_CONFIG_H +#if defined (emacs) || defined (CONFIG_BROKETS) +/* We use <config.h> instead of "config.h" so that a compilation + using -I. -I$srcdir will use ./config.h rather than $srcdir/config.h + (which it would do because it found this file in $srcdir). */ +#include <config.h> +#else +#include "config.h" +#endif +#endif + +#include "getopt.h" + +#ifndef __STDC__ +/* This is a separate conditional since some stdc systems + reject `defined (const)'. */ +#ifndef const +#define const +#endif +#endif + +#include <stdio.h> + +/* Comment out all this code if we are using the GNU C Library, and are not + actually compiling the library itself. This code is part of the GNU C + Library, but also included in many other GNU distributions. Compiling + and linking in this code is a waste when using the GNU C library + (especially if it is a shared library). Rather than having every GNU + program understand `configure --with-gnu-libc' and omit the object files, + it is simpler to just do this in the source for each such file. */ + +#if defined (_LIBC) || !defined (__GNU_LIBRARY__) + + +/* This needs to come after some library #include + to get __GNU_LIBRARY__ defined. */ +#ifdef __GNU_LIBRARY__ +#include <stdlib.h> +#else +char *getenv (); +#endif + +#ifndef NULL +#define NULL 0 +#endif + +int +getopt_long (argc, argv, options, long_options, opt_index) + int argc; + char *const *argv; + const char *options; + const struct option *long_options; + int *opt_index; +{ + return _getopt_internal (argc, argv, options, long_options, opt_index, 0); +} + +/* Like getopt_long, but '-' as well as '--' can indicate a long option. + If an option that starts with '-' (not '--') doesn't match a long option, + but does match a short option, it is parsed as a short option + instead. */ + +int +getopt_long_only (argc, argv, options, long_options, opt_index) + int argc; + char *const *argv; + const char *options; + const struct option *long_options; + int *opt_index; +{ + return _getopt_internal (argc, argv, options, long_options, opt_index, 1); +} + + +#endif /* _LIBC or not __GNU_LIBRARY__. */ + +#ifdef TEST + +#include <stdio.h> + +int +main (argc, argv) + int argc; + char **argv; +{ + int c; + int digit_optind = 0; + + while (1) + { + int this_option_optind = optind ? optind : 1; + int option_index = 0; + static struct option long_options[] = + { + {"add", 1, 0, 0}, + {"append", 0, 0, 0}, + {"delete", 1, 0, 0}, + {"verbose", 0, 0, 0}, + {"create", 0, 0, 0}, + {"file", 1, 0, 0}, + {0, 0, 0, 0} + }; + + c = getopt_long (argc, argv, "abc:d:0123456789", + long_options, &option_index); + if (c == EOF) + break; + + switch (c) + { + case 0: + printf ("option %s", long_options[option_index].name); + if (optarg) + printf (" with arg %s", optarg); + printf ("\n"); + break; + + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + if (digit_optind != 0 && digit_optind != this_option_optind) + printf ("digits occur in two different argv-elements.\n"); + digit_optind = this_option_optind; + printf ("option %c\n", c); + break; + + case 'a': + printf ("option a\n"); + break; + + case 'b': + printf ("option b\n"); + break; + + case 'c': + printf ("option c with value `%s'\n", optarg); + break; + + case 'd': + printf ("option d with value `%s'\n", optarg); + break; + + case '?': + break; + + default: + printf ("?? getopt returned character code 0%o ??\n", c); + } + } + + if (optind < argc) + { + printf ("non-option ARGV-elements: "); + while (optind < argc) + printf ("%s ", argv[optind++]); + printf ("\n"); + } + + exit (0); +} + +#endif /* TEST */ diff --git a/contrib/cvs/lib/getwd.c b/contrib/cvs/lib/getwd.c new file mode 100644 index 0000000..573a788 --- /dev/null +++ b/contrib/cvs/lib/getwd.c @@ -0,0 +1,35 @@ +/* getwd.c -- get current working directory pathname + Copyright (C) 1992 Free Software Foundation, Inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2, or (at your option) + any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ + +/* Some systems which include both getwd() and getcwd() have an implementation + of getwd() which is much faster than getcwd(). As a result, we use the + system's getwd() if it is available */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "system.h" + +/* Get the current working directory into PATHNAME */ + +char * +getwd (pathname) + char *pathname; +{ + return (getcwd(pathname, PATH_MAX)); +} diff --git a/contrib/cvs/lib/hostname.c b/contrib/cvs/lib/hostname.c new file mode 100644 index 0000000..34be15e --- /dev/null +++ b/contrib/cvs/lib/hostname.c @@ -0,0 +1,49 @@ +/* hostname.c -- use uname() to get the name of the host + Copyright (C) 1992 Free Software Foundation, Inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2, or (at your option) + any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#if defined(STDC_HEADERS) || defined(USG) +#include <string.h> +#ifndef index +#define index strchr +#endif +#else +#include <strings.h> +#endif + +#include <sys/utsname.h> + +/* Put this host's name into NAME, using at most NAMELEN characters */ + +int +gethostname(name, namelen) + char *name; + int namelen; +{ + struct utsname ugnm; + + if (uname(&ugnm) < 0) + return (-1); + + (void) strncpy(name, ugnm.nodename, namelen-1); + name[namelen-1] = '\0'; + + return (0); +} diff --git a/contrib/cvs/lib/md5.c b/contrib/cvs/lib/md5.c new file mode 100644 index 0000000..4ad99cd --- /dev/null +++ b/contrib/cvs/lib/md5.c @@ -0,0 +1,277 @@ +/* + * This code implements the MD5 message-digest algorithm. + * The algorithm is due to Ron Rivest. This code was + * written by Colin Plumb in 1993, no copyright is claimed. + * This code is in the public domain; do with it what you wish. + * + * Equivalent code is available from RSA Data Security, Inc. + * This code has been tested against that, and is equivalent, + * except that you don't need to include two pages of legalese + * with every copy. + * + * To compute the message digest of a chunk of bytes, declare an + * MD5Context structure, pass it to MD5Init, call MD5Update as + * needed on buffers full of bytes, and then call MD5Final, which + * will fill a supplied 16-byte array with the digest. + */ + +#include "config.h" + +#if HAVE_STRING_H || STDC_HEADERS +#include <string.h> /* for memcpy() */ +#endif + +/* Add prototype support. */ +#ifndef PROTO +#if defined (USE_PROTOTYPES) ? USE_PROTOTYPES : defined (__STDC__) +#define PROTO(ARGS) ARGS +#else +#define PROTO(ARGS) () +#endif +#endif + +#include "md5.h" + +void byteReverse PROTO ((unsigned char *buf, unsigned longs)); + +#ifndef ASM_MD5 +/* + * Note: this code is harmless on little-endian machines. + */ +void byteReverse (buf, longs) + unsigned char *buf; + unsigned longs; +{ + uint32 t; + do { + t = (uint32)((unsigned)buf[3]<<8 | buf[2]) << 16 | + ((unsigned)buf[1]<<8 | buf[0]); + *(uint32 *)buf = t; + buf += 4; + } while (--longs); +} +#endif + +/* + * Start MD5 accumulation. Set bit count to 0 and buffer to mysterious + * initialization constants. + */ +void +MD5Init(ctx) + struct MD5Context *ctx; +{ + ctx->buf[0] = 0x67452301; + ctx->buf[1] = 0xefcdab89; + ctx->buf[2] = 0x98badcfe; + ctx->buf[3] = 0x10325476; + + ctx->bits[0] = 0; + ctx->bits[1] = 0; +} + +/* + * Update context to reflect the concatenation of another buffer full + * of bytes. + */ +void +MD5Update(ctx, buf, len) + struct MD5Context *ctx; + unsigned char const *buf; + unsigned len; +{ + uint32 t; + + /* Update bitcount */ + + t = ctx->bits[0]; + if ((ctx->bits[0] = t + ((uint32)len << 3)) < t) + ctx->bits[1]++; /* Carry from low to high */ + ctx->bits[1] += len >> 29; + + t = (t >> 3) & 0x3f; /* Bytes already in shsInfo->data */ + + /* Handle any leading odd-sized chunks */ + + if ( t ) { + unsigned char *p = (unsigned char *)ctx->in + t; + + t = 64-t; + if (len < t) { + memcpy(p, buf, len); + return; + } + memcpy(p, buf, t); + byteReverse(ctx->in, 16); + MD5Transform(ctx->buf, (uint32 *)ctx->in); + buf += t; + len -= t; + } + + /* Process data in 64-byte chunks */ + + while (len >= 64) { + memcpy(ctx->in, buf, 64); + byteReverse(ctx->in, 16); + MD5Transform(ctx->buf, (uint32 *)ctx->in); + buf += 64; + len -= 64; + } + + /* Handle any remaining bytes of data. */ + + memcpy(ctx->in, buf, len); +} + +/* + * Final wrapup - pad to 64-byte boundary with the bit pattern + * 1 0* (64-bit count of bits processed, MSB-first) + */ +void +MD5Final(digest, ctx) + unsigned char digest[16]; + struct MD5Context *ctx; +{ + unsigned count; + unsigned char *p; + + /* Compute number of bytes mod 64 */ + count = (ctx->bits[0] >> 3) & 0x3F; + + /* Set the first char of padding to 0x80. This is safe since there is + always at least one byte free */ + p = ctx->in + count; + *p++ = 0x80; + + /* Bytes of padding needed to make 64 bytes */ + count = 64 - 1 - count; + + /* Pad out to 56 mod 64 */ + if (count < 8) { + /* Two lots of padding: Pad the first block to 64 bytes */ + memset(p, 0, count); + byteReverse(ctx->in, 16); + MD5Transform(ctx->buf, (uint32 *)ctx->in); + + /* Now fill the next block with 56 bytes */ + memset(ctx->in, 0, 56); + } else { + /* Pad block to 56 bytes */ + memset(p, 0, count-8); + } + byteReverse(ctx->in, 14); + + /* Append length in bits and transform */ + ((uint32 *)ctx->in)[ 14 ] = ctx->bits[0]; + ((uint32 *)ctx->in)[ 15 ] = ctx->bits[1]; + + MD5Transform(ctx->buf, (uint32 *)ctx->in); + byteReverse((unsigned char *)ctx->buf, 4); + memcpy(digest, ctx->buf, 16); + memset(ctx, 0, sizeof(ctx)); /* In case it's sensitive */ +} + +#ifndef ASM_MD5 + +/* The four core functions - F1 is optimized somewhat */ + +/* #define F1(x, y, z) (x & y | ~x & z) */ +#define F1(x, y, z) (z ^ (x & (y ^ z))) +#define F2(x, y, z) F1(z, x, y) +#define F3(x, y, z) (x ^ y ^ z) +#define F4(x, y, z) (y ^ (x | ~z)) + +/* This is the central step in the MD5 algorithm. */ +#define MD5STEP(f, w, x, y, z, data, s) \ + ( w += f(x, y, z) + data, w = w<<s | w>>(32-s), w += x ) + +/* + * The core of the MD5 algorithm, this alters an existing MD5 hash to + * reflect the addition of 16 longwords of new data. MD5Update blocks + * the data and converts bytes into longwords for this routine. + */ +void +MD5Transform(buf, in) + uint32 buf[4]; + uint32 const in[16]; +{ + register uint32 a, b, c, d; + + a = buf[0]; + b = buf[1]; + c = buf[2]; + d = buf[3]; + + MD5STEP(F1, a, b, c, d, in[ 0]+0xd76aa478, 7); + MD5STEP(F1, d, a, b, c, in[ 1]+0xe8c7b756, 12); + MD5STEP(F1, c, d, a, b, in[ 2]+0x242070db, 17); + MD5STEP(F1, b, c, d, a, in[ 3]+0xc1bdceee, 22); + MD5STEP(F1, a, b, c, d, in[ 4]+0xf57c0faf, 7); + MD5STEP(F1, d, a, b, c, in[ 5]+0x4787c62a, 12); + MD5STEP(F1, c, d, a, b, in[ 6]+0xa8304613, 17); + MD5STEP(F1, b, c, d, a, in[ 7]+0xfd469501, 22); + MD5STEP(F1, a, b, c, d, in[ 8]+0x698098d8, 7); + MD5STEP(F1, d, a, b, c, in[ 9]+0x8b44f7af, 12); + MD5STEP(F1, c, d, a, b, in[10]+0xffff5bb1, 17); + MD5STEP(F1, b, c, d, a, in[11]+0x895cd7be, 22); + MD5STEP(F1, a, b, c, d, in[12]+0x6b901122, 7); + MD5STEP(F1, d, a, b, c, in[13]+0xfd987193, 12); + MD5STEP(F1, c, d, a, b, in[14]+0xa679438e, 17); + MD5STEP(F1, b, c, d, a, in[15]+0x49b40821, 22); + + MD5STEP(F2, a, b, c, d, in[ 1]+0xf61e2562, 5); + MD5STEP(F2, d, a, b, c, in[ 6]+0xc040b340, 9); + MD5STEP(F2, c, d, a, b, in[11]+0x265e5a51, 14); + MD5STEP(F2, b, c, d, a, in[ 0]+0xe9b6c7aa, 20); + MD5STEP(F2, a, b, c, d, in[ 5]+0xd62f105d, 5); + MD5STEP(F2, d, a, b, c, in[10]+0x02441453, 9); + MD5STEP(F2, c, d, a, b, in[15]+0xd8a1e681, 14); + MD5STEP(F2, b, c, d, a, in[ 4]+0xe7d3fbc8, 20); + MD5STEP(F2, a, b, c, d, in[ 9]+0x21e1cde6, 5); + MD5STEP(F2, d, a, b, c, in[14]+0xc33707d6, 9); + MD5STEP(F2, c, d, a, b, in[ 3]+0xf4d50d87, 14); + MD5STEP(F2, b, c, d, a, in[ 8]+0x455a14ed, 20); + MD5STEP(F2, a, b, c, d, in[13]+0xa9e3e905, 5); + MD5STEP(F2, d, a, b, c, in[ 2]+0xfcefa3f8, 9); + MD5STEP(F2, c, d, a, b, in[ 7]+0x676f02d9, 14); + MD5STEP(F2, b, c, d, a, in[12]+0x8d2a4c8a, 20); + + MD5STEP(F3, a, b, c, d, in[ 5]+0xfffa3942, 4); + MD5STEP(F3, d, a, b, c, in[ 8]+0x8771f681, 11); + MD5STEP(F3, c, d, a, b, in[11]+0x6d9d6122, 16); + MD5STEP(F3, b, c, d, a, in[14]+0xfde5380c, 23); + MD5STEP(F3, a, b, c, d, in[ 1]+0xa4beea44, 4); + MD5STEP(F3, d, a, b, c, in[ 4]+0x4bdecfa9, 11); + MD5STEP(F3, c, d, a, b, in[ 7]+0xf6bb4b60, 16); + MD5STEP(F3, b, c, d, a, in[10]+0xbebfbc70, 23); + MD5STEP(F3, a, b, c, d, in[13]+0x289b7ec6, 4); + MD5STEP(F3, d, a, b, c, in[ 0]+0xeaa127fa, 11); + MD5STEP(F3, c, d, a, b, in[ 3]+0xd4ef3085, 16); + MD5STEP(F3, b, c, d, a, in[ 6]+0x04881d05, 23); + MD5STEP(F3, a, b, c, d, in[ 9]+0xd9d4d039, 4); + MD5STEP(F3, d, a, b, c, in[12]+0xe6db99e5, 11); + MD5STEP(F3, c, d, a, b, in[15]+0x1fa27cf8, 16); + MD5STEP(F3, b, c, d, a, in[ 2]+0xc4ac5665, 23); + + MD5STEP(F4, a, b, c, d, in[ 0]+0xf4292244, 6); + MD5STEP(F4, d, a, b, c, in[ 7]+0x432aff97, 10); + MD5STEP(F4, c, d, a, b, in[14]+0xab9423a7, 15); + MD5STEP(F4, b, c, d, a, in[ 5]+0xfc93a039, 21); + MD5STEP(F4, a, b, c, d, in[12]+0x655b59c3, 6); + MD5STEP(F4, d, a, b, c, in[ 3]+0x8f0ccc92, 10); + MD5STEP(F4, c, d, a, b, in[10]+0xffeff47d, 15); + MD5STEP(F4, b, c, d, a, in[ 1]+0x85845dd1, 21); + MD5STEP(F4, a, b, c, d, in[ 8]+0x6fa87e4f, 6); + MD5STEP(F4, d, a, b, c, in[15]+0xfe2ce6e0, 10); + MD5STEP(F4, c, d, a, b, in[ 6]+0xa3014314, 15); + MD5STEP(F4, b, c, d, a, in[13]+0x4e0811a1, 21); + MD5STEP(F4, a, b, c, d, in[ 4]+0xf7537e82, 6); + MD5STEP(F4, d, a, b, c, in[11]+0xbd3af235, 10); + MD5STEP(F4, c, d, a, b, in[ 2]+0x2ad7d2bb, 15); + MD5STEP(F4, b, c, d, a, in[ 9]+0xeb86d391, 21); + + buf[0] += a; + buf[1] += b; + buf[2] += c; + buf[3] += d; +} +#endif diff --git a/contrib/cvs/lib/md5.h b/contrib/cvs/lib/md5.h new file mode 100644 index 0000000..bfe79cc --- /dev/null +++ b/contrib/cvs/lib/md5.h @@ -0,0 +1,31 @@ +#ifndef MD5_H +#define MD5_H + +#if SIZEOF_LONG == 4 +typedef unsigned long uint32; +#else +#if SIZEOF_INT == 4 +typedef unsigned int uint32; +#else +Congratulations! You get to rewrite this code so that it does not require +a 32-bit integer type! (Or maybe you just need to reconfigure.) +#endif +#endif + +struct MD5Context { + uint32 buf[4]; + uint32 bits[2]; + unsigned char in[64]; +}; + +void MD5Init PROTO((struct MD5Context *context)); +void MD5Update PROTO((struct MD5Context *context, unsigned char const *buf, unsigned len)); +void MD5Final PROTO((unsigned char digest[16], struct MD5Context *context)); +void MD5Transform PROTO((uint32 buf[4], uint32 const in[16])); + +/* + * This is needed to make RSAREF happy on some MS-DOS compilers. + */ +typedef struct MD5Context MD5_CTX; + +#endif /* !MD5_H */ diff --git a/contrib/cvs/lib/memmove.c b/contrib/cvs/lib/memmove.c new file mode 100644 index 0000000..8818d46 --- /dev/null +++ b/contrib/cvs/lib/memmove.c @@ -0,0 +1,57 @@ +/* memmove -- copy memory regions of arbitary length + Copyright (C) 1991 Free Software Foundation, Inc. + +This file is part of the libiberty library. +Libiberty is free software; you can redistribute it and/or +modify it under the terms of the GNU Library General Public +License as published by the Free Software Foundation; either +version 2 of the License, or (at your option) any later version. + +Libiberty 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 +Library General Public License for more details. + +You should have received a copy of the GNU Library General Public +License along with libiberty; see the file COPYING.LIB. If +not, write to the Free Software Foundation, Inc., 675 Mass Ave, +Cambridge, MA 02139, USA. */ + + +/* + +NAME + + memmove -- copy memory regions of arbitary length + +SYNOPSIS + + void memmove (void *out, const void *in, size_t n); + +DESCRIPTION + + Copy LENGTH bytes from memory region pointed to by IN to memory + region pointed to by OUT. + + Regions can be overlapping. +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifdef __STDC__ +#include <stddef.h> +#else +#define size_t unsigned long +#endif + +void * +memmove (out, in, length) + void *out; + const void* in; + size_t length; +{ + bcopy(in, out, length); + return out; +} diff --git a/contrib/cvs/lib/mkdir.c b/contrib/cvs/lib/mkdir.c new file mode 100644 index 0000000..89ed4b6 --- /dev/null +++ b/contrib/cvs/lib/mkdir.c @@ -0,0 +1,129 @@ +/* mkrmdir.c -- BSD compatible directory functions for System V + Copyright (C) 1988, 1990 Free Software Foundation, Inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2, or (at your option) + any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <sys/types.h> +#include <sys/stat.h> +#include <errno.h> +#ifndef STDC_HEADERS +extern int errno; +#endif + +/* mkdir and rmdir adapted from GNU tar. */ + +/* Make directory DPATH, with permission mode DMODE. + + Written by Robert Rother, Mariah Corporation, August 1985 + (sdcsvax!rmr or rmr@uscd). If you want it, it's yours. + + Severely hacked over by John Gilmore to make a 4.2BSD compatible + subroutine. 11Mar86; hoptoad!gnu + + Modified by rmtodd@uokmax 6-28-87 -- when making an already existing dir, + subroutine didn't return EEXIST. It does now. */ + +int +mkdir (dpath, dmode) + char *dpath; + int dmode; +{ + int cpid, status; + struct stat statbuf; + + if (stat (dpath, &statbuf) == 0) + { + errno = EEXIST; /* stat worked, so it already exists. */ + return -1; + } + + /* If stat fails for a reason other than non-existence, return error. */ + if (! existence_error (errno)) + return -1; + + cpid = fork (); + switch (cpid) + { + case -1: /* Cannot fork. */ + return -1; /* errno is set already. */ + + case 0: /* Child process. */ + /* Cheap hack to set mode of new directory. Since this child + process is going away anyway, we zap its umask. + This won't suffice to set SUID, SGID, etc. on this + directory, so the parent process calls chmod afterward. */ + status = umask (0); /* Get current umask. */ + umask (status | (0777 & ~dmode)); /* Set for mkdir. */ + execl ("/bin/mkdir", "mkdir", dpath, (char *) 0); + _exit (1); + + default: /* Parent process. */ + while (wait (&status) != cpid) /* Wait for kid to finish. */ + /* Do nothing. */ ; + + if (status & 0xFFFF) + { + errno = EIO; /* /bin/mkdir failed. */ + return -1; + } + return chmod (dpath, dmode); + } +} + +/* Remove directory DPATH. + Return 0 if successful, -1 if not. */ + +int +rmdir (dpath) + char *dpath; +{ + int cpid, status; + struct stat statbuf; + + if (stat (dpath, &statbuf) != 0) + return -1; /* stat set errno. */ + + if ((statbuf.st_mode & S_IFMT) != S_IFDIR) + { + errno = ENOTDIR; + return -1; + } + + cpid = fork (); + switch (cpid) + { + case -1: /* Cannot fork. */ + return -1; /* errno is set already. */ + + case 0: /* Child process. */ + execl ("/bin/rmdir", "rmdir", dpath, (char *) 0); + _exit (1); + + default: /* Parent process. */ + while (wait (&status) != cpid) /* Wait for kid to finish. */ + /* Do nothing. */ ; + + if (status & 0xFFFF) + { + errno = EIO; /* /bin/rmdir failed. */ + return -1; + } + return 0; + } +} diff --git a/contrib/cvs/lib/regex.c b/contrib/cvs/lib/regex.c new file mode 100644 index 0000000..03fc721 --- /dev/null +++ b/contrib/cvs/lib/regex.c @@ -0,0 +1,4952 @@ +/* Extended regular expression matching and search library, + version 0.12. + (Implements POSIX draft P10003.2/D11.2, except for + internationalization features.) + + Copyright (C) 1993 Free Software Foundation, Inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2, or (at your option) + any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ + +/* Trying to define this in the makefile would get hairy, unless we can + more gracefully do it for NT, OS/2, unix, etc. */ +#define REGEX_MALLOC 1 + +/* AIX requires this to be the first thing in the file. */ +#if defined (_AIX) && !defined (REGEX_MALLOC) + #pragma alloca +#endif + +#define _GNU_SOURCE + +/* We need this for `regex.h', and perhaps for the Emacs include files. */ +#include <sys/types.h> + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +/* The `emacs' switch turns on certain matching commands + that make sense only in Emacs. */ +#ifdef emacs + +#include "lisp.h" +#include "buffer.h" +#include "syntax.h" + +/* Emacs uses `NULL' as a predicate. */ +#undef NULL + +#else /* not emacs */ + +/* We used to test for `BSTRING' here, but only GCC and Emacs define + `BSTRING', as far as I know, and neither of them use this code. */ +#if HAVE_STRING_H || STDC_HEADERS +#include <string.h> +#ifndef bcmp +#define bcmp(s1, s2, n) memcmp ((s1), (s2), (n)) +#endif +#ifndef bcopy +#define bcopy(s, d, n) memcpy ((d), (s), (n)) +#endif +#ifndef bzero +#define bzero(s, n) memset ((s), 0, (n)) +#endif +#else +#include <strings.h> +#endif + +#ifdef STDC_HEADERS +#include <stdlib.h> +#else +char *malloc (); +char *realloc (); +#endif + + +/* Define the syntax stuff for \<, \>, etc. */ + +/* This must be nonzero for the wordchar and notwordchar pattern + commands in re_match_2. */ +#ifndef Sword +#define Sword 1 +#endif + +#ifdef SYNTAX_TABLE + +extern char *re_syntax_table; + +#else /* not SYNTAX_TABLE */ + +/* How many characters in the character set. */ +#define CHAR_SET_SIZE 256 + +static char re_syntax_table[CHAR_SET_SIZE]; + +static void +init_syntax_once () +{ + register int c; + static int done = 0; + + if (done) + return; + + bzero (re_syntax_table, sizeof re_syntax_table); + + for (c = 'a'; c <= 'z'; c++) + re_syntax_table[c] = Sword; + + for (c = 'A'; c <= 'Z'; c++) + re_syntax_table[c] = Sword; + + for (c = '0'; c <= '9'; c++) + re_syntax_table[c] = Sword; + + re_syntax_table['_'] = Sword; + + done = 1; +} + +#endif /* not SYNTAX_TABLE */ + +#define SYNTAX(c) re_syntax_table[c] + +#endif /* not emacs */ + +/* Get the interface, including the syntax bits. */ +#include "regex.h" + +/* isalpha etc. are used for the character classes. */ +#include <ctype.h> + +#ifndef isascii +#define isascii(c) 1 +#endif + +#ifdef isblank +#define ISBLANK(c) (isascii (c) && isblank (c)) +#else +#define ISBLANK(c) ((c) == ' ' || (c) == '\t') +#endif +#ifdef isgraph +#define ISGRAPH(c) (isascii (c) && isgraph (c)) +#else +#define ISGRAPH(c) (isascii (c) && isprint (c) && !isspace (c)) +#endif + +#define ISPRINT(c) (isascii (c) && isprint (c)) +#define ISDIGIT(c) (isascii (c) && isdigit (c)) +#define ISALNUM(c) (isascii (c) && isalnum (c)) +#define ISALPHA(c) (isascii (c) && isalpha (c)) +#define ISCNTRL(c) (isascii (c) && iscntrl (c)) +#define ISLOWER(c) (isascii (c) && islower (c)) +#define ISPUNCT(c) (isascii (c) && ispunct (c)) +#define ISSPACE(c) (isascii (c) && isspace (c)) +#define ISUPPER(c) (isascii (c) && isupper (c)) +#define ISXDIGIT(c) (isascii (c) && isxdigit (c)) + +#ifndef NULL +#define NULL 0 +#endif + +/* 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.) */ +#undef SIGN_EXTEND_CHAR +#if __STDC__ +#define SIGN_EXTEND_CHAR(c) ((signed char) (c)) +#else /* not __STDC__ */ +/* As in Harbison and Steele. */ +#define SIGN_EXTEND_CHAR(c) ((((unsigned char) (c)) ^ 128) - 128) +#endif + +/* Should we use malloc or alloca? If REGEX_MALLOC is not defined, we + use `alloca' instead of `malloc'. This is because using malloc in + re_search* or re_match* could cause memory leaks when C-g is used in + Emacs; also, malloc is slower and causes storage fragmentation. On + the other hand, malloc is more portable, and easier to debug. + + Because we sometimes use alloca, some routines have to be macros, + not functions -- `alloca'-allocated space disappears at the end of the + function it is called in. */ + +#ifdef REGEX_MALLOC + +#define REGEX_ALLOCATE malloc +#define REGEX_REALLOCATE(source, osize, nsize) realloc (source, nsize) + +#else /* not REGEX_MALLOC */ + +/* Emacs already defines alloca, sometimes. */ +#ifndef alloca + +/* Make alloca work the best possible way. */ +#ifdef __GNUC__ +#define alloca __builtin_alloca +#else /* not __GNUC__ */ +#if HAVE_ALLOCA_H +#include <alloca.h> +#else /* not __GNUC__ or HAVE_ALLOCA_H */ +#ifndef _AIX /* Already did AIX, up at the top. */ +char *alloca (); +#endif /* not _AIX */ +#endif /* not HAVE_ALLOCA_H */ +#endif /* not __GNUC__ */ + +#endif /* not alloca */ + +#define REGEX_ALLOCATE alloca + +/* Assumes a `char *destination' variable. */ +#define REGEX_REALLOCATE(source, osize, nsize) \ + (destination = (char *) alloca (nsize), \ + bcopy (source, destination, osize), \ + destination) + +#endif /* not REGEX_MALLOC */ + + +/* True if `size1' is non-NULL and PTR is pointing anywhere inside + `string1' or just past its end. This works if PTR is NULL, which is + a good thing. */ +#define FIRST_STRING_P(ptr) \ + (size1 && string1 <= (ptr) && (ptr) <= string1 + size1) + +/* (Re)Allocate N items of type T using malloc, or fail. */ +#define TALLOC(n, t) ((t *) malloc ((n) * sizeof (t))) +#define RETALLOC(addr, n, t) ((addr) = (t *) realloc (addr, (n) * sizeof (t))) +#define REGEX_TALLOC(n, t) ((t *) REGEX_ALLOCATE ((n) * sizeof (t))) + +#define BYTEWIDTH 8 /* In bits. */ + +#define STREQ(s1, s2) ((strcmp (s1, s2) == 0)) + +#define MAX(a, b) ((a) > (b) ? (a) : (b)) +#define MIN(a, b) ((a) < (b) ? (a) : (b)) + +typedef char boolean; +#define false 0 +#define true 1 + +/* These are the command codes that appear in compiled regular + expressions. Some opcodes are followed by argument bytes. A + command code can specify any interpretation whatsoever for its + arguments. Zero bytes may appear in the compiled regular expression. + + The value of `exactn' is needed in search.c (search_buffer) in Emacs. + So regex.h defines a symbol `RE_EXACTN_VALUE' to be 1; the value of + `exactn' we use here must also be 1. */ + +typedef enum +{ + no_op = 0, + + /* Followed by one byte giving n, then by n literal bytes. */ + exactn = 1, + + /* Matches any (more or less) character. */ + anychar, + + /* Matches any one char belonging to specified set. First + following byte is number of bitmap bytes. Then come bytes + for a bitmap saying which chars are in. Bits in each byte + are ordered low-bit-first. A character is in the set if its + bit is 1. A character too large to have a bit in the map is + automatically not in the set. */ + charset, + + /* Same parameters as charset, but match any character that is + not one of those specified. */ + charset_not, + + /* Start remembering the text that is matched, for storing in a + register. Followed by one byte with the register number, in + the range 0 to one less than the pattern buffer's re_nsub + field. Then followed by one byte with the number of groups + inner to this one. (This last has to be part of the + start_memory only because we need it in the on_failure_jump + of re_match_2.) */ + start_memory, + + /* Stop remembering the text that is matched and store it in a + memory register. Followed by one byte with the register + number, in the range 0 to one less than `re_nsub' in the + pattern buffer, and one byte with the number of inner groups, + just like `start_memory'. (We need the number of inner + groups here because we don't have any easy way of finding the + corresponding start_memory when we're at a stop_memory.) */ + stop_memory, + + /* Match a duplicate of something remembered. Followed by one + byte containing the register number. */ + duplicate, + + /* Fail unless at beginning of line. */ + begline, + + /* Fail unless at end of line. */ + endline, + + /* Succeeds if at beginning of buffer (if emacs) or at beginning + of string to be matched (if not). */ + begbuf, + + /* Analogously, for end of buffer/string. */ + endbuf, + + /* Followed by two byte relative address to which to jump. */ + jump, + + /* Same as jump, but marks the end of an alternative. */ + jump_past_alt, + + /* Followed by two-byte relative address of place to resume at + in case of failure. */ + on_failure_jump, + + /* Like on_failure_jump, but pushes a placeholder instead of the + current string position when executed. */ + on_failure_keep_string_jump, + + /* Throw away latest failure point and then jump to following + two-byte relative address. */ + pop_failure_jump, + + /* Change to pop_failure_jump if know won't have to backtrack to + match; otherwise change to jump. This is used to jump + back to the beginning of a repeat. If what follows this jump + clearly won't match what the repeat does, such that we can be + sure that there is no use backtracking out of repetitions + already matched, then we change it to a pop_failure_jump. + Followed by two-byte address. */ + maybe_pop_jump, + + /* Jump to following two-byte address, and push a dummy failure + point. This failure point will be thrown away if an attempt + is made to use it for a failure. A `+' construct makes this + before the first repeat. Also used as an intermediary kind + of jump when compiling an alternative. */ + dummy_failure_jump, + + /* Push a dummy failure point and continue. Used at the end of + alternatives. */ + push_dummy_failure, + + /* Followed by two-byte relative address and two-byte number n. + After matching N times, jump to the address upon failure. */ + succeed_n, + + /* Followed by two-byte relative address, and two-byte number n. + Jump to the address N times, then fail. */ + jump_n, + + /* Set the following two-byte relative address to the + subsequent two-byte number. The address *includes* the two + bytes of number. */ + set_number_at, + + wordchar, /* Matches any word-constituent character. */ + notwordchar, /* Matches any char that is not a word-constituent. */ + + wordbeg, /* Succeeds if at word beginning. */ + wordend, /* Succeeds if at word end. */ + + wordbound, /* Succeeds if at a word boundary. */ + notwordbound /* Succeeds if not at a word boundary. */ + +#ifdef emacs + ,before_dot, /* Succeeds if before point. */ + at_dot, /* Succeeds if at point. */ + after_dot, /* Succeeds if after point. */ + + /* Matches any character whose syntax is specified. Followed by + a byte which contains a syntax code, e.g., Sword. */ + syntaxspec, + + /* Matches any character whose syntax is not that specified. */ + notsyntaxspec +#endif /* emacs */ +} re_opcode_t; + +/* Common operations on the compiled pattern. */ + +/* Store NUMBER in two contiguous bytes starting at DESTINATION. */ + +#define STORE_NUMBER(destination, number) \ + do { \ + (destination)[0] = (number) & 0377; \ + (destination)[1] = (number) >> 8; \ + } while (0) + +/* Same as STORE_NUMBER, except increment DESTINATION to + the byte after where the number is stored. Therefore, DESTINATION + must be an lvalue. */ + +#define STORE_NUMBER_AND_INCR(destination, number) \ + do { \ + STORE_NUMBER (destination, number); \ + (destination) += 2; \ + } while (0) + +/* Put into DESTINATION a number stored in two contiguous bytes starting + at SOURCE. */ + +#define EXTRACT_NUMBER(destination, source) \ + do { \ + (destination) = *(source) & 0377; \ + (destination) += SIGN_EXTEND_CHAR (*((source) + 1)) << 8; \ + } while (0) + +#ifdef DEBUG +static void +extract_number (dest, source) + int *dest; + unsigned char *source; +{ + int temp = SIGN_EXTEND_CHAR (*(source + 1)); + *dest = *source & 0377; + *dest += temp << 8; +} + +#ifndef EXTRACT_MACROS /* To debug the macros. */ +#undef EXTRACT_NUMBER +#define EXTRACT_NUMBER(dest, src) extract_number (&dest, src) +#endif /* not EXTRACT_MACROS */ + +#endif /* DEBUG */ + +/* Same as EXTRACT_NUMBER, except increment SOURCE to after the number. + SOURCE must be an lvalue. */ + +#define EXTRACT_NUMBER_AND_INCR(destination, source) \ + do { \ + EXTRACT_NUMBER (destination, source); \ + (source) += 2; \ + } while (0) + +#ifdef DEBUG +static void +extract_number_and_incr (destination, source) + int *destination; + unsigned char **source; +{ + extract_number (destination, *source); + *source += 2; +} + +#ifndef EXTRACT_MACROS +#undef EXTRACT_NUMBER_AND_INCR +#define EXTRACT_NUMBER_AND_INCR(dest, src) \ + extract_number_and_incr (&dest, &src) +#endif /* not EXTRACT_MACROS */ + +#endif /* DEBUG */ + +/* If DEBUG is defined, Regex prints many voluminous messages about what + it is doing (if the variable `debug' is nonzero). If linked with the + main program in `iregex.c', you can enter patterns and strings + interactively. And if linked with the main program in `main.c' and + the other test files, you can run the already-written tests. */ + +#ifdef DEBUG + +/* We use standard I/O for debugging. */ +#include <stdio.h> + +/* It is useful to test things that ``must'' be true when debugging. */ +#include <assert.h> + +static int debug = 0; + +#define DEBUG_STATEMENT(e) e +#define DEBUG_PRINT1(x) if (debug) printf (x) +#define DEBUG_PRINT2(x1, x2) if (debug) printf (x1, x2) +#define DEBUG_PRINT3(x1, x2, x3) if (debug) printf (x1, x2, x3) +#define DEBUG_PRINT4(x1, x2, x3, x4) if (debug) printf (x1, x2, x3, x4) +#define DEBUG_PRINT_COMPILED_PATTERN(p, s, e) \ + if (debug) print_partial_compiled_pattern (s, e) +#define DEBUG_PRINT_DOUBLE_STRING(w, s1, sz1, s2, sz2) \ + if (debug) print_double_string (w, s1, sz1, s2, sz2) + + +extern void printchar (); + +/* Print the fastmap in human-readable form. */ + +void +print_fastmap (fastmap) + char *fastmap; +{ + unsigned was_a_range = 0; + unsigned i = 0; + + while (i < (1 << BYTEWIDTH)) + { + if (fastmap[i++]) + { + was_a_range = 0; + printchar (i - 1); + while (i < (1 << BYTEWIDTH) && fastmap[i]) + { + was_a_range = 1; + i++; + } + if (was_a_range) + { + printf ("-"); + printchar (i - 1); + } + } + } + putchar ('\n'); +} + + +/* Print a compiled pattern string in human-readable form, starting at + the START pointer into it and ending just before the pointer END. */ + +void +print_partial_compiled_pattern (start, end) + unsigned char *start; + unsigned char *end; +{ + int mcnt, mcnt2; + unsigned char *p = start; + unsigned char *pend = end; + + if (start == NULL) + { + printf ("(null)\n"); + return; + } + + /* Loop over pattern commands. */ + while (p < pend) + { + switch ((re_opcode_t) *p++) + { + case no_op: + printf ("/no_op"); + break; + + case exactn: + mcnt = *p++; + printf ("/exactn/%d", mcnt); + do + { + putchar ('/'); + printchar (*p++); + } + while (--mcnt); + break; + + case start_memory: + mcnt = *p++; + printf ("/start_memory/%d/%d", mcnt, *p++); + break; + + case stop_memory: + mcnt = *p++; + printf ("/stop_memory/%d/%d", mcnt, *p++); + break; + + case duplicate: + printf ("/duplicate/%d", *p++); + break; + + case anychar: + printf ("/anychar"); + break; + + case charset: + case charset_not: + { + register int c; + + printf ("/charset%s", + (re_opcode_t) *(p - 1) == charset_not ? "_not" : ""); + + assert (p + *p < pend); + + for (c = 0; c < *p; c++) + { + unsigned bit; + unsigned char map_byte = p[1 + c]; + + putchar ('/'); + + for (bit = 0; bit < BYTEWIDTH; bit++) + if (map_byte & (1 << bit)) + printchar (c * BYTEWIDTH + bit); + } + p += 1 + *p; + break; + } + + case begline: + printf ("/begline"); + break; + + case endline: + printf ("/endline"); + break; + + case on_failure_jump: + extract_number_and_incr (&mcnt, &p); + printf ("/on_failure_jump/0/%d", mcnt); + break; + + case on_failure_keep_string_jump: + extract_number_and_incr (&mcnt, &p); + printf ("/on_failure_keep_string_jump/0/%d", mcnt); + break; + + case dummy_failure_jump: + extract_number_and_incr (&mcnt, &p); + printf ("/dummy_failure_jump/0/%d", mcnt); + break; + + case push_dummy_failure: + printf ("/push_dummy_failure"); + break; + + case maybe_pop_jump: + extract_number_and_incr (&mcnt, &p); + printf ("/maybe_pop_jump/0/%d", mcnt); + break; + + case pop_failure_jump: + extract_number_and_incr (&mcnt, &p); + printf ("/pop_failure_jump/0/%d", mcnt); + break; + + case jump_past_alt: + extract_number_and_incr (&mcnt, &p); + printf ("/jump_past_alt/0/%d", mcnt); + break; + + case jump: + extract_number_and_incr (&mcnt, &p); + printf ("/jump/0/%d", mcnt); + break; + + case succeed_n: + extract_number_and_incr (&mcnt, &p); + extract_number_and_incr (&mcnt2, &p); + printf ("/succeed_n/0/%d/0/%d", mcnt, mcnt2); + break; + + case jump_n: + extract_number_and_incr (&mcnt, &p); + extract_number_and_incr (&mcnt2, &p); + printf ("/jump_n/0/%d/0/%d", mcnt, mcnt2); + break; + + case set_number_at: + extract_number_and_incr (&mcnt, &p); + extract_number_and_incr (&mcnt2, &p); + printf ("/set_number_at/0/%d/0/%d", mcnt, mcnt2); + break; + + case wordbound: + printf ("/wordbound"); + break; + + case notwordbound: + printf ("/notwordbound"); + break; + + case wordbeg: + printf ("/wordbeg"); + break; + + case wordend: + printf ("/wordend"); + +#ifdef emacs + case before_dot: + printf ("/before_dot"); + break; + + case at_dot: + printf ("/at_dot"); + break; + + case after_dot: + printf ("/after_dot"); + break; + + case syntaxspec: + printf ("/syntaxspec"); + mcnt = *p++; + printf ("/%d", mcnt); + break; + + case notsyntaxspec: + printf ("/notsyntaxspec"); + mcnt = *p++; + printf ("/%d", mcnt); + break; +#endif /* emacs */ + + case wordchar: + printf ("/wordchar"); + break; + + case notwordchar: + printf ("/notwordchar"); + break; + + case begbuf: + printf ("/begbuf"); + break; + + case endbuf: + printf ("/endbuf"); + break; + + default: + printf ("?%d", *(p-1)); + } + } + printf ("/\n"); +} + + +void +print_compiled_pattern (bufp) + struct re_pattern_buffer *bufp; +{ + unsigned char *buffer = bufp->buffer; + + print_partial_compiled_pattern (buffer, buffer + bufp->used); + printf ("%d bytes used/%d bytes allocated.\n", bufp->used, bufp->allocated); + + if (bufp->fastmap_accurate && bufp->fastmap) + { + printf ("fastmap: "); + print_fastmap (bufp->fastmap); + } + + printf ("re_nsub: %d\t", bufp->re_nsub); + printf ("regs_alloc: %d\t", bufp->regs_allocated); + printf ("can_be_null: %d\t", bufp->can_be_null); + printf ("newline_anchor: %d\n", bufp->newline_anchor); + printf ("no_sub: %d\t", bufp->no_sub); + printf ("not_bol: %d\t", bufp->not_bol); + printf ("not_eol: %d\t", bufp->not_eol); + printf ("syntax: %d\n", bufp->syntax); + /* Perhaps we should print the translate table? */ +} + + +void +print_double_string (where, string1, size1, string2, size2) + const char *where; + const char *string1; + const char *string2; + int size1; + int size2; +{ + unsigned this_char; + + if (where == NULL) + printf ("(null)"); + else + { + if (FIRST_STRING_P (where)) + { + for (this_char = where - string1; this_char < size1; this_char++) + printchar (string1[this_char]); + + where = string2; + } + + for (this_char = where - string2; this_char < size2; this_char++) + printchar (string2[this_char]); + } +} + +#else /* not DEBUG */ + +#undef assert +#define assert(e) + +#define DEBUG_STATEMENT(e) +#define DEBUG_PRINT1(x) +#define DEBUG_PRINT2(x1, x2) +#define DEBUG_PRINT3(x1, x2, x3) +#define DEBUG_PRINT4(x1, x2, x3, x4) +#define DEBUG_PRINT_COMPILED_PATTERN(p, s, e) +#define DEBUG_PRINT_DOUBLE_STRING(w, s1, sz1, s2, sz2) + +#endif /* not DEBUG */ + +/* Set by `re_set_syntax' to the current regexp syntax to recognize. Can + also be assigned to arbitrarily: each pattern buffer stores its own + syntax, so it can be changed between regex compilations. */ +reg_syntax_t re_syntax_options = RE_SYNTAX_EMACS; + + +/* Specify the precise syntax of regexps for compilation. This provides + for compatibility for various utilities which historically have + different, incompatible syntaxes. + + The argument SYNTAX is a bit mask comprised of the various bits + defined in regex.h. We return the old syntax. */ + +reg_syntax_t +re_set_syntax (syntax) + reg_syntax_t syntax; +{ + reg_syntax_t ret = re_syntax_options; + + re_syntax_options = syntax; + return ret; +} + +/* This table gives an error message for each of the error codes listed + in regex.h. Obviously the order here has to be same as there. */ + +static const char *re_error_msg[] = + { NULL, /* REG_NOERROR */ + "No match", /* REG_NOMATCH */ + "Invalid regular expression", /* REG_BADPAT */ + "Invalid collation character", /* REG_ECOLLATE */ + "Invalid character class name", /* REG_ECTYPE */ + "Trailing backslash", /* REG_EESCAPE */ + "Invalid back reference", /* REG_ESUBREG */ + "Unmatched [ or [^", /* REG_EBRACK */ + "Unmatched ( or \\(", /* REG_EPAREN */ + "Unmatched \\{", /* REG_EBRACE */ + "Invalid content of \\{\\}", /* REG_BADBR */ + "Invalid range end", /* REG_ERANGE */ + "Memory exhausted", /* REG_ESPACE */ + "Invalid preceding regular expression", /* REG_BADRPT */ + "Premature end of regular expression", /* REG_EEND */ + "Regular expression too big", /* REG_ESIZE */ + "Unmatched ) or \\)", /* REG_ERPAREN */ + }; + +/* Subroutine declarations and macros for regex_compile. */ + +static void store_op1 (), store_op2 (); +static void insert_op1 (), insert_op2 (); +static boolean at_begline_loc_p (), at_endline_loc_p (); +static boolean group_in_compile_stack (); +static reg_errcode_t compile_range (); + +/* Fetch the next character in the uncompiled pattern---translating it + if necessary. Also cast from a signed character in the constant + string passed to us by the user to an unsigned char that we can use + as an array index (in, e.g., `translate'). */ +#define PATFETCH(c) \ + do {if (p == pend) return REG_EEND; \ + c = (unsigned char) *p++; \ + if (translate) c = translate[c]; \ + } while (0) + +/* Fetch the next character in the uncompiled pattern, with no + translation. */ +#define PATFETCH_RAW(c) \ + do {if (p == pend) return REG_EEND; \ + c = (unsigned char) *p++; \ + } while (0) + +/* Go backwards one character in the pattern. */ +#define PATUNFETCH p-- + + +/* If `translate' is non-null, return translate[D], else just D. We + cast the subscript to translate because some data is declared as + `char *', to avoid warnings when a string constant is passed. But + when we use a character as a subscript we must make it unsigned. */ +#define TRANSLATE(d) (translate ? translate[(unsigned char) (d)] : (d)) + + +/* Macros for outputting the compiled pattern into `buffer'. */ + +/* If the buffer isn't allocated when it comes in, use this. */ +#define INIT_BUF_SIZE 32 + +/* Make sure we have at least N more bytes of space in buffer. */ +#define GET_BUFFER_SPACE(n) \ + while (b - bufp->buffer + (n) > bufp->allocated) \ + EXTEND_BUFFER () + +/* Make sure we have one more byte of buffer space and then add C to it. */ +#define BUF_PUSH(c) \ + do { \ + GET_BUFFER_SPACE (1); \ + *b++ = (unsigned char) (c); \ + } while (0) + + +/* Ensure we have two more bytes of buffer space and then append C1 and C2. */ +#define BUF_PUSH_2(c1, c2) \ + do { \ + GET_BUFFER_SPACE (2); \ + *b++ = (unsigned char) (c1); \ + *b++ = (unsigned char) (c2); \ + } while (0) + + +/* As with BUF_PUSH_2, except for three bytes. */ +#define BUF_PUSH_3(c1, c2, c3) \ + do { \ + GET_BUFFER_SPACE (3); \ + *b++ = (unsigned char) (c1); \ + *b++ = (unsigned char) (c2); \ + *b++ = (unsigned char) (c3); \ + } while (0) + + +/* Store a jump with opcode OP at LOC to location TO. We store a + relative address offset by the three bytes the jump itself occupies. */ +#define STORE_JUMP(op, loc, to) \ + store_op1 (op, loc, (to) - (loc) - 3) + +/* Likewise, for a two-argument jump. */ +#define STORE_JUMP2(op, loc, to, arg) \ + store_op2 (op, loc, (to) - (loc) - 3, arg) + +/* Like `STORE_JUMP', but for inserting. Assume `b' is the buffer end. */ +#define INSERT_JUMP(op, loc, to) \ + insert_op1 (op, loc, (to) - (loc) - 3, b) + +/* Like `STORE_JUMP2', but for inserting. Assume `b' is the buffer end. */ +#define INSERT_JUMP2(op, loc, to, arg) \ + insert_op2 (op, loc, (to) - (loc) - 3, arg, b) + + +/* This is not an arbitrary limit: the arguments which represent offsets + into the pattern are two bytes long. So if 2^16 bytes turns out to + be too small, many things would have to change. */ +#define MAX_BUF_SIZE (1L << 16) + + +/* Extend the buffer by twice its current size via realloc and + reset the pointers that pointed into the old block to point to the + correct places in the new one. If extending the buffer results in it + being larger than MAX_BUF_SIZE, then flag memory exhausted. */ +#define EXTEND_BUFFER() \ + do { \ + unsigned char *old_buffer = bufp->buffer; \ + if (bufp->allocated == MAX_BUF_SIZE) \ + return REG_ESIZE; \ + bufp->allocated <<= 1; \ + if (bufp->allocated > MAX_BUF_SIZE) \ + bufp->allocated = MAX_BUF_SIZE; \ + bufp->buffer = (unsigned char *) realloc (bufp->buffer, bufp->allocated);\ + if (bufp->buffer == NULL) \ + return REG_ESPACE; \ + /* If the buffer moved, move all the pointers into it. */ \ + if (old_buffer != bufp->buffer) \ + { \ + b = (b - old_buffer) + bufp->buffer; \ + begalt = (begalt - old_buffer) + bufp->buffer; \ + if (fixup_alt_jump) \ + fixup_alt_jump = (fixup_alt_jump - old_buffer) + bufp->buffer;\ + if (laststart) \ + laststart = (laststart - old_buffer) + bufp->buffer; \ + if (pending_exact) \ + pending_exact = (pending_exact - old_buffer) + bufp->buffer; \ + } \ + } while (0) + + +/* Since we have one byte reserved for the register number argument to + {start,stop}_memory, the maximum number of groups we can report + things about is what fits in that byte. */ +#define MAX_REGNUM 255 + +/* But patterns can have more than `MAX_REGNUM' registers. We just + ignore the excess. */ +typedef unsigned regnum_t; + + +/* Macros for the compile stack. */ + +/* Since offsets can go either forwards or backwards, this type needs to + be able to hold values from -(MAX_BUF_SIZE - 1) to MAX_BUF_SIZE - 1. */ +typedef int pattern_offset_t; + +typedef struct +{ + pattern_offset_t begalt_offset; + pattern_offset_t fixup_alt_jump; + pattern_offset_t inner_group_offset; + pattern_offset_t laststart_offset; + regnum_t regnum; +} compile_stack_elt_t; + + +typedef struct +{ + compile_stack_elt_t *stack; + unsigned size; + unsigned avail; /* Offset of next open position. */ +} compile_stack_type; + + +#define INIT_COMPILE_STACK_SIZE 32 + +#define COMPILE_STACK_EMPTY (compile_stack.avail == 0) +#define COMPILE_STACK_FULL (compile_stack.avail == compile_stack.size) + +/* The next available element. */ +#define COMPILE_STACK_TOP (compile_stack.stack[compile_stack.avail]) + + +/* Set the bit for character C in a list. */ +#define SET_LIST_BIT(c) \ + (b[((unsigned char) (c)) / BYTEWIDTH] \ + |= 1 << (((unsigned char) c) % BYTEWIDTH)) + + +/* Get the next unsigned number in the uncompiled pattern. */ +#define GET_UNSIGNED_NUMBER(num) \ + { if (p != pend) \ + { \ + PATFETCH (c); \ + while (ISDIGIT (c)) \ + { \ + if (num < 0) \ + num = 0; \ + num = num * 10 + c - '0'; \ + if (p == pend) \ + break; \ + PATFETCH (c); \ + } \ + } \ + } + +#define CHAR_CLASS_MAX_LENGTH 6 /* Namely, `xdigit'. */ + +#define IS_CHAR_CLASS(string) \ + (STREQ (string, "alpha") || STREQ (string, "upper") \ + || STREQ (string, "lower") || STREQ (string, "digit") \ + || STREQ (string, "alnum") || STREQ (string, "xdigit") \ + || STREQ (string, "space") || STREQ (string, "print") \ + || STREQ (string, "punct") || STREQ (string, "graph") \ + || STREQ (string, "cntrl") || STREQ (string, "blank")) + +/* `regex_compile' compiles PATTERN (of length SIZE) according to SYNTAX. + Returns one of error codes defined in `regex.h', or zero for success. + + Assumes the `allocated' (and perhaps `buffer') and `translate' + fields are set in BUFP on entry. + + If it succeeds, results are put in BUFP (if it returns an error, the + contents of BUFP are undefined): + `buffer' is the compiled pattern; + `syntax' is set to SYNTAX; + `used' is set to the length of the compiled pattern; + `fastmap_accurate' is zero; + `re_nsub' is the number of subexpressions in PATTERN; + `not_bol' and `not_eol' are zero; + + The `fastmap' and `newline_anchor' fields are neither + examined nor set. */ + +static reg_errcode_t +regex_compile (pattern, size, syntax, bufp) + const char *pattern; + int size; + reg_syntax_t syntax; + struct re_pattern_buffer *bufp; +{ + /* We fetch characters from PATTERN here. Even though PATTERN is + `char *' (i.e., signed), we declare these variables as unsigned, so + they can be reliably used as array indices. */ + register unsigned char c, c1; + + /* A random tempory spot in PATTERN. */ + const char *p1; + + /* Points to the end of the buffer, where we should append. */ + register unsigned char *b; + + /* Keeps track of unclosed groups. */ + compile_stack_type compile_stack; + + /* Points to the current (ending) position in the pattern. */ + const char *p = pattern; + const char *pend = pattern + size; + + /* How to translate the characters in the pattern. */ + char *translate = bufp->translate; + + /* Address of the count-byte of the most recently inserted `exactn' + command. This makes it possible to tell if a new exact-match + character can be added to that command or if the character requires + a new `exactn' command. */ + unsigned char *pending_exact = 0; + + /* Address of start of the most recently finished expression. + This tells, e.g., postfix * where to find the start of its + operand. Reset at the beginning of groups and alternatives. */ + unsigned char *laststart = 0; + + /* Address of beginning of regexp, or inside of last group. */ + unsigned char *begalt; + + /* Place in the uncompiled pattern (i.e., the {) to + which to go back if the interval is invalid. */ + const char *beg_interval; + + /* Address of the place where a forward jump should go to the end of + the containing expression. Each alternative of an `or' -- except the + last -- ends with a forward jump of this sort. */ + unsigned char *fixup_alt_jump = 0; + + /* Counts open-groups as they are encountered. Remembered for the + matching close-group on the compile stack, so the same register + number is put in the stop_memory as the start_memory. */ + regnum_t regnum = 0; + +#ifdef DEBUG + DEBUG_PRINT1 ("\nCompiling pattern: "); + if (debug) + { + unsigned debug_count; + + for (debug_count = 0; debug_count < size; debug_count++) + printchar (pattern[debug_count]); + putchar ('\n'); + } +#endif /* DEBUG */ + + /* Initialize the compile stack. */ + compile_stack.stack = TALLOC (INIT_COMPILE_STACK_SIZE, compile_stack_elt_t); + if (compile_stack.stack == NULL) + return REG_ESPACE; + + compile_stack.size = INIT_COMPILE_STACK_SIZE; + compile_stack.avail = 0; + + /* Initialize the pattern buffer. */ + bufp->syntax = syntax; + bufp->fastmap_accurate = 0; + bufp->not_bol = bufp->not_eol = 0; + + /* Set `used' to zero, so that if we return an error, the pattern + printer (for debugging) will think there's no pattern. We reset it + at the end. */ + bufp->used = 0; + + /* Always count groups, whether or not bufp->no_sub is set. */ + bufp->re_nsub = 0; + +#if !defined (emacs) && !defined (SYNTAX_TABLE) + /* Initialize the syntax table. */ + init_syntax_once (); +#endif + + if (bufp->allocated == 0) + { + if (bufp->buffer) + { /* If zero allocated, but buffer is non-null, try to realloc + enough space. This loses if buffer's address is bogus, but + that is the user's responsibility. */ + RETALLOC (bufp->buffer, INIT_BUF_SIZE, unsigned char); + } + else + { /* Caller did not allocate a buffer. Do it for them. */ + bufp->buffer = TALLOC (INIT_BUF_SIZE, unsigned char); + } + if (!bufp->buffer) return REG_ESPACE; + + bufp->allocated = INIT_BUF_SIZE; + } + + begalt = b = bufp->buffer; + + /* Loop through the uncompiled pattern until we're at the end. */ + while (p != pend) + { + PATFETCH (c); + + switch (c) + { + case '^': + { + if ( /* If at start of pattern, it's an operator. */ + p == pattern + 1 + /* If context independent, it's an operator. */ + || syntax & RE_CONTEXT_INDEP_ANCHORS + /* Otherwise, depends on what's come before. */ + || at_begline_loc_p (pattern, p, syntax)) + BUF_PUSH (begline); + else + goto normal_char; + } + break; + + + case '$': + { + if ( /* If at end of pattern, it's an operator. */ + p == pend + /* If context independent, it's an operator. */ + || syntax & RE_CONTEXT_INDEP_ANCHORS + /* Otherwise, depends on what's next. */ + || at_endline_loc_p (p, pend, syntax)) + BUF_PUSH (endline); + else + goto normal_char; + } + break; + + + case '+': + case '?': + if ((syntax & RE_BK_PLUS_QM) + || (syntax & RE_LIMITED_OPS)) + goto normal_char; + handle_plus: + case '*': + /* If there is no previous pattern... */ + if (!laststart) + { + if (syntax & RE_CONTEXT_INVALID_OPS) + return REG_BADRPT; + else if (!(syntax & RE_CONTEXT_INDEP_OPS)) + goto normal_char; + } + + { + /* Are we optimizing this jump? */ + boolean keep_string_p = false; + + /* 1 means zero (many) matches is allowed. */ + char zero_times_ok = 0, many_times_ok = 0; + + /* If there is a sequence of repetition chars, collapse it + down to just one (the right one). We can't combine + interval operators with these because of, e.g., `a{2}*', + which should only match an even number of `a's. */ + + for (;;) + { + zero_times_ok |= c != '+'; + many_times_ok |= c != '?'; + + if (p == pend) + break; + + PATFETCH (c); + + if (c == '*' + || (!(syntax & RE_BK_PLUS_QM) && (c == '+' || c == '?'))) + ; + + else if (syntax & RE_BK_PLUS_QM && c == '\\') + { + if (p == pend) return REG_EESCAPE; + + PATFETCH (c1); + if (!(c1 == '+' || c1 == '?')) + { + PATUNFETCH; + PATUNFETCH; + break; + } + + c = c1; + } + else + { + PATUNFETCH; + break; + } + + /* If we get here, we found another repeat character. */ + } + + /* Star, etc. applied to an empty pattern is equivalent + to an empty pattern. */ + if (!laststart) + break; + + /* Now we know whether or not zero matches is allowed + and also whether or not two or more matches is allowed. */ + if (many_times_ok) + { /* More than one repetition is allowed, so put in at the + end a backward relative jump from `b' to before the next + jump we're going to put in below (which jumps from + laststart to after this jump). + + But if we are at the `*' in the exact sequence `.*\n', + insert an unconditional jump backwards to the ., + instead of the beginning of the loop. This way we only + push a failure point once, instead of every time + through the loop. */ + assert (p - 1 > pattern); + + /* Allocate the space for the jump. */ + GET_BUFFER_SPACE (3); + + /* We know we are not at the first character of the pattern, + because laststart was nonzero. And we've already + incremented `p', by the way, to be the character after + the `*'. Do we have to do something analogous here + for null bytes, because of RE_DOT_NOT_NULL? */ + if (TRANSLATE (*(p - 2)) == TRANSLATE ('.') + && zero_times_ok + && p < pend && TRANSLATE (*p) == TRANSLATE ('\n') + && !(syntax & RE_DOT_NEWLINE)) + { /* We have .*\n. */ + STORE_JUMP (jump, b, laststart); + keep_string_p = true; + } + else + /* Anything else. */ + STORE_JUMP (maybe_pop_jump, b, laststart - 3); + + /* We've added more stuff to the buffer. */ + b += 3; + } + + /* On failure, jump from laststart to b + 3, which will be the + end of the buffer after this jump is inserted. */ + GET_BUFFER_SPACE (3); + INSERT_JUMP (keep_string_p ? on_failure_keep_string_jump + : on_failure_jump, + laststart, b + 3); + pending_exact = 0; + b += 3; + + if (!zero_times_ok) + { + /* At least one repetition is required, so insert a + `dummy_failure_jump' before the initial + `on_failure_jump' instruction of the loop. This + effects a skip over that instruction the first time + we hit that loop. */ + GET_BUFFER_SPACE (3); + INSERT_JUMP (dummy_failure_jump, laststart, laststart + 6); + b += 3; + } + } + break; + + + case '.': + laststart = b; + BUF_PUSH (anychar); + break; + + + case '[': + { + boolean had_char_class = false; + + if (p == pend) return REG_EBRACK; + + /* Ensure that we have enough space to push a charset: the + opcode, the length count, and the bitset; 34 bytes in all. */ + GET_BUFFER_SPACE (34); + + laststart = b; + + /* We test `*p == '^' twice, instead of using an if + statement, so we only need one BUF_PUSH. */ + BUF_PUSH (*p == '^' ? charset_not : charset); + if (*p == '^') + p++; + + /* Remember the first position in the bracket expression. */ + p1 = p; + + /* Push the number of bytes in the bitmap. */ + BUF_PUSH ((1 << BYTEWIDTH) / BYTEWIDTH); + + /* Clear the whole map. */ + bzero (b, (1 << BYTEWIDTH) / BYTEWIDTH); + + /* charset_not matches newline according to a syntax bit. */ + if ((re_opcode_t) b[-2] == charset_not + && (syntax & RE_HAT_LISTS_NOT_NEWLINE)) + SET_LIST_BIT ('\n'); + + /* Read in characters and ranges, setting map bits. */ + for (;;) + { + if (p == pend) return REG_EBRACK; + + PATFETCH (c); + + /* \ might escape characters inside [...] and [^...]. */ + if ((syntax & RE_BACKSLASH_ESCAPE_IN_LISTS) && c == '\\') + { + if (p == pend) return REG_EESCAPE; + + PATFETCH (c1); + SET_LIST_BIT (c1); + continue; + } + + /* Could be the end of the bracket expression. If it's + not (i.e., when the bracket expression is `[]' so + far), the ']' character bit gets set way below. */ + if (c == ']' && p != p1 + 1) + break; + + /* Look ahead to see if it's a range when the last thing + was a character class. */ + if (had_char_class && c == '-' && *p != ']') + return REG_ERANGE; + + /* Look ahead to see if it's a range when the last thing + was a character: if this is a hyphen not at the + beginning or the end of a list, then it's the range + operator. */ + if (c == '-' + && !(p - 2 >= pattern && p[-2] == '[') + && !(p - 3 >= pattern && p[-3] == '[' && p[-2] == '^') + && *p != ']') + { + reg_errcode_t ret + = compile_range (&p, pend, translate, syntax, b); + if (ret != REG_NOERROR) return ret; + } + + else if (p[0] == '-' && p[1] != ']') + { /* This handles ranges made up of characters only. */ + reg_errcode_t ret; + + /* Move past the `-'. */ + PATFETCH (c1); + + ret = compile_range (&p, pend, translate, syntax, b); + if (ret != REG_NOERROR) return ret; + } + + /* See if we're at the beginning of a possible character + class. */ + + else if (syntax & RE_CHAR_CLASSES && c == '[' && *p == ':') + { /* Leave room for the null. */ + char str[CHAR_CLASS_MAX_LENGTH + 1]; + + PATFETCH (c); + c1 = 0; + + /* If pattern is `[[:'. */ + if (p == pend) return REG_EBRACK; + + for (;;) + { + PATFETCH (c); + if (c == ':' || c == ']' || p == pend + || c1 == CHAR_CLASS_MAX_LENGTH) + break; + str[c1++] = c; + } + str[c1] = '\0'; + + /* If isn't a word bracketed by `[:' and:`]': + undo the ending character, the letters, and leave + the leading `:' and `[' (but set bits for them). */ + if (c == ':' && *p == ']') + { + int ch; + boolean is_alnum = STREQ (str, "alnum"); + boolean is_alpha = STREQ (str, "alpha"); + boolean is_blank = STREQ (str, "blank"); + boolean is_cntrl = STREQ (str, "cntrl"); + boolean is_digit = STREQ (str, "digit"); + boolean is_graph = STREQ (str, "graph"); + boolean is_lower = STREQ (str, "lower"); + boolean is_print = STREQ (str, "print"); + boolean is_punct = STREQ (str, "punct"); + boolean is_space = STREQ (str, "space"); + boolean is_upper = STREQ (str, "upper"); + boolean is_xdigit = STREQ (str, "xdigit"); + + if (!IS_CHAR_CLASS (str)) return REG_ECTYPE; + + /* Throw away the ] at the end of the character + class. */ + PATFETCH (c); + + if (p == pend) return REG_EBRACK; + + for (ch = 0; ch < 1 << BYTEWIDTH; ch++) + { + if ( (is_alnum && ISALNUM (ch)) + || (is_alpha && ISALPHA (ch)) + || (is_blank && ISBLANK (ch)) + || (is_cntrl && ISCNTRL (ch)) + || (is_digit && ISDIGIT (ch)) + || (is_graph && ISGRAPH (ch)) + || (is_lower && ISLOWER (ch)) + || (is_print && ISPRINT (ch)) + || (is_punct && ISPUNCT (ch)) + || (is_space && ISSPACE (ch)) + || (is_upper && ISUPPER (ch)) + || (is_xdigit && ISXDIGIT (ch))) + SET_LIST_BIT (ch); + } + had_char_class = true; + } + else + { + c1++; + while (c1--) + PATUNFETCH; + SET_LIST_BIT ('['); + SET_LIST_BIT (':'); + had_char_class = false; + } + } + else + { + had_char_class = false; + SET_LIST_BIT (c); + } + } + + /* Discard any (non)matching list bytes that are all 0 at the + end of the map. Decrease the map-length byte too. */ + while ((int) b[-1] > 0 && b[b[-1] - 1] == 0) + b[-1]--; + b += b[-1]; + } + break; + + + case '(': + if (syntax & RE_NO_BK_PARENS) + goto handle_open; + else + goto normal_char; + + + case ')': + if (syntax & RE_NO_BK_PARENS) + goto handle_close; + else + goto normal_char; + + + case '\n': + if (syntax & RE_NEWLINE_ALT) + goto handle_alt; + else + goto normal_char; + + + case '|': + if (syntax & RE_NO_BK_VBAR) + goto handle_alt; + else + goto normal_char; + + + case '{': + if (syntax & RE_INTERVALS && syntax & RE_NO_BK_BRACES) + goto handle_interval; + else + goto normal_char; + + + case '\\': + if (p == pend) return REG_EESCAPE; + + /* Do not translate the character after the \, so that we can + distinguish, e.g., \B from \b, even if we normally would + translate, e.g., B to b. */ + PATFETCH_RAW (c); + + switch (c) + { + case '(': + if (syntax & RE_NO_BK_PARENS) + goto normal_backslash; + + handle_open: + bufp->re_nsub++; + regnum++; + + if (COMPILE_STACK_FULL) + { + RETALLOC (compile_stack.stack, compile_stack.size << 1, + compile_stack_elt_t); + if (compile_stack.stack == NULL) return REG_ESPACE; + + compile_stack.size <<= 1; + } + + /* These are the values to restore when we hit end of this + group. They are all relative offsets, so that if the + whole pattern moves because of realloc, they will still + be valid. */ + COMPILE_STACK_TOP.begalt_offset = begalt - bufp->buffer; + COMPILE_STACK_TOP.fixup_alt_jump + = fixup_alt_jump ? fixup_alt_jump - bufp->buffer + 1 : 0; + COMPILE_STACK_TOP.laststart_offset = b - bufp->buffer; + COMPILE_STACK_TOP.regnum = regnum; + + /* We will eventually replace the 0 with the number of + groups inner to this one. But do not push a + start_memory for groups beyond the last one we can + represent in the compiled pattern. */ + if (regnum <= MAX_REGNUM) + { + COMPILE_STACK_TOP.inner_group_offset = b - bufp->buffer + 2; + BUF_PUSH_3 (start_memory, regnum, 0); + } + + compile_stack.avail++; + + fixup_alt_jump = 0; + laststart = 0; + begalt = b; + /* If we've reached MAX_REGNUM groups, then this open + won't actually generate any code, so we'll have to + clear pending_exact explicitly. */ + pending_exact = 0; + break; + + + case ')': + if (syntax & RE_NO_BK_PARENS) goto normal_backslash; + + if (COMPILE_STACK_EMPTY) + if (syntax & RE_UNMATCHED_RIGHT_PAREN_ORD) + goto normal_backslash; + else + return REG_ERPAREN; + + handle_close: + if (fixup_alt_jump) + { /* Push a dummy failure point at the end of the + alternative for a possible future + `pop_failure_jump' to pop. See comments at + `push_dummy_failure' in `re_match_2'. */ + BUF_PUSH (push_dummy_failure); + + /* We allocated space for this jump when we assigned + to `fixup_alt_jump', in the `handle_alt' case below. */ + STORE_JUMP (jump_past_alt, fixup_alt_jump, b - 1); + } + + /* See similar code for backslashed left paren above. */ + if (COMPILE_STACK_EMPTY) + if (syntax & RE_UNMATCHED_RIGHT_PAREN_ORD) + goto normal_char; + else + return REG_ERPAREN; + + /* Since we just checked for an empty stack above, this + ``can't happen''. */ + assert (compile_stack.avail != 0); + { + /* We don't just want to restore into `regnum', because + later groups should continue to be numbered higher, + as in `(ab)c(de)' -- the second group is #2. */ + regnum_t this_group_regnum; + + compile_stack.avail--; + begalt = bufp->buffer + COMPILE_STACK_TOP.begalt_offset; + fixup_alt_jump + = COMPILE_STACK_TOP.fixup_alt_jump + ? bufp->buffer + COMPILE_STACK_TOP.fixup_alt_jump - 1 + : 0; + laststart = bufp->buffer + COMPILE_STACK_TOP.laststart_offset; + this_group_regnum = COMPILE_STACK_TOP.regnum; + /* If we've reached MAX_REGNUM groups, then this open + won't actually generate any code, so we'll have to + clear pending_exact explicitly. */ + pending_exact = 0; + + /* We're at the end of the group, so now we know how many + groups were inside this one. */ + if (this_group_regnum <= MAX_REGNUM) + { + unsigned char *inner_group_loc + = bufp->buffer + COMPILE_STACK_TOP.inner_group_offset; + + *inner_group_loc = regnum - this_group_regnum; + BUF_PUSH_3 (stop_memory, this_group_regnum, + regnum - this_group_regnum); + } + } + break; + + + case '|': /* `\|'. */ + if (syntax & RE_LIMITED_OPS || syntax & RE_NO_BK_VBAR) + goto normal_backslash; + handle_alt: + if (syntax & RE_LIMITED_OPS) + goto normal_char; + + /* Insert before the previous alternative a jump which + jumps to this alternative if the former fails. */ + GET_BUFFER_SPACE (3); + INSERT_JUMP (on_failure_jump, begalt, b + 6); + pending_exact = 0; + b += 3; + + /* The alternative before this one has a jump after it + which gets executed if it gets matched. Adjust that + jump so it will jump to this alternative's analogous + jump (put in below, which in turn will jump to the next + (if any) alternative's such jump, etc.). The last such + jump jumps to the correct final destination. A picture: + _____ _____ + | | | | + | v | v + a | b | c + + If we are at `b', then fixup_alt_jump right now points to a + three-byte space after `a'. We'll put in the jump, set + fixup_alt_jump to right after `b', and leave behind three + bytes which we'll fill in when we get to after `c'. */ + + if (fixup_alt_jump) + STORE_JUMP (jump_past_alt, fixup_alt_jump, b); + + /* Mark and leave space for a jump after this alternative, + to be filled in later either by next alternative or + when know we're at the end of a series of alternatives. */ + fixup_alt_jump = b; + GET_BUFFER_SPACE (3); + b += 3; + + laststart = 0; + begalt = b; + break; + + + case '{': + /* If \{ is a literal. */ + if (!(syntax & RE_INTERVALS) + /* If we're at `\{' and it's not the open-interval + operator. */ + || ((syntax & RE_INTERVALS) && (syntax & RE_NO_BK_BRACES)) + || (p - 2 == pattern && p == pend)) + goto normal_backslash; + + handle_interval: + { + /* If got here, then the syntax allows intervals. */ + + /* At least (most) this many matches must be made. */ + int lower_bound = -1, upper_bound = -1; + + beg_interval = p - 1; + + if (p == pend) + { + if (syntax & RE_NO_BK_BRACES) + goto unfetch_interval; + else + return REG_EBRACE; + } + + GET_UNSIGNED_NUMBER (lower_bound); + + if (c == ',') + { + GET_UNSIGNED_NUMBER (upper_bound); + if (upper_bound < 0) upper_bound = RE_DUP_MAX; + } + else + /* Interval such as `{1}' => match exactly once. */ + upper_bound = lower_bound; + + if (lower_bound < 0 || upper_bound > RE_DUP_MAX + || lower_bound > upper_bound) + { + if (syntax & RE_NO_BK_BRACES) + goto unfetch_interval; + else + return REG_BADBR; + } + + if (!(syntax & RE_NO_BK_BRACES)) + { + if (c != '\\') return REG_EBRACE; + + PATFETCH (c); + } + + if (c != '}') + { + if (syntax & RE_NO_BK_BRACES) + goto unfetch_interval; + else + return REG_BADBR; + } + + /* We just parsed a valid interval. */ + + /* If it's invalid to have no preceding re. */ + if (!laststart) + { + if (syntax & RE_CONTEXT_INVALID_OPS) + return REG_BADRPT; + else if (syntax & RE_CONTEXT_INDEP_OPS) + laststart = b; + else + goto unfetch_interval; + } + + /* If the upper bound is zero, don't want to succeed at + all; jump from `laststart' to `b + 3', which will be + the end of the buffer after we insert the jump. */ + if (upper_bound == 0) + { + GET_BUFFER_SPACE (3); + INSERT_JUMP (jump, laststart, b + 3); + b += 3; + } + + /* Otherwise, we have a nontrivial interval. When + we're all done, the pattern will look like: + set_number_at <jump count> <upper bound> + set_number_at <succeed_n count> <lower bound> + succeed_n <after jump addr> <succed_n count> + <body of loop> + jump_n <succeed_n addr> <jump count> + (The upper bound and `jump_n' are omitted if + `upper_bound' is 1, though.) */ + else + { /* If the upper bound is > 1, we need to insert + more at the end of the loop. */ + unsigned nbytes = 10 + (upper_bound > 1) * 10; + + GET_BUFFER_SPACE (nbytes); + + /* Initialize lower bound of the `succeed_n', even + though it will be set during matching by its + attendant `set_number_at' (inserted next), + because `re_compile_fastmap' needs to know. + Jump to the `jump_n' we might insert below. */ + INSERT_JUMP2 (succeed_n, laststart, + b + 5 + (upper_bound > 1) * 5, + lower_bound); + b += 5; + + /* Code to initialize the lower bound. Insert + before the `succeed_n'. The `5' is the last two + bytes of this `set_number_at', plus 3 bytes of + the following `succeed_n'. */ + insert_op2 (set_number_at, laststart, 5, lower_bound, b); + b += 5; + + if (upper_bound > 1) + { /* More than one repetition is allowed, so + append a backward jump to the `succeed_n' + that starts this interval. + + When we've reached this during matching, + we'll have matched the interval once, so + jump back only `upper_bound - 1' times. */ + STORE_JUMP2 (jump_n, b, laststart + 5, + upper_bound - 1); + b += 5; + + /* The location we want to set is the second + parameter of the `jump_n'; that is `b-2' as + an absolute address. `laststart' will be + the `set_number_at' we're about to insert; + `laststart+3' the number to set, the source + for the relative address. But we are + inserting into the middle of the pattern -- + so everything is getting moved up by 5. + Conclusion: (b - 2) - (laststart + 3) + 5, + i.e., b - laststart. + + We insert this at the beginning of the loop + so that if we fail during matching, we'll + reinitialize the bounds. */ + insert_op2 (set_number_at, laststart, b - laststart, + upper_bound - 1, b); + b += 5; + } + } + pending_exact = 0; + beg_interval = NULL; + } + break; + + unfetch_interval: + /* If an invalid interval, match the characters as literals. */ + assert (beg_interval); + p = beg_interval; + beg_interval = NULL; + + /* normal_char and normal_backslash need `c'. */ + PATFETCH (c); + + if (!(syntax & RE_NO_BK_BRACES)) + { + if (p > pattern && p[-1] == '\\') + goto normal_backslash; + } + goto normal_char; + +#ifdef emacs + /* There is no way to specify the before_dot and after_dot + operators. rms says this is ok. --karl */ + case '=': + BUF_PUSH (at_dot); + break; + + case 's': + laststart = b; + PATFETCH (c); + BUF_PUSH_2 (syntaxspec, syntax_spec_code[c]); + break; + + case 'S': + laststart = b; + PATFETCH (c); + BUF_PUSH_2 (notsyntaxspec, syntax_spec_code[c]); + break; +#endif /* emacs */ + + + case 'w': + laststart = b; + BUF_PUSH (wordchar); + break; + + + case 'W': + laststart = b; + BUF_PUSH (notwordchar); + break; + + + case '<': + BUF_PUSH (wordbeg); + break; + + case '>': + BUF_PUSH (wordend); + break; + + case 'b': + BUF_PUSH (wordbound); + break; + + case 'B': + BUF_PUSH (notwordbound); + break; + + case '`': + BUF_PUSH (begbuf); + break; + + case '\'': + BUF_PUSH (endbuf); + break; + + case '1': case '2': case '3': case '4': case '5': + case '6': case '7': case '8': case '9': + if (syntax & RE_NO_BK_REFS) + goto normal_char; + + c1 = c - '0'; + + if (c1 > regnum) + return REG_ESUBREG; + + /* Can't back reference to a subexpression if inside of it. */ + if (group_in_compile_stack (compile_stack, c1)) + goto normal_char; + + laststart = b; + BUF_PUSH_2 (duplicate, c1); + break; + + + case '+': + case '?': + if (syntax & RE_BK_PLUS_QM) + goto handle_plus; + else + goto normal_backslash; + + default: + normal_backslash: + /* You might think it would be useful for \ to mean + not to translate; but if we don't translate it + it will never match anything. */ + c = TRANSLATE (c); + goto normal_char; + } + break; + + + default: + /* Expects the character in `c'. */ + normal_char: + /* If no exactn currently being built. */ + if (!pending_exact + + /* If last exactn not at current position. */ + || pending_exact + *pending_exact + 1 != b + + /* We have only one byte following the exactn for the count. */ + || *pending_exact == (1 << BYTEWIDTH) - 1 + + /* If followed by a repetition operator. */ + || *p == '*' || *p == '^' + || ((syntax & RE_BK_PLUS_QM) + ? *p == '\\' && (p[1] == '+' || p[1] == '?') + : (*p == '+' || *p == '?')) + || ((syntax & RE_INTERVALS) + && ((syntax & RE_NO_BK_BRACES) + ? *p == '{' + : (p[0] == '\\' && p[1] == '{')))) + { + /* Start building a new exactn. */ + + laststart = b; + + BUF_PUSH_2 (exactn, 0); + pending_exact = b - 1; + } + + BUF_PUSH (c); + (*pending_exact)++; + break; + } /* switch (c) */ + } /* while p != pend */ + + + /* Through the pattern now. */ + + if (fixup_alt_jump) + STORE_JUMP (jump_past_alt, fixup_alt_jump, b); + + if (!COMPILE_STACK_EMPTY) + return REG_EPAREN; + + free (compile_stack.stack); + + /* We have succeeded; set the length of the buffer. */ + bufp->used = b - bufp->buffer; + +#ifdef DEBUG + if (debug) + { + DEBUG_PRINT1 ("\nCompiled pattern: "); + print_compiled_pattern (bufp); + } +#endif /* DEBUG */ + + return REG_NOERROR; +} /* regex_compile */ + +/* Subroutines for `regex_compile'. */ + +/* Store OP at LOC followed by two-byte integer parameter ARG. */ + +static void +store_op1 (op, loc, arg) + re_opcode_t op; + unsigned char *loc; + int arg; +{ + *loc = (unsigned char) op; + STORE_NUMBER (loc + 1, arg); +} + + +/* Like `store_op1', but for two two-byte parameters ARG1 and ARG2. */ + +static void +store_op2 (op, loc, arg1, arg2) + re_opcode_t op; + unsigned char *loc; + int arg1, arg2; +{ + *loc = (unsigned char) op; + STORE_NUMBER (loc + 1, arg1); + STORE_NUMBER (loc + 3, arg2); +} + + +/* Copy the bytes from LOC to END to open up three bytes of space at LOC + for OP followed by two-byte integer parameter ARG. */ + +static void +insert_op1 (op, loc, arg, end) + re_opcode_t op; + unsigned char *loc; + int arg; + unsigned char *end; +{ + register unsigned char *pfrom = end; + register unsigned char *pto = end + 3; + + while (pfrom != loc) + *--pto = *--pfrom; + + store_op1 (op, loc, arg); +} + + +/* Like `insert_op1', but for two two-byte parameters ARG1 and ARG2. */ + +static void +insert_op2 (op, loc, arg1, arg2, end) + re_opcode_t op; + unsigned char *loc; + int arg1, arg2; + unsigned char *end; +{ + register unsigned char *pfrom = end; + register unsigned char *pto = end + 5; + + while (pfrom != loc) + *--pto = *--pfrom; + + store_op2 (op, loc, arg1, arg2); +} + + +/* P points to just after a ^ in PATTERN. Return true if that ^ comes + after an alternative or a begin-subexpression. We assume there is at + least one character before the ^. */ + +static boolean +at_begline_loc_p (pattern, p, syntax) + const char *pattern, *p; + reg_syntax_t syntax; +{ + const char *prev = p - 2; + boolean prev_prev_backslash = prev > pattern && prev[-1] == '\\'; + + return + /* After a subexpression? */ + (*prev == '(' && (syntax & RE_NO_BK_PARENS || prev_prev_backslash)) + /* After an alternative? */ + || (*prev == '|' && (syntax & RE_NO_BK_VBAR || prev_prev_backslash)); +} + + +/* The dual of at_begline_loc_p. This one is for $. We assume there is + at least one character after the $, i.e., `P < PEND'. */ + +static boolean +at_endline_loc_p (p, pend, syntax) + const char *p, *pend; + int syntax; +{ + const char *next = p; + boolean next_backslash = *next == '\\'; + const char *next_next = p + 1 < pend ? p + 1 : NULL; + + return + /* Before a subexpression? */ + (syntax & RE_NO_BK_PARENS ? *next == ')' + : next_backslash && next_next && *next_next == ')') + /* Before an alternative? */ + || (syntax & RE_NO_BK_VBAR ? *next == '|' + : next_backslash && next_next && *next_next == '|'); +} + + +/* Returns true if REGNUM is in one of COMPILE_STACK's elements and + false if it's not. */ + +static boolean +group_in_compile_stack (compile_stack, regnum) + compile_stack_type compile_stack; + regnum_t regnum; +{ + int this_element; + + for (this_element = compile_stack.avail - 1; + this_element >= 0; + this_element--) + if (compile_stack.stack[this_element].regnum == regnum) + return true; + + return false; +} + + +/* Read the ending character of a range (in a bracket expression) from the + uncompiled pattern *P_PTR (which ends at PEND). We assume the + starting character is in `P[-2]'. (`P[-1]' is the character `-'.) + Then we set the translation of all bits between the starting and + ending characters (inclusive) in the compiled pattern B. + + Return an error code. + + We use these short variable names so we can use the same macros as + `regex_compile' itself. */ + +static reg_errcode_t +compile_range (p_ptr, pend, translate, syntax, b) + const char **p_ptr, *pend; + char *translate; + reg_syntax_t syntax; + unsigned char *b; +{ + unsigned this_char; + + const char *p = *p_ptr; + int range_start, range_end; + + if (p == pend) + return REG_ERANGE; + + /* Even though the pattern is a signed `char *', we need to fetch + with unsigned char *'s; if the high bit of the pattern character + is set, the range endpoints will be negative if we fetch using a + signed char *. + + We also want to fetch the endpoints without translating them; the + appropriate translation is done in the bit-setting loop below. */ + range_start = ((unsigned char *) p)[-2]; + range_end = ((unsigned char *) p)[0]; + + /* Have to increment the pointer into the pattern string, so the + caller isn't still at the ending character. */ + (*p_ptr)++; + + /* If the start is after the end, the range is empty. */ + if (range_start > range_end) + return syntax & RE_NO_EMPTY_RANGES ? REG_ERANGE : REG_NOERROR; + + /* Here we see why `this_char' has to be larger than an `unsigned + char' -- the range is inclusive, so if `range_end' == 0xff + (assuming 8-bit characters), we would otherwise go into an infinite + loop, since all characters <= 0xff. */ + for (this_char = range_start; this_char <= range_end; this_char++) + { + SET_LIST_BIT (TRANSLATE (this_char)); + } + + return REG_NOERROR; +} + +/* Failure stack declarations and macros; both re_compile_fastmap and + re_match_2 use a failure stack. These have to be macros because of + REGEX_ALLOCATE. */ + + +/* Number of failure points for which to initially allocate space + when matching. If this number is exceeded, we allocate more + space, so it is not a hard limit. */ +#ifndef INIT_FAILURE_ALLOC +#define INIT_FAILURE_ALLOC 5 +#endif + +/* Roughly the maximum number of failure points on the stack. Would be + exactly that if always used MAX_FAILURE_SPACE each time we failed. + This is a variable only so users of regex can assign to it; we never + change it ourselves. */ +int re_max_failures = 2000; + +typedef const unsigned char *fail_stack_elt_t; + +typedef struct +{ + fail_stack_elt_t *stack; + unsigned size; + unsigned avail; /* Offset of next open position. */ +} fail_stack_type; + +#define FAIL_STACK_EMPTY() (fail_stack.avail == 0) +#define FAIL_STACK_PTR_EMPTY() (fail_stack_ptr->avail == 0) +#define FAIL_STACK_FULL() (fail_stack.avail == fail_stack.size) +#define FAIL_STACK_TOP() (fail_stack.stack[fail_stack.avail]) + + +/* Initialize `fail_stack'. Do `return -2' if the alloc fails. */ + +#define INIT_FAIL_STACK() \ + do { \ + fail_stack.stack = (fail_stack_elt_t *) \ + REGEX_ALLOCATE (INIT_FAILURE_ALLOC * sizeof (fail_stack_elt_t)); \ + \ + if (fail_stack.stack == NULL) \ + return -2; \ + \ + fail_stack.size = INIT_FAILURE_ALLOC; \ + fail_stack.avail = 0; \ + } while (0) + + +/* Double the size of FAIL_STACK, up to approximately `re_max_failures' items. + + Return 1 if succeeds, and 0 if either ran out of memory + allocating space for it or it was already too large. + + REGEX_REALLOCATE requires `destination' be declared. */ + +#define DOUBLE_FAIL_STACK(fail_stack) \ + ((fail_stack).size > re_max_failures * MAX_FAILURE_ITEMS \ + ? 0 \ + : ((fail_stack).stack = (fail_stack_elt_t *) \ + REGEX_REALLOCATE ((fail_stack).stack, \ + (fail_stack).size * sizeof (fail_stack_elt_t), \ + ((fail_stack).size << 1) * sizeof (fail_stack_elt_t)), \ + \ + (fail_stack).stack == NULL \ + ? 0 \ + : ((fail_stack).size <<= 1, \ + 1))) + + +/* Push PATTERN_OP on FAIL_STACK. + + Return 1 if was able to do so and 0 if ran out of memory allocating + space to do so. */ +#define PUSH_PATTERN_OP(pattern_op, fail_stack) \ + ((FAIL_STACK_FULL () \ + && !DOUBLE_FAIL_STACK (fail_stack)) \ + ? 0 \ + : ((fail_stack).stack[(fail_stack).avail++] = pattern_op, \ + 1)) + +/* This pushes an item onto the failure stack. Must be a four-byte + value. Assumes the variable `fail_stack'. Probably should only + be called from within `PUSH_FAILURE_POINT'. */ +#define PUSH_FAILURE_ITEM(item) \ + fail_stack.stack[fail_stack.avail++] = (fail_stack_elt_t) item + +/* The complement operation. Assumes `fail_stack' is nonempty. */ +#define POP_FAILURE_ITEM() fail_stack.stack[--fail_stack.avail] + +/* Used to omit pushing failure point id's when we're not debugging. */ +#ifdef DEBUG +#define DEBUG_PUSH PUSH_FAILURE_ITEM +#define DEBUG_POP(item_addr) *(item_addr) = POP_FAILURE_ITEM () +#else +#define DEBUG_PUSH(item) +#define DEBUG_POP(item_addr) +#endif + + +/* Push the information about the state we will need + if we ever fail back to it. + + Requires variables fail_stack, regstart, regend, reg_info, and + num_regs be declared. DOUBLE_FAIL_STACK requires `destination' be + declared. + + Does `return FAILURE_CODE' if runs out of memory. */ + +#define PUSH_FAILURE_POINT(pattern_place, string_place, failure_code) \ + do { \ + char *destination; \ + /* Must be int, so when we don't save any registers, the arithmetic \ + of 0 + -1 isn't done as unsigned. */ \ + int this_reg; \ + \ + DEBUG_STATEMENT (failure_id++); \ + DEBUG_STATEMENT (nfailure_points_pushed++); \ + DEBUG_PRINT2 ("\nPUSH_FAILURE_POINT #%u:\n", failure_id); \ + DEBUG_PRINT2 (" Before push, next avail: %d\n", (fail_stack).avail);\ + DEBUG_PRINT2 (" size: %d\n", (fail_stack).size);\ + \ + DEBUG_PRINT2 (" slots needed: %d\n", NUM_FAILURE_ITEMS); \ + DEBUG_PRINT2 (" available: %d\n", REMAINING_AVAIL_SLOTS); \ + \ + /* Ensure we have enough space allocated for what we will push. */ \ + while (REMAINING_AVAIL_SLOTS < NUM_FAILURE_ITEMS) \ + { \ + if (!DOUBLE_FAIL_STACK (fail_stack)) \ + return failure_code; \ + \ + DEBUG_PRINT2 ("\n Doubled stack; size now: %d\n", \ + (fail_stack).size); \ + DEBUG_PRINT2 (" slots available: %d\n", REMAINING_AVAIL_SLOTS);\ + } \ + \ + /* Push the info, starting with the registers. */ \ + DEBUG_PRINT1 ("\n"); \ + \ + for (this_reg = lowest_active_reg; this_reg <= highest_active_reg; \ + this_reg++) \ + { \ + DEBUG_PRINT2 (" Pushing reg: %d\n", this_reg); \ + DEBUG_STATEMENT (num_regs_pushed++); \ + \ + DEBUG_PRINT2 (" start: 0x%x\n", regstart[this_reg]); \ + PUSH_FAILURE_ITEM (regstart[this_reg]); \ + \ + DEBUG_PRINT2 (" end: 0x%x\n", regend[this_reg]); \ + PUSH_FAILURE_ITEM (regend[this_reg]); \ + \ + DEBUG_PRINT2 (" info: 0x%x\n ", reg_info[this_reg]); \ + DEBUG_PRINT2 (" match_null=%d", \ + REG_MATCH_NULL_STRING_P (reg_info[this_reg])); \ + DEBUG_PRINT2 (" active=%d", IS_ACTIVE (reg_info[this_reg])); \ + DEBUG_PRINT2 (" matched_something=%d", \ + MATCHED_SOMETHING (reg_info[this_reg])); \ + DEBUG_PRINT2 (" ever_matched=%d", \ + EVER_MATCHED_SOMETHING (reg_info[this_reg])); \ + DEBUG_PRINT1 ("\n"); \ + PUSH_FAILURE_ITEM (reg_info[this_reg].word); \ + } \ + \ + DEBUG_PRINT2 (" Pushing low active reg: %d\n", lowest_active_reg);\ + PUSH_FAILURE_ITEM (lowest_active_reg); \ + \ + DEBUG_PRINT2 (" Pushing high active reg: %d\n", highest_active_reg);\ + PUSH_FAILURE_ITEM (highest_active_reg); \ + \ + DEBUG_PRINT2 (" Pushing pattern 0x%x: ", pattern_place); \ + DEBUG_PRINT_COMPILED_PATTERN (bufp, pattern_place, pend); \ + PUSH_FAILURE_ITEM (pattern_place); \ + \ + DEBUG_PRINT2 (" Pushing string 0x%x: `", string_place); \ + DEBUG_PRINT_DOUBLE_STRING (string_place, string1, size1, string2, \ + size2); \ + DEBUG_PRINT1 ("'\n"); \ + PUSH_FAILURE_ITEM (string_place); \ + \ + DEBUG_PRINT2 (" Pushing failure id: %u\n", failure_id); \ + DEBUG_PUSH (failure_id); \ + } while (0) + +/* This is the number of items that are pushed and popped on the stack + for each register. */ +#define NUM_REG_ITEMS 3 + +/* Individual items aside from the registers. */ +#ifdef DEBUG +#define NUM_NONREG_ITEMS 5 /* Includes failure point id. */ +#else +#define NUM_NONREG_ITEMS 4 +#endif + +/* We push at most this many items on the stack. */ +#define MAX_FAILURE_ITEMS ((num_regs - 1) * NUM_REG_ITEMS + NUM_NONREG_ITEMS) + +/* We actually push this many items. */ +#define NUM_FAILURE_ITEMS \ + ((highest_active_reg - lowest_active_reg + 1) * NUM_REG_ITEMS \ + + NUM_NONREG_ITEMS) + +/* How many items can still be added to the stack without overflowing it. */ +#define REMAINING_AVAIL_SLOTS ((fail_stack).size - (fail_stack).avail) + + +/* Pops what PUSH_FAIL_STACK pushes. + + We restore into the parameters, all of which should be lvalues: + STR -- the saved data position. + PAT -- the saved pattern position. + LOW_REG, HIGH_REG -- the highest and lowest active registers. + REGSTART, REGEND -- arrays of string positions. + REG_INFO -- array of information about each subexpression. + + Also assumes the variables `fail_stack' and (if debugging), `bufp', + `pend', `string1', `size1', `string2', and `size2'. */ + +#define POP_FAILURE_POINT(str, pat, low_reg, high_reg, regstart, regend, reg_info)\ +{ \ + DEBUG_STATEMENT (fail_stack_elt_t failure_id;) \ + int this_reg; \ + const unsigned char *string_temp; \ + \ + assert (!FAIL_STACK_EMPTY ()); \ + \ + /* Remove failure points and point to how many regs pushed. */ \ + DEBUG_PRINT1 ("POP_FAILURE_POINT:\n"); \ + DEBUG_PRINT2 (" Before pop, next avail: %d\n", fail_stack.avail); \ + DEBUG_PRINT2 (" size: %d\n", fail_stack.size); \ + \ + assert (fail_stack.avail >= NUM_NONREG_ITEMS); \ + \ + DEBUG_POP (&failure_id); \ + DEBUG_PRINT2 (" Popping failure id: %u\n", failure_id); \ + \ + /* If the saved string location is NULL, it came from an \ + on_failure_keep_string_jump opcode, and we want to throw away the \ + saved NULL, thus retaining our current position in the string. */ \ + string_temp = POP_FAILURE_ITEM (); \ + if (string_temp != NULL) \ + str = (const char *) string_temp; \ + \ + DEBUG_PRINT2 (" Popping string 0x%x: `", str); \ + DEBUG_PRINT_DOUBLE_STRING (str, string1, size1, string2, size2); \ + DEBUG_PRINT1 ("'\n"); \ + \ + pat = (unsigned char *) POP_FAILURE_ITEM (); \ + DEBUG_PRINT2 (" Popping pattern 0x%x: ", pat); \ + DEBUG_PRINT_COMPILED_PATTERN (bufp, pat, pend); \ + \ + /* Restore register info. */ \ + high_reg = (unsigned) POP_FAILURE_ITEM (); \ + DEBUG_PRINT2 (" Popping high active reg: %d\n", high_reg); \ + \ + low_reg = (unsigned) POP_FAILURE_ITEM (); \ + DEBUG_PRINT2 (" Popping low active reg: %d\n", low_reg); \ + \ + for (this_reg = high_reg; this_reg >= low_reg; this_reg--) \ + { \ + DEBUG_PRINT2 (" Popping reg: %d\n", this_reg); \ + \ + reg_info[this_reg].word = POP_FAILURE_ITEM (); \ + DEBUG_PRINT2 (" info: 0x%x\n", reg_info[this_reg]); \ + \ + regend[this_reg] = (const char *) POP_FAILURE_ITEM (); \ + DEBUG_PRINT2 (" end: 0x%x\n", regend[this_reg]); \ + \ + regstart[this_reg] = (const char *) POP_FAILURE_ITEM (); \ + DEBUG_PRINT2 (" start: 0x%x\n", regstart[this_reg]); \ + } \ + \ + DEBUG_STATEMENT (nfailure_points_popped++); \ +} /* POP_FAILURE_POINT */ + +/* re_compile_fastmap computes a ``fastmap'' for the compiled pattern in + BUFP. A fastmap records which of the (1 << BYTEWIDTH) possible + characters can start a string that matches the pattern. This fastmap + is used by re_search to skip quickly over impossible starting points. + + The caller must supply the address of a (1 << BYTEWIDTH)-byte data + area as BUFP->fastmap. + + We set the `fastmap', `fastmap_accurate', and `can_be_null' fields in + the pattern buffer. + + Returns 0 if we succeed, -2 if an internal error. */ + +int +re_compile_fastmap (bufp) + struct re_pattern_buffer *bufp; +{ + int j, k; + fail_stack_type fail_stack; +#ifndef REGEX_MALLOC + char *destination; +#endif + /* We don't push any register information onto the failure stack. */ + unsigned num_regs = 0; + + register char *fastmap = bufp->fastmap; + unsigned char *pattern = bufp->buffer; + unsigned long size = bufp->used; + const unsigned char *p = pattern; + register unsigned char *pend = pattern + size; + + /* Assume that each path through the pattern can be null until + proven otherwise. We set this false at the bottom of switch + statement, to which we get only if a particular path doesn't + match the empty string. */ + boolean path_can_be_null = true; + + /* We aren't doing a `succeed_n' to begin with. */ + boolean succeed_n_p = false; + + assert (fastmap != NULL && p != NULL); + + INIT_FAIL_STACK (); + bzero (fastmap, 1 << BYTEWIDTH); /* Assume nothing's valid. */ + bufp->fastmap_accurate = 1; /* It will be when we're done. */ + bufp->can_be_null = 0; + + while (p != pend || !FAIL_STACK_EMPTY ()) + { + if (p == pend) + { + bufp->can_be_null |= path_can_be_null; + + /* Reset for next path. */ + path_can_be_null = true; + + p = fail_stack.stack[--fail_stack.avail]; + } + + /* We should never be about to go beyond the end of the pattern. */ + assert (p < pend); + +#ifdef SWITCH_ENUM_BUG + switch ((int) ((re_opcode_t) *p++)) +#else + switch ((re_opcode_t) *p++) +#endif + { + + /* I guess the idea here is to simply not bother with a fastmap + if a backreference is used, since it's too hard to figure out + the fastmap for the corresponding group. Setting + `can_be_null' stops `re_search_2' from using the fastmap, so + that is all we do. */ + case duplicate: + bufp->can_be_null = 1; + return 0; + + + /* Following are the cases which match a character. These end + with `break'. */ + + case exactn: + fastmap[p[1]] = 1; + break; + + + case charset: + for (j = *p++ * BYTEWIDTH - 1; j >= 0; j--) + if (p[j / BYTEWIDTH] & (1 << (j % BYTEWIDTH))) + fastmap[j] = 1; + break; + + + case charset_not: + /* Chars beyond end of map must be allowed. */ + for (j = *p * BYTEWIDTH; j < (1 << BYTEWIDTH); j++) + fastmap[j] = 1; + + for (j = *p++ * BYTEWIDTH - 1; j >= 0; j--) + if (!(p[j / BYTEWIDTH] & (1 << (j % BYTEWIDTH)))) + fastmap[j] = 1; + break; + + + case wordchar: + for (j = 0; j < (1 << BYTEWIDTH); j++) + if (SYNTAX (j) == Sword) + fastmap[j] = 1; + break; + + + case notwordchar: + for (j = 0; j < (1 << BYTEWIDTH); j++) + if (SYNTAX (j) != Sword) + fastmap[j] = 1; + break; + + + case anychar: + /* `.' matches anything ... */ + for (j = 0; j < (1 << BYTEWIDTH); j++) + fastmap[j] = 1; + + /* ... except perhaps newline. */ + if (!(bufp->syntax & RE_DOT_NEWLINE)) + fastmap['\n'] = 0; + + /* Return if we have already set `can_be_null'; if we have, + then the fastmap is irrelevant. Something's wrong here. */ + else if (bufp->can_be_null) + return 0; + + /* Otherwise, have to check alternative paths. */ + break; + + +#ifdef emacs + case syntaxspec: + k = *p++; + for (j = 0; j < (1 << BYTEWIDTH); j++) + if (SYNTAX (j) == (enum syntaxcode) k) + fastmap[j] = 1; + break; + + + case notsyntaxspec: + k = *p++; + for (j = 0; j < (1 << BYTEWIDTH); j++) + if (SYNTAX (j) != (enum syntaxcode) k) + fastmap[j] = 1; + break; + + + /* All cases after this match the empty string. These end with + `continue'. */ + + + case before_dot: + case at_dot: + case after_dot: + continue; +#endif /* not emacs */ + + + case no_op: + case begline: + case endline: + case begbuf: + case endbuf: + case wordbound: + case notwordbound: + case wordbeg: + case wordend: + case push_dummy_failure: + continue; + + + case jump_n: + case pop_failure_jump: + case maybe_pop_jump: + case jump: + case jump_past_alt: + case dummy_failure_jump: + EXTRACT_NUMBER_AND_INCR (j, p); + p += j; + if (j > 0) + continue; + + /* Jump backward implies we just went through the body of a + loop and matched nothing. Opcode jumped to should be + `on_failure_jump' or `succeed_n'. Just treat it like an + ordinary jump. For a * loop, it has pushed its failure + point already; if so, discard that as redundant. */ + if ((re_opcode_t) *p != on_failure_jump + && (re_opcode_t) *p != succeed_n) + continue; + + p++; + EXTRACT_NUMBER_AND_INCR (j, p); + p += j; + + /* If what's on the stack is where we are now, pop it. */ + if (!FAIL_STACK_EMPTY () + && fail_stack.stack[fail_stack.avail - 1] == p) + fail_stack.avail--; + + continue; + + + case on_failure_jump: + case on_failure_keep_string_jump: + handle_on_failure_jump: + EXTRACT_NUMBER_AND_INCR (j, p); + + /* For some patterns, e.g., `(a?)?', `p+j' here points to the + end of the pattern. We don't want to push such a point, + since when we restore it above, entering the switch will + increment `p' past the end of the pattern. We don't need + to push such a point since we obviously won't find any more + fastmap entries beyond `pend'. Such a pattern can match + the null string, though. */ + if (p + j < pend) + { + if (!PUSH_PATTERN_OP (p + j, fail_stack)) + return -2; + } + else + bufp->can_be_null = 1; + + if (succeed_n_p) + { + EXTRACT_NUMBER_AND_INCR (k, p); /* Skip the n. */ + succeed_n_p = false; + } + + continue; + + + case succeed_n: + /* Get to the number of times to succeed. */ + p += 2; + + /* Increment p past the n for when k != 0. */ + EXTRACT_NUMBER_AND_INCR (k, p); + if (k == 0) + { + p -= 4; + succeed_n_p = true; /* Spaghetti code alert. */ + goto handle_on_failure_jump; + } + continue; + + + case set_number_at: + p += 4; + continue; + + + case start_memory: + case stop_memory: + p += 2; + continue; + + + default: + abort (); /* We have listed all the cases. */ + } /* switch *p++ */ + + /* Getting here means we have found the possible starting + characters for one path of the pattern -- and that the empty + string does not match. We need not follow this path further. + Instead, look at the next alternative (remembered on the + stack), or quit if no more. The test at the top of the loop + does these things. */ + path_can_be_null = false; + p = pend; + } /* while p */ + + /* Set `can_be_null' for the last path (also the first path, if the + pattern is empty). */ + bufp->can_be_null |= path_can_be_null; + return 0; +} /* re_compile_fastmap */ + +/* Set REGS to hold NUM_REGS registers, storing them in STARTS and + ENDS. Subsequent matches using PATTERN_BUFFER and REGS will use + this memory for recording register information. STARTS and ENDS + must be allocated using the malloc library routine, and must each + be at least NUM_REGS * sizeof (regoff_t) bytes long. + + If NUM_REGS == 0, then subsequent matches should allocate their own + register data. + + Unless this function is called, the first search or match using + PATTERN_BUFFER will allocate its own register data, without + freeing the old data. */ + +void +re_set_registers (bufp, regs, num_regs, starts, ends) + struct re_pattern_buffer *bufp; + struct re_registers *regs; + unsigned num_regs; + regoff_t *starts, *ends; +{ + if (num_regs) + { + bufp->regs_allocated = REGS_REALLOCATE; + regs->num_regs = num_regs; + regs->start = starts; + regs->end = ends; + } + else + { + bufp->regs_allocated = REGS_UNALLOCATED; + regs->num_regs = 0; + regs->start = regs->end = (regoff_t *) 0; + } +} + +/* Searching routines. */ + +/* Like re_search_2, below, but only one string is specified, and + doesn't let you say where to stop matching. */ + +int +re_search (bufp, string, size, startpos, range, regs) + struct re_pattern_buffer *bufp; + const char *string; + int size, startpos, range; + struct re_registers *regs; +{ + return re_search_2 (bufp, NULL, 0, string, size, startpos, range, + regs, size); +} + + +/* Using the compiled pattern in BUFP->buffer, first tries to match the + virtual concatenation of STRING1 and STRING2, starting first at index + STARTPOS, then at STARTPOS + 1, and so on. + + STRING1 and STRING2 have length SIZE1 and SIZE2, respectively. + + RANGE is how far to scan while trying to match. RANGE = 0 means try + only at STARTPOS; in general, the last start tried is STARTPOS + + RANGE. + + In REGS, return the indices of the virtual concatenation of STRING1 + and STRING2 that matched the entire BUFP->buffer and its contained + subexpressions. + + Do not consider matching one past the index STOP in the virtual + concatenation of STRING1 and STRING2. + + We return either the position in the strings at which the match was + found, -1 if no match, or -2 if error (such as failure + stack overflow). */ + +int +re_search_2 (bufp, string1, size1, string2, size2, startpos, range, regs, stop) + struct re_pattern_buffer *bufp; + const char *string1, *string2; + int size1, size2; + int startpos; + int range; + struct re_registers *regs; + int stop; +{ + int val; + register char *fastmap = bufp->fastmap; + register char *translate = bufp->translate; + int total_size = size1 + size2; + int endpos = startpos + range; + + /* Check for out-of-range STARTPOS. */ + if (startpos < 0 || startpos > total_size) + return -1; + + /* Fix up RANGE if it might eventually take us outside + the virtual concatenation of STRING1 and STRING2. */ + if (endpos < -1) + range = -1 - startpos; + else if (endpos > total_size) + range = total_size - startpos; + + /* If the search isn't to be a backwards one, don't waste time in a + search for a pattern that must be anchored. */ + if (bufp->used > 0 && (re_opcode_t) bufp->buffer[0] == begbuf && range > 0) + { + if (startpos > 0) + return -1; + else + range = 1; + } + + /* Update the fastmap now if not correct already. */ + if (fastmap && !bufp->fastmap_accurate) + if (re_compile_fastmap (bufp) == -2) + return -2; + + /* Loop through the string, looking for a place to start matching. */ + for (;;) + { + /* If a fastmap is supplied, skip quickly over characters that + cannot be the start of a match. If the pattern can match the + null string, however, we don't need to skip characters; we want + the first null string. */ + if (fastmap && startpos < total_size && !bufp->can_be_null) + { + if (range > 0) /* Searching forwards. */ + { + register const char *d; + register int lim = 0; + int irange = range; + + if (startpos < size1 && startpos + range >= size1) + lim = range - (size1 - startpos); + + d = (startpos >= size1 ? string2 - size1 : string1) + startpos; + + /* Written out as an if-else to avoid testing `translate' + inside the loop. */ + if (translate) + while (range > lim + && !fastmap[(unsigned char) + translate[(unsigned char) *d++]]) + range--; + else + while (range > lim && !fastmap[(unsigned char) *d++]) + range--; + + startpos += irange - range; + } + else /* Searching backwards. */ + { + register char c = (size1 == 0 || startpos >= size1 + ? string2[startpos - size1] + : string1[startpos]); + + if (!fastmap[(unsigned char) TRANSLATE (c)]) + goto advance; + } + } + + /* If can't match the null string, and that's all we have left, fail. */ + if (range >= 0 && startpos == total_size && fastmap + && !bufp->can_be_null) + return -1; + + val = re_match_2 (bufp, string1, size1, string2, size2, + startpos, regs, stop); + if (val >= 0) + return startpos; + + if (val == -2) + return -2; + + advance: + if (!range) + break; + else if (range > 0) + { + range--; + startpos++; + } + else + { + range++; + startpos--; + } + } + return -1; +} /* re_search_2 */ + +/* Declarations and macros for re_match_2. */ + +static int bcmp_translate (); +static boolean alt_match_null_string_p (), + common_op_match_null_string_p (), + group_match_null_string_p (); + +/* Structure for per-register (a.k.a. per-group) information. + This must not be longer than one word, because we push this value + onto the failure stack. Other register information, such as the + starting and ending positions (which are addresses), and the list of + inner groups (which is a bits list) are maintained in separate + variables. + + We are making a (strictly speaking) nonportable assumption here: that + the compiler will pack our bit fields into something that fits into + the type of `word', i.e., is something that fits into one item on the + failure stack. */ +typedef union +{ + fail_stack_elt_t word; + struct + { + /* This field is one if this group can match the empty string, + zero if not. If not yet determined, `MATCH_NULL_UNSET_VALUE'. */ +#define MATCH_NULL_UNSET_VALUE 3 + unsigned match_null_string_p : 2; + unsigned is_active : 1; + unsigned matched_something : 1; + unsigned ever_matched_something : 1; + } bits; +} register_info_type; + +#define REG_MATCH_NULL_STRING_P(R) ((R).bits.match_null_string_p) +#define IS_ACTIVE(R) ((R).bits.is_active) +#define MATCHED_SOMETHING(R) ((R).bits.matched_something) +#define EVER_MATCHED_SOMETHING(R) ((R).bits.ever_matched_something) + + +/* Call this when have matched a real character; it sets `matched' flags + for the subexpressions which we are currently inside. Also records + that those subexprs have matched. */ +#define SET_REGS_MATCHED() \ + do \ + { \ + unsigned r; \ + for (r = lowest_active_reg; r <= highest_active_reg; r++) \ + { \ + MATCHED_SOMETHING (reg_info[r]) \ + = EVER_MATCHED_SOMETHING (reg_info[r]) \ + = 1; \ + } \ + } \ + while (0) + + +/* This converts PTR, a pointer into one of the search strings `string1' + and `string2' into an offset from the beginning of that string. */ +#define POINTER_TO_OFFSET(ptr) \ + (FIRST_STRING_P (ptr) ? (ptr) - string1 : (ptr) - string2 + size1) + +/* Registers are set to a sentinel when they haven't yet matched. */ +#define REG_UNSET_VALUE ((char *) -1) +#define REG_UNSET(e) ((e) == REG_UNSET_VALUE) + + +/* Macros for dealing with the split strings in re_match_2. */ + +#define MATCHING_IN_FIRST_STRING (dend == end_match_1) + +/* Call before fetching a character with *d. This switches over to + string2 if necessary. */ +#define PREFETCH() \ + while (d == dend) \ + { \ + /* End of string2 => fail. */ \ + if (dend == end_match_2) \ + goto fail; \ + /* End of string1 => advance to string2. */ \ + d = string2; \ + dend = end_match_2; \ + } + + +/* Test if at very beginning or at very end of the virtual concatenation + of `string1' and `string2'. If only one string, it's `string2'. */ +#define AT_STRINGS_BEG(d) ((d) == (size1 ? string1 : string2) || !size2) +#define AT_STRINGS_END(d) ((d) == end2) + + +/* Test if D points to a character which is word-constituent. We have + two special cases to check for: if past the end of string1, look at + the first character in string2; and if before the beginning of + string2, look at the last character in string1. */ +#define WORDCHAR_P(d) \ + (SYNTAX ((d) == end1 ? *string2 \ + : (d) == string2 - 1 ? *(end1 - 1) : *(d)) \ + == Sword) + +/* Test if the character before D and the one at D differ with respect + to being word-constituent. */ +#define AT_WORD_BOUNDARY(d) \ + (AT_STRINGS_BEG (d) || AT_STRINGS_END (d) \ + || WORDCHAR_P (d - 1) != WORDCHAR_P (d)) + + +/* Free everything we malloc. */ +#ifdef REGEX_MALLOC +#define FREE_VAR(var) if (var) free (var); var = NULL +#define FREE_VARIABLES() \ + do { \ + FREE_VAR (fail_stack.stack); \ + FREE_VAR (regstart); \ + FREE_VAR (regend); \ + FREE_VAR (old_regstart); \ + FREE_VAR (old_regend); \ + FREE_VAR (best_regstart); \ + FREE_VAR (best_regend); \ + FREE_VAR (reg_info); \ + FREE_VAR (reg_dummy); \ + FREE_VAR (reg_info_dummy); \ + } while (0) +#else /* not REGEX_MALLOC */ +/* Some MIPS systems (at least) want this to free alloca'd storage. */ +#define FREE_VARIABLES() alloca (0) +#endif /* not REGEX_MALLOC */ + + +/* These values must meet several constraints. They must not be valid + register values; since we have a limit of 255 registers (because + we use only one byte in the pattern for the register number), we can + use numbers larger than 255. They must differ by 1, because of + NUM_FAILURE_ITEMS above. And the value for the lowest register must + be larger than the value for the highest register, so we do not try + to actually save any registers when none are active. */ +#define NO_HIGHEST_ACTIVE_REG (1 << BYTEWIDTH) +#define NO_LOWEST_ACTIVE_REG (NO_HIGHEST_ACTIVE_REG + 1) + +/* Matching routines. */ + +#ifndef emacs /* Emacs never uses this. */ +/* re_match is like re_match_2 except it takes only a single string. */ + +int +re_match (bufp, string, size, pos, regs) + struct re_pattern_buffer *bufp; + const char *string; + int size, pos; + struct re_registers *regs; + { + return re_match_2 (bufp, NULL, 0, string, size, pos, regs, size); +} +#endif /* not emacs */ + + +/* re_match_2 matches the compiled pattern in BUFP against the + the (virtual) concatenation of STRING1 and STRING2 (of length SIZE1 + and SIZE2, respectively). We start matching at POS, and stop + matching at STOP. + + If REGS is non-null and the `no_sub' field of BUFP is nonzero, we + store offsets for the substring each group matched in REGS. See the + documentation for exactly how many groups we fill. + + We return -1 if no match, -2 if an internal error (such as the + failure stack overflowing). Otherwise, we return the length of the + matched substring. */ + +int +re_match_2 (bufp, string1, size1, string2, size2, pos, regs, stop) + struct re_pattern_buffer *bufp; + const char *string1, *string2; + int size1, size2; + int pos; + struct re_registers *regs; + int stop; +{ + /* General temporaries. */ + int mcnt; + unsigned char *p1; + + /* Just past the end of the corresponding string. */ + const char *end1, *end2; + + /* Pointers into string1 and string2, just past the last characters in + each to consider matching. */ + const char *end_match_1, *end_match_2; + + /* Where we are in the data, and the end of the current string. */ + const char *d, *dend; + + /* Where we are in the pattern, and the end of the pattern. */ + unsigned char *p = bufp->buffer; + register unsigned char *pend = p + bufp->used; + + /* We use this to map every character in the string. */ + char *translate = bufp->translate; + + /* Failure point stack. Each place that can handle a failure further + down the line pushes a failure point on this stack. It consists of + restart, regend, and reg_info for all registers corresponding to + the subexpressions we're currently inside, plus the number of such + registers, and, finally, two char *'s. The first char * is where + to resume scanning the pattern; the second one is where to resume + scanning the strings. If the latter is zero, the failure point is + a ``dummy''; if a failure happens and the failure point is a dummy, + it gets discarded and the next next one is tried. */ + fail_stack_type fail_stack; +#ifdef DEBUG + static unsigned failure_id = 0; + unsigned nfailure_points_pushed = 0, nfailure_points_popped = 0; +#endif + + /* We fill all the registers internally, independent of what we + return, for use in backreferences. The number here includes + an element for register zero. */ + unsigned num_regs = bufp->re_nsub + 1; + + /* The currently active registers. */ + unsigned lowest_active_reg = NO_LOWEST_ACTIVE_REG; + unsigned highest_active_reg = NO_HIGHEST_ACTIVE_REG; + + /* Information on the contents of registers. These are pointers into + the input strings; they record just what was matched (on this + attempt) by a subexpression part of the pattern, that is, the + regnum-th regstart pointer points to where in the pattern we began + matching and the regnum-th regend points to right after where we + stopped matching the regnum-th subexpression. (The zeroth register + keeps track of what the whole pattern matches.) */ + const char **regstart, **regend; + + /* If a group that's operated upon by a repetition operator fails to + match anything, then the register for its start will need to be + restored because it will have been set to wherever in the string we + are when we last see its open-group operator. Similarly for a + register's end. */ + const char **old_regstart, **old_regend; + + /* The is_active field of reg_info helps us keep track of which (possibly + nested) subexpressions we are currently in. The matched_something + field of reg_info[reg_num] helps us tell whether or not we have + matched any of the pattern so far this time through the reg_num-th + subexpression. These two fields get reset each time through any + loop their register is in. */ + register_info_type *reg_info; + + /* The following record the register info as found in the above + variables when we find a match better than any we've seen before. + This happens as we backtrack through the failure points, which in + turn happens only if we have not yet matched the entire string. */ + unsigned best_regs_set = false; + const char **best_regstart, **best_regend; + + /* Logically, this is `best_regend[0]'. But we don't want to have to + allocate space for that if we're not allocating space for anything + else (see below). Also, we never need info about register 0 for + any of the other register vectors, and it seems rather a kludge to + treat `best_regend' differently than the rest. So we keep track of + the end of the best match so far in a separate variable. We + initialize this to NULL so that when we backtrack the first time + and need to test it, it's not garbage. */ + const char *match_end = NULL; + + /* Used when we pop values we don't care about. */ + const char **reg_dummy; + register_info_type *reg_info_dummy; + +#ifdef DEBUG + /* Counts the total number of registers pushed. */ + unsigned num_regs_pushed = 0; +#endif + + DEBUG_PRINT1 ("\n\nEntering re_match_2.\n"); + + INIT_FAIL_STACK (); + + /* Do not bother to initialize all the register variables if there are + no groups in the pattern, as it takes a fair amount of time. If + there are groups, we include space for register 0 (the whole + pattern), even though we never use it, since it simplifies the + array indexing. We should fix this. */ + if (bufp->re_nsub) + { + regstart = REGEX_TALLOC (num_regs, const char *); + regend = REGEX_TALLOC (num_regs, const char *); + old_regstart = REGEX_TALLOC (num_regs, const char *); + old_regend = REGEX_TALLOC (num_regs, const char *); + best_regstart = REGEX_TALLOC (num_regs, const char *); + best_regend = REGEX_TALLOC (num_regs, const char *); + reg_info = REGEX_TALLOC (num_regs, register_info_type); + reg_dummy = REGEX_TALLOC (num_regs, const char *); + reg_info_dummy = REGEX_TALLOC (num_regs, register_info_type); + + if (!(regstart && regend && old_regstart && old_regend && reg_info + && best_regstart && best_regend && reg_dummy && reg_info_dummy)) + { + FREE_VARIABLES (); + return -2; + } + } +#ifdef REGEX_MALLOC + else + { + /* We must initialize all our variables to NULL, so that + `FREE_VARIABLES' doesn't try to free them. */ + regstart = regend = old_regstart = old_regend = best_regstart + = best_regend = reg_dummy = NULL; + reg_info = reg_info_dummy = (register_info_type *) NULL; + } +#endif /* REGEX_MALLOC */ + + /* The starting position is bogus. */ + if (pos < 0 || pos > size1 + size2) + { + FREE_VARIABLES (); + return -1; + } + + /* Initialize subexpression text positions to -1 to mark ones that no + start_memory/stop_memory has been seen for. Also initialize the + register information struct. */ + for (mcnt = 1; mcnt < num_regs; mcnt++) + { + regstart[mcnt] = regend[mcnt] + = old_regstart[mcnt] = old_regend[mcnt] = REG_UNSET_VALUE; + + REG_MATCH_NULL_STRING_P (reg_info[mcnt]) = MATCH_NULL_UNSET_VALUE; + IS_ACTIVE (reg_info[mcnt]) = 0; + MATCHED_SOMETHING (reg_info[mcnt]) = 0; + EVER_MATCHED_SOMETHING (reg_info[mcnt]) = 0; + } + + /* We move `string1' into `string2' if the latter's empty -- but not if + `string1' is null. */ + if (size2 == 0 && string1 != NULL) + { + string2 = string1; + size2 = size1; + string1 = 0; + size1 = 0; + } + end1 = string1 + size1; + end2 = string2 + size2; + + /* Compute where to stop matching, within the two strings. */ + if (stop <= size1) + { + end_match_1 = string1 + stop; + end_match_2 = string2; + } + else + { + end_match_1 = end1; + end_match_2 = string2 + stop - size1; + } + + /* `p' scans through the pattern as `d' scans through the data. + `dend' is the end of the input string that `d' points within. `d' + is advanced into the following input string whenever necessary, but + this happens before fetching; therefore, at the beginning of the + loop, `d' can be pointing at the end of a string, but it cannot + equal `string2'. */ + if (size1 > 0 && pos <= size1) + { + d = string1 + pos; + dend = end_match_1; + } + else + { + d = string2 + pos - size1; + dend = end_match_2; + } + + DEBUG_PRINT1 ("The compiled pattern is: "); + DEBUG_PRINT_COMPILED_PATTERN (bufp, p, pend); + DEBUG_PRINT1 ("The string to match is: `"); + DEBUG_PRINT_DOUBLE_STRING (d, string1, size1, string2, size2); + DEBUG_PRINT1 ("'\n"); + + /* This loops over pattern commands. It exits by returning from the + function if the match is complete, or it drops through if the match + fails at this starting point in the input data. */ + for (;;) + { + DEBUG_PRINT2 ("\n0x%x: ", p); + + if (p == pend) + { /* End of pattern means we might have succeeded. */ + DEBUG_PRINT1 ("end of pattern ... "); + + /* If we haven't matched the entire string, and we want the + longest match, try backtracking. */ + if (d != end_match_2) + { + DEBUG_PRINT1 ("backtracking.\n"); + + if (!FAIL_STACK_EMPTY ()) + { /* More failure points to try. */ + boolean same_str_p = (FIRST_STRING_P (match_end) + == MATCHING_IN_FIRST_STRING); + + /* If exceeds best match so far, save it. */ + if (!best_regs_set + || (same_str_p && d > match_end) + || (!same_str_p && !MATCHING_IN_FIRST_STRING)) + { + best_regs_set = true; + match_end = d; + + DEBUG_PRINT1 ("\nSAVING match as best so far.\n"); + + for (mcnt = 1; mcnt < num_regs; mcnt++) + { + best_regstart[mcnt] = regstart[mcnt]; + best_regend[mcnt] = regend[mcnt]; + } + } + goto fail; + } + + /* If no failure points, don't restore garbage. */ + else if (best_regs_set) + { + restore_best_regs: + /* Restore best match. It may happen that `dend == + end_match_1' while the restored d is in string2. + For example, the pattern `x.*y.*z' against the + strings `x-' and `y-z-', if the two strings are + not consecutive in memory. */ + DEBUG_PRINT1 ("Restoring best registers.\n"); + + d = match_end; + dend = ((d >= string1 && d <= end1) + ? end_match_1 : end_match_2); + + for (mcnt = 1; mcnt < num_regs; mcnt++) + { + regstart[mcnt] = best_regstart[mcnt]; + regend[mcnt] = best_regend[mcnt]; + } + } + } /* d != end_match_2 */ + + DEBUG_PRINT1 ("Accepting match.\n"); + + /* If caller wants register contents data back, do it. */ + if (regs && !bufp->no_sub) + { + /* Have the register data arrays been allocated? */ + if (bufp->regs_allocated == REGS_UNALLOCATED) + { /* No. So allocate them with malloc. We need one + extra element beyond `num_regs' for the `-1' marker + GNU code uses. */ + regs->num_regs = MAX (RE_NREGS, num_regs + 1); + regs->start = TALLOC (regs->num_regs, regoff_t); + regs->end = TALLOC (regs->num_regs, regoff_t); + if (regs->start == NULL || regs->end == NULL) + return -2; + bufp->regs_allocated = REGS_REALLOCATE; + } + else if (bufp->regs_allocated == REGS_REALLOCATE) + { /* Yes. If we need more elements than were already + allocated, reallocate them. If we need fewer, just + leave it alone. */ + if (regs->num_regs < num_regs + 1) + { + regs->num_regs = num_regs + 1; + RETALLOC (regs->start, regs->num_regs, regoff_t); + RETALLOC (regs->end, regs->num_regs, regoff_t); + if (regs->start == NULL || regs->end == NULL) + return -2; + } + } + else + assert (bufp->regs_allocated == REGS_FIXED); + + /* Convert the pointer data in `regstart' and `regend' to + indices. Register zero has to be set differently, + since we haven't kept track of any info for it. */ + if (regs->num_regs > 0) + { + regs->start[0] = pos; + regs->end[0] = (MATCHING_IN_FIRST_STRING ? d - string1 + : d - string2 + size1); + } + + /* Go through the first `min (num_regs, regs->num_regs)' + registers, since that is all we initialized. */ + for (mcnt = 1; mcnt < MIN (num_regs, regs->num_regs); mcnt++) + { + if (REG_UNSET (regstart[mcnt]) || REG_UNSET (regend[mcnt])) + regs->start[mcnt] = regs->end[mcnt] = -1; + else + { + regs->start[mcnt] = POINTER_TO_OFFSET (regstart[mcnt]); + regs->end[mcnt] = POINTER_TO_OFFSET (regend[mcnt]); + } + } + + /* If the regs structure we return has more elements than + were in the pattern, set the extra elements to -1. If + we (re)allocated the registers, this is the case, + because we always allocate enough to have at least one + -1 at the end. */ + for (mcnt = num_regs; mcnt < regs->num_regs; mcnt++) + regs->start[mcnt] = regs->end[mcnt] = -1; + } /* regs && !bufp->no_sub */ + + FREE_VARIABLES (); + DEBUG_PRINT4 ("%u failure points pushed, %u popped (%u remain).\n", + nfailure_points_pushed, nfailure_points_popped, + nfailure_points_pushed - nfailure_points_popped); + DEBUG_PRINT2 ("%u registers pushed.\n", num_regs_pushed); + + mcnt = d - pos - (MATCHING_IN_FIRST_STRING + ? string1 + : string2 - size1); + + DEBUG_PRINT2 ("Returning %d from re_match_2.\n", mcnt); + + return mcnt; + } + + /* Otherwise match next pattern command. */ +#ifdef SWITCH_ENUM_BUG + switch ((int) ((re_opcode_t) *p++)) +#else + switch ((re_opcode_t) *p++) +#endif + { + /* Ignore these. Used to ignore the n of succeed_n's which + currently have n == 0. */ + case no_op: + DEBUG_PRINT1 ("EXECUTING no_op.\n"); + break; + + + /* Match the next n pattern characters exactly. The following + byte in the pattern defines n, and the n bytes after that + are the characters to match. */ + case exactn: + mcnt = *p++; + DEBUG_PRINT2 ("EXECUTING exactn %d.\n", mcnt); + + /* This is written out as an if-else so we don't waste time + testing `translate' inside the loop. */ + if (translate) + { + do + { + PREFETCH (); + if (translate[(unsigned char) *d++] != (char) *p++) + goto fail; + } + while (--mcnt); + } + else + { + do + { + PREFETCH (); + if (*d++ != (char) *p++) goto fail; + } + while (--mcnt); + } + SET_REGS_MATCHED (); + break; + + + /* Match any character except possibly a newline or a null. */ + case anychar: + DEBUG_PRINT1 ("EXECUTING anychar.\n"); + + PREFETCH (); + + if ((!(bufp->syntax & RE_DOT_NEWLINE) && TRANSLATE (*d) == '\n') + || (bufp->syntax & RE_DOT_NOT_NULL && TRANSLATE (*d) == '\000')) + goto fail; + + SET_REGS_MATCHED (); + DEBUG_PRINT2 (" Matched `%d'.\n", *d); + d++; + break; + + + case charset: + case charset_not: + { + register unsigned char c; + boolean not = (re_opcode_t) *(p - 1) == charset_not; + + DEBUG_PRINT2 ("EXECUTING charset%s.\n", not ? "_not" : ""); + + PREFETCH (); + c = TRANSLATE (*d); /* The character to match. */ + + /* Cast to `unsigned' instead of `unsigned char' in case the + bit list is a full 32 bytes long. */ + if (c < (unsigned) (*p * BYTEWIDTH) + && p[1 + c / BYTEWIDTH] & (1 << (c % BYTEWIDTH))) + not = !not; + + p += 1 + *p; + + if (!not) goto fail; + + SET_REGS_MATCHED (); + d++; + break; + } + + + /* The beginning of a group is represented by start_memory. + The arguments are the register number in the next byte, and the + number of groups inner to this one in the next. The text + matched within the group is recorded (in the internal + registers data structure) under the register number. */ + case start_memory: + DEBUG_PRINT3 ("EXECUTING start_memory %d (%d):\n", *p, p[1]); + + /* Find out if this group can match the empty string. */ + p1 = p; /* To send to group_match_null_string_p. */ + + if (REG_MATCH_NULL_STRING_P (reg_info[*p]) == MATCH_NULL_UNSET_VALUE) + REG_MATCH_NULL_STRING_P (reg_info[*p]) + = group_match_null_string_p (&p1, pend, reg_info); + + /* Save the position in the string where we were the last time + we were at this open-group operator in case the group is + operated upon by a repetition operator, e.g., with `(a*)*b' + against `ab'; then we want to ignore where we are now in + the string in case this attempt to match fails. */ + old_regstart[*p] = REG_MATCH_NULL_STRING_P (reg_info[*p]) + ? REG_UNSET (regstart[*p]) ? d : regstart[*p] + : regstart[*p]; + DEBUG_PRINT2 (" old_regstart: %d\n", + POINTER_TO_OFFSET (old_regstart[*p])); + + regstart[*p] = d; + DEBUG_PRINT2 (" regstart: %d\n", POINTER_TO_OFFSET (regstart[*p])); + + IS_ACTIVE (reg_info[*p]) = 1; + MATCHED_SOMETHING (reg_info[*p]) = 0; + + /* This is the new highest active register. */ + highest_active_reg = *p; + + /* If nothing was active before, this is the new lowest active + register. */ + if (lowest_active_reg == NO_LOWEST_ACTIVE_REG) + lowest_active_reg = *p; + + /* Move past the register number and inner group count. */ + p += 2; + break; + + + /* The stop_memory opcode represents the end of a group. Its + arguments are the same as start_memory's: the register + number, and the number of inner groups. */ + case stop_memory: + DEBUG_PRINT3 ("EXECUTING stop_memory %d (%d):\n", *p, p[1]); + + /* We need to save the string position the last time we were at + this close-group operator in case the group is operated + upon by a repetition operator, e.g., with `((a*)*(b*)*)*' + against `aba'; then we want to ignore where we are now in + the string in case this attempt to match fails. */ + old_regend[*p] = REG_MATCH_NULL_STRING_P (reg_info[*p]) + ? REG_UNSET (regend[*p]) ? d : regend[*p] + : regend[*p]; + DEBUG_PRINT2 (" old_regend: %d\n", + POINTER_TO_OFFSET (old_regend[*p])); + + regend[*p] = d; + DEBUG_PRINT2 (" regend: %d\n", POINTER_TO_OFFSET (regend[*p])); + + /* This register isn't active anymore. */ + IS_ACTIVE (reg_info[*p]) = 0; + + /* If this was the only register active, nothing is active + anymore. */ + if (lowest_active_reg == highest_active_reg) + { + lowest_active_reg = NO_LOWEST_ACTIVE_REG; + highest_active_reg = NO_HIGHEST_ACTIVE_REG; + } + else + { /* We must scan for the new highest active register, since + it isn't necessarily one less than now: consider + (a(b)c(d(e)f)g). When group 3 ends, after the f), the + new highest active register is 1. */ + unsigned char r = *p - 1; + while (r > 0 && !IS_ACTIVE (reg_info[r])) + r--; + + /* If we end up at register zero, that means that we saved + the registers as the result of an `on_failure_jump', not + a `start_memory', and we jumped to past the innermost + `stop_memory'. For example, in ((.)*) we save + registers 1 and 2 as a result of the *, but when we pop + back to the second ), we are at the stop_memory 1. + Thus, nothing is active. */ + if (r == 0) + { + lowest_active_reg = NO_LOWEST_ACTIVE_REG; + highest_active_reg = NO_HIGHEST_ACTIVE_REG; + } + else + highest_active_reg = r; + } + + /* If just failed to match something this time around with a + group that's operated on by a repetition operator, try to + force exit from the ``loop'', and restore the register + information for this group that we had before trying this + last match. */ + if ((!MATCHED_SOMETHING (reg_info[*p]) + || (re_opcode_t) p[-3] == start_memory) + && (p + 2) < pend) + { + boolean is_a_jump_n = false; + + p1 = p + 2; + mcnt = 0; + switch ((re_opcode_t) *p1++) + { + case jump_n: + is_a_jump_n = true; + case pop_failure_jump: + case maybe_pop_jump: + case jump: + case dummy_failure_jump: + EXTRACT_NUMBER_AND_INCR (mcnt, p1); + if (is_a_jump_n) + p1 += 2; + break; + + default: + /* do nothing */ ; + } + p1 += mcnt; + + /* If the next operation is a jump backwards in the pattern + to an on_failure_jump right before the start_memory + corresponding to this stop_memory, exit from the loop + by forcing a failure after pushing on the stack the + on_failure_jump's jump in the pattern, and d. */ + if (mcnt < 0 && (re_opcode_t) *p1 == on_failure_jump + && (re_opcode_t) p1[3] == start_memory && p1[4] == *p) + { + /* If this group ever matched anything, then restore + what its registers were before trying this last + failed match, e.g., with `(a*)*b' against `ab' for + regstart[1], and, e.g., with `((a*)*(b*)*)*' + against `aba' for regend[3]. + + Also restore the registers for inner groups for, + e.g., `((a*)(b*))*' against `aba' (register 3 would + otherwise get trashed). */ + + if (EVER_MATCHED_SOMETHING (reg_info[*p])) + { + unsigned r; + + 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]; + } + } + p1++; + EXTRACT_NUMBER_AND_INCR (mcnt, p1); + PUSH_FAILURE_POINT (p1 + mcnt, d, -2); + + goto fail; + } + } + + /* Move past the register number and the inner group count. */ + p += 2; + break; + + + /* \<digit> has been turned into a `duplicate' command which is + followed by the numeric value of <digit> as the register number. */ + case duplicate: + { + register const char *d2, *dend2; + int regno = *p++; /* Get which register to match against. */ + DEBUG_PRINT2 ("EXECUTING duplicate %d.\n", regno); + + /* Can't back reference a group which we've never matched. */ + if (REG_UNSET (regstart[regno]) || REG_UNSET (regend[regno])) + goto fail; + + /* Where in input to try to start matching. */ + d2 = regstart[regno]; + + /* Where to stop matching; if both the place to start and + the place to stop matching are in the same string, then + set to the place to stop, otherwise, for now have to use + the end of the first string. */ + + dend2 = ((FIRST_STRING_P (regstart[regno]) + == FIRST_STRING_P (regend[regno])) + ? regend[regno] : end_match_1); + for (;;) + { + /* If necessary, advance to next segment in register + contents. */ + while (d2 == dend2) + { + if (dend2 == end_match_2) break; + if (dend2 == regend[regno]) break; + + /* End of string1 => advance to string2. */ + d2 = string2; + dend2 = regend[regno]; + } + /* At end of register contents => success */ + if (d2 == dend2) break; + + /* If necessary, advance to next segment in data. */ + PREFETCH (); + + /* How many characters left in this segment to match. */ + mcnt = dend - d; + + /* Want how many consecutive characters we can match in + one shot, so, if necessary, adjust the count. */ + if (mcnt > dend2 - d2) + mcnt = dend2 - d2; + + /* Compare that many; failure if mismatch, else move + past them. */ + if (translate + ? bcmp_translate (d, d2, mcnt, translate) + : bcmp (d, d2, mcnt)) + goto fail; + d += mcnt, d2 += mcnt; + } + } + break; + + + /* begline matches the empty string at the beginning of the string + (unless `not_bol' is set in `bufp'), and, if + `newline_anchor' is set, after newlines. */ + case begline: + DEBUG_PRINT1 ("EXECUTING begline.\n"); + + if (AT_STRINGS_BEG (d)) + { + if (!bufp->not_bol) break; + } + else if (d[-1] == '\n' && bufp->newline_anchor) + { + break; + } + /* In all other cases, we fail. */ + goto fail; + + + /* endline is the dual of begline. */ + case endline: + DEBUG_PRINT1 ("EXECUTING endline.\n"); + + if (AT_STRINGS_END (d)) + { + if (!bufp->not_eol) break; + } + + /* We have to ``prefetch'' the next character. */ + else if ((d == end1 ? *string2 : *d) == '\n' + && bufp->newline_anchor) + { + break; + } + goto fail; + + + /* Match at the very beginning of the data. */ + case begbuf: + DEBUG_PRINT1 ("EXECUTING begbuf.\n"); + if (AT_STRINGS_BEG (d)) + break; + goto fail; + + + /* Match at the very end of the data. */ + case endbuf: + DEBUG_PRINT1 ("EXECUTING endbuf.\n"); + if (AT_STRINGS_END (d)) + break; + goto fail; + + + /* on_failure_keep_string_jump is used to optimize `.*\n'. It + pushes NULL as the value for the string on the stack. Then + `pop_failure_point' will keep the current value for the + string, instead of restoring it. To see why, consider + matching `foo\nbar' against `.*\n'. The .* matches the foo; + then the . fails against the \n. But the next thing we want + to do is match the \n against the \n; if we restored the + string value, we would be back at the foo. + + Because this is used only in specific cases, we don't need to + check all the things that `on_failure_jump' does, to make + sure the right things get saved on the stack. Hence we don't + share its code. The only reason to push anything on the + stack at all is that otherwise we would have to change + `anychar's code to do something besides goto fail in this + case; that seems worse than this. */ + case on_failure_keep_string_jump: + DEBUG_PRINT1 ("EXECUTING on_failure_keep_string_jump"); + + EXTRACT_NUMBER_AND_INCR (mcnt, p); + DEBUG_PRINT3 (" %d (to 0x%x):\n", mcnt, p + mcnt); + + PUSH_FAILURE_POINT (p + mcnt, NULL, -2); + break; + + + /* Uses of on_failure_jump: + + Each alternative starts with an on_failure_jump that points + to the beginning of the next alternative. Each alternative + except the last ends with a jump that in effect jumps past + the rest of the alternatives. (They really jump to the + ending jump of the following alternative, because tensioning + these jumps is a hassle.) + + Repeats start with an on_failure_jump that points past both + the repetition text and either the following jump or + pop_failure_jump back to this on_failure_jump. */ + case on_failure_jump: + on_failure: + DEBUG_PRINT1 ("EXECUTING on_failure_jump"); + + EXTRACT_NUMBER_AND_INCR (mcnt, p); + DEBUG_PRINT3 (" %d (to 0x%x)", mcnt, p + mcnt); + + /* If this on_failure_jump comes right before a group (i.e., + the original * applied to a group), save the information + for that group and all inner ones, so that if we fail back + to this point, the group's information will be correct. + For example, in \(a*\)*\1, we need the preceding group, + and in \(\(a*\)b*\)\2, we need the inner group. */ + + /* We can't use `p' to check ahead because we push + a failure point to `p + mcnt' after we do this. */ + p1 = p; + + /* We need to skip no_op's before we look for the + start_memory in case this on_failure_jump is happening as + the result of a completed succeed_n, as in \(a\)\{1,3\}b\1 + against aba. */ + while (p1 < pend && (re_opcode_t) *p1 == no_op) + p1++; + + if (p1 < pend && (re_opcode_t) *p1 == start_memory) + { + /* We have a new highest active register now. This will + get reset at the start_memory we are about to get to, + but we will have saved all the registers relevant to + this repetition op, as described above. */ + highest_active_reg = *(p1 + 1) + *(p1 + 2); + if (lowest_active_reg == NO_LOWEST_ACTIVE_REG) + lowest_active_reg = *(p1 + 1); + } + + DEBUG_PRINT1 (":\n"); + PUSH_FAILURE_POINT (p + mcnt, d, -2); + break; + + + /* A smart repeat ends with `maybe_pop_jump'. + We change it to either `pop_failure_jump' or `jump'. */ + case maybe_pop_jump: + EXTRACT_NUMBER_AND_INCR (mcnt, p); + DEBUG_PRINT2 ("EXECUTING maybe_pop_jump %d.\n", mcnt); + { + register unsigned char *p2 = p; + + /* Compare the beginning of the repeat with what in the + pattern follows its end. If we can establish that there + is nothing that they would both match, i.e., that we + would have to backtrack because of (as in, e.g., `a*a') + then we can change to pop_failure_jump, because we'll + never have to backtrack. + + This is not true in the case of alternatives: in + `(a|ab)*' we do need to backtrack to the `ab' alternative + (e.g., if the string was `ab'). But instead of trying to + detect that here, the alternative has put on a dummy + failure point which is what we will end up popping. */ + + /* Skip over open/close-group commands. */ + while (p2 + 2 < pend + && ((re_opcode_t) *p2 == stop_memory + || (re_opcode_t) *p2 == start_memory)) + p2 += 3; /* Skip over args, too. */ + + /* If we're at the end of the pattern, we can change. */ + if (p2 == pend) + { + /* Consider what happens when matching ":\(.*\)" + against ":/". I don't really understand this code + yet. */ + p[-3] = (unsigned char) pop_failure_jump; + DEBUG_PRINT1 + (" End of pattern: change to `pop_failure_jump'.\n"); + } + + else if ((re_opcode_t) *p2 == exactn + || (bufp->newline_anchor && (re_opcode_t) *p2 == endline)) + { + register unsigned char c + = *p2 == (unsigned char) endline ? '\n' : p2[2]; + p1 = p + mcnt; + + /* p1[0] ... p1[2] are the `on_failure_jump' corresponding + to the `maybe_finalize_jump' of this case. Examine what + follows. */ + if ((re_opcode_t) p1[3] == exactn && p1[5] != c) + { + p[-3] = (unsigned char) pop_failure_jump; + DEBUG_PRINT3 (" %c != %c => pop_failure_jump.\n", + c, p1[5]); + } + + else if ((re_opcode_t) p1[3] == charset + || (re_opcode_t) p1[3] == charset_not) + { + int not = (re_opcode_t) p1[3] == charset_not; + + if (c < (unsigned char) (p1[4] * BYTEWIDTH) + && p1[5 + c / BYTEWIDTH] & (1 << (c % BYTEWIDTH))) + not = !not; + + /* `not' is equal to 1 if c would match, which means + that we can't change to pop_failure_jump. */ + if (!not) + { + p[-3] = (unsigned char) pop_failure_jump; + DEBUG_PRINT1 (" No match => pop_failure_jump.\n"); + } + } + } + } + p -= 2; /* Point at relative address again. */ + if ((re_opcode_t) p[-1] != pop_failure_jump) + { + p[-1] = (unsigned char) jump; + DEBUG_PRINT1 (" Match => jump.\n"); + goto unconditional_jump; + } + /* Note fall through. */ + + + /* The end of a simple repeat has a pop_failure_jump back to + its matching on_failure_jump, where the latter will push a + failure point. The pop_failure_jump takes off failure + points put on by this pop_failure_jump's matching + on_failure_jump; we got through the pattern to here from the + matching on_failure_jump, so didn't fail. */ + case pop_failure_jump: + { + /* We need to pass separate storage for the lowest and + highest registers, even though we don't care about the + actual values. Otherwise, we will restore only one + register from the stack, since lowest will == highest in + `pop_failure_point'. */ + unsigned dummy_low_reg, dummy_high_reg; + unsigned char *pdummy; + const char *sdummy; + + DEBUG_PRINT1 ("EXECUTING pop_failure_jump.\n"); + POP_FAILURE_POINT (sdummy, pdummy, + dummy_low_reg, dummy_high_reg, + reg_dummy, reg_dummy, reg_info_dummy); + } + /* Note fall through. */ + + + /* Unconditionally jump (without popping any failure points). */ + case jump: + unconditional_jump: + EXTRACT_NUMBER_AND_INCR (mcnt, p); /* Get the amount to jump. */ + DEBUG_PRINT2 ("EXECUTING jump %d ", mcnt); + p += mcnt; /* Do the jump. */ + DEBUG_PRINT2 ("(to 0x%x).\n", p); + break; + + + /* We need this opcode so we can detect where alternatives end + in `group_match_null_string_p' et al. */ + case jump_past_alt: + DEBUG_PRINT1 ("EXECUTING jump_past_alt.\n"); + goto unconditional_jump; + + + /* Normally, the on_failure_jump pushes a failure point, which + then gets popped at pop_failure_jump. We will end up at + pop_failure_jump, also, and with a pattern of, say, `a+', we + are skipping over the on_failure_jump, so we have to push + something meaningless for pop_failure_jump to pop. */ + case dummy_failure_jump: + DEBUG_PRINT1 ("EXECUTING dummy_failure_jump.\n"); + /* It doesn't matter what we push for the string here. What + the code at `fail' tests is the value for the pattern. */ + PUSH_FAILURE_POINT (0, 0, -2); + goto unconditional_jump; + + + /* At the end of an alternative, we need to push a dummy failure + point in case we are followed by a `pop_failure_jump', because + we don't want the failure point for the alternative to be + popped. For example, matching `(a|ab)*' against `aab' + requires that we match the `ab' alternative. */ + case push_dummy_failure: + DEBUG_PRINT1 ("EXECUTING push_dummy_failure.\n"); + /* See comments just above at `dummy_failure_jump' about the + two zeroes. */ + PUSH_FAILURE_POINT (0, 0, -2); + break; + + /* Have to succeed matching what follows at least n times. + After that, handle like `on_failure_jump'. */ + case succeed_n: + EXTRACT_NUMBER (mcnt, p + 2); + DEBUG_PRINT2 ("EXECUTING succeed_n %d.\n", mcnt); + + assert (mcnt >= 0); + /* Originally, this is how many times we HAVE to succeed. */ + if (mcnt > 0) + { + mcnt--; + p += 2; + STORE_NUMBER_AND_INCR (p, mcnt); + DEBUG_PRINT3 (" Setting 0x%x to %d.\n", p, mcnt); + } + else if (mcnt == 0) + { + DEBUG_PRINT2 (" Setting two bytes from 0x%x to no_op.\n", p+2); + p[2] = (unsigned char) no_op; + p[3] = (unsigned char) no_op; + goto on_failure; + } + break; + + case jump_n: + EXTRACT_NUMBER (mcnt, p + 2); + DEBUG_PRINT2 ("EXECUTING jump_n %d.\n", mcnt); + + /* Originally, this is how many times we CAN jump. */ + if (mcnt) + { + mcnt--; + STORE_NUMBER (p + 2, mcnt); + goto unconditional_jump; + } + /* If don't have to jump any more, skip over the rest of command. */ + else + p += 4; + break; + + case set_number_at: + { + DEBUG_PRINT1 ("EXECUTING set_number_at.\n"); + + EXTRACT_NUMBER_AND_INCR (mcnt, p); + p1 = p + mcnt; + EXTRACT_NUMBER_AND_INCR (mcnt, p); + DEBUG_PRINT3 (" Setting 0x%x to %d.\n", p1, mcnt); + STORE_NUMBER (p1, mcnt); + break; + } + + case wordbound: + DEBUG_PRINT1 ("EXECUTING wordbound.\n"); + if (AT_WORD_BOUNDARY (d)) + break; + goto fail; + + case notwordbound: + DEBUG_PRINT1 ("EXECUTING notwordbound.\n"); + if (AT_WORD_BOUNDARY (d)) + goto fail; + break; + + case wordbeg: + DEBUG_PRINT1 ("EXECUTING wordbeg.\n"); + if (WORDCHAR_P (d) && (AT_STRINGS_BEG (d) || !WORDCHAR_P (d - 1))) + break; + goto fail; + + case wordend: + DEBUG_PRINT1 ("EXECUTING wordend.\n"); + if (!AT_STRINGS_BEG (d) && WORDCHAR_P (d - 1) + && (!WORDCHAR_P (d) || AT_STRINGS_END (d))) + break; + goto fail; + +#ifdef emacs +#ifdef emacs19 + case before_dot: + DEBUG_PRINT1 ("EXECUTING before_dot.\n"); + if (PTR_CHAR_POS ((unsigned char *) d) >= point) + goto fail; + break; + + case at_dot: + DEBUG_PRINT1 ("EXECUTING at_dot.\n"); + if (PTR_CHAR_POS ((unsigned char *) d) != point) + goto fail; + break; + + case after_dot: + DEBUG_PRINT1 ("EXECUTING after_dot.\n"); + if (PTR_CHAR_POS ((unsigned char *) d) <= point) + goto fail; + break; +#else /* not emacs19 */ + case at_dot: + DEBUG_PRINT1 ("EXECUTING at_dot.\n"); + if (PTR_CHAR_POS ((unsigned char *) d) + 1 != point) + goto fail; + break; +#endif /* not emacs19 */ + + case syntaxspec: + DEBUG_PRINT2 ("EXECUTING syntaxspec %d.\n", mcnt); + mcnt = *p++; + goto matchsyntax; + + case wordchar: + DEBUG_PRINT1 ("EXECUTING Emacs wordchar.\n"); + mcnt = (int) Sword; + matchsyntax: + PREFETCH (); + if (SYNTAX (*d++) != (enum syntaxcode) mcnt) + goto fail; + SET_REGS_MATCHED (); + break; + + case notsyntaxspec: + DEBUG_PRINT2 ("EXECUTING notsyntaxspec %d.\n", mcnt); + mcnt = *p++; + goto matchnotsyntax; + + case notwordchar: + DEBUG_PRINT1 ("EXECUTING Emacs notwordchar.\n"); + mcnt = (int) Sword; + matchnotsyntax: + PREFETCH (); + if (SYNTAX (*d++) == (enum syntaxcode) mcnt) + goto fail; + SET_REGS_MATCHED (); + break; + +#else /* not emacs */ + case wordchar: + DEBUG_PRINT1 ("EXECUTING non-Emacs wordchar.\n"); + PREFETCH (); + if (!WORDCHAR_P (d)) + goto fail; + SET_REGS_MATCHED (); + d++; + break; + + case notwordchar: + DEBUG_PRINT1 ("EXECUTING non-Emacs notwordchar.\n"); + PREFETCH (); + if (WORDCHAR_P (d)) + goto fail; + SET_REGS_MATCHED (); + d++; + break; +#endif /* not emacs */ + + default: + abort (); + } + continue; /* Successfully executed one pattern command; keep going. */ + + + /* We goto here if a matching operation fails. */ + fail: + if (!FAIL_STACK_EMPTY ()) + { /* A restart point is known. Restore to that state. */ + DEBUG_PRINT1 ("\nFAIL:\n"); + POP_FAILURE_POINT (d, p, + lowest_active_reg, highest_active_reg, + regstart, regend, reg_info); + + /* If this failure point is a dummy, try the next one. */ + if (!p) + goto fail; + + /* If we failed to the end of the pattern, don't examine *p. */ + assert (p <= pend); + if (p < pend) + { + boolean is_a_jump_n = false; + + /* If failed to a backwards jump that's part of a repetition + loop, need to pop this failure point and use the next one. */ + switch ((re_opcode_t) *p) + { + case jump_n: + is_a_jump_n = true; + case maybe_pop_jump: + case pop_failure_jump: + case jump: + p1 = p + 1; + EXTRACT_NUMBER_AND_INCR (mcnt, p1); + p1 += mcnt; + + if ((is_a_jump_n && (re_opcode_t) *p1 == succeed_n) + || (!is_a_jump_n + && (re_opcode_t) *p1 == on_failure_jump)) + goto fail; + break; + default: + /* do nothing */ ; + } + } + + if (d >= string1 && d <= end1) + dend = end_match_1; + } + else + break; /* Matching at this starting point really fails. */ + } /* for (;;) */ + + if (best_regs_set) + goto restore_best_regs; + + FREE_VARIABLES (); + + return -1; /* Failure to match. */ +} /* re_match_2 */ + +/* Subroutine definitions for re_match_2. */ + + +/* We are passed P pointing to a register number after a start_memory. + + Return true if the pattern up to the corresponding stop_memory can + match the empty string, and false otherwise. + + If we find the matching stop_memory, sets P to point to one past its number. + Otherwise, sets P to an undefined byte less than or equal to END. + + We don't handle duplicates properly (yet). */ + +static boolean +group_match_null_string_p (p, end, reg_info) + unsigned char **p, *end; + register_info_type *reg_info; +{ + int mcnt; + /* Point to after the args to the start_memory. */ + unsigned char *p1 = *p + 2; + + while (p1 < end) + { + /* Skip over opcodes that can match nothing, and return true or + false, as appropriate, when we get to one that can't, or to the + matching stop_memory. */ + + switch ((re_opcode_t) *p1) + { + /* Could be either a loop or a series of alternatives. */ + case on_failure_jump: + p1++; + EXTRACT_NUMBER_AND_INCR (mcnt, p1); + + /* If the next operation is not a jump backwards in the + pattern. */ + + if (mcnt >= 0) + { + /* Go through the on_failure_jumps of the alternatives, + seeing if any of the alternatives cannot match nothing. + The last alternative starts with only a jump, + whereas the rest start with on_failure_jump and end + with a jump, e.g., here is the pattern for `a|b|c': + + /on_failure_jump/0/6/exactn/1/a/jump_past_alt/0/6 + /on_failure_jump/0/6/exactn/1/b/jump_past_alt/0/3 + /exactn/1/c + + So, we have to first go through the first (n-1) + alternatives and then deal with the last one separately. */ + + + /* Deal with the first (n-1) alternatives, which start + with an on_failure_jump (see above) that jumps to right + past a jump_past_alt. */ + + while ((re_opcode_t) p1[mcnt-3] == jump_past_alt) + { + /* `mcnt' holds how many bytes long the alternative + is, including the ending `jump_past_alt' and + its number. */ + + if (!alt_match_null_string_p (p1, p1 + mcnt - 3, + reg_info)) + return false; + + /* Move to right after this alternative, including the + jump_past_alt. */ + p1 += mcnt; + + /* Break if it's the beginning of an n-th alternative + that doesn't begin with an on_failure_jump. */ + if ((re_opcode_t) *p1 != on_failure_jump) + break; + + /* Still have to check that it's not an n-th + alternative that starts with an on_failure_jump. */ + p1++; + EXTRACT_NUMBER_AND_INCR (mcnt, p1); + if ((re_opcode_t) p1[mcnt-3] != jump_past_alt) + { + /* Get to the beginning of the n-th alternative. */ + p1 -= 3; + break; + } + } + + /* Deal with the last alternative: go back and get number + of the `jump_past_alt' just before it. `mcnt' contains + the length of the alternative. */ + EXTRACT_NUMBER (mcnt, p1 - 2); + + if (!alt_match_null_string_p (p1, p1 + mcnt, reg_info)) + return false; + + p1 += mcnt; /* Get past the n-th alternative. */ + } /* if mcnt > 0 */ + break; + + + case stop_memory: + assert (p1[1] == **p); + *p = p1 + 2; + return true; + + + default: + if (!common_op_match_null_string_p (&p1, end, reg_info)) + return false; + } + } /* while p1 < end */ + + return false; +} /* group_match_null_string_p */ + + +/* Similar to group_match_null_string_p, but doesn't deal with alternatives: + It expects P to be the first byte of a single alternative and END one + byte past the last. The alternative can contain groups. */ + +static boolean +alt_match_null_string_p (p, end, reg_info) + unsigned char *p, *end; + register_info_type *reg_info; +{ + int mcnt; + unsigned char *p1 = p; + + while (p1 < end) + { + /* Skip over opcodes that can match nothing, and break when we get + to one that can't. */ + + switch ((re_opcode_t) *p1) + { + /* It's a loop. */ + case on_failure_jump: + p1++; + EXTRACT_NUMBER_AND_INCR (mcnt, p1); + p1 += mcnt; + break; + + default: + if (!common_op_match_null_string_p (&p1, end, reg_info)) + return false; + } + } /* while p1 < end */ + + return true; +} /* alt_match_null_string_p */ + + +/* Deals with the ops common to group_match_null_string_p and + alt_match_null_string_p. + + Sets P to one after the op and its arguments, if any. */ + +static boolean +common_op_match_null_string_p (p, end, reg_info) + unsigned char **p, *end; + register_info_type *reg_info; +{ + int mcnt; + boolean ret; + int reg_no; + unsigned char *p1 = *p; + + switch ((re_opcode_t) *p1++) + { + case no_op: + case begline: + case endline: + case begbuf: + case endbuf: + case wordbeg: + case wordend: + case wordbound: + case notwordbound: +#ifdef emacs + case before_dot: + case at_dot: + case after_dot: +#endif + break; + + case start_memory: + reg_no = *p1; + assert (reg_no > 0 && reg_no <= MAX_REGNUM); + ret = group_match_null_string_p (&p1, end, reg_info); + + /* Have to set this here in case we're checking a group which + contains a group and a back reference to it. */ + + if (REG_MATCH_NULL_STRING_P (reg_info[reg_no]) == MATCH_NULL_UNSET_VALUE) + REG_MATCH_NULL_STRING_P (reg_info[reg_no]) = ret; + + if (!ret) + return false; + break; + + /* If this is an optimized succeed_n for zero times, make the jump. */ + case jump: + EXTRACT_NUMBER_AND_INCR (mcnt, p1); + if (mcnt >= 0) + p1 += mcnt; + else + return false; + break; + + case succeed_n: + /* Get to the number of times to succeed. */ + p1 += 2; + EXTRACT_NUMBER_AND_INCR (mcnt, p1); + + if (mcnt == 0) + { + p1 -= 4; + EXTRACT_NUMBER_AND_INCR (mcnt, p1); + p1 += mcnt; + } + else + return false; + break; + + case duplicate: + if (!REG_MATCH_NULL_STRING_P (reg_info[*p1])) + return false; + break; + + case set_number_at: + p1 += 4; + + default: + /* All other opcodes mean we cannot match the empty string. */ + return false; + } + + *p = p1; + return true; +} /* common_op_match_null_string_p */ + + +/* Return zero if TRANSLATE[S1] and TRANSLATE[S2] are identical for LEN + bytes; nonzero otherwise. */ + +static int +bcmp_translate (s1, s2, len, translate) + unsigned char *s1, *s2; + register int len; + char *translate; +{ + register unsigned char *p1 = s1, *p2 = s2; + while (len) + { + if (translate[*p1++] != translate[*p2++]) return 1; + len--; + } + return 0; +} + +/* Entry points for GNU code. */ + +/* re_compile_pattern is the GNU regular expression compiler: it + compiles PATTERN (of length SIZE) and puts the result in BUFP. + Returns 0 if the pattern was valid, otherwise an error string. + + Assumes the `allocated' (and perhaps `buffer') and `translate' fields + are set in BUFP on entry. + + We call regex_compile to do the actual compilation. */ + +const char * +re_compile_pattern (pattern, length, bufp) + const char *pattern; + int length; + struct re_pattern_buffer *bufp; +{ + reg_errcode_t ret; + + /* GNU code is written to assume at least RE_NREGS registers will be set + (and at least one extra will be -1). */ + bufp->regs_allocated = REGS_UNALLOCATED; + + /* And GNU code determines whether or not to get register information + by passing null for the REGS argument to re_match, etc., not by + setting no_sub. */ + bufp->no_sub = 0; + + /* Match anchors at newline. */ + bufp->newline_anchor = 1; + + ret = regex_compile (pattern, length, re_syntax_options, bufp); + + return re_error_msg[(int) ret]; +} + +/* Entry points compatible with 4.2 BSD regex library. We don't define + them if this is an Emacs or POSIX compilation. */ + +#if !defined (emacs) + +/* BSD has one and only one pattern buffer. */ +static struct re_pattern_buffer re_comp_buf; + +char * +re_comp (s) + const char *s; +{ + reg_errcode_t ret; + + if (!s) + { + if (!re_comp_buf.buffer) + return "No previous regular expression"; + return 0; + } + + if (!re_comp_buf.buffer) + { + re_comp_buf.buffer = (unsigned char *) malloc (200); + if (re_comp_buf.buffer == NULL) + return "Memory exhausted"; + re_comp_buf.allocated = 200; + + re_comp_buf.fastmap = (char *) malloc (1 << BYTEWIDTH); + if (re_comp_buf.fastmap == NULL) + return "Memory exhausted"; + } + + /* Since `re_exec' always passes NULL for the `regs' argument, we + don't need to initialize the pattern buffer fields which affect it. */ + + /* Match anchors at newlines. */ + re_comp_buf.newline_anchor = 1; + + ret = regex_compile (s, strlen (s), re_syntax_options, &re_comp_buf); + + /* Yes, we're discarding `const' here. */ + return (char *) re_error_msg[(int) ret]; +} + + +int +re_exec (s) + const char *s; +{ + const int len = strlen (s); + return + 0 <= re_search (&re_comp_buf, s, len, 0, len, (struct re_registers *) 0); +} +#endif /* not emacs and not _POSIX_SOURCE */ + +/* POSIX.2 functions. Don't define these for Emacs. */ + +#ifndef emacs + +/* regcomp takes a regular expression as a string and compiles it. + + PREG is a regex_t *. We do not expect any fields to be initialized, + since POSIX says we shouldn't. Thus, we set + + `buffer' to the compiled pattern; + `used' to the length of the compiled pattern; + `syntax' to RE_SYNTAX_POSIX_EXTENDED if the + REG_EXTENDED bit in CFLAGS is set; otherwise, to + RE_SYNTAX_POSIX_BASIC; + `newline_anchor' to REG_NEWLINE being set in CFLAGS; + `fastmap' and `fastmap_accurate' to zero; + `re_nsub' to the number of subexpressions in PATTERN. + + PATTERN is the address of the pattern string. + + CFLAGS is a series of bits which affect compilation. + + If REG_EXTENDED is set, we use POSIX extended syntax; otherwise, we + use POSIX basic syntax. + + If REG_NEWLINE is set, then . and [^...] don't match newline. + Also, regexec will try a match beginning after every newline. + + If REG_ICASE is set, then we considers upper- and lowercase + versions of letters to be equivalent when matching. + + If REG_NOSUB is set, then when PREG is passed to regexec, that + routine will report only success or failure, and nothing about the + registers. + + It returns 0 if it succeeds, nonzero if it doesn't. (See regex.h for + the return codes and their meanings.) */ + +int +regcomp (preg, pattern, cflags) + regex_t *preg; + const char *pattern; + int cflags; +{ + reg_errcode_t ret; + unsigned syntax + = (cflags & REG_EXTENDED) ? + RE_SYNTAX_POSIX_EXTENDED : RE_SYNTAX_POSIX_BASIC; + + /* regex_compile will allocate the space for the compiled pattern. */ + preg->buffer = 0; + preg->allocated = 0; + + /* Don't bother to use a fastmap when searching. This simplifies the + REG_NEWLINE case: if we used a fastmap, we'd have to put all the + characters after newlines into the fastmap. This way, we just try + every character. */ + preg->fastmap = 0; + + if (cflags & REG_ICASE) + { + unsigned i; + + preg->translate = (char *) malloc (CHAR_SET_SIZE); + if (preg->translate == NULL) + return (int) REG_ESPACE; + + /* Map uppercase characters to corresponding lowercase ones. */ + for (i = 0; i < CHAR_SET_SIZE; i++) + preg->translate[i] = ISUPPER (i) ? tolower (i) : i; + } + else + preg->translate = NULL; + + /* If REG_NEWLINE is set, newlines are treated differently. */ + if (cflags & REG_NEWLINE) + { /* REG_NEWLINE implies neither . nor [^...] match newline. */ + syntax &= ~RE_DOT_NEWLINE; + syntax |= RE_HAT_LISTS_NOT_NEWLINE; + /* It also changes the matching behavior. */ + preg->newline_anchor = 1; + } + else + preg->newline_anchor = 0; + + preg->no_sub = !!(cflags & REG_NOSUB); + + /* POSIX says a null character in the pattern terminates it, so we + can use strlen here in compiling the pattern. */ + ret = regex_compile (pattern, strlen (pattern), syntax, preg); + + /* POSIX doesn't distinguish between an unmatched open-group and an + unmatched close-group: both are REG_EPAREN. */ + if (ret == REG_ERPAREN) ret = REG_EPAREN; + + return (int) ret; +} + + +/* regexec searches for a given pattern, specified by PREG, in the + string STRING. + + If NMATCH is zero or REG_NOSUB was set in the cflags argument to + `regcomp', we ignore PMATCH. Otherwise, we assume PMATCH has at + least NMATCH elements, and we set them to the offsets of the + corresponding matched substrings. + + EFLAGS specifies `execution flags' which affect matching: if + REG_NOTBOL is set, then ^ does not match at the beginning of the + string; if REG_NOTEOL is set, then $ does not match at the end. + + We return 0 if we find a match and REG_NOMATCH if not. */ + +int +regexec (preg, string, nmatch, pmatch, eflags) + const regex_t *preg; + const char *string; + size_t nmatch; + regmatch_t pmatch[]; + int eflags; +{ + int ret; + struct re_registers regs; + regex_t private_preg; + int len = strlen (string); + boolean want_reg_info = !preg->no_sub && nmatch > 0; + + private_preg = *preg; + + private_preg.not_bol = !!(eflags & REG_NOTBOL); + private_preg.not_eol = !!(eflags & REG_NOTEOL); + + /* The user has told us exactly how many registers to return + information about, via `nmatch'. We have to pass that on to the + matching routines. */ + private_preg.regs_allocated = REGS_FIXED; + + if (want_reg_info) + { + regs.num_regs = nmatch; + regs.start = TALLOC (nmatch, regoff_t); + regs.end = TALLOC (nmatch, regoff_t); + if (regs.start == NULL || regs.end == NULL) + return (int) REG_NOMATCH; + } + + /* Perform the searching operation. */ + ret = re_search (&private_preg, string, len, + /* start: */ 0, /* range: */ len, + want_reg_info ? ®s : (struct re_registers *) 0); + + /* Copy the register information to the POSIX structure. */ + if (want_reg_info) + { + if (ret >= 0) + { + unsigned r; + + for (r = 0; r < nmatch; r++) + { + pmatch[r].rm_so = regs.start[r]; + pmatch[r].rm_eo = regs.end[r]; + } + } + + /* If we needed the temporary register info, free the space now. */ + free (regs.start); + free (regs.end); + } + + /* We want zero return to mean success, unlike `re_search'. */ + return ret >= 0 ? (int) REG_NOERROR : (int) REG_NOMATCH; +} + + +/* Returns a message corresponding to an error code, ERRCODE, returned + from either regcomp or regexec. We don't use PREG here. */ + +size_t +regerror (errcode, preg, errbuf, errbuf_size) + int errcode; + const regex_t *preg; + char *errbuf; + size_t errbuf_size; +{ + const char *msg; + size_t msg_size; + + if (errcode < 0 + || errcode >= (sizeof (re_error_msg) / sizeof (re_error_msg[0]))) + /* Only error codes returned by the rest of the code should be passed + to this routine. If we are given anything else, or if other regex + code generates an invalid error code, then the program has a bug. + Dump core so we can fix it. */ + abort (); + + msg = re_error_msg[errcode]; + + /* POSIX doesn't require that we do anything in this case, but why + not be nice. */ + if (! msg) + msg = "Success"; + + msg_size = strlen (msg) + 1; /* Includes the null. */ + + if (errbuf_size != 0) + { + if (msg_size > errbuf_size) + { + strncpy (errbuf, msg, errbuf_size - 1); + errbuf[errbuf_size - 1] = 0; + } + else + strcpy (errbuf, msg); + } + + return msg_size; +} + + +/* Free dynamically allocated space used by PREG. */ + +void +regfree (preg) + regex_t *preg; +{ + if (preg->buffer != NULL) + free (preg->buffer); + preg->buffer = NULL; + + preg->allocated = 0; + preg->used = 0; + + if (preg->fastmap != NULL) + free (preg->fastmap); + preg->fastmap = NULL; + preg->fastmap_accurate = 0; + + if (preg->translate != NULL) + free (preg->translate); + preg->translate = NULL; +} + +#endif /* not emacs */ + +/* +Local variables: +make-backup-files: t +version-control: t +trim-versions-without-asking: nil +End: +*/ diff --git a/contrib/cvs/lib/regex.h b/contrib/cvs/lib/regex.h new file mode 100644 index 0000000..408dd21 --- /dev/null +++ b/contrib/cvs/lib/regex.h @@ -0,0 +1,490 @@ +/* Definitions for data structures and routines for the regular + expression library, version 0.12. + + Copyright (C) 1985, 1989, 1990, 1991, 1992, 1993 Free Software Foundation, Inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2, or (at your option) + any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ + +#ifndef __REGEXP_LIBRARY_H__ +#define __REGEXP_LIBRARY_H__ + +/* POSIX says that <sys/types.h> must be included (by the caller) before + <regex.h>. */ + +#ifdef VMS +/* VMS doesn't have `size_t' in <sys/types.h>, even though POSIX says it + should be there. */ +#include <stddef.h> +#endif + + +/* The following bits are used to determine the regexp syntax we + recognize. The set/not-set meanings are chosen so that Emacs syntax + remains the value 0. The bits are given in alphabetical order, and + the definitions shifted by one from the previous bit; thus, when we + add or remove a bit, only one other definition need change. */ +typedef unsigned reg_syntax_t; + +/* If this bit is not set, then \ inside a bracket expression is literal. + If set, then such a \ quotes the following character. */ +#define RE_BACKSLASH_ESCAPE_IN_LISTS (1) + +/* If this bit is not set, then + and ? are operators, and \+ and \? are + literals. + If set, then \+ and \? are operators and + and ? are literals. */ +#define RE_BK_PLUS_QM (RE_BACKSLASH_ESCAPE_IN_LISTS << 1) + +/* If this bit is set, then character classes are supported. They are: + [:alpha:], [:upper:], [:lower:], [:digit:], [:alnum:], [:xdigit:], + [:space:], [:print:], [:punct:], [:graph:], and [:cntrl:]. + If not set, then character classes are not supported. */ +#define RE_CHAR_CLASSES (RE_BK_PLUS_QM << 1) + +/* If this bit is set, then ^ and $ are always anchors (outside bracket + expressions, of course). + If this bit is not set, then it depends: + ^ is an anchor if it is at the beginning of a regular + expression or after an open-group or an alternation operator; + $ is an anchor if it is at the end of a regular expression, or + before a close-group or an alternation operator. + + This bit could be (re)combined with RE_CONTEXT_INDEP_OPS, because + POSIX draft 11.2 says that * etc. in leading positions is undefined. + We already implemented a previous draft which made those constructs + invalid, though, so we haven't changed the code back. */ +#define RE_CONTEXT_INDEP_ANCHORS (RE_CHAR_CLASSES << 1) + +/* If this bit is set, then special characters are always special + regardless of where they are in the pattern. + If this bit is not set, then special characters are special only in + some contexts; otherwise they are ordinary. Specifically, + * + ? and intervals are only special when not after the beginning, + open-group, or alternation operator. */ +#define RE_CONTEXT_INDEP_OPS (RE_CONTEXT_INDEP_ANCHORS << 1) + +/* If this bit is set, then *, +, ?, and { cannot be first in an re or + immediately after an alternation or begin-group operator. */ +#define RE_CONTEXT_INVALID_OPS (RE_CONTEXT_INDEP_OPS << 1) + +/* If this bit is set, then . matches newline. + If not set, then it doesn't. */ +#define RE_DOT_NEWLINE (RE_CONTEXT_INVALID_OPS << 1) + +/* If this bit is set, then . doesn't match NUL. + If not set, then it does. */ +#define RE_DOT_NOT_NULL (RE_DOT_NEWLINE << 1) + +/* If this bit is set, nonmatching lists [^...] do not match newline. + If not set, they do. */ +#define RE_HAT_LISTS_NOT_NEWLINE (RE_DOT_NOT_NULL << 1) + +/* If this bit is set, either \{...\} or {...} defines an + interval, depending on RE_NO_BK_BRACES. + If not set, \{, \}, {, and } are literals. */ +#define RE_INTERVALS (RE_HAT_LISTS_NOT_NEWLINE << 1) + +/* If this bit is set, +, ? and | aren't recognized as operators. + If not set, they are. */ +#define RE_LIMITED_OPS (RE_INTERVALS << 1) + +/* If this bit is set, newline is an alternation operator. + If not set, newline is literal. */ +#define RE_NEWLINE_ALT (RE_LIMITED_OPS << 1) + +/* If this bit is set, then `{...}' defines an interval, and \{ and \} + are literals. + If not set, then `\{...\}' defines an interval. */ +#define RE_NO_BK_BRACES (RE_NEWLINE_ALT << 1) + +/* If this bit is set, (...) defines a group, and \( and \) are literals. + If not set, \(...\) defines a group, and ( and ) are literals. */ +#define RE_NO_BK_PARENS (RE_NO_BK_BRACES << 1) + +/* If this bit is set, then \<digit> matches <digit>. + If not set, then \<digit> is a back-reference. */ +#define RE_NO_BK_REFS (RE_NO_BK_PARENS << 1) + +/* If this bit is set, then | is an alternation operator, and \| is literal. + If not set, then \| is an alternation operator, and | is literal. */ +#define RE_NO_BK_VBAR (RE_NO_BK_REFS << 1) + +/* If this bit is set, then an ending range point collating higher + than the starting range point, as in [z-a], is invalid. + If not set, then when ending range point collates higher than the + starting range point, the range is ignored. */ +#define RE_NO_EMPTY_RANGES (RE_NO_BK_VBAR << 1) + +/* If this bit is set, then an unmatched ) is ordinary. + If not set, then an unmatched ) is invalid. */ +#define RE_UNMATCHED_RIGHT_PAREN_ORD (RE_NO_EMPTY_RANGES << 1) + +/* This global variable defines the particular regexp syntax to use (for + some interfaces). When a regexp is compiled, the syntax used is + stored in the pattern buffer, so changing this does not affect + already-compiled regexps. */ +extern reg_syntax_t re_syntax_options; + +/* Define combinations of the above bits for the standard possibilities. + (The [[[ comments delimit what gets put into the Texinfo file, so + don't delete them!) */ +/* [[[begin syntaxes]]] */ +#define RE_SYNTAX_EMACS 0 + +#define RE_SYNTAX_AWK \ + (RE_BACKSLASH_ESCAPE_IN_LISTS | RE_DOT_NOT_NULL \ + | RE_NO_BK_PARENS | RE_NO_BK_REFS \ + | RE_NO_BK_VBAR | RE_NO_EMPTY_RANGES \ + | RE_UNMATCHED_RIGHT_PAREN_ORD) + +#define RE_SYNTAX_POSIX_AWK \ + (RE_SYNTAX_POSIX_EXTENDED | RE_BACKSLASH_ESCAPE_IN_LISTS) + +#define RE_SYNTAX_GREP \ + (RE_BK_PLUS_QM | RE_CHAR_CLASSES \ + | RE_HAT_LISTS_NOT_NEWLINE | RE_INTERVALS \ + | RE_NEWLINE_ALT) + +#define RE_SYNTAX_EGREP \ + (RE_CHAR_CLASSES | RE_CONTEXT_INDEP_ANCHORS \ + | RE_CONTEXT_INDEP_OPS | RE_HAT_LISTS_NOT_NEWLINE \ + | RE_NEWLINE_ALT | RE_NO_BK_PARENS \ + | RE_NO_BK_VBAR) + +#define RE_SYNTAX_POSIX_EGREP \ + (RE_SYNTAX_EGREP | RE_INTERVALS | RE_NO_BK_BRACES) + +/* P1003.2/D11.2, section 4.20.7.1, lines 5078ff. */ +#define RE_SYNTAX_ED RE_SYNTAX_POSIX_BASIC + +#define RE_SYNTAX_SED RE_SYNTAX_POSIX_BASIC + +/* Syntax bits common to both basic and extended POSIX regex syntax. */ +#define _RE_SYNTAX_POSIX_COMMON \ + (RE_CHAR_CLASSES | RE_DOT_NEWLINE | RE_DOT_NOT_NULL \ + | RE_INTERVALS | RE_NO_EMPTY_RANGES) + +#define RE_SYNTAX_POSIX_BASIC \ + (_RE_SYNTAX_POSIX_COMMON | RE_BK_PLUS_QM) + +/* Differs from ..._POSIX_BASIC only in that RE_BK_PLUS_QM becomes + RE_LIMITED_OPS, i.e., \? \+ \| are not recognized. Actually, this + isn't minimal, since other operators, such as \`, aren't disabled. */ +#define RE_SYNTAX_POSIX_MINIMAL_BASIC \ + (_RE_SYNTAX_POSIX_COMMON | RE_LIMITED_OPS) + +#define RE_SYNTAX_POSIX_EXTENDED \ + (_RE_SYNTAX_POSIX_COMMON | RE_CONTEXT_INDEP_ANCHORS \ + | RE_CONTEXT_INDEP_OPS | RE_NO_BK_BRACES \ + | RE_NO_BK_PARENS | RE_NO_BK_VBAR \ + | RE_UNMATCHED_RIGHT_PAREN_ORD) + +/* Differs from ..._POSIX_EXTENDED in that RE_CONTEXT_INVALID_OPS + replaces RE_CONTEXT_INDEP_OPS and RE_NO_BK_REFS is added. */ +#define RE_SYNTAX_POSIX_MINIMAL_EXTENDED \ + (_RE_SYNTAX_POSIX_COMMON | RE_CONTEXT_INDEP_ANCHORS \ + | RE_CONTEXT_INVALID_OPS | RE_NO_BK_BRACES \ + | RE_NO_BK_PARENS | RE_NO_BK_REFS \ + | RE_NO_BK_VBAR | RE_UNMATCHED_RIGHT_PAREN_ORD) +/* [[[end syntaxes]]] */ + +/* Maximum number of duplicates an interval can allow. Some systems + (erroneously) define this in other header files, but we want our + value, so remove any previous define. */ +#ifdef RE_DUP_MAX +#undef RE_DUP_MAX +#endif +#define RE_DUP_MAX ((1 << 15) - 1) + + +/* POSIX `cflags' bits (i.e., information for `regcomp'). */ + +/* If this bit is set, then use extended regular expression syntax. + If not set, then use basic regular expression syntax. */ +#define REG_EXTENDED 1 + +/* If this bit is set, then ignore case when matching. + If not set, then case is significant. */ +#define REG_ICASE (REG_EXTENDED << 1) + +/* If this bit is set, then anchors do not match at newline + characters in the string. + If not set, then anchors do match at newlines. */ +#define REG_NEWLINE (REG_ICASE << 1) + +/* If this bit is set, then report only success or fail in regexec. + If not set, then returns differ between not matching and errors. */ +#define REG_NOSUB (REG_NEWLINE << 1) + + +/* POSIX `eflags' bits (i.e., information for regexec). */ + +/* If this bit is set, then the beginning-of-line operator doesn't match + the beginning of the string (presumably because it's not the + beginning of a line). + If not set, then the beginning-of-line operator does match the + beginning of the string. */ +#define REG_NOTBOL 1 + +/* Like REG_NOTBOL, except for the end-of-line. */ +#define REG_NOTEOL (1 << 1) + + +/* If any error codes are removed, changed, or added, update the + `re_error_msg' table in regex.c. */ +typedef enum +{ + REG_NOERROR = 0, /* Success. */ + REG_NOMATCH, /* Didn't find a match (for regexec). */ + + /* POSIX regcomp return error codes. (In the order listed in the + standard.) */ + REG_BADPAT, /* Invalid pattern. */ + REG_ECOLLATE, /* Not implemented. */ + REG_ECTYPE, /* Invalid character class name. */ + REG_EESCAPE, /* Trailing backslash. */ + REG_ESUBREG, /* Invalid back reference. */ + REG_EBRACK, /* Unmatched left bracket. */ + REG_EPAREN, /* Parenthesis imbalance. */ + REG_EBRACE, /* Unmatched \{. */ + REG_BADBR, /* Invalid contents of \{\}. */ + REG_ERANGE, /* Invalid range end. */ + REG_ESPACE, /* Ran out of memory. */ + REG_BADRPT, /* No preceding re for repetition op. */ + + /* Error codes we've added. */ + REG_EEND, /* Premature end. */ + REG_ESIZE, /* Compiled pattern bigger than 2^16 bytes. */ + REG_ERPAREN /* Unmatched ) or \); not returned from regcomp. */ +} reg_errcode_t; + +/* This data structure represents a compiled pattern. Before calling + the pattern compiler, the fields `buffer', `allocated', `fastmap', + `translate', and `no_sub' can be set. After the pattern has been + compiled, the `re_nsub' field is available. All other fields are + private to the regex routines. */ + +struct re_pattern_buffer +{ +/* [[[begin pattern_buffer]]] */ + /* Space that holds the compiled pattern. It is declared as + `unsigned char *' because its elements are + sometimes used as array indexes. */ + unsigned char *buffer; + + /* Number of bytes to which `buffer' points. */ + unsigned long allocated; + + /* Number of bytes actually used in `buffer'. */ + unsigned long used; + + /* Syntax setting with which the pattern was compiled. */ + reg_syntax_t syntax; + + /* Pointer to a fastmap, if any, otherwise zero. re_search uses + the fastmap, if there is one, to skip over impossible + starting points for matches. */ + char *fastmap; + + /* Either a translate table to apply to all characters before + comparing them, or zero for no translation. The translation + is applied to a pattern when it is compiled and to a string + when it is matched. */ + char *translate; + + /* Number of subexpressions found by the compiler. */ + size_t re_nsub; + + /* Zero if this pattern cannot match the empty string, one else. + Well, in truth it's used only in `re_search_2', to see + whether or not we should use the fastmap, so we don't set + this absolutely perfectly; see `re_compile_fastmap' (the + `duplicate' case). */ + unsigned can_be_null : 1; + + /* If REGS_UNALLOCATED, allocate space in the `regs' structure + for `max (RE_NREGS, re_nsub + 1)' groups. + If REGS_REALLOCATE, reallocate space if necessary. + If REGS_FIXED, use what's there. */ +#define REGS_UNALLOCATED 0 +#define REGS_REALLOCATE 1 +#define REGS_FIXED 2 + unsigned regs_allocated : 2; + + /* Set to zero when `regex_compile' compiles a pattern; set to one + by `re_compile_fastmap' if it updates the fastmap. */ + unsigned fastmap_accurate : 1; + + /* If set, `re_match_2' does not return information about + subexpressions. */ + unsigned no_sub : 1; + + /* If set, a beginning-of-line anchor doesn't match at the + beginning of the string. */ + unsigned not_bol : 1; + + /* Similarly for an end-of-line anchor. */ + unsigned not_eol : 1; + + /* If true, an anchor at a newline matches. */ + unsigned newline_anchor : 1; + +/* [[[end pattern_buffer]]] */ +}; + +typedef struct re_pattern_buffer regex_t; + + +/* search.c (search_buffer) in Emacs needs this one opcode value. It is + defined both in `regex.c' and here. */ +#define RE_EXACTN_VALUE 1 + +/* Type for byte offsets within the string. POSIX mandates this. */ +typedef int regoff_t; + + +/* This is the structure we store register match data in. See + regex.texinfo for a full description of what registers match. */ +struct re_registers +{ + unsigned num_regs; + regoff_t *start; + regoff_t *end; +}; + + +/* If `regs_allocated' is REGS_UNALLOCATED in the pattern buffer, + `re_match_2' returns information about at least this many registers + the first time a `regs' structure is passed. */ +#ifndef RE_NREGS +#define RE_NREGS 30 +#endif + + +/* POSIX specification for registers. Aside from the different names than + `re_registers', POSIX uses an array of structures, instead of a + structure of arrays. */ +typedef struct +{ + regoff_t rm_so; /* Byte offset from string's start to substring's start. */ + regoff_t rm_eo; /* Byte offset from string's start to substring's end. */ +} regmatch_t; + +/* Declarations for routines. */ + +/* To avoid duplicating every routine declaration -- once with a + prototype (if we are ANSI), and once without (if we aren't) -- we + use the following macro to declare argument types. This + unfortunately clutters up the declarations a bit, but I think it's + worth it. */ + +#if __STDC__ + +#define _RE_ARGS(args) args + +#else /* not __STDC__ */ + +#define _RE_ARGS(args) () + +#endif /* not __STDC__ */ + +/* Sets the current default syntax to SYNTAX, and return the old syntax. + You can also simply assign to the `re_syntax_options' variable. */ +extern reg_syntax_t re_set_syntax _RE_ARGS ((reg_syntax_t syntax)); + +/* Compile the regular expression PATTERN, with length LENGTH + and syntax given by the global `re_syntax_options', into the buffer + BUFFER. Return NULL if successful, and an error string if not. */ +extern const char *re_compile_pattern + _RE_ARGS ((const char *pattern, int length, + struct re_pattern_buffer *buffer)); + + +/* Compile a fastmap for the compiled pattern in BUFFER; used to + accelerate searches. Return 0 if successful and -2 if was an + internal error. */ +extern int re_compile_fastmap _RE_ARGS ((struct re_pattern_buffer *buffer)); + + +/* Search in the string STRING (with length LENGTH) for the pattern + compiled into BUFFER. Start searching at position START, for RANGE + characters. Return the starting position of the match, -1 for no + match, or -2 for an internal error. Also return register + information in REGS (if REGS and BUFFER->no_sub are nonzero). */ +extern int re_search + _RE_ARGS ((struct re_pattern_buffer *buffer, const char *string, + int length, int start, int range, struct re_registers *regs)); + + +/* Like `re_search', but search in the concatenation of STRING1 and + STRING2. Also, stop searching at index START + STOP. */ +extern int re_search_2 + _RE_ARGS ((struct re_pattern_buffer *buffer, const char *string1, + int length1, const char *string2, int length2, + int start, int range, struct re_registers *regs, int stop)); + + +/* Like `re_search', but return how many characters in STRING the regexp + in BUFFER matched, starting at position START. */ +extern int re_match + _RE_ARGS ((struct re_pattern_buffer *buffer, const char *string, + int length, int start, struct re_registers *regs)); + + +/* Relates to `re_match' as `re_search_2' relates to `re_search'. */ +extern int re_match_2 + _RE_ARGS ((struct re_pattern_buffer *buffer, const char *string1, + int length1, const char *string2, int length2, + int start, struct re_registers *regs, int stop)); + + +/* Set REGS to hold NUM_REGS registers, storing them in STARTS and + ENDS. Subsequent matches using BUFFER and REGS will use this memory + for recording register information. STARTS and ENDS must be + allocated with malloc, and must each be at least `NUM_REGS * sizeof + (regoff_t)' bytes long. + + If NUM_REGS == 0, then subsequent matches should allocate their own + register data. + + Unless this function is called, the first search or match using + PATTERN_BUFFER will allocate its own register data, without + freeing the old data. */ +extern void re_set_registers + _RE_ARGS ((struct re_pattern_buffer *buffer, struct re_registers *regs, + unsigned num_regs, regoff_t *starts, regoff_t *ends)); + +/* 4.2 bsd compatibility. */ +extern char *re_comp _RE_ARGS ((const char *)); +extern int re_exec _RE_ARGS ((const char *)); + +/* POSIX compatibility. */ +extern int regcomp _RE_ARGS ((regex_t *preg, const char *pattern, int cflags)); +extern int regexec + _RE_ARGS ((const regex_t *preg, const char *string, size_t nmatch, + regmatch_t pmatch[], int eflags)); +extern size_t regerror + _RE_ARGS ((int errcode, const regex_t *preg, char *errbuf, + size_t errbuf_size)); +extern void regfree _RE_ARGS ((regex_t *preg)); + +#endif /* not __REGEXP_LIBRARY_H__ */ + +/* +Local variables: +make-backup-files: t +version-control: t +trim-versions-without-asking: nil +End: +*/ diff --git a/contrib/cvs/lib/rename.c b/contrib/cvs/lib/rename.c new file mode 100644 index 0000000..ce2805b --- /dev/null +++ b/contrib/cvs/lib/rename.c @@ -0,0 +1,84 @@ +/* rename.c -- BSD compatible directory function for System V + Copyright (C) 1988, 1990 Free Software Foundation, Inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2, or (at your option) + any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <sys/types.h> +#include <sys/stat.h> +#include <errno.h> +#ifndef STDC_HEADERS +extern int errno; +#endif + +/* Rename file FROM to file TO. + Return 0 if successful, -1 if not. */ + +int +rename (from, to) + char *from; + char *to; +{ + struct stat from_stats; + int pid, status; + + if (stat (from, &from_stats) == 0) + { + /* We don't check existence_error because the systems which need it + have rename(). */ + if (unlink (to) && errno != ENOENT) + return -1; + if ((from_stats.st_mode & S_IFMT) == S_IFDIR) + { +#ifdef MVDIR + /* I don't think MVDIR ever gets defined, but I don't think + it matters, because I don't think CVS ever calls rename() + on directories. */ + + /* Need a setuid root process to link and unlink directories. */ + pid = fork (); + switch (pid) + { + case -1: /* Error. */ + error (1, errno, "cannot fork"); + + case 0: /* Child. */ + execl (MVDIR, "mvdir", from, to, (char *) 0); + error (255, errno, "cannot run `%s'", MVDIR); + + default: /* Parent. */ + while (wait (&status) != pid) + /* Do nothing. */ ; + + errno = 0; /* mvdir printed the system error message. */ + return status != 0 ? -1 : 0; + } +#else /* no MVDIR */ + error (1, 0, "internal error: cannot move directories"); +#endif /* no MVDIR */ + } + else + { + /* We don't check existence_error because the systems which need it + have rename(). */ + if (link (from, to) == 0 && (unlink (from) == 0 || errno == ENOENT)) + return 0; + } + } + return -1; +} diff --git a/contrib/cvs/lib/savecwd.c b/contrib/cvs/lib/savecwd.c new file mode 100644 index 0000000..b5b7cb8 --- /dev/null +++ b/contrib/cvs/lib/savecwd.c @@ -0,0 +1,141 @@ +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include <stdio.h> + +#ifdef STDC_HEADERS +# include <stdlib.h> +#endif + +#ifdef HAVE_UNISTD_H +# include <unistd.h> +#endif + +#ifdef HAVE_FCNTL_H +# include <fcntl.h> +#else +# include <sys/file.h> +#endif + +#ifdef HAVE_DIRECT_H +# include <direct.h> +#endif + +#ifdef HAVE_IO_H +# include <io.h> +#endif + +#include <errno.h> +# ifndef errno +extern int errno; +#endif + +#include "savecwd.h" +#include "error.h" + +char *xgetwd __PROTO((void)); + +/* Record the location of the current working directory in CWD so that + the program may change to other directories and later use restore_cwd + to return to the recorded location. This function may allocate + space using malloc (via xgetwd) or leave a file descriptor open; + use free_cwd to perform the necessary free or close. Upon failure, + no memory is allocated, any locally opened file descriptors are + closed; return non-zero -- in that case, free_cwd need not be + called, but doing so is ok. Otherwise, return zero. */ + +int +save_cwd (cwd) + struct saved_cwd *cwd; +{ + static int have_working_fchdir = 1; + + cwd->desc = -1; + cwd->name = NULL; + + if (have_working_fchdir) + { +#ifdef HAVE_FCHDIR + cwd->desc = open (".", O_RDONLY); + if (cwd->desc < 0) + { + error (0, errno, "cannot open current directory"); + return 1; + } + +# if __sun__ || sun + /* On SunOS 4, fchdir returns EINVAL if accounting is enabled, + so we have to fall back to chdir. */ + if (fchdir (cwd->desc)) + { + if (errno == EINVAL) + { + close (cwd->desc); + cwd->desc = -1; + have_working_fchdir = 0; + } + else + { + error (0, errno, "current directory"); + close (cwd->desc); + cwd->desc = -1; + return 1; + } + } +# endif /* __sun__ || sun */ +#else +#define fchdir(x) (abort (), 0) + have_working_fchdir = 0; +#endif + } + + if (!have_working_fchdir) + { + cwd->name = xgetwd (); + if (cwd->name == NULL) + { + error (0, errno, "cannot get current directory"); + return 1; + } + } + return 0; +} + +/* Change to recorded location, CWD, in directory hierarchy. + If "saved working directory", NULL)) + */ + +int +restore_cwd (cwd, dest) + const struct saved_cwd *cwd; + const char *dest; +{ + int fail = 0; + if (cwd->desc >= 0) + { + if (fchdir (cwd->desc)) + { + error (0, errno, "cannot return to %s", + (dest ? dest : "saved working directory")); + fail = 1; + } + } + else if (chdir (cwd->name) < 0) + { + error (0, errno, "%s", cwd->name); + fail = 1; + } + return fail; +} + +void +free_cwd (cwd) + struct saved_cwd *cwd; +{ + if (cwd->desc >= 0) + close (cwd->desc); + if (cwd->name) + free (cwd->name); +} + diff --git a/contrib/cvs/lib/savecwd.h b/contrib/cvs/lib/savecwd.h new file mode 100644 index 0000000..f9802f8 --- /dev/null +++ b/contrib/cvs/lib/savecwd.h @@ -0,0 +1,20 @@ +#ifndef SAVE_CWD_H +#define SAVE_CWD_H 1 + +struct saved_cwd + { + int desc; + char *name; + }; + +#if defined (__GNUC__) || (defined (__STDC__) && __STDC__) +#define __PROTO(args) args +#else +#define __PROTO(args) () +#endif /* GCC. */ + +int save_cwd __PROTO((struct saved_cwd *cwd)); +int restore_cwd __PROTO((const struct saved_cwd *cwd, const char *dest)); +void free_cwd __PROTO((struct saved_cwd *cwd)); + +#endif /* SAVE_CWD_H */ diff --git a/contrib/cvs/lib/sighandle.c b/contrib/cvs/lib/sighandle.c new file mode 100644 index 0000000..ace7db3 --- /dev/null +++ b/contrib/cvs/lib/sighandle.c @@ -0,0 +1,414 @@ +/* sighandle.c -- Library routines for manipulating chains of signal handlers + Copyright (C) 1992 Free Software Foundation, Inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2, or (at your option) + any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ + +/* Written by Paul Sander, HaL Computer Systems, Inc. <paul@hal.com> + Brian Berliner <berliner@Sun.COM> added POSIX support */ + +/************************************************************************* + * + * signal.c -- This file contains code that manipulates chains of signal + * handlers. + * + * Facilities are provided to register a signal handler for + * any specific signal. When a signal is received, all of the + * registered signal handlers are invoked in the reverse order + * in which they are registered. Note that the signal handlers + * must not themselves make calls to the signal handling + * facilities. + * + * $CVSid: @(#)sighandle.c 1.13 94/10/07 $ + * + *************************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif +#include "system.h" + +#include <sys/types.h> +#include <stdio.h> +#include <signal.h> + +/* Add prototype support. */ +#ifndef PROTO +#if defined (USE_PROTOTYPES) ? USE_PROTOTYPES : defined (__STDC__) +#define PROTO(ARGS) ARGS +#else +#define PROTO(ARGS) () +#endif +#endif + +#ifdef STDC_HEADERS +#include <stdlib.h> +#else +#if __STDC__ +char *calloc(unsigned nelem, unsigned size); +char *malloc(unsigned size); +#else +char *calloc(); +char *malloc(); +#endif /* __STDC__ */ +#endif /* STDC_HEADERS */ + +/* Define the highest signal number (usually) */ +#ifndef SIGMAX +#define SIGMAX 64 +#endif + +/* Define linked list of signal handlers structure */ +struct SIG_hlist { + RETSIGTYPE (*handler)(); + struct SIG_hlist *next; +}; + +/* + * Define array of lists of signal handlers. Note that this depends on + * the implementation to initialize each element to a null pointer. + */ + +static struct SIG_hlist **SIG_handlers; + +/* Define array of default signal vectors */ + +#ifdef POSIX_SIGNALS +static struct sigaction *SIG_defaults; +#else +#ifdef BSD_SIGNALS +static struct sigvec *SIG_defaults; +#else +static RETSIGTYPE (**SIG_defaults) PROTO ((int)); +#endif +#endif + +/* Critical section housekeeping */ +static int SIG_crSectNest = 0; /* Nesting level */ +#ifdef POSIX_SIGNALS +static sigset_t SIG_crSectMask; /* Signal mask */ +#else +static int SIG_crSectMask; /* Signal mask */ +#endif + +/* + * Initialize the signal handler arrays + */ + +static int SIG_init() +{ + int i; +#ifdef POSIX_SIGNALS + sigset_t sigset_test; +#endif + + if (SIG_defaults && SIG_handlers) /* already allocated */ + return (0); + +#ifdef POSIX_SIGNALS + (void) sigfillset(&sigset_test); + for (i = 1; i < SIGMAX && sigismember(&sigset_test, i) == 1; i++) + ; + if (i < SIGMAX) + i = SIGMAX; + i++; + if (!SIG_defaults) + SIG_defaults = (struct sigaction *) + calloc(i, sizeof(struct sigaction)); + (void) sigemptyset(&SIG_crSectMask); +#else + i = SIGMAX+1; +#ifdef BSD_SIGNALS + if (!SIG_defaults) + SIG_defaults = (struct sigvec *) + calloc(i, sizeof(struct sigvec)); +#else + if (!SIG_defaults) + SIG_defaults = (RETSIGTYPE (**) PROTO ((int)) ) + calloc(i, sizeof(RETSIGTYPE (**) PROTO ((int)) )); +#endif + SIG_crSectMask = 0; +#endif + if (!SIG_handlers) + SIG_handlers = (struct SIG_hlist **) + calloc(i, sizeof(struct SIG_hlist *)); + return (!SIG_defaults || !SIG_handlers); +} + +/* + * The following invokes each signal handler in the reverse order in which + * they were registered. + */ +static RETSIGTYPE SIG_handle PROTO ((int)); + +static RETSIGTYPE SIG_handle(sig) +int sig; +{ + struct SIG_hlist *this; + + /* Dispatch signal handlers */ + this = SIG_handlers[sig]; + while (this != (struct SIG_hlist *) NULL) + { + (*this->handler)(sig); + this = this->next; + } + + return; +} + +/* + * The following registers a signal handler. If the handler is already + * registered, it is not registered twice, nor is the order in which signal + * handlers are invoked changed. If this is the first signal handler + * registered for a given signal, the old sigvec structure is saved for + * restoration later. + */ + +int SIG_register(sig,fn) +int sig; +RETSIGTYPE (*fn)(); +{ + int val; + struct SIG_hlist *this; +#ifdef POSIX_SIGNALS + struct sigaction act; + sigset_t sigset_mask, sigset_omask; +#else +#ifdef BSD_SIGNALS + struct sigvec vec; + int mask; +#endif +#endif + + /* Initialize */ + if (SIG_init() != 0) + return (-1); + val = 0; + + /* Block this signal while we look at handler chain */ +#ifdef POSIX_SIGNALS + (void) sigemptyset(&sigset_mask); + (void) sigaddset(&sigset_mask, sig); + (void) sigprocmask(SIG_BLOCK, &sigset_mask, &sigset_omask); +#else +#ifdef BSD_SIGNALS + mask = sigblock(sigmask(sig)); +#endif +#endif + + /* See if this handler was already registered */ + this = SIG_handlers[sig]; + while (this != (struct SIG_hlist *) NULL) + { + if (this->handler == fn) break; + this = this->next; + } + + /* Register the new handler only if it is not already registered. */ + if (this == (struct SIG_hlist *) NULL) + { + + /* + * If this is the first handler registered for this signal, + * set up the signal handler dispatcher + */ + + if (SIG_handlers[sig] == (struct SIG_hlist *) NULL) + { +#ifdef POSIX_SIGNALS + act.sa_handler = SIG_handle; + (void) sigemptyset(&act.sa_mask); + act.sa_flags = 0; + val = sigaction(sig, &act, &SIG_defaults[sig]); +#else +#ifdef BSD_SIGNALS + memset (&vec, 0, sizeof (vec)); + vec.sv_handler = SIG_handle; + val = sigvec(sig, &vec, &SIG_defaults[sig]); +#else + if ((SIG_defaults[sig] = signal(sig, SIG_handle)) == SIG_ERR) + val = -1; +#endif +#endif + } + + /* If not, register it */ + if ((val == 0) && (this == (struct SIG_hlist *) NULL)) + { + this = (struct SIG_hlist *) + malloc(sizeof(struct SIG_hlist)); + if (this == NULL) + { + val = -1; + } + else + { + this->handler = fn; + this->next = SIG_handlers[sig]; + SIG_handlers[sig] = this; + } + } + } + + /* Unblock the signal */ +#ifdef POSIX_SIGNALS + (void) sigprocmask(SIG_SETMASK, &sigset_omask, NULL); +#else +#ifdef BSD_SIGNALS + (void) sigsetmask(mask); +#endif +#endif + + return val; +} + +/* + * The following deregisters a signal handler. If the last signal handler for + * a given signal is deregistered, the default sigvec information is restored. + */ + +int SIG_deregister(sig,fn) +int sig; +RETSIGTYPE (*fn)(); +{ + int val; + struct SIG_hlist *this; + struct SIG_hlist *last; +#ifdef POSIX_SIGNALS + sigset_t sigset_mask, sigset_omask; +#else +#ifdef BSD_SIGNALS + int mask; +#endif +#endif + + /* Initialize */ + if (SIG_init() != 0) + return (-1); + val = 0; + last = (struct SIG_hlist *) NULL; + + /* Block this signal while we look at handler chain */ +#ifdef POSIX_SIGNALS + (void) sigemptyset(&sigset_mask); + (void) sigaddset(&sigset_mask, sig); + (void) sigprocmask(SIG_BLOCK, &sigset_mask, &sigset_omask); +#else +#ifdef BSD_SIGNALS + mask = sigblock(sigmask(sig)); +#endif +#endif + + /* Search for the signal handler */ + this = SIG_handlers[sig]; + while ((this != (struct SIG_hlist *) NULL) && (this->handler != fn)) + { + last = this; + this = this->next; + } + + /* If it was registered, remove it */ + if (this != (struct SIG_hlist *) NULL) + { + if (last == (struct SIG_hlist *) NULL) + { + SIG_handlers[sig] = this->next; + } + else + { + last->next = this->next; + } + free((char *) this); + } + + /* Restore default behavior if there are no registered handlers */ + if (SIG_handlers[sig] == (struct SIG_hlist *) NULL) + { +#ifdef POSIX_SIGNALS + val = sigaction(sig, &SIG_defaults[sig], + (struct sigaction *) NULL); +#else +#ifdef BSD_SIGNALS + val = sigvec(sig, &SIG_defaults[sig], (struct sigvec *) NULL); +#else + if (signal(sig, SIG_defaults[sig]) == SIG_ERR) + val = -1; +#endif +#endif + } + + /* Unblock the signal */ +#ifdef POSIX_SIGNALS + (void) sigprocmask(SIG_SETMASK, &sigset_omask, NULL); +#else +#ifdef BSD_SIGNALS + (void) sigsetmask(mask); +#endif +#endif + + return val; +} + +/* + * The following begins a critical section. + */ + +void SIG_beginCrSect() +{ + if (SIG_init() == 0) + { + if (SIG_crSectNest == 0) + { +#ifdef POSIX_SIGNALS + sigset_t sigset_mask; + + (void) sigfillset(&sigset_mask); + (void) sigprocmask(SIG_SETMASK, + &sigset_mask, &SIG_crSectMask); +#else +#ifdef BSD_SIGNALS + SIG_crSectMask = sigblock(~0); +#else + /* TBD */ +#endif +#endif + } + SIG_crSectNest++; + } +} + +/* + * The following ends a critical section. + */ + +void SIG_endCrSect() +{ + if (SIG_init() == 0) + { + SIG_crSectNest--; + if (SIG_crSectNest == 0) + { +#ifdef POSIX_SIGNALS + (void) sigprocmask(SIG_SETMASK, &SIG_crSectMask, NULL); +#else +#ifdef BSD_SIGNALS + (void) sigsetmask(SIG_crSectMask); +#else + /* TBD */ +#endif +#endif + } + } +} diff --git a/contrib/cvs/lib/strdup.c b/contrib/cvs/lib/strdup.c new file mode 100644 index 0000000..46fc8a0 --- /dev/null +++ b/contrib/cvs/lib/strdup.c @@ -0,0 +1,43 @@ +/* strdup.c -- return a newly allocated copy of a string + Copyright (C) 1990 Free Software Foundation, Inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2, or (at your option) + any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifdef STDC_HEADERS +#include <string.h> +#include <stdlib.h> +#else +char *malloc (); +char *strcpy (); +#endif + +/* Return a newly allocated copy of STR, + or 0 if out of memory. */ + +char * +strdup (str) + char *str; +{ + char *newstr; + + newstr = (char *) malloc (strlen (str) + 1); + if (newstr) + strcpy (newstr, str); + return newstr; +} diff --git a/contrib/cvs/lib/strerror.c b/contrib/cvs/lib/strerror.c new file mode 100644 index 0000000..b0bec13 --- /dev/null +++ b/contrib/cvs/lib/strerror.c @@ -0,0 +1,813 @@ +/* Extended support for using errno values. + Copyright (C) 1992 Free Software Foundation, Inc. + Written by Fred Fish. fnf@cygnus.com + +This file is part of the libiberty library. +Libiberty is free software; you can redistribute it and/or +modify it under the terms of the GNU Library General Public +License as published by the Free Software Foundation; either +version 2 of the License, or (at your option) any later version. + +Libiberty 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 +Library General Public License for more details. + +You should have received a copy of the GNU Library General Public +License along with libiberty; see the file COPYING.LIB. If +not, write to the Free Software Foundation, Inc., 675 Mass Ave, +Cambridge, MA 02139, USA. */ + +#include "config.h" + +#ifndef NEED_sys_errlist +/* Note that errno.h (not sure what OS) or stdio.h (BSD 4.4, at least) + might declare sys_errlist in a way that the compiler might consider + incompatible with our later declaration, perhaps by using const + attributes. So we hide the declaration in errno.h (if any) using a + macro. */ +#define sys_errlist sys_errlist__ +#endif + +#include <stdio.h> +#include <errno.h> + +#ifndef NEED_sys_errlist +#undef sys_errlist +#endif + +/* Routines imported from standard C runtime libraries. */ + +#ifdef __STDC__ +#include <stddef.h> +extern void *malloc (size_t size); /* 4.10.3.3 */ +extern void *memset (void *s, int c, size_t n); /* 4.11.6.1 */ +#else /* !__STDC__ */ +extern char *malloc (); /* Standard memory allocater */ +extern char *memset (); +#endif /* __STDC__ */ + +#ifndef MAX +# define MAX(a,b) ((a) > (b) ? (a) : (b)) +#endif + +/* Translation table for errno values. See intro(2) in most UNIX systems + Programmers Reference Manuals. + + Note that this table is generally only accessed when it is used at runtime + to initialize errno name and message tables that are indexed by errno + value. + + Not all of these errnos will exist on all systems. This table is the only + thing that should have to be updated as new error numbers are introduced. + It's sort of ugly, but at least its portable. */ + +struct error_info +{ + int value; /* The numeric value from <errno.h> */ + char *name; /* The equivalent symbolic value */ +#ifdef NEED_sys_errlist + char *msg; /* Short message about this value */ +#endif +}; + +#ifdef NEED_sys_errlist +# define ENTRY(value, name, msg) {value, name, msg} +#else +# define ENTRY(value, name, msg) {value, name} +#endif + +static const struct error_info error_table[] = +{ +#if defined (EPERM) + ENTRY(EPERM, "EPERM", "Not owner"), +#endif +#if defined (ENOENT) + ENTRY(ENOENT, "ENOENT", "No such file or directory"), +#endif +#if defined (ESRCH) + ENTRY(ESRCH, "ESRCH", "No such process"), +#endif +#if defined (EINTR) + ENTRY(EINTR, "EINTR", "Interrupted system call"), +#endif +#if defined (EIO) + ENTRY(EIO, "EIO", "I/O error"), +#endif +#if defined (ENXIO) + ENTRY(ENXIO, "ENXIO", "No such device or address"), +#endif +#if defined (E2BIG) + ENTRY(E2BIG, "E2BIG", "Arg list too long"), +#endif +#if defined (ENOEXEC) + ENTRY(ENOEXEC, "ENOEXEC", "Exec format error"), +#endif +#if defined (EBADF) + ENTRY(EBADF, "EBADF", "Bad file number"), +#endif +#if defined (ECHILD) + ENTRY(ECHILD, "ECHILD", "No child processes"), +#endif +#if defined (EWOULDBLOCK) /* Put before EAGAIN, sometimes aliased */ + ENTRY(EWOULDBLOCK, "EWOULDBLOCK", "Operation would block"), +#endif +#if defined (EAGAIN) + ENTRY(EAGAIN, "EAGAIN", "No more processes"), +#endif +#if defined (ENOMEM) + ENTRY(ENOMEM, "ENOMEM", "Not enough space"), +#endif +#if defined (EACCES) + ENTRY(EACCES, "EACCES", "Permission denied"), +#endif +#if defined (EFAULT) + ENTRY(EFAULT, "EFAULT", "Bad address"), +#endif +#if defined (ENOTBLK) + ENTRY(ENOTBLK, "ENOTBLK", "Block device required"), +#endif +#if defined (EBUSY) + ENTRY(EBUSY, "EBUSY", "Device busy"), +#endif +#if defined (EEXIST) + ENTRY(EEXIST, "EEXIST", "File exists"), +#endif +#if defined (EXDEV) + ENTRY(EXDEV, "EXDEV", "Cross-device link"), +#endif +#if defined (ENODEV) + ENTRY(ENODEV, "ENODEV", "No such device"), +#endif +#if defined (ENOTDIR) + ENTRY(ENOTDIR, "ENOTDIR", "Not a directory"), +#endif +#if defined (EISDIR) + ENTRY(EISDIR, "EISDIR", "Is a directory"), +#endif +#if defined (EINVAL) + ENTRY(EINVAL, "EINVAL", "Invalid argument"), +#endif +#if defined (ENFILE) + ENTRY(ENFILE, "ENFILE", "File table overflow"), +#endif +#if defined (EMFILE) + ENTRY(EMFILE, "EMFILE", "Too many open files"), +#endif +#if defined (ENOTTY) + ENTRY(ENOTTY, "ENOTTY", "Not a typewriter"), +#endif +#if defined (ETXTBSY) + ENTRY(ETXTBSY, "ETXTBSY", "Text file busy"), +#endif +#if defined (EFBIG) + ENTRY(EFBIG, "EFBIG", "File too large"), +#endif +#if defined (ENOSPC) + ENTRY(ENOSPC, "ENOSPC", "No space left on device"), +#endif +#if defined (ESPIPE) + ENTRY(ESPIPE, "ESPIPE", "Illegal seek"), +#endif +#if defined (EROFS) + ENTRY(EROFS, "EROFS", "Read-only file system"), +#endif +#if defined (EMLINK) + ENTRY(EMLINK, "EMLINK", "Too many links"), +#endif +#if defined (EPIPE) + ENTRY(EPIPE, "EPIPE", "Broken pipe"), +#endif +#if defined (EDOM) + ENTRY(EDOM, "EDOM", "Math argument out of domain of func"), +#endif +#if defined (ERANGE) + ENTRY(ERANGE, "ERANGE", "Math result not representable"), +#endif +#if defined (ENOMSG) + ENTRY(ENOMSG, "ENOMSG", "No message of desired type"), +#endif +#if defined (EIDRM) + ENTRY(EIDRM, "EIDRM", "Identifier removed"), +#endif +#if defined (ECHRNG) + ENTRY(ECHRNG, "ECHRNG", "Channel number out of range"), +#endif +#if defined (EL2NSYNC) + ENTRY(EL2NSYNC, "EL2NSYNC", "Level 2 not synchronized"), +#endif +#if defined (EL3HLT) + ENTRY(EL3HLT, "EL3HLT", "Level 3 halted"), +#endif +#if defined (EL3RST) + ENTRY(EL3RST, "EL3RST", "Level 3 reset"), +#endif +#if defined (ELNRNG) + ENTRY(ELNRNG, "ELNRNG", "Link number out of range"), +#endif +#if defined (EUNATCH) + ENTRY(EUNATCH, "EUNATCH", "Protocol driver not attached"), +#endif +#if defined (ENOCSI) + ENTRY(ENOCSI, "ENOCSI", "No CSI structure available"), +#endif +#if defined (EL2HLT) + ENTRY(EL2HLT, "EL2HLT", "Level 2 halted"), +#endif +#if defined (EDEADLK) + ENTRY(EDEADLK, "EDEADLK", "Deadlock condition"), +#endif +#if defined (ENOLCK) + ENTRY(ENOLCK, "ENOLCK", "No record locks available"), +#endif +#if defined (EBADE) + ENTRY(EBADE, "EBADE", "Invalid exchange"), +#endif +#if defined (EBADR) + ENTRY(EBADR, "EBADR", "Invalid request descriptor"), +#endif +#if defined (EXFULL) + ENTRY(EXFULL, "EXFULL", "Exchange full"), +#endif +#if defined (ENOANO) + ENTRY(ENOANO, "ENOANO", "No anode"), +#endif +#if defined (EBADRQC) + ENTRY(EBADRQC, "EBADRQC", "Invalid request code"), +#endif +#if defined (EBADSLT) + ENTRY(EBADSLT, "EBADSLT", "Invalid slot"), +#endif +#if defined (EDEADLOCK) + ENTRY(EDEADLOCK, "EDEADLOCK", "File locking deadlock error"), +#endif +#if defined (EBFONT) + ENTRY(EBFONT, "EBFONT", "Bad font file format"), +#endif +#if defined (ENOSTR) + ENTRY(ENOSTR, "ENOSTR", "Device not a stream"), +#endif +#if defined (ENODATA) + ENTRY(ENODATA, "ENODATA", "No data available"), +#endif +#if defined (ETIME) + ENTRY(ETIME, "ETIME", "Timer expired"), +#endif +#if defined (ENOSR) + ENTRY(ENOSR, "ENOSR", "Out of streams resources"), +#endif +#if defined (ENONET) + ENTRY(ENONET, "ENONET", "Machine is not on the network"), +#endif +#if defined (ENOPKG) + ENTRY(ENOPKG, "ENOPKG", "Package not installed"), +#endif +#if defined (EREMOTE) + ENTRY(EREMOTE, "EREMOTE", "Object is remote"), +#endif +#if defined (ENOLINK) + ENTRY(ENOLINK, "ENOLINK", "Link has been severed"), +#endif +#if defined (EADV) + ENTRY(EADV, "EADV", "Advertise error"), +#endif +#if defined (ESRMNT) + ENTRY(ESRMNT, "ESRMNT", "Srmount error"), +#endif +#if defined (ECOMM) + ENTRY(ECOMM, "ECOMM", "Communication error on send"), +#endif +#if defined (EPROTO) + ENTRY(EPROTO, "EPROTO", "Protocol error"), +#endif +#if defined (EMULTIHOP) + ENTRY(EMULTIHOP, "EMULTIHOP", "Multihop attempted"), +#endif +#if defined (EDOTDOT) + ENTRY(EDOTDOT, "EDOTDOT", "RFS specific error"), +#endif +#if defined (EBADMSG) + ENTRY(EBADMSG, "EBADMSG", "Not a data message"), +#endif +#if defined (ENAMETOOLONG) + ENTRY(ENAMETOOLONG, "ENAMETOOLONG", "File name too long"), +#endif +#if defined (EOVERFLOW) + ENTRY(EOVERFLOW, "EOVERFLOW", "Value too large for defined data type"), +#endif +#if defined (ENOTUNIQ) + ENTRY(ENOTUNIQ, "ENOTUNIQ", "Name not unique on network"), +#endif +#if defined (EBADFD) + ENTRY(EBADFD, "EBADFD", "File descriptor in bad state"), +#endif +#if defined (EREMCHG) + ENTRY(EREMCHG, "EREMCHG", "Remote address changed"), +#endif +#if defined (ELIBACC) + ENTRY(ELIBACC, "ELIBACC", "Can not access a needed shared library"), +#endif +#if defined (ELIBBAD) + ENTRY(ELIBBAD, "ELIBBAD", "Accessing a corrupted shared library"), +#endif +#if defined (ELIBSCN) + ENTRY(ELIBSCN, "ELIBSCN", ".lib section in a.out corrupted"), +#endif +#if defined (ELIBMAX) + ENTRY(ELIBMAX, "ELIBMAX", "Attempting to link in too many shared libraries"), +#endif +#if defined (ELIBEXEC) + ENTRY(ELIBEXEC, "ELIBEXEC", "Cannot exec a shared library directly"), +#endif +#if defined (EILSEQ) + ENTRY(EILSEQ, "EILSEQ", "Illegal byte sequence"), +#endif +#if defined (ENOSYS) + ENTRY(ENOSYS, "ENOSYS", "Operation not applicable"), +#endif +#if defined (ELOOP) + ENTRY(ELOOP, "ELOOP", "Too many symbolic links encountered"), +#endif +#if defined (ERESTART) + ENTRY(ERESTART, "ERESTART", "Interrupted system call should be restarted"), +#endif +#if defined (ESTRPIPE) + ENTRY(ESTRPIPE, "ESTRPIPE", "Streams pipe error"), +#endif +#if defined (ENOTEMPTY) + ENTRY(ENOTEMPTY, "ENOTEMPTY", "Directory not empty"), +#endif +#if defined (EUSERS) + ENTRY(EUSERS, "EUSERS", "Too many users"), +#endif +#if defined (ENOTSOCK) + ENTRY(ENOTSOCK, "ENOTSOCK", "Socket operation on non-socket"), +#endif +#if defined (EDESTADDRREQ) + ENTRY(EDESTADDRREQ, "EDESTADDRREQ", "Destination address required"), +#endif +#if defined (EMSGSIZE) + ENTRY(EMSGSIZE, "EMSGSIZE", "Message too long"), +#endif +#if defined (EPROTOTYPE) + ENTRY(EPROTOTYPE, "EPROTOTYPE", "Protocol wrong type for socket"), +#endif +#if defined (ENOPROTOOPT) + ENTRY(ENOPROTOOPT, "ENOPROTOOPT", "Protocol not available"), +#endif +#if defined (EPROTONOSUPPORT) + ENTRY(EPROTONOSUPPORT, "EPROTONOSUPPORT", "Protocol not supported"), +#endif +#if defined (ESOCKTNOSUPPORT) + ENTRY(ESOCKTNOSUPPORT, "ESOCKTNOSUPPORT", "Socket type not supported"), +#endif +#if defined (EOPNOTSUPP) + ENTRY(EOPNOTSUPP, "EOPNOTSUPP", "Operation not supported on transport endpoint"), +#endif +#if defined (EPFNOSUPPORT) + ENTRY(EPFNOSUPPORT, "EPFNOSUPPORT", "Protocol family not supported"), +#endif +#if defined (EAFNOSUPPORT) + ENTRY(EAFNOSUPPORT, "EAFNOSUPPORT", "Address family not supported by protocol"), +#endif +#if defined (EADDRINUSE) + ENTRY(EADDRINUSE, "EADDRINUSE", "Address already in use"), +#endif +#if defined (EADDRNOTAVAIL) + ENTRY(EADDRNOTAVAIL, "EADDRNOTAVAIL","Cannot assign requested address"), +#endif +#if defined (ENETDOWN) + ENTRY(ENETDOWN, "ENETDOWN", "Network is down"), +#endif +#if defined (ENETUNREACH) + ENTRY(ENETUNREACH, "ENETUNREACH", "Network is unreachable"), +#endif +#if defined (ENETRESET) + ENTRY(ENETRESET, "ENETRESET", "Network dropped connection because of reset"), +#endif +#if defined (ECONNABORTED) + ENTRY(ECONNABORTED, "ECONNABORTED", "Software caused connection abort"), +#endif +#if defined (ECONNRESET) + ENTRY(ECONNRESET, "ECONNRESET", "Connection reset by peer"), +#endif +#if defined (ENOBUFS) + ENTRY(ENOBUFS, "ENOBUFS", "No buffer space available"), +#endif +#if defined (EISCONN) + ENTRY(EISCONN, "EISCONN", "Transport endpoint is already connected"), +#endif +#if defined (ENOTCONN) + ENTRY(ENOTCONN, "ENOTCONN", "Transport endpoint is not connected"), +#endif +#if defined (ESHUTDOWN) + ENTRY(ESHUTDOWN, "ESHUTDOWN", "Cannot send after transport endpoint shutdown"), +#endif +#if defined (ETOOMANYREFS) + ENTRY(ETOOMANYREFS, "ETOOMANYREFS", "Too many references: cannot splice"), +#endif +#if defined (ETIMEDOUT) + ENTRY(ETIMEDOUT, "ETIMEDOUT", "Connection timed out"), +#endif +#if defined (ECONNREFUSED) + ENTRY(ECONNREFUSED, "ECONNREFUSED", "Connection refused"), +#endif +#if defined (EHOSTDOWN) + ENTRY(EHOSTDOWN, "EHOSTDOWN", "Host is down"), +#endif +#if defined (EHOSTUNREACH) + ENTRY(EHOSTUNREACH, "EHOSTUNREACH", "No route to host"), +#endif +#if defined (EALREADY) + ENTRY(EALREADY, "EALREADY", "Operation already in progress"), +#endif +#if defined (EINPROGRESS) + ENTRY(EINPROGRESS, "EINPROGRESS", "Operation now in progress"), +#endif +#if defined (ESTALE) + ENTRY(ESTALE, "ESTALE", "Stale NFS file handle"), +#endif +#if defined (EUCLEAN) + ENTRY(EUCLEAN, "EUCLEAN", "Structure needs cleaning"), +#endif +#if defined (ENOTNAM) + ENTRY(ENOTNAM, "ENOTNAM", "Not a XENIX named type file"), +#endif +#if defined (ENAVAIL) + ENTRY(ENAVAIL, "ENAVAIL", "No XENIX semaphores available"), +#endif +#if defined (EISNAM) + ENTRY(EISNAM, "EISNAM", "Is a named type file"), +#endif +#if defined (EREMOTEIO) + ENTRY(EREMOTEIO, "EREMOTEIO", "Remote I/O error"), +#endif + ENTRY(0, NULL, NULL) +}; + +/* Translation table allocated and initialized at runtime. Indexed by the + errno value to find the equivalent symbolic value. */ + +static char **error_names; +static int num_error_names = 0; + +/* Translation table allocated and initialized at runtime, if it does not + already exist in the host environment. Indexed by the errno value to find + the descriptive string. + + We don't export it for use in other modules because even though it has the + same name, it differs from other implementations in that it is dynamically + initialized rather than statically initialized. */ + +#ifdef NEED_sys_errlist + +static int sys_nerr; +static char **sys_errlist; + +#else + +extern int sys_nerr; +extern char *sys_errlist[]; + +#endif + + +/* + +NAME + + init_error_tables -- initialize the name and message tables + +SYNOPSIS + + static void init_error_tables (); + +DESCRIPTION + + Using the error_table, which is initialized at compile time, generate + the error_names and the sys_errlist (if needed) tables, which are + indexed at runtime by a specific errno value. + +BUGS + + The initialization of the tables may fail under low memory conditions, + in which case we don't do anything particularly useful, but we don't + bomb either. Who knows, it might succeed at a later point if we free + some memory in the meantime. In any case, the other routines know + how to deal with lack of a table after trying to initialize it. This + may or may not be considered to be a bug, that we don't specifically + warn about this particular failure mode. + +*/ + +static void +init_error_tables () +{ + const struct error_info *eip; + int nbytes; + + /* If we haven't already scanned the error_table once to find the maximum + errno value, then go find it now. */ + + if (num_error_names == 0) + { + for (eip = error_table; eip -> name != NULL; eip++) + { + if (eip -> value >= num_error_names) + { + num_error_names = eip -> value + 1; + } + } + } + + /* Now attempt to allocate the error_names table, zero it out, and then + initialize it from the statically initialized error_table. */ + + if (error_names == NULL) + { + nbytes = num_error_names * sizeof (char *); + if ((error_names = (char **) malloc (nbytes)) != NULL) + { + memset (error_names, 0, nbytes); + for (eip = error_table; eip -> name != NULL; eip++) + { + error_names[eip -> value] = eip -> name; + } + } + } + +#ifdef NEED_sys_errlist + + /* Now attempt to allocate the sys_errlist table, zero it out, and then + initialize it from the statically initialized error_table. */ + + if (sys_errlist == NULL) + { + nbytes = num_error_names * sizeof (char *); + if ((sys_errlist = (char **) malloc (nbytes)) != NULL) + { + memset (sys_errlist, 0, nbytes); + sys_nerr = num_error_names; + for (eip = error_table; eip -> name != NULL; eip++) + { + sys_errlist[eip -> value] = eip -> msg; + } + } + } + +#endif + +} + +/* + +NAME + + errno_max -- return the max errno value + +SYNOPSIS + + int errno_max (); + +DESCRIPTION + + Returns the maximum errno value for which a corresponding symbolic + name or message is available. Note that in the case where + we use the sys_errlist supplied by the system, it is possible for + there to be more symbolic names than messages, or vice versa. + In fact, the manual page for perror(3C) explicitly warns that one + should check the size of the table (sys_nerr) before indexing it, + since new error codes may be added to the system before they are + added to the table. Thus sys_nerr might be smaller than value + implied by the largest errno value defined in <errno.h>. + + We return the maximum value that can be used to obtain a meaningful + symbolic name or message. + +*/ + +int +errno_max () +{ + int maxsize; + + if (error_names == NULL) + { + init_error_tables (); + } + maxsize = MAX (sys_nerr, num_error_names); + return (maxsize - 1); +} + +/* + +NAME + + strerror -- map an error number to an error message string + +SYNOPSIS + + char *strerror (int errnoval) + +DESCRIPTION + + Maps an errno number to an error message string, the contents of + which are implementation defined. On systems which have the external + variables sys_nerr and sys_errlist, these strings will be the same + as the ones used by perror(). + + If the supplied error number is within the valid range of indices + for the sys_errlist, but no message is available for the particular + error number, then returns the string "Error NUM", where NUM is the + error number. + + If the supplied error number is not a valid index into sys_errlist, + returns NULL. + + The returned string is only guaranteed to be valid only until the + next call to strerror. + +*/ + +char * +strerror (errnoval) + int errnoval; +{ + char *msg; + static char buf[32]; + +#ifdef NEED_sys_errlist + + if (error_names == NULL) + { + init_error_tables (); + } + +#endif + + if ((errnoval < 0) || (errnoval >= sys_nerr)) + { + /* Out of range, just return NULL */ + msg = NULL; + } + else if ((sys_errlist == NULL) || (sys_errlist[errnoval] == NULL)) + { + /* In range, but no sys_errlist or no entry at this index. */ + sprintf (buf, "Error %d", errnoval); + msg = buf; + } + else + { + /* In range, and a valid message. Just return the message. */ + msg = sys_errlist[errnoval]; + } + + return (msg); +} + + + +/* + +NAME + + strerrno -- map an error number to a symbolic name string + +SYNOPSIS + + char *strerrno (int errnoval) + +DESCRIPTION + + Given an error number returned from a system call (typically + returned in errno), returns a pointer to a string containing the + symbolic name of that error number, as found in <errno.h>. + + If the supplied error number is within the valid range of indices + for symbolic names, but no name is available for the particular + error number, then returns the string "Error NUM", where NUM is + the error number. + + If the supplied error number is not within the range of valid + indices, then returns NULL. + +BUGS + + The contents of the location pointed to are only guaranteed to be + valid until the next call to strerrno. + +*/ + +char * +strerrno (errnoval) + int errnoval; +{ + char *name; + static char buf[32]; + + if (error_names == NULL) + { + init_error_tables (); + } + + if ((errnoval < 0) || (errnoval >= num_error_names)) + { + /* Out of range, just return NULL */ + name = NULL; + } + else if ((error_names == NULL) || (error_names[errnoval] == NULL)) + { + /* In range, but no error_names or no entry at this index. */ + sprintf (buf, "Error %d", errnoval); + name = buf; + } + else + { + /* In range, and a valid name. Just return the name. */ + name = error_names[errnoval]; + } + + return (name); +} + +/* + +NAME + + strtoerrno -- map a symbolic errno name to a numeric value + +SYNOPSIS + + int strtoerrno (char *name) + +DESCRIPTION + + Given the symbolic name of a error number, map it to an errno value. + If no translation is found, returns 0. + +*/ + +int +strtoerrno (name) + char *name; +{ + int errnoval = 0; + + if (name != NULL) + { + if (error_names == NULL) + { + init_error_tables (); + } + for (errnoval = 0; errnoval < num_error_names; errnoval++) + { + if ((error_names[errnoval] != NULL) && + (strcmp (name, error_names[errnoval]) == 0)) + { + break; + } + } + if (errnoval == num_error_names) + { + errnoval = 0; + } + } + return (errnoval); +} + + +/* A simple little main that does nothing but print all the errno translations + if MAIN is defined and this file is compiled and linked. */ + +#ifdef MAIN + +main () +{ + int errn; + int errnmax; + char *name; + char *msg; + char *strerrno (); + char *strerror (); + + errnmax = errno_max (); + printf ("%d entries in names table.\n", num_error_names); + printf ("%d entries in messages table.\n", sys_nerr); + printf ("%d is max useful index.\n", errnmax); + + /* Keep printing values until we get to the end of *both* tables, not + *either* table. Note that knowing the maximum useful index does *not* + relieve us of the responsibility of testing the return pointer for + NULL. */ + + for (errn = 0; errn <= errnmax; errn++) + { + name = strerrno (errn); + name = (name == NULL) ? "<NULL>" : name; + msg = strerror (errn); + msg = (msg == NULL) ? "<NULL>" : msg; + printf ("%-4d%-18s%s\n", errn, name, msg); + } +} + +#endif diff --git a/contrib/cvs/lib/strippath.c b/contrib/cvs/lib/strippath.c new file mode 100644 index 0000000..39687f9 --- /dev/null +++ b/contrib/cvs/lib/strippath.c @@ -0,0 +1,80 @@ +/* strippath.c -- remove unnecessary components from a path specifier + Copyright (C) 1992 Free Software Foundation, Inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2, or (at your option) + any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#if STDC_HEADERS || HAVE_STRING_H +#include <string.h> +/* An ANSI string.h and pre-ANSI memory.h might conflict. */ +#if !STDC_HEADERS && HAVE_MEMORY_H +#include <memory.h> +#endif /* not STDC_HEADERS and HAVE_MEMORY_H */ +#else /* not STDC_HJEADERS and not HAVE_STRING_H */ +#include <strings.h> +/* memory.h and strings.h conflict on some systems. */ +#endif /* not STDC_HEADERS and not HAVE_STRING_H */ + +#include <stdio.h> + +#if __STDC__ +static void remove_component(char *beginc, char *endc); +void strip_trailing_slashes(char *path); +#else +static void remove_component(); +void strip_trailing_slashes(); +#endif /* __STDC__ */ + +/* Remove unnecessary components from PATH. */ + +void +strip_path (path) + char *path; +{ + int stripped = 0; + char *cp, *slash; + + for (cp = path; (slash = strchr(cp, '/')) != NULL; cp = slash) + { + *slash = '\0'; + if ((!*cp && (cp != path || stripped)) || + strcmp(cp, ".") == 0 || strcmp(cp, "/") == 0) + { + stripped = 1; + remove_component(cp, slash); + slash = cp; + } + else + { + *slash++ = '/'; + } + } + strip_trailing_slashes(path); +} + +/* Remove the component delimited by BEGINC and ENDC from the path */ + +static void +remove_component (beginc, endc) + char *beginc; + char *endc; +{ + for (endc++; *endc; endc++) + *beginc++ = *endc; + *beginc = '\0'; +} diff --git a/contrib/cvs/lib/stripslash.c b/contrib/cvs/lib/stripslash.c new file mode 100644 index 0000000..265950e --- /dev/null +++ b/contrib/cvs/lib/stripslash.c @@ -0,0 +1,44 @@ +/* stripslash.c -- remove trailing slashes from a string + Copyright (C) 1990 Free Software Foundation, Inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2, or (at your option) + any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#if STDC_HEADERS || HAVE_STRING_H +#include <string.h> +/* An ANSI string.h and pre-ANSI memory.h might conflict. */ +#if !STDC_HEADERS && HAVE_MEMORY_H +#include <memory.h> +#endif /* not STDC_HEADERS and HAVE_MEMORY_H */ +#else /* not STDC_HJEADERS and not HAVE_STRING_H */ +#include <strings.h> +/* memory.h and strings.h conflict on some systems. */ +#endif /* not STDC_HEADERS and not HAVE_STRING_H */ + +/* Remove trailing slashes from PATH. */ + +void +strip_trailing_slashes (path) + char *path; +{ + int last; + + last = strlen (path) - 1; + while (last > 0 && path[last] == '/') + path[last--] = '\0'; +} diff --git a/contrib/cvs/lib/strstr.c b/contrib/cvs/lib/strstr.c new file mode 100644 index 0000000..e43bca0 --- /dev/null +++ b/contrib/cvs/lib/strstr.c @@ -0,0 +1,40 @@ +/****************************************************************************** +* * +* s t r s t r * +* * +* Find the first occurrence of a string in another string. * +* * +* Format: * +* return = strstr(Source,What); * +* * +* Parameters: * +* * +* Returns: * +* * +* Scope: PUBLIC * +* * +******************************************************************************/ + +char *strstr(Source, What) +register const char *Source; +register const char *What; +{ +register char WhatChar; +register char SourceChar; +register long Length; + + + if ((WhatChar = *What++) != 0) { + Length = strlen(What); + do { + do { + if ((SourceChar = *Source++) == 0) { + return (0); + } + } while (SourceChar != WhatChar); + } while (strncmp(Source, What, Length) != 0); + Source--; + } + return ((char *)Source); + +}/*strstr*/ diff --git a/contrib/cvs/lib/strtoul.c b/contrib/cvs/lib/strtoul.c new file mode 100644 index 0000000..7d42c21 --- /dev/null +++ b/contrib/cvs/lib/strtoul.c @@ -0,0 +1,100 @@ +/* + * strtol : convert a string to long. + * + * Andy Wilson, 2-Oct-89. + */ + +#include <errno.h> +#include <ctype.h> +#include <stdio.h> + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifndef ULONG_MAX +#define ULONG_MAX ((unsigned long)(~0L)) /* 0xFFFFFFFF */ +#endif + +extern int errno; + +unsigned long +strtoul(s, ptr, base) + const char *s; char **ptr; int base; +{ + unsigned long total = 0; + unsigned digit; + const char *start=s; + int did_conversion=0; + int overflow = 0; + int negate = 0; + unsigned long maxdiv, maxrem; + + if (s==NULL) + { + errno = ERANGE; + if (!ptr) + *ptr = (char *)start; + return 0L; + } + + while (isspace(*s)) + s++; + if (*s == '+') + s++; + else if (*s == '-') + s++, negate = 1; + if (base==0 || base==16) /* the 'base==16' is for handling 0x */ + { + int tmp; + + /* + * try to infer base from the string + */ + if (*s != '0') + tmp = 10; /* doesn't start with 0 - assume decimal */ + else if (s[1] == 'X' || s[1] == 'x') + tmp = 16, s += 2; /* starts with 0x or 0X - hence hex */ + else + tmp = 8; /* starts with 0 - hence octal */ + if (base==0) + base = (int)tmp; + } + + maxdiv = ULONG_MAX / base; + maxrem = ULONG_MAX % base; + + while ((digit = *s) != '\0') + { + if (digit >= '0' && digit < ('0'+base)) + digit -= '0'; + else + if (base > 10) + { + if (digit >= 'a' && digit < ('a'+(base-10))) + digit = digit - 'a' + 10; + else if (digit >= 'A' && digit < ('A'+(base-10))) + digit = digit - 'A' + 10; + else + break; + } + else + break; + did_conversion = 1; + if (total > maxdiv + || (total == maxdiv && digit > maxrem)) + overflow = 1; + total = (total * base) + digit; + s++; + } + if (overflow) + { + errno = ERANGE; + if (ptr != NULL) + *ptr = (char *)s; + return (ULONG_MAX); + } + if (ptr != NULL) + *ptr = (char *) ((did_conversion) ? (char *)s : (char *)start); + return negate ? -total : total; +} diff --git a/contrib/cvs/lib/system.h b/contrib/cvs/lib/system.h new file mode 100644 index 0000000..363124d --- /dev/null +++ b/contrib/cvs/lib/system.h @@ -0,0 +1,468 @@ +/* system-dependent definitions for CVS. + Copyright (C) 1989-1992 Free Software Foundation, Inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2, or (at your option) + any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ + +/* $CVSid: @(#)system.h 1.18 94/09/25 $ */ + +#include <sys/types.h> +#include <sys/stat.h> + +#ifdef STAT_MACROS_BROKEN +#undef S_ISBLK +#undef S_ISCHR +#undef S_ISDIR +#undef S_ISREG +#undef S_ISFIFO +#undef S_ISLNK +#undef S_ISSOCK +#undef S_ISMPB +#undef S_ISMPC +#undef S_ISNWK +#endif + +/* Not all systems have S_IFMT, but we probably want to use it if we + do. See ChangeLog for a more detailed discussion. */ + +#if !defined(S_ISBLK) && defined(S_IFBLK) +# if defined(S_IFMT) +# define S_ISBLK(m) (((m) & S_IFMT) == S_IFBLK) +# else +# define S_ISBLK(m) ((m) & S_IFBLK) +# endif +#endif + +#if !defined(S_ISCHR) && defined(S_IFCHR) +# if defined(S_IFMT) +# define S_ISCHR(m) (((m) & S_IFMT) == S_IFCHR) +# else +# define S_ISCHR(m) ((m) & S_IFCHR) +# endif +#endif + +#if !defined(S_ISDIR) && defined(S_IFDIR) +# if defined(S_IFMT) +# define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR) +# else +# define S_ISDIR(m) ((m) & S_IFDIR) +# endif +#endif + +#if !defined(S_ISREG) && defined(S_IFREG) +# if defined(S_IFMT) +# define S_ISREG(m) (((m) & S_IFMT) == S_IFREG) +# else +# define S_ISREG(m) ((m) & S_IFREG) +# endif +#endif + +#if !defined(S_ISFIFO) && defined(S_IFIFO) +# if defined(S_IFMT) +# define S_ISFIFO(m) (((m) & S_IFMT) == S_IFIFO) +# else +# define S_ISFIFO(m) ((m) & S_IFIFO) +# endif +#endif + +#if !defined(S_ISLNK) && defined(S_IFLNK) +# if defined(S_IFMT) +# define S_ISLNK(m) (((m) & S_IFMT) == S_IFLNK) +# else +# define S_ISLNK(m) ((m) & S_IFLNK) +# endif +#endif + +#if !defined(S_ISSOCK) && defined(S_IFSOCK) +# if defined(S_IFMT) +# define S_ISSOCK(m) (((m) & S_IFMT) == S_IFSOCK) +# else +# define S_ISSOCK(m) ((m) & S_IFSOCK) +# endif +#endif + +#if !defined(S_ISMPB) && defined(S_IFMPB) /* V7 */ +# if defined(S_IFMT) +# define S_ISMPB(m) (((m) & S_IFMT) == S_IFMPB) +# define S_ISMPC(m) (((m) & S_IFMT) == S_IFMPC) +# else +# define S_ISMPB(m) ((m) & S_IFMPB) +# define S_ISMPC(m) ((m) & S_IFMPC) +# endif +#endif + +#if !defined(S_ISNWK) && defined(S_IFNWK) /* HP/UX */ +# if defined(S_IFMT) +# define S_ISNWK(m) (((m) & S_IFMT) == S_IFNWK) +# else +# define S_ISNWK(m) ((m) & S_IFNWK) +# endif +#endif + +#if !defined(HAVE_MKFIFO) +#define mkfifo(path, mode) (mknod ((path), (mode) | S_IFIFO, 0)) +#endif + +#ifdef NEED_DECOY_PERMISSIONS /* OS/2, really */ + +#define S_IRUSR S_IREAD +#define S_IWUSR S_IWRITE +#define S_IXUSR S_IEXEC +#define S_IRWXU (S_IRUSR | S_IWUSR | S_IXUSR) +#define S_IRGRP S_IREAD +#define S_IWGRP S_IWRITE +#define S_IXGRP S_IEXEC +#define S_IRWXG (S_IRGRP | S_IWGRP | S_IXGRP) +#define S_IROTH S_IREAD +#define S_IWOTH S_IWRITE +#define S_IXOTH S_IEXEC +#define S_IRWXO (S_IROTH | S_IWOTH | S_IXOTH) + +#else /* ! NEED_DECOY_PERMISSIONS */ + +#ifndef S_IRUSR +#define S_IRUSR 0400 +#define S_IWUSR 0200 +#define S_IXUSR 0100 +/* Read, write, and execute by owner. */ +#define S_IRWXU (S_IRUSR|S_IWUSR|S_IXUSR) + +#define S_IRGRP (S_IRUSR >> 3) /* Read by group. */ +#define S_IWGRP (S_IWUSR >> 3) /* Write by group. */ +#define S_IXGRP (S_IXUSR >> 3) /* Execute by group. */ +/* Read, write, and execute by group. */ +#define S_IRWXG (S_IRWXU >> 3) + +#define S_IROTH (S_IRGRP >> 3) /* Read by others. */ +#define S_IWOTH (S_IWGRP >> 3) /* Write by others. */ +#define S_IXOTH (S_IXGRP >> 3) /* Execute by others. */ +/* Read, write, and execute by others. */ +#define S_IRWXO (S_IRWXG >> 3) +#endif /* !def S_IRUSR */ +#endif /* NEED_DECOY_PERMISSIONS */ + +#if defined(POSIX) || defined(HAVE_UNISTD_H) +#include <unistd.h> +#include <limits.h> +#else +off_t lseek (); +#endif + +#if TIME_WITH_SYS_TIME +# include <sys/time.h> +# include <time.h> +#else +# if HAVE_SYS_TIME_H +# include <sys/time.h> +# else +# include <time.h> +# endif +#endif + +#ifdef HAVE_IO_H +#include <io.h> +#endif + +#ifdef HAVE_DIRECT_H +#include <direct.h> +#endif + +#ifdef timezone +#undef timezone /* needed for sgi */ +#endif + +#ifdef HAVE_SYS_TIMEB_H +#include <sys/timeb.h> +#else +struct timeb { + time_t time; /* Seconds since the epoch */ + unsigned short millitm; /* Field not used */ + short timezone; + short dstflag; /* Field not used */ +}; +#endif + +#if !defined(HAVE_FTIME) && !defined(HAVE_TIMEZONE) +#if !defined(timezone) +extern long timezone; +#endif +#endif + + +/* +** MAXPATHLEN and PATH_MAX +** +** On most systems MAXPATHLEN is defined in sys/param.h to be 1024. Of +** those that this is not true, again most define PATH_MAX in limits.h +** or sys/limits.h which usually gets included by limits.h. On the few +** remaining systems that neither statement is true, _POSIX_PATH_MAX +** is defined. +** +** So: +** 1. If PATH_MAX is defined just use it. +** 2. If MAXPATHLEN is defined but not PATH_MAX, then define +** PATH_MAX in terms of MAXPATHLEN. +** 3. If neither is defined, include limits.h and check for +** PATH_MAX again. +** 3.1 If we now have PATHSIZE, define PATH_MAX in terms of that. +** and ignore the rest. Since _POSIX_PATH_MAX (checked for +** next) is the *most* restrictive (smallest) value, if we +** trust _POSIX_PATH_MAX, several of our buffers are too small. +** 4. If PATH_MAX is still not defined but _POSIX_PATH_MAX is, +** then define PATH_MAX in terms of _POSIX_PATH_MAX. +** 5. And if even _POSIX_PATH_MAX doesn't exist just put in +** a reasonable value. +** *. All in all, this is an excellent argument for using pathconf() +** when at all possible. Or better yet, dynamically allocate +** our buffers and use getcwd() not getwd(). +** +** This works on: +** Sun Sparc 10 SunOS 4.1.3 & Solaris 1.2 +** HP 9000/700 HP/UX 8.07 & HP/UX 9.01 +** Tektronix XD88/10 UTekV 3.2e +** IBM RS6000 AIX 3.2 +** Dec Alpha OSF 1 ???? +** Intel 386 BSDI BSD/386 +** Intel 386 SCO OpenServer Release 5 +** Apollo Domain 10.4 +** NEC SVR4 +*/ + +/* On MOST systems this will get you MAXPATHLEN. + Windows NT doesn't have this file, tho. */ +#ifdef HAVE_SYS_PARAM_H +#include <sys/param.h> +#endif + +#ifndef PATH_MAX +# ifdef MAXPATHLEN +# define PATH_MAX MAXPATHLEN +# else +# include <limits.h> +# ifndef PATH_MAX +# ifdef PATHSIZE +# define PATH_MAX PATHSIZE +# else /* no PATHSIZE */ +# ifdef _POSIX_PATH_MAX +# define PATH_MAX _POSIX_PATH_MAX +# else +# define PATH_MAX 1024 +# endif /* no _POSIX_PATH_MAX */ +# endif /* no PATHSIZE */ +# endif /* no PATH_MAX */ +# endif /* MAXPATHLEN */ +#endif /* PATH_MAX */ + + +/* The NeXT (without _POSIX_SOURCE, which we don't want) has a utime.h + which doesn't define anything. It would be cleaner to have configure + check for struct utimbuf, but for now I'm checking NeXT here (so I don't + have to debug the configure check across all the machines). */ +#if defined (HAVE_UTIME_H) && !defined (NeXT) +#include <utime.h> +#elif defined (HAVE_SYS_UTIME_H) +# include <sys/utime.h> +#else +#ifndef ALTOS +struct utimbuf +{ + long actime; + long modtime; +}; +#endif +int utime (); +#endif + +#if STDC_HEADERS || HAVE_STRING_H +# include <string.h> + /* An ANSI string.h and pre-ANSI memory.h might conflict. */ +# if !STDC_HEADERS && HAVE_MEMORY_H +# include <memory.h> +# endif /* not STDC_HEADERS and HAVE_MEMORY_H */ +#else /* not STDC_HEADERS and not HAVE_STRING_H */ +# include <strings.h> + /* memory.h and strings.h conflict on some systems. */ +#endif /* not STDC_HEADERS and not HAVE_STRING_H */ + +#include <errno.h> + +/* Not all systems set the same error code on a non-existent-file + error. This tries to ask the question somewhat portably. + On systems that don't have ENOTEXIST, this should behave just like + x == ENOENT. "x" is probably errno, of course. */ + +#ifdef ENOTEXIST +# ifdef EOS2ERR +# define existence_error(x) \ + (((x) == ENOTEXIST) || ((x) == ENOENT) || ((x) == EOS2ERR)) +# else +# define existence_error(x) \ + (((x) == ENOTEXIST) || ((x) == ENOENT)) +# endif +#else +# ifdef EVMSERR +# define existence_error(x) \ +((x) == ENOENT || (x) == EINVAL || (x) == EVMSERR) +# else +# define existence_error(x) ((x) == ENOENT) +# endif +#endif + + +#ifdef STDC_HEADERS +#include <stdlib.h> +#else +char *getenv (); +char *malloc (); +char *realloc (); +char *calloc (); +extern int errno; +#endif + +/* SunOS4 apparently does not define this in stdlib.h. */ +#ifndef EXIT_FAILURE +#define EXIT_FAILURE 1 +#endif + +#if defined(USG) || defined(POSIX) +char *getcwd (); +#else +char *getwd (); +#endif + +/* check for POSIX signals */ +#if defined(HAVE_SIGACTION) && defined(HAVE_SIGPROCMASK) +# define POSIX_SIGNALS +#endif + +/* MINIX 1.6 doesn't properly support sigaction */ +#if defined(_MINIX) +# undef POSIX_SIGNALS +#endif + +/* If !POSIX, try for BSD.. Reason: 4.4BSD implements these as wrappers */ +#if !defined(POSIX_SIGNALS) +# if defined(HAVE_SIGVEC) && defined(HAVE_SIGSETMASK) && defined(HAVE_SIGBLOCK) +# define BSD_SIGNALS +# endif +#endif + +/* Under OS/2, this must be included _after_ stdio.h; that's why we do + it here. */ +#ifdef USE_OWN_TCPIP_H +#include "tcpip.h" +#endif + +#ifdef HAVE_FCNTL_H +#include <fcntl.h> +#else +#include <sys/file.h> +#endif + +#ifndef SEEK_SET +#define SEEK_SET 0 +#define SEEK_CUR 1 +#define SEEK_END 2 +#endif + +#ifndef F_OK +#define F_OK 0 +#define X_OK 1 +#define W_OK 2 +#define R_OK 4 +#endif + +#if HAVE_DIRENT_H +# include <dirent.h> +# define NAMLEN(dirent) strlen((dirent)->d_name) +#else +# define dirent direct +# define NAMLEN(dirent) (dirent)->d_namlen +# if HAVE_SYS_NDIR_H +# include <sys/ndir.h> +# endif +# if HAVE_SYS_DIR_H +# include <sys/dir.h> +# endif +# if HAVE_NDIR_H +# include <ndir.h> +# endif +#endif + +/* Convert B 512-byte blocks to kilobytes if K is nonzero, + otherwise return it unchanged. */ +#define convert_blocks(b, k) ((k) ? ((b) + 1) / 2 : (b)) + +#ifndef S_ISLNK +#define lstat stat +#endif + +/* + * Some UNIX distributions don't include these in their stat.h Defined here + * because "config.h" is always included last. + */ +#ifndef S_IWRITE +#define S_IWRITE 0000200 /* write permission, owner */ +#endif +#ifndef S_IWGRP +#define S_IWGRP 0000020 /* write permission, grougroup */ +#endif +#ifndef S_IWOTH +#define S_IWOTH 0000002 /* write permission, other */ +#endif + +/* Under MS-DOS and its derivatives (like Windows NT), mkdir takes only one + argument; permission is handled very differently on those systems than in + in Unix. So we leave such systems a hook on which they can hang their + own definitions. */ +#ifndef CVS_MKDIR +#define CVS_MKDIR mkdir +#endif + +/* Some file systems are case-insensitive. If FOLD_FN_CHAR is + #defined, it maps the character C onto its "canonical" form. In a + case-insensitive system, it would map all alphanumeric characters + to lower case. Under Windows NT, / and \ are both path component + separators, so FOLD_FN_CHAR would map them both to /. */ +#ifndef FOLD_FN_CHAR +#define FOLD_FN_CHAR(c) (c) +#define fnfold(filename) (filename) +#define fncmp strcmp +#endif + +/* Different file systems have different path component separators. + For the VMS port we might need to abstract further back than this. */ +#ifndef ISDIRSEP +#define ISDIRSEP(c) ((c) == '/') +#endif + + +/* On some systems, lines in text files should be terminated with CRLF, + not just LF, and the read and write routines do this translation + for you. LINES_CRLF_TERMINATED is #defined on such systems. + - OPEN_BINARY is the flag to pass to the open function for + untranslated I/O. + - FOPEN_BINARY_READ is the string to pass to fopen to get + untranslated reading. + - FOPEN_BINARY_WRITE is the string to pass to fopen to get + untranslated writing. */ +#if LINES_CRLF_TERMINATED +#define OPEN_BINARY (O_BINARY) +#define FOPEN_BINARY_READ ("rb") +#define FOPEN_BINARY_WRITE ("wb") +#else +#define OPEN_BINARY (0) +#define FOPEN_BINARY_READ ("r") +#define FOPEN_BINARY_WRITE ("w") +#endif diff --git a/contrib/cvs/lib/valloc.c b/contrib/cvs/lib/valloc.c new file mode 100644 index 0000000..674b60f --- /dev/null +++ b/contrib/cvs/lib/valloc.c @@ -0,0 +1,25 @@ +/* valloc -- return memory aligned to the page size. */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "system.h" + +#ifndef HAVE_GETPAGESIZE +#define getpagesize() 4096 +#endif + +void * +valloc (bytes) + size_t bytes; +{ + long pagesize; + char *ret; + + pagesize = getpagesize (); + ret = (char *) malloc (bytes + pagesize - 1); + if (ret) + ret = (char *) ((long) (ret + pagesize - 1) &~ (pagesize - 1)); + return ret; +} diff --git a/contrib/cvs/lib/vasprintf.c b/contrib/cvs/lib/vasprintf.c new file mode 100644 index 0000000..45253b1 --- /dev/null +++ b/contrib/cvs/lib/vasprintf.c @@ -0,0 +1,171 @@ +/* Like vsprintf but provides a pointer to malloc'd storage, which must + be freed by the caller. + Copyright (C) 1994 Free Software Foundation, Inc. + +This file is part of the libiberty library. +Libiberty is free software; you can redistribute it and/or +modify it under the terms of the GNU Library General Public +License as published by the Free Software Foundation; either +version 2 of the License, or (at your option) any later version. + +Libiberty 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 +Library General Public License for more details. + +You should have received a copy of the GNU Library General Public +License along with libiberty; see the file COPYING.LIB. If +not, write to the Free Software Foundation, Inc., 675 Mass Ave, +Cambridge, MA 02139, USA. */ + +#include <stdio.h> +#include <string.h> + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifdef __STDC__ +#include <stdarg.h> +#else +#include <varargs.h> +#endif + +extern int abs (); + +#ifdef TEST +int global_total_width; +#endif + +unsigned long strtoul (); +char *malloc (); + +static int +int_vasprintf (result, format, args) + char **result; + const char *format; + va_list *args; +{ + const char *p = format; + /* Add one to make sure that it is never zero, which might cause malloc + to return NULL. */ + int total_width = strlen (format) + 1; + va_list ap; + + memcpy (&ap, args, sizeof (va_list)); + + while (*p != '\0') + { + if (*p++ == '%') + { + while (strchr ("-+ #0", *p)) + ++p; + if (*p == '*') + { + ++p; + total_width += abs (va_arg (ap, int)); + } + else + total_width += strtoul (p, &p, 10); + if (*p == '.') + { + ++p; + if (*p == '*') + { + ++p; + total_width += abs (va_arg (ap, int)); + } + else + total_width += strtoul (p, &p, 10); + } + while (strchr ("hlL", *p)) + ++p; + /* Should be big enough for any format specifier except %s. */ + total_width += 30; + switch (*p) + { + case 'd': + case 'i': + case 'o': + case 'u': + case 'x': + case 'X': + case 'c': + (void) va_arg (ap, int); + break; + case 'f': + case 'e': + case 'E': + case 'g': + case 'G': + (void) va_arg (ap, double); + break; + case 's': + total_width += strlen (va_arg (ap, char *)); + break; + case 'p': + case 'n': + (void) va_arg (ap, char *); + break; + } + } + } +#ifdef TEST + global_total_width = total_width; +#endif + *result = malloc (total_width); + if (*result != NULL) + return vsprintf (*result, format, *args); + else + return 0; +} + +int +vasprintf (result, format, args) + char **result; + const char *format; + va_list args; +{ + return int_vasprintf (result, format, &args); +} + +#ifdef TEST +void +checkit +#ifdef __STDC__ + (const char* format, ...) +#else + (va_alist) + va_dcl +#endif +{ + va_list args; + char *result; + +#ifdef __STDC__ + va_start (args, format); +#else + char *format; + va_start (args); + format = va_arg (args, char *); +#endif + vasprintf (&result, format, args); + if (strlen (result) < global_total_width) + printf ("PASS: "); + else + printf ("FAIL: "); + printf ("%d %s\n", global_total_width, result); +} + +int +main () +{ + checkit ("%d", 0x12345678); + checkit ("%200d", 5); + checkit ("%.300d", 6); + checkit ("%100.150d", 7); + checkit ("%s", "jjjjjjjjjiiiiiiiiiiiiiiioooooooooooooooooppppppppppppaa\n\ +777777777777777777333333333333366666666666622222222222777777777777733333"); + checkit ("%f%s%d%s", 1.0, "foo", 77, "asdjffffffffffffffiiiiiiiiiiixxxxx"); +} +#endif /* TEST */ diff --git a/contrib/cvs/lib/wait.h b/contrib/cvs/lib/wait.h new file mode 100644 index 0000000..db60434 --- /dev/null +++ b/contrib/cvs/lib/wait.h @@ -0,0 +1,32 @@ +/* wait.h -- POSIX macros for evaluating exit statuses + Copyright (C) 1990 Free Software Foundation, Inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2, or (at your option) + any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ + +#ifdef HAVE_SYS_WAIT_H +#include <sys/types.h> /* For pid_t. */ +#ifdef HAVE_SYS_RESOURCE_H +#include <sys/resource.h> /* for rusage */ +#endif +#include <sys/wait.h> +#else +#define WIFSTOPPED(w) (((w) & 0xff) == 0x7f) +#define WIFSIGNALED(w) (((w) & 0xff) != 0x7f && ((w) & 0xff) != 0) +#define WIFEXITED(w) (((w) & 0xff) == 0) + +#define WSTOPSIG(w) (((w) >> 8) & 0xff) +#define WTERMSIG(w) ((w) & 0x7f) +#define WEXITSTATUS(w) (((w) >> 8) & 0xff) +#endif diff --git a/contrib/cvs/lib/waitpid.c b/contrib/cvs/lib/waitpid.c new file mode 100644 index 0000000..e8ddeb8 --- /dev/null +++ b/contrib/cvs/lib/waitpid.c @@ -0,0 +1,76 @@ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "system.h" +#include "wait.h" + +#include <stdio.h> + +struct unreaped { + pid_t pid; + int status; +}; +static struct unreaped *unreaped; +static int n; + +static struct unreaped *ualloc (oldptr, n) + struct unreaped *oldptr; + int n; +{ + n *= sizeof (struct unreaped); + if (n == 0) + n = 1; + if (oldptr) + oldptr = (struct unreaped *) realloc ((char *) oldptr, n); + else + oldptr = (struct unreaped *) malloc (n); + if (oldptr == 0) + { + fprintf (stderr, "cannot allocate %d bytes\n", n); + exit (1); + } + return oldptr; +} + +pid_t waitpid (pid, status, options) + pid_t pid; + int *status; + int options; +{ + int i; + + /* initialize */ + if (unreaped == 0) + { + unreaped = ualloc (unreaped, 1); + unreaped[0].pid = 0; + n = 1; + } + + for (i = 0; unreaped[i].pid; i++) + if (unreaped[i].pid == pid) + { + *status = unreaped[i].status; + while (unreaped[i].pid) + { + unreaped[i] = unreaped[i+1]; + i++; + } + n--; + return pid; + } + + while (1) + { + pid_t p = wait3 (status, options, (struct rusage *) 0); + + if (p == 0 || p == -1 || p == pid) + return p; + + n++; + unreaped = ualloc (unreaped, n); + unreaped[n-1].pid = p; + unreaped[n-1].status = *status; + } +} diff --git a/contrib/cvs/lib/xgetwd.c b/contrib/cvs/lib/xgetwd.c new file mode 100644 index 0000000..8fe4ec1 --- /dev/null +++ b/contrib/cvs/lib/xgetwd.c @@ -0,0 +1,79 @@ +/* xgetwd.c -- return current directory with unlimited length + Copyright (C) 1992 Free Software Foundation, Inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2, or (at your option) + any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ + +/* Derived from xgetcwd.c in e.g. the GNU sh-utils. */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include "system.h" + +#include <stdio.h> +#include <errno.h> +#ifndef errno +extern int errno; +#endif +#include <sys/types.h> + +#ifndef HAVE_GETWD +char *getwd (); +#define GETWD(buf, max) getwd (buf) +#else +char *getcwd (); +#define GETWD(buf, max) getcwd (buf, max) +#endif + +/* Amount by which to increase buffer size when allocating more space. */ +#define PATH_INCR 32 + +char *xmalloc (); +char *xrealloc (); + +/* Return the current directory, newly allocated, arbitrarily long. + Return NULL and set errno on error. */ + +char * +xgetwd () +{ + char *cwd; + char *ret; + unsigned path_max; + + errno = 0; + path_max = (unsigned) PATH_MAX; + path_max += 2; /* The getcwd docs say to do this. */ + + cwd = xmalloc (path_max); + + errno = 0; + while ((ret = GETWD (cwd, path_max)) == NULL && errno == ERANGE) + { + path_max += PATH_INCR; + cwd = xrealloc (cwd, path_max); + errno = 0; + } + + if (ret == NULL) + { + int save_errno = errno; + free (cwd); + errno = save_errno; + return NULL; + } + return cwd; +} diff --git a/contrib/cvs/lib/yesno.c b/contrib/cvs/lib/yesno.c new file mode 100644 index 0000000..86b0798 --- /dev/null +++ b/contrib/cvs/lib/yesno.c @@ -0,0 +1,42 @@ +/* yesno.c -- read a yes/no response from stdin + Copyright (C) 1990 Free Software Foundation, Inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2, or (at your option) + any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <stdio.h> + +/* Read one line from standard input + and return nonzero if that line begins with y or Y, + otherwise return 0. */ + +int +yesno () +{ + int c; + int rv; + + fflush (stderr); + fflush (stdout); + c = getchar (); + rv = (c == 'y') || (c == 'Y'); + while (c != EOF && c != '\n') + c = getchar (); + + return rv; +} diff --git a/contrib/cvs/man/ChangeLog b/contrib/cvs/man/ChangeLog new file mode 100644 index 0000000..69017c9 --- /dev/null +++ b/contrib/cvs/man/ChangeLog @@ -0,0 +1,141 @@ +Wed Mar 13 17:06:39 1996 Jim Kingdon <kingdon@harvey.cyclic.com> + + * cvsinit.8: Removed. + * Makefile.in, cvs.1, cvs.5: Remove references to cvsinit. + +Tue Feb 13 22:30:54 1996 Samuel Tardieu <sam@inf.enst.fr> + + * Makefile.in: Remove reference to mkmodules.1 + +Mon Feb 12 16:30:40 1996 Jim Kingdon <kingdon@harvey.cyclic.com> + + * cvs.1, cvsinit.8: Remove references to mkmodules, rm, sort + * cvs.5: Remove references to mkmodules. + * mkmodules.1: Removed. + +Tue Jan 30 18:32:27 1996 Jim Kingdon <kingdon@harvey.cyclic.com> + + * Makefile.in: Revise comment regarding install and installdirs. + +Tue Nov 14 15:47:44 1995 Greg A. Woods <woods@most.weird.com> + + * cvs.5: + - list filenames in alpha sort order + - describe the cvswrappers file + - describe the taginfo file + - fix cvsinit chapter number + (This is by no means complete -- it's just stuff I noticed.) + + * cvs.1: put the filenames in alpha sort order + + * cvsinit.8: remove the .pl extension from filenames + +Wed Oct 18 11:07:07 1995 Vince Demarco <vdemarco@bou.shl.com> + + * cvs.1 (Flag): Updated the CVSROOT/wrappers stuff. Everyone seems + to think this is a NEXT specific thing it isn't. + +Tue Oct 17 17:38:27 1995 Warren Jones <wjones@tc.fluke.com> + + * cvs.1: Change \. to \&. at start of line. + +Tue Oct 3 13:43:33 1995 Del <del@matra.com.au> + + * cvs.1: Updated man page for all the new features of 1.6 + (including some that were missed in 1.5 and 1.4.xx). This includes: + - -f and -z global options. + - tag -F, and -r options. + - rtag -F options + - CVSROOT/taginfo and CVSROOT/wrappers files (the latter could use a touch + up because I don't really understand how wrappers work or why anyone would + use them -- I haven't ever played with a NEXT. + - export -k option + - New environment variables CVS_IGNORE_REMOTE_ROOT, CVS_RSH, CVS_SERVER, and + CVSWRAPPERS. I left CVS_CLIENT_LOG, CVS_CLIENT_PORT, and CVS_SERVER_SLEEP + undocumented because these appear to be for testing / debugging only. + Note that TMPDIR, HOME and PATH are used as well and strictly speaking + should be documented. + - New files ~/.cvsrc and ~/.cvswrappers + +Tue Aug 15 08:13:14 1995 Karl Fogel <kfogel@floss.cyclic.com> + + * Makefile.in (MANFILES): include $MAN8FILES too, so they get + tarred up in the distribution just like anything else. + +Mon Jul 24 19:11:15 1995 James Kingdon <kingdon@harvey.cyclic.com> + + * cvs.1: Remove references to -q and -Q command options. + +Fri Jul 14 23:30:33 1995 Jim Blandy <jimb@totoro.cyclic.com> + + * Makefile.in (prefix): Don't forget to give this a value. + +Sun Jul 9 21:22:56 1995 Karl Fogel <kfogel@floss.cyclic.com> + + Greg Woods' change: + + * cvsbug.8, cvsinit.8: new files. + +Sun Jul 9 19:03:00 1995 Greg A. Woods <woods@most.weird.com> + + * cvs.1: document 'cvs status [-qQ]' + - note reference to cvsinit(8) and cvsbug(8) + (from previous local changes) + + * Makefile.in: add support for installing in man8, and new cvsbug + and cvsinit pages (from previous local changes) + +Sat May 27 08:46:00 1995 Jim Meyering (meyering@comco.com) + + * Makefile.in (Makefile): Regenerate only Makefile in current + directory when Makefile.in is out of date. Depend on ../config.status. + +Fri Apr 28 22:51:31 1995 Jim Blandy <jimb@totoro.bio.indiana.edu> + + * Makefile.in (DISTFILES): Updated. + (dist-dir): Renamed from dist; changed to work with DISTDIR + variable passed from parent. + +Fri Jul 15 12:58:14 1994 Ian Lance Taylor (ian@sanguine.cygnus.com) + + * Makefile.in (install): Do not depend upon installdirs. + +Sat Dec 18 01:23:13 1993 david d zuhn (zoo@monad.armadillo.com) + + * Makefile.in (VPATH): don't use $(srcdir), but @srcdir@ instead + +Mon Jun 14 12:20:33 1993 david d `zoo' zuhn (zoo at rtl.cygnus.com) + + * Makefile.in (install): remove parentdir support + +Mon Aug 31 01:42:43 1992 david d [zoo] zuhn (zoo at cirdan.cygnus.com) + + * Makefile.in (install): create $(man1dir) and $(man5dir) before + installing the man pages + +Wed Feb 26 18:04:40 1992 K. Richard Pixley (rich@cygnus.com) + + * Makefile.in, configure.in: removed traces of namesubdir, + -subdirs, $(subdir), $(unsubdir), some rcs triggers. Forced + copyrights to '92, changed some from Cygnus to FSF. + +Tue Dec 10 04:07:08 1991 K. Richard Pixley (rich at rtl.cygnus.com) + + * Makefile.in: infodir belongs in datadir. + +Tue Dec 10 03:59:10 1991 K. Richard Pixley (rich at cygnus.com) + + * cvs.man: small correction to an explanation of an example. + +Thu Dec 5 22:45:59 1991 K. Richard Pixley (rich at rtl.cygnus.com) + + * Makefile.in: idestdir and ddestdir go away. Added copyrights + and shift gpl to v2. Added ChangeLog if it didn't exist. docdir + and mandir now keyed off datadir by default. + +Wed Nov 27 02:46:20 1991 K. Richard Pixley (rich at sendai) + + * brought Makefile.in's up to standards.text. + + * fresh changelog. + diff --git a/contrib/cvs/man/Makefile.in b/contrib/cvs/man/Makefile.in new file mode 100644 index 0000000..894e4d78 --- /dev/null +++ b/contrib/cvs/man/Makefile.in @@ -0,0 +1,96 @@ +# Makefile for GNU CVS documentation. +# Do not use this makefile directly, but only from `../Makefile'. +# Copyright (C) 1986-1992 Free Software Foundation, Inc. + +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2, or (at your option) +# any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +SHELL = /bin/sh + +srcdir = @srcdir@ +top_srcdir = @top_srcdir@ +VPATH = @srcdir@ + +MAN1FILES = cvs.1 +MAN5FILES = cvs.5 +MAN8FILES = cvsbug.8 +MANFILES = $(MAN1FILES) $(MAN5FILES) $(MAN8FILES) + +DISTFILES = .cvsignore ChangeLog Makefile.in $(MANFILES) +INSTALL = @INSTALL@ +INSTALL_DATA = $(INSTALL) +prefix = @prefix@ +mandir = $(prefix)/man +man1dir = $(mandir)/man1 +man5dir = $(mandir)/man5 +man8dir = $(mandir)/man8 + +all: +.PHONY: all + +# This used to depend on installdirs, but someone took it out (I think +# maybe it is/was some kind of Cygnus convention to not depend on installdirs; +# I'm not sure). I don't know what the pro(s) and con(s) are. +install: all + for f in $(MAN1FILES); do \ + $(INSTALL_DATA) $(srcdir)/$$f $(man1dir)/$$f; \ + done + for f in $(MAN5FILES); do \ + $(INSTALL_DATA) $(srcdir)/$$f $(man5dir)/$$f; \ + done + for f in $(MAN8FILES); do \ + $(INSTALL_DATA) $(srcdir)/$$f $(man8dir)/$$f; \ + done + +installdirs: + $(SHELL) $(top_srcdir)/mkinstalldirs $(man1dir) $(man5dir) $(man8dir) + +.PHONY: install installdirs + +tags: +.PHONY: tags + +TAGS: +.PHONY: TAGS + +ls: + @true +.PHONY: ls + +clean: +.PHONY: clean + +distclean: clean + rm -f Makefile +.PHONY: distclean + +realclean: distclean +.PHONY: realclean + +dist-dir: + mkdir ${DISTDIR} + for i in ${DISTFILES}; do \ + ln $(srcdir)/$${i} ${DISTDIR}; \ + done +.PHONY: dist-dir + +subdir = man +Makefile: ../config.status Makefile.in + cd .. && CONFIG_FILES=$(subdir)/$@ CONFIG_HEADERS= ./config.status + +#../config.status: ../configure +# cd .. ; $(SHELL) config.status --recheck + +#../configure: ../configure.in +# cd $(top_srcdir) ; autoconf diff --git a/contrib/cvs/man/cvs.1 b/contrib/cvs/man/cvs.1 new file mode 100644 index 0000000..7cef6e5 --- /dev/null +++ b/contrib/cvs/man/cvs.1 @@ -0,0 +1,2181 @@ +.de Id +.ds Rv \\$3 +.ds Dt \\$4 +.. +.Id $Id: cvs.1,v 1.9 1996/03/13 22:59:06 kingdon Exp $ +.TH CVS 1 "\*(Dt" +.\" Full space in nroff; half space in troff +.de SP +.if n .sp +.if t .sp .5 +.. +.\" quoted command +.de ` +.RB ` "\|\\$1\|" '\\$2 +.. +.SH "NAME" +cvs \- Concurrent Versions System +.SH "SYNOPSIS" +.TP +\fBcvs\fP [ \fIcvs_options\fP ] +.I cvs_command +[ +.I command_options +] [ +.I command_args +] +.SH "DESCRIPTION" +.IX "revision control system" "\fLcvs\fR" +.IX cvs "" "\fLcvs\fP \- concurrent versions system" +.IX "concurrent versions system \- \fLcvs\fP" +.IX "release control system" "cvs command" "" "\fLcvs\fP \- concurrent versions system" +.IX "source control system" "cvs command" "" "\fLcvs\fP \- concurrent versions system" +.IX revisions "cvs command" "" "\fLcvs\fP \- source control" +.B cvs +is a front end to the +.BR rcs ( 1 ) +revision control system which extends +the notion of revision control from a collection of files in a single +directory to a hierarchical collection of directories consisting of +revision controlled files. +These directories and files can be combined together to form a software +release. +.B cvs +provides the functions necessary to manage these software releases and to +control the concurrent editing of source files among multiple software +developers. +.SP +.B cvs +keeps a single copy of the master sources. +This copy is called the source ``repository''; it contains all the +information to permit extracting previous software releases at any +time based on either a symbolic revision tag, or a date in the past. +.SH "ESSENTIAL COMMANDS" +.B cvs +provides a rich variety of commands (\fIcvs_command\fP in the +Synopsis), each of which often has a wealth of options, to satisfy the +many needs of source management in distributed environments. However, +you don't have to master every detail to do useful work with +.BR cvs ; +in fact, five commands are sufficient to use (and contribute to) +the source repository. +.TP +\fBcvs checkout\fP \fImodules\fP\|.\|.\|. +A necessary preliminary for most \fBcvs\fP work: creates your private +copy of the source for \fImodules\fP (named collections of source; you +can also use a path relative to the source repository here). You can +work with this copy without interfering with others' work. At least +one subdirectory level is always created. +.TP +.B cvs update +Execute this command from \fIwithin\fP your private source +directory when you wish to update your copies of source files from +changes that other developers have made to the source in the +repository. +.TP +\fBcvs add\fP \fIfile\fP\|.\|.\|. +Use this command to enroll new files in \fBcvs\fP records of your +working directory. The files will be added to the repository the next +time you run +.` "cvs commit". +Note: +You should use the +.` "cvs import" +command to bootstrap new sources into the source repository. +.` "cvs add" +is only used for new files to an already checked-out module. +.TP +\fBcvs remove\fP \fIfile\fP\|.\|.\|. +Use this command (after erasing any files listed) to declare that you +wish to eliminate files from the repository. The removal does not +affect others until you run +.` "cvs commit". +.TP +\fBcvs commit\fP \fIfile\fP\|.\|.\|. +Use this command when you wish to ``publish'' your changes to other +developers, by incorporating them in the source repository. +.SH "OPTIONS" +The +.B cvs +command line can include +.IR cvs_options , +which apply to the overall +.B cvs +program; a +.IR cvs_command , +which specifies a particular action on the source repository; and +.I command_options +and +.I command_arguments +to fully specify what the +.I cvs_command +will do. +.SP +.I Warning: +you must be careful of precisely where you place options relative to the +.IR cvs_command . +The same option can mean different things depending on whether it +is in the +.I cvs_options +position (to the left of a +.B cvs +command) or in the +.I command_options +position (to the right of a +.B cvs +command). +.SP +There are only two situations where you may omit +.IR cvs_command : +.` "cvs \-H" +or +.` "cvs --help" +elicits a list of available commands, and +.` "cvs \-v" +or +.` "cvs --version" +displays version information on \fBcvs\fP itself. +.SP +.SH "CVS OPTIONS" +As of release 1.6, +.B cvs +supports +.SM GNU +style long options as well as short options. Only +a few long options are currently supported, these are listed in +brackets after the short options whose functions they duplicate. +.SP +Use these options to control the overall +.B cvs +program: +.TP +.B \-H [ --help ] +Display usage information about the specified +.I cvs_command +(but do not actually execute the command). If you don't specify a +command name, +.` "cvs \-H" +displays a summary of all the commands available. +.TP +.B \-Q +Causes the command to be +.I really +quiet; the command will generate output only for serious problems. +.TP +.B \-q +Causes the command to be somewhat quiet; informational messages, such +as reports of recursion through subdirectories, are suppressed. +.TP +\fB\-b\fP \fIbindir\fP +Use +.I bindir +as the directory where +.SM RCS +programs are located. +Overrides the setting of the +.SM RCSBIN +environment variable. +This value should be specified as an absolute pathname. +.TP +\fB\-d\fP \fICVS_root_directory\fP +Use +.I CVS_root_directory +as the root directory pathname of the master +.SM RCS +source repository. +Overrides the setting of the +.SM CVSROOT +environment variable. +This value should be specified as an absolute pathname. +.TP +\fB\-e\fP \fIeditor\fP +Use +.I editor +to enter revision log information. +Overrides the setting of the +.SM CVSEDITOR +and the +.SM EDITOR +environment variables. +.TP +.B \-f +Do not read the +.B cvs +startup file (\fI~/.cvsrc\fP). +.TP +.B \-l +Do not log the +.I cvs_command +in the command history (but execute it anyway). See the description +of the +.B history +command for information on command history. +.TP +.B \-n +Do not change any files. Attempt to execute the +.IR cvs_command , +but only to issue reports; do not remove, update, or merge any +existing files, or create any new files. +.TP +.B \-t +Trace program execution; display messages showing the steps of +.B cvs +activity. Particularly useful with +.B \-n +to explore the potential impact of an unfamiliar command. +.TP +.B \-r +Makes new working files read-only. +Same effect as if the +.SM CVSREAD +environment variable is set. +.TP +.B \-v [ --version ] +Displays version and copyright information for +.BR cvs . +.TP +.B \-w +Makes new working files read-write (default). +Overrides the setting of the +.SM CVSREAD +environment variable. +.TP +\fB\-z\fP \fIcompression\-level\fP +When transferring files across the network use +.B gzip +with compression level \fIcompression\-level\fP to compress and +de-compress data as it is transferred. Requires the presence of +the +.SM GNU +.B gzip +program in the current search path at both ends of the link. +.SH "USAGE" +Except when requesting general help with +.` "cvs \-H", +you must specify a +.I cvs_command +to +.B cvs +to select a specific release control function to perform. +Each +.B cvs +command accepts its own collection of options and arguments. +However, many options are available across several commands. +You can display a usage summary for each command by specifying the +.B \-H +option with the command. +.SH "CVS STARTUP FILE" +Normally, when CVS starts up, it reads the +.I .cvsrc +file from the home directory of the user reading it. This startup +procedure can be turned off with the +.B \-f +flag. +.SP +The +.I .cvsrc +file lists CVS commands with a list of arguments, one command per +line. For example, the following line in \fI.cvsrc\fP: +.SP +diff \-c +.SP +will mean that the +.` "cvs diff" +command will always be passed the \-c option in addition to any +other options that are specified in the command line (in this case +it will have the effect of producing context sensitive diffs for +all executions of +.` "cvs diff" +). +.SH "CVS COMMAND SUMMARY" +Here are brief descriptions of all the +.B cvs +commands: +.TP +.B add +Add a new file or directory to the repository, pending a +.` "cvs commit" +on the same file. +Can only be done from within sources created by a previous +.` "cvs checkout" +invocation. +Use +.` "cvs import" +to place whole new hierarchies of sources under +.B cvs +control. +(Does not directly affect repository; changes +working directory.) +.TP +.B admin +Execute +.SM RCS +control functions on the source repository. (Changes +repository directly; uses working directory without changing it.) +.TP +.B checkout +Make a working directory of source files for editing. (Creates or changes +working directory.) +.TP +.B commit +Apply to the source repository changes, additions, and deletions from your +working directory. (Changes repository.) +.TP +.B diff +Show differences between files in working directory and source +repository, or between two revisions in source repository. +(Does not change either repository or working directory.) +.TP +.B export +Prepare copies of a set of source files for shipment off site. +Differs from +.` "cvs checkout" +in that no +.B cvs +administrative directories are created (and therefore +.` "cvs commit" +cannot be executed from a directory prepared with +.` "cvs export"), +and a symbolic tag must be specified. +(Does not change repository; creates directory similar to working +directories). +.TP +.B history +Show reports on +.B cvs +commands that you or others have executed on a particular file or +directory in the source repository. (Does not change repository or +working directory.) History logs are kept only if enabled by creation +of the +.` "$CVSROOT/CVSROOT/history" +file; see +.BR cvs ( 5 ). +.TP +.B import +Incorporate a set of updates from off-site into the source repository, +as a ``vendor branch''. (Changes repository.) +.TP +.B log +Display +.SM RCS +log information. +(Does not change repository or working directory.) +.TP +.B rdiff +Prepare a collection of diffs as a patch file between two releases in +the repository. (Does not change repository or working directory.) +.TP +.B release +Cancel a +.` "cvs checkout", +abandoning any changes. +(Can delete working directory; no effect on repository.) +.TP +.B remove +Remove files from the source repository, pending a +.` "cvs commit" +on the same files. (Does not directly affect repository; +changes working directory.) +.TP +.B rtag +Explicitly specify a symbolic tag for particular revisions of files in the +source repository. See also +.` "cvs tag". +(Changes repository directly; does not require or affect +working directory.) +.TP +.B status +Show current status of files: latest version, version in working +directory, whether working version has been edited and, optionally, +symbolic tags in the +.SM RCS +file. (Does not change +repository or working directory.) +.TP +.B tag +Specify a symbolic tag for files in the repository. By default, tags +the revisions +that were last synchronized with your working directory. (Changes +repository directly; uses working directory without changing it.) +.TP +.B update +Bring your working directory up to date with changes from the +repository. Merges are performed automatically when possible; a +warning is issued if manual resolution is required for conflicting +changes. (Changes working directory; does not change repository.) +.SH "COMMON COMMAND OPTIONS" +This section describes the +.I command_options +that are available across several +.B cvs +commands. Not all commands support all of these options; each option +is only supported for commands where it makes sense. However, when +a command has one of these options you can count on the same meaning +for the option as in other commands. (Other command +options, which are listed with the individual commands, may have +different meanings from one +.B cvs +command to another.) +.I "Warning:" +the +.B history +command is an exception; +it supports many options that conflict +even with these standard options. +.TP +\fB\-D\fP \fIdate_spec\fP +Use the most recent revision no later than \fIdate_spec\fP (a single +argument, date description specifying a date in the +past). A wide variety of date formats are supported by the underlying +.SM RCS +facilities, similar to those described in +.BR co ( 1 ), +but not exactly the same. +The \fIdate_spec\fP is interpreted as being in the local timezone, unless a +specific timezone is specified. +The specification is ``sticky'' when you use it to make a +private copy of a source file; that is, when you get a working file +using \fB\-D\fP, \fBcvs\fP records the date you +specified, so that further updates in the same directory will use the +same date (unless you explicitly override it; see the description of +the \fBupdate\fP command). +.B \-D +is available with the +.BR checkout ", " diff ", " history ", " export ", " +.BR rdiff ", " rtag ", and " +.B update +commands. +Examples of valid date specifications include: +.in +1i +.ft B +.nf +1 month ago +2 hours ago +400000 seconds ago +last year +last Monday +yesterday +a fortnight ago +3/31/92 10:00:07 PST +January 23, 1987 10:05pm +22:00 GMT +.fi +.ft P +.in -1i +.TP +.B \-f +When you specify a particular date or tag to \fBcvs\fP commands, they +normally ignore files that do not contain the tag (or did not exist on +the date) that you specified. Use the \fB\-f\fP option if you want +files retrieved even when there is no match for the tag or date. (The +most recent version is used in this situation.) +.B \-f +is available with these commands: +.BR checkout ", " export ", " +.BR rdiff ", " rtag ", and " update . +.TP +.B \-H +Help; describe the options available for this command. This is the +only option supported for +.I all +.B cvs +commands. +.TP +\fB\-k\fP \fIkflag\fP +Alter the default +.SM RCS +processing of keywords; all the +.B \-k +options described in +.BR co ( 1 ) +are available. The \fB\-k\fP option is available with the +.BR add ", " checkout ", " diff ", " export ", " +.BR rdiff ", and " update +commands. Your \fIkflag\fP specification is ``sticky'' when you use +it to create a private copy of a source file; that is, when you use +this option with the \fBcheckout\fP or \fBupdate\fP commands, +\fBcvs\fP associates your selected \fIkflag\fP with the file, and +continues to use it with future \fBupdate\fP commands on the same file +until you specify otherwise. +.SP +Some of the more useful \fIkflag\fPs are \-ko and \-kb (for binary files, +only compatible with +.SM RCS +version 5.7 or later), and \-kv which is useful for an +.B export +where you wish to retain keyword information after an +.B import +at some other site. +.TP +.B \-l +Local; run only in current working directory, rather than recurring through +subdirectories. Available with the following commands: +.BR checkout ", " commit ", " diff ", " +.BR export ", " remove ", " rdiff ", " rtag ", " +.BR status ", " tag ", and " update . +.I Warning: +this is not the same +as the overall +.` "cvs \-l" +option, which you can specify to the +.I left +of a +.B cvs +command! +.TP +.B \-n +Do +.I not +run any +.BR checkout / commit / tag / update +program. (A program can be specified to run on each of these +activities, in the modules database; this option bypasses it.) +Available with the +.BR checkout ", " commit ", " export ", and " +.B rtag +commands. +.I Warning: +this is not the same +as the overall +.` "cvs \-n" +option, which you can specify to the +.I left +of a +.B cvs +command! +.TP +.B \-P +Prune (remove) directories that are empty after being updated, on +.BR checkout ", or " update . +Normally, an empty directory (one that is void of revision-controlled +files) is left alone. +Specifying +.B \-P +will cause these directories to be silently removed from your checked-out +sources. +This does not remove the directory from the repository, only from your +checked out copy. +Note that this option is implied by the +.B \-r +or +.B \-D +options of +.BR checkout " and " export . +.TP +.B \-p +Pipe the files retrieved from the repository to standard output, +rather than writing them in the current directory. Available with the +.BR checkout " and " update +commands. +.TP +\fB\-r\fP \fItag\fP +Use the revision specified by the +.I tag +argument instead of the default ``head'' revision. As well as +arbitrary tags defined with the \fBtag\fP or \fBrtag\fP command, two +special tags are always available: +.` "HEAD" +refers to the most +recent version available in the repository, and +.` "BASE" +refers to the revision you last checked out into the current working +directory. +.SP +The \fItag\fP specification is ``sticky'' when you use +this option with +.` "cvs checkout" +or +.` "cvs update" +to +make your own copy of a file: \fBcvs\fP remembers the \fItag\fP and +continues to use it on future \fBupdate\fP commands, until you specify +otherwise. +.I tag +can be either a symbolic or numeric tag, in +.SM RCS +fashion. +Specifying the +.B \-q +global option along with the +.B \-r +command option is often useful, to suppress the warning messages when the +.SM RCS +file does not contain the specified tag. +.B \-r +is available with the +.BR checkout ", " commit ", " diff ", " +.BR history ", " export ", " +.BR rdiff ", " rtag ", and " update +commands. +.I Warning: +this is not the same +as the overall +.` "cvs \-r" +option, which you can specify to the +.I left +of a +.B cvs +command! +.SH "CVS COMMANDS" +Here (finally) are details on all the +.B cvs +commands and the options each accepts. The summary lines at the top +of each command's description highlight three kinds of things: +.TP 1i +\ \ \ \ Command Options and Arguments +Special options are described in detail below; common command options +may appear only in the summary line. +.TP 1i +\ \ \ \ Working Directory, or Repository? +Some \fBcvs\fP commands require a working directory to operate; some +require a repository. Also, some commands \fIchange\fP the +repository, some change the working directory, and some change +nothing. +.TP 1i +\ \ \ \ Synonyms +Many commands have synonyms, which you may find easier to +remember (or type) than the principal name. +.PP +.TP +\fBadd\fP [\fB\-k\fP \fIkflag\fP] [\fB\-m '\fP\fImessage\fP\fB'\fP] \fIfiles.\|.\|.\fP +.I Requires: +repository, working directory. +.br +.I Changes: +working directory. +.br +.I Synonym: +.B new +.br +Use the +.B add +command to create a new file or directory in the +.SM RCS +source repository. +The files or directories specified with +.B add +must already exist in the current directory (which must have been created +with the +.B checkout +command). +To add a whole new directory hierarchy to the source repository +(for example, files received from a third-party vendor), use the +.` "cvs import" +command instead. +.SP +If the argument to +.` "cvs add" +refers to an immediate sub-directory, the directory is +created at the correct place in the +.SM RCS +source repository, and the necessary +.B cvs +administration files are created in your working directory. +If the directory already exists in the source repository, +.` "cvs add" +still creates the administration files in your version of the directory. +This allows you to use +.` "cvs add" +to add a particular directory to your private sources even if +someone else created that directory after your +.B checkout +of the sources. You can do the following: +.SP +.in +1i +.ft B +.nf +example% mkdir new_directory +example% cvs add new_directory +example% cvs update new_directory +.fi +.ft P +.in -1i +.SP +An alternate approach using +.` "cvs update" +might be: +.SP +.in +1i +.ft B +.nf +example% cvs update -d new_directory +.fi +.ft P +.in -1i +.SP +(To add \fIany available\fP new directories to your working directory, it's +probably simpler to use +.` "cvs checkout" +or +.` "cvs update -d".) +.SP +The added files are not placed in the +.SM RCS +source repository until you use +.` "cvs commit" +to make the change permanent. +Doing a +.` "cvs add" +on a file that was removed with the +.` "cvs remove" +command will resurrect the file, if no +.` "cvs commit" +command intervened. +.SP +You will have the opportunity to specify a logging message, as usual, +when you use +.` "cvs commit" +to make the new file permanent. If you'd like to have another +logging message associated with just +.I creation +of the file (for example, to describe the file's purpose), you can +specify it with the +.` "\-m \fImessage\fP" +option to the +.B add +command. +.SP +The +.` "-k kflag" +option specifies the default way that this +file will be checked out. +The +.` "kflag" +argument is stored in the +.SM RCS +file and can be changed with +.` "cvs admin". +Specifying +.` "-ko" +is useful for checking in binaries that +shouldn't have the +.SM RCS +id strings expanded. +.TP +\fBadmin\fP [\fIrcs-options\fP] \fIfiles.\|.\|.\fP +.I Requires: +repository, working directory. +.br +.I Changes: +repository. +.br +.I Synonym: +.B rcs +.br +This is the +.B cvs +interface to assorted administrative +.SM RCS +facilities, documented in +.BR rcs ( 1 ). +.` "cvs admin" +simply passes all its options and arguments to the +.B rcs +command; it does no filtering or other processing. +This command does work recursively, however, so extreme care should be +used. +.TP +\fBcheckout\fP [\fBoptions\fP] \fImodules\fP.\|.\|. +.I Requires: +repository. +.br +.I Changes: +working directory. +.br +.I Synonyms: +.BR co ", " get +.br +Make a working directory containing copies of the source files specified by +.IR modules . +You must execute +.` "cvs checkout" +before using most of the other +.B cvs +commands, since most of them operate on your working directory. +.SP +\fImodules\fP are either symbolic names (themselves defined as the +module +.` "modules" +in the source repository; see +.BR cvs ( 5 )) +for some collection of source directories and files, or paths to +directories or files in the repository. +.SP +Depending on the +.I modules +you specify, +.B checkout +may recursively create directories and populate them with the appropriate +source files. +You can then edit these source files at any time (regardless of whether +other software developers are editing their own copies of the sources); +update them to include new changes applied by others to the source +repository; or commit your work as a permanent change to the +.SM RCS +repository. +.SP +Note that +.B checkout +is used to create directories. +The top-level directory created is always added to the directory +where +.B checkout +is invoked, and usually has the same name as the specified +.IR module . +In the case of a +.I module +alias, the created sub-directory may have a different name, but you can be +sure that it will be a sub-directory, and that +.B checkout +will show the relative path leading to each file as it is extracted into +your private work area (unless you specify the +.B \-Q +global option). +.SP +Running +.` "cvs checkout" +on a directory that was already built by a prior +.B checkout +is also permitted, and +has the same effect as specifying the +.B \-d +option to the +.B update +command described below. +.SP +The +.I options +permitted with +.` "cvs checkout" +include the standard command options +.BR \-P ", " \-f ", " +.BI \-k " kflag" +\&, +.BR \-l ", " \-n ", " \-p ", " +.BR \-r +.IR tag ", and" +.BI \-D " date"\c +\&. +.SP +In addition to those, you can use these special command options +with +.BR checkout : +.SP +Use the +.B \-A +option to reset any sticky tags, dates, or +.B \-k +options. (If you get a working file using one of the +\fB\-r\fP, \fB\-D\fP, or \fB\-k\fP options, \fBcvs\fP remembers the +corresponding tag, date, or \fIkflag\fP and continues using it on +future updates; use the \fB\-A\fP option to make \fBcvs\fP forget these +specifications, and retrieve the ``head'' version of the file). +.SP +The +.BI \-j " branch" +option merges the changes made between the +resulting revision and the revision that it is based on (e.g., if +the tag refers to a branch, +.B cvs +will merge all changes made in that branch into your working file). +.SP +With two \fB-j\fP options, +.B cvs +will merge in the changes between the two respective revisions. +This can be used to ``remove'' a certain delta from your working file. +.SP +In addition, each \fB-j\fP option can contain on optional date +specification which, when used with branches, can limit the chosen +revision to one within a specific date. +An optional date is specified by adding a colon (:) to the tag. +An example might be what +.` "cvs import" +tells you to do when you have +just imported sources that have conflicts with local changes: +.SP +.in +1i +.ft B +.nf +example% cvs checkout -jTAG:yesterday -jTAG module +.fi +.ft P +.in -1i +.SP +Use the +.B \-N +option with +.` "\-d \fIdir\fP" +to avoid shortening module paths in your working directory. (Normally, \fBcvs\fP shortens paths as much as possible when you specify an explicit target directory.) +.SP +Use the +.B \-c +option to copy the module file, sorted, to the standard output, +instead of creating or modifying any files or directories in your +working directory. +.SP +Use the +.BI \-d " dir" +option to create a directory called +.I dir +for the working files, instead of using the module name. Unless you +also use \fB\-N\fP, the paths created under \fIdir\fP will be as short +as possible. +.SP +Use the +.B \-s +option to display per-module status information stored with +the +.B \-s +option within the modules file. +.TP +\fBcommit\fP [\fB\-lnR\fP] [\fB\-m\fP '\fIlog_message\fP' | \fB\-f\fP \fIfile\fP] [\fB\-r\fP \fIrevision\fP] [\fIfiles.\|.\|.\fP] +.I Requires: +working directory, repository. +.br +.I Changes: +repository. +.br +.I Synonym: +.B ci +.br +Use +.` "cvs commit" +when you want to incorporate changes from your working source +files into the general source repository. +.SP +If you don't specify particular \fIfiles\fP to commit, all +of the files in your working current directory are examined. +.B commit +is careful to change in the repository only those files that you have +really changed. By default (or if you explicitly specify the +.B \-R +option), files +in subdirectories are also examined and committed if they have +changed; you can use the +.B \-l +option to limit +.B commit +to the current directory only. +Sometimes you may want to force a file to be committed even though it +is unchanged; this is achieved with the +.B \-f +flag, which also has the effect of disabling recursion (you can turn +it back on with +.B \-R +of course). +.SP +.B commit +verifies that the selected files are up to date with the current revisions +in the source repository; it will notify you, and exit without +committing, if any of the specified files must be made current first +with +.` "cvs update". +.B commit +does not call the +.B update +command for you, but rather leaves that for you to do when +the time is right. +.SP +When all is well, an editor is invoked to allow you to enter a log +message that will be written to one or more logging programs and placed in the +.SM RCS +source repository file. +You can instead specify the log message on the command line with the +.B \-m +option, thus suppressing the editor invocation, or use the +.B \-F +option to specify that the argument \fIfile\fP contains the log message. +.SP +The +.B \-r +option can be used to commit to a particular symbolic or numeric revision +within the +.SM RCS +file. +For example, to bring all your files up to the +.SM RCS +revision ``3.0'' (including those that haven't changed), you might do: +.SP +.in +1i +.ft B +.nf +example% cvs commit -r3.0 +.fi +.ft P +.in -1i +.SP +.B cvs +will only allow you to commit to a revision that is on the main trunk (a +revision with a single dot). +However, you can also commit to a branch revision (one that has an even +number of dots) with the +.B \-r +option. +To create a branch revision, one typically use the +.B \-b +option of the +.BR rtag " or " tag +commands. +Then, either +.BR checkout " or " update +can be used to base your sources on the newly created branch. +From that point on, all +.B commit +changes made within these working sources will be automatically added +to a branch revision, thereby not perturbing main-line development in any +way. +For example, if you had to create a patch to the 1.2 version of the +product, even though the 2.0 version is already under development, you +might do: +.SP +.in +1i +.ft B +.nf +example% cvs rtag -b -rFCS1_2 FCS1_2_Patch product_module +example% cvs checkout -rFCS1_2_Patch product_module +example% cd product_module +[[ hack away ]] +example% cvs commit +.fi +.ft P +.in -1i +.SP +Say you have been working on some extremely experimental software, based on +whatever revision you happened to checkout last week. +If others in your group would like to work on this software with you, but +without disturbing main-line development, you could commit your change to a +new branch. +Others can then checkout your experimental stuff and utilize the full +benefit of +.B cvs +conflict resolution. +The scenario might look like: +.SP +.in +1i +.ft B +.nf +example% cvs tag -b EXPR1 +example% cvs update -rEXPR1 +[[ hack away ]] +example% cvs commit +.fi +.ft P +.in -1i +.SP +Others would simply do +.` "cvs checkout -rEXPR1 whatever_module" +to work with you on the experimental change. +.TP +\fBdiff\fP [\fB\-kl\fP] [\fIrcsdiff_options\fP] [[\fB\-r\fP \fIrev1\fP | \fB\-D\fP \fIdate1\fP] [\fB\-r\fP \fIrev2\fP | \fB\-D\fP \fIdate2\fP]] [\fIfiles.\|.\|.\fP] +.I Requires: +working directory, repository. +.br +.I Changes: +nothing. +.br +You can compare your working files with revisions in the source +repository, with the +.` "cvs diff" +command. If you don't specify a particular revision, your files +are compared with the revisions they were based on. You can also use +the standard +.B cvs +command option +.B \-r +to specify a particular revision to compare your files with. Finally, +if you use +.B \-r +twice, you can see differences between two revisions in the +repository. +You can also specify +.B \-D +options to diff against a revision in the past. +The +.B \-r +and +.B \-D +options can be mixed together with at most two options ever specified. +.SP +See +.BR rcsdiff ( 1 ) +for a list of other accepted options. +.SP +If you don't specify any files, +.B diff +will display differences for all those files in the current directory +(and its subdirectories, unless you use the standard option +.BR \-l ) +that +differ from the corresponding revision in the source repository +(i.e. files that +.I you +have changed), or that differ from the revision specified. +.TP +\fBexport\fP [\-\fBf\|lNnQq\fP] \fB\-r\fP \fIrev\fP\||\|\fB\-D\fP \fIdate\fP [\fB\-d\fP \fIdir\fP] [\fB\-k\fP \fIkflag\fP] \fImodule\fP.\|.\|. +.I Requires: +repository. +.br +.I Changes: +current directory. +.br +This command is a variant of +.` "cvs checkout"; +use it when you want a copy of the source for \fImodule\fP +without the \fBcvs\fP administrative directories. For example, you +might use +.` "cvs export" +to prepare source for shipment +off-site. This command \fIrequires\fP that you specify a date or tag +(with \fB\-D\fP or \fB\-r\fP), so that you can count on reproducing +the source you ship to others. +.SP +The only non-standard options are +.` "\-d \fIdir\fP" +(write the +source into directory \fIdir\fP) and +.` "\-N" +(don't shorten +module paths). +These have the same meanings as the same options in +.` "cvs checkout". +.SP +The +.B \-kv +option is useful when +.B export +is used. +This causes any +.SM RCS +keywords to be expanded such that an +.B import +done at some other site will not lose the keyword revision information. +Other \fIkflag\fPs may be used with +.` "cvs export" +and are described in +.BR co ( 1 ). +.TP +\fBhistory\fP [\fB\-\fP\fIreport\fP] [\fB\-\fP\fIflags\fP] [\fB\-\fP\fIoptions args\fP] [\fIfiles\fP.\|.\|.] +.I Requires: +the file +.` "$CVSROOT/CVSROOT/history" +.br +.I Changes: +nothing. +.br +\fBcvs\fP keeps a history file that tracks each use of the +\fBcheckout\fP, \fBcommit\fP, \fBrtag\fP, \fBupdate\fP, and \fBrelease\fP +commands. You can use +.` "cvs history" +to display this +information in various formats. +.SP +.I Warning: +.` "cvs history" +uses +.` "\-f", +.` "\-l", +.` "\-n", +and +.` "\-p" +in ways that conflict with the +descriptions in +.SM +COMMON COMMAND OPTIONS\c +\&. +.SP +Several options (shown above as \fB\-\fP\fIreport\fP) control what +kind of report is generated: +.TP 1i +.B \ \ \ \ \ \ \-c +Report on each time \fBcommit\fP was used (i.e., each time the +repository was modified). +.TP 1i +\fB\ \ \ \ \ \ \-m\fP \fImodule\fP +Report on a particular \fImodule\fP. (You can meaningfully use +\fB\-m\fP more than once on the command line.) +.TP 1i +.B \ \ \ \ \ \ \-o +Report on checked-out modules. +.TP 1i +.B \ \ \ \ \ \ \-T +Report on all tags. +.TP 1i +\fB\ \ \ \ \ \ \-x\fP \fItype\fP +Extract a particular set of record types \fIX\fP from the \fBcvs\fP +history. The types are indicated by single letters, which you may +specify in combination. +Certain commands have a single record type: \fBcheckout\fP (type `O'), +\fBrelease\fP (type `F'), and \fBrtag\fP (type `T'). One of four +record types may result from an \fBupdate\fP: `W', when the working copy +of a file is deleted during update (because it was gone from the +repository); `U', when a working file was copied from the +repository; `G', when a merge was necessary and it succeeded; and 'C', +when a merge was necessary but collisions were detected (requiring +manual merging). Finally, one of three record types results from +\fBcommit\fP: `M', when a file was modified; `A', when a file is first +added; and `R', when a file is removed. +.TP 1i +.B \ \ \ \ \ \ \-e +Everything (all record types); equivalent to specifying +.` "\-xMACFROGWUT". +.TP 1i +\fB\ \ \ \ \ \ \-z\fP \fIzone\fP +Use time zone +.I zone +when outputting history records. +The zone name +.B LT +stands for local time; +numeric offsets stand for hours and minutes ahead of UTC. +For example, +.B +0530 +stands for 5 hours and 30 minutes ahead of (i.e. east of) UTC. +.PP +.RS .5i +The options shown as \fB\-\fP\fIflags\fP constrain the report without +requiring option arguments: +.RE +.TP 1i +.B \ \ \ \ \ \ \-a +Show data for all users (the default is to show data only for the user +executing +.` "cvs history"). +.TP 1i +.B \ \ \ \ \ \ \-l +Show last modification only. +.TP 1i +.B \ \ \ \ \ \ \-w +Show only the records for modifications done from the same working +directory where +.` "cvs history" +is executing. +.PP +.RS .5i +The options shown as \fB\-\fP\fIoptions args\fP constrain the report +based on an argument: +.RE +.TP 1i +\fB\ \ \ \ \ \ \-b\fP \fIstr\fP +Show data back to a record containing the string \fIstr\fP in either +the module name, the file name, or the repository path. +.TP 1i +\fB\ \ \ \ \ \ \-D\fP \fIdate\fP +Show data since \fIdate\fP. +.TP 1i +\fB\ \ \ \ \ \ \-p\fP \fIrepository\fP +Show data for a particular source repository (you can specify several +\fB\-p\fP options on the same command line). +.TP 1i +\fB\ \ \ \ \ \ \-r\fP \fIrev\fP +Show records referring to revisions since the revision or tag +named \fIrev\fP appears in individual RCS files. +Each +.SM RCS +file is searched for the revision or tag. +.TP 1i +\fB\ \ \ \ \ \ \-t\fP \fItag\fP +Show records since tag \fItag\fP was last added to the the history file. +This differs from the \fB-r\fP flag above in that it reads +only the history file, not the +.SM RCS +files, and is much faster. +.TP 1i +\fB\ \ \ \ \ \ \-u\fP \fIname\fP +Show records for user \fIname\fP. +.PP +.TP +\fBimport\fP [\fB\-\fP\fIoptions\fP] \fIrepository vendortag releasetag\fP.\|.\|. +.I Requires: +Repository, source distribution directory. +.br +.I Changes: +repository. +.br +Use +.` "cvs import" +to incorporate an entire source +distribution from an outside source (e.g., a source vendor) into your +source repository directory. You can use this command both for +initial creation of a repository, and for wholesale updates to the +module form the outside source. +.SP +The \fIrepository\fP argument gives a directory name (or a path to a +directory) under the CVS root directory for repositories; if the +directory did not exist, \fBimport\fP creates it. +.SP +When you use \fBimport\fP for updates to source that has been modified in your +source repository (since a prior \fBimport\fP), it +will notify you of any files that conflict in the two branches of +development; use +.` "cvs checkout -j" +to reconcile the differences, as \fBimport\fP instructs you to do. +.SP +By default, certain file names are ignored during +.` "cvs import": +names associated with +.SM CVS +administration, or with other common source control systems; common +names for patch files, object files, archive files, and editor backup +files; and other names that are usually artifacts of assorted utilities. +Currently, the default list of ignored files includes files matching +these names: +.SP +.in +1i +.ft B +.nf +RCSLOG RCS SCCS +CVS* cvslog.* +tags TAGS +\&.make.state .nse_depinfo +*~ #* .#* ,* +*.old *.bak *.BAK *.orig *.rej .del\-* +*.a *.o *.so *.Z *.elc *.ln core +.fi +.ft P +.in -1i +.SP +The outside source is saved in a first-level +.SM RCS +branch, by default +.` "1.1.1". +Updates are leaves of this +branch; for example, files from the first imported collection of +source will be revision +.` "1.1.1.1", +then files from the first +imported update will be revision +.` "1.1.1.2", +and so on. +.SP +At least three arguments are required. \fIrepository\fP is needed to +identify the collection of source. \fIvendortag\fP is a tag for the +entire branch (e.g., for +.` "1.1.1"). +You must also specify at +least one \fIreleasetag\fP to identify the files at the leaves created +each time you execute +.` "cvs import". +.SP +One of the standard +.B cvs +command options is available: \fB\-m\fP +\fImessage\fP. If you do not specify a logging message with +\fB\-m\fP, your editor is invoked (as with \fBcommit\fP) to allow you +to enter one. +.SP +There are three additional special options. +.SP +Use +.` "\-d" +to specify that each file's time of last modification should be used +for the checkin date and time. +.SP +Use +.` "\-b \fIbranch\fP" +to specify a first-level branch other +than +.` "1.1.1". +.SP +Use +.` "\-I \fIname\fP" +to specify file names that should be +ignored during \fBimport\fP. You can use this option repeatedly. +To avoid ignoring any files at all (even those ignored by default), +specify +.` "\-I !". +.TP +\fBlog\fP [\fB\-l\fP] \fIrlog-options [files\fP\|.\|.\|.] +.I Requires: +repository, working directory. +.br +.I Changes: +nothing. +.br +.I Synonym: +.B rlog +.br +Display log information for \fIfiles\fP. +.` "cvs log" +calls +the +.SM RCS +utility \fBrlog\fP; all the options described in +.BR rlog ( 1 ) +are available. Among the more useful \fBrlog\fP options are \fB\-h\fP +to display only the header (including tag definitions, but omitting +most of the full log); \fB\-r\fP to select logs on particular +revisions or ranges of revisions; and \fB\-d\fP to select particular +dates or date ranges. See +.BR rlog ( 1 ) +for full explanations. +This command is recursive by default, unless the +.B \-l +option is specified. +.TP +\fBrdiff\fP [\fB\-\fP\fIflags\fP] [\fB\-V\fP \fIvn\fP] [\fB\-r\fP \fIt\fP|\fB\-D\fP \fId\fP [\fB\-r\fP \fIt2\fP|\fB\-D\fP \fId2\fP]] \fImodules\|.\|.\|.\fP +.I Requires: +repository. +.br +.I Changes: +nothing. +.br +.I Synonym: +.B patch +.br +Builds a Larry Wall format +.BR patch ( 1 ) +file between two releases, that can be fed directly into the +.B patch +program to bring an old release up-to-date with the new release. +(This is one of the few \fBcvs\fP commands that operates directly from +the repository, and doesn't require a prior +.BR checkout .) +The diff output is sent to the standard output device. +You can specify (using the standard \fB\-r\fP and \fB\-D\fP options) +any combination of one or two revisions or dates. +If only one revision or date is specified, the +patch file reflects differences between that revision or date and the +current ``head'' revisions in the +.SM RCS +file. +.SP +Note that if the software release affected +is contained in more than one directory, then it may be necessary to +specify the +.B \-p +option to the +.B patch +command when patching the old sources, so that +.B patch +is able to find the files that are located in other directories. +.SP +If you use the option \fB\-V\fP \fIvn\fP, +.SM RCS +keywords are expanded according to the rules current in +.SM RCS +version \fIvn\fP (the expansion format changed with +.SM RCS +version 5). +.SP +The standard option \fIflags\fP \fB\-f\fP, and \fB\-l\fP +are available with this command. There are also several +special options flags: +.SP +If you use the +.B \-s +option, no patch output is produced. +Instead, a summary of the changed or added files between the two +releases is sent to the standard output device. +This is useful for finding out, for example, which files have changed +between two dates or revisions. +.SP +If you use the +.B \-t +option, a diff of the top two revisions is sent to the standard output device. +This is most useful for seeing what the last change to a file was. +.SP +If you use the +.B \-u +option, the patch output uses the newer ``unidiff'' format for context +diffs. +.SP +You can use +.B \-c +to explicitly specify the +.` "diff \-c" +form of context diffs +(which is the default), if you like. +.TP +\fBrelease\fP [\fB\-dQq\fP] \fImodules\fP\|.\|.\|. +.I Requires: +Working directory. +.br +.I Changes: +Working directory, history log. +.br +This command is meant to safely cancel the effect of +.` "cvs checkout'. +Since +.B cvs +doesn't lock files, it isn't strictly necessary to use this command. +You can always simply delete your working directory, if you +like; but you risk losing changes you may have forgotten, and you +leave no trace in the +.B cvs +history file that you've abandoned your checkout. +.SP +Use +.` "cvs release" +to avoid these problems. This command +checks that no un-committed changes are present; that you are +executing it from immediately above, or inside, a \fBcvs\fP working +directory; and that the repository recorded for your files is the same +as the repository defined in the module database. +.SP +If all these conditions are true, +.` "cvs release" +leaves a +record of its execution (attesting to your intentionally abandoning +your checkout) in the +.B cvs +history log. +.SP +You can use the \fB\-d\fP flag to request that your working copies of +the source files be deleted if the \fBrelease\fP succeeds. +.TP +\fBremove\fP [\fB\-lR\fP] [\fIfiles\|.\|.\|.\fP] +.I Requires: +Working directory. +.br +.I Changes: +Working directory. +.br +.I Synonyms: +.BR rm ", " delete +.br +Use this command to declare that you wish to remove \fIfiles\fP from +the source repository. Like most +.B cvs +commands, +.` "cvs remove" +works on files in your working +directory, not directly on the repository. As a safeguard, it also +requires that you first erase the specified files from your working +directory. +.SP +The files are not actually removed until you apply your changes to the +repository with +.BR commit ; +at that point, the corresponding +.SM RCS +files in the source repository are +.I moved +into the +.` "Attic" +directory (also within the source repository). +.SP +This command is recursive by default, scheduling all physically removed +files that it finds for removal by the next +.BR commit . +Use the +.B \-l +option to avoid this recursion, or just specify that actual files that you +wish remove to consider. +.TP +\fBrtag\fP [\fB\-f\|alnRQq\fP] [\fB\-b\fP] [\fB\-d\fP] [\fB\-r\fP \fItag\fP | \fB\-D\fP \fIdate\fP] \fIsymbolic_tag\fP \fImodules\|.\|.\|.\fP +.I Requires: +repository. +.br +.I Changes: +repository. +.br +.I Synonym: +.B rfreeze +.br +You can use this command to assign symbolic tags to particular, +explicitly specified source versions in the repository. +.` "cvs rtag" +works directly on the repository contents (and requires no +prior +.BR checkout ). +Use +.` "cvs tag" +instead, to base the selection of +versions to tag on the contents of your working directory. +.SP +In general, tags (often the symbolic names of software distributions) +should not be removed, but the +.B \-d +option is available as a means to remove completely obsolete symbolic names +if necessary (as might be the case for an Alpha release, say). +.SP +.` "cvs rtag" +will not move a tag that already exists. With the \fB\-F\fP option, +however, +.` "cvs rtag" +will re-locate any instance of \fIsymbolic_tag\fP that already exists +on that file to the new repository versions. Without the \fB\-F\fP +option, attempting to use +.` "cvs rtag" +to apply a tag that already exists on that file will produce an error +message. +.SP +The \fB-b\fP option makes the tag a ``branch'' tag, allowing +concurrent, isolated development. +This is most useful for creating a patch to a previously released software +distribution. +.SP +You can use the standard \fB\-r\fP and \fB\-D\fP options to tag only those +files that already contain a certain tag. This method would be used +to rename a tag: tag only the files identified by the old tag, then delete the +old tag, leaving the new tag on exactly the same files as the old tag. +.SP +.B rtag +executes recursively by default, tagging all subdirectories of +\fImodules\fP you specify in the argument. You can restrict its +operation to top-level directories with the standard \fB\-l\fP option; +or you can explicitly request recursion with \fB\-R\fP. +.SP +The modules database can specify a program to execute whenever a tag +is specified; a typical use is to send electronic mail to a group of +interested parties. If you want to bypass that program, use the +standard \fB\-n\fP option. +.SP +Use the +.B \-a +option to have +.B rtag +look in the +.` "Attic" +for removed files that contain the specified tag. +The tag is removed from these files, which makes it convenient to re-use a +symbolic tag as development continues (and files get removed from the +up-coming distribution). +.TP +\fBstatus\fP [\fB\-lRqQ\fP] [\fB\-v\fP] [\fIfiles\fP\|.\|.\|.] +.I Requires: +working directory, repository. +.br +.I Changes: +nothing. +.br +Display a brief report on the current status of \fIfiles\fP with +respect to the source repository, including any ``sticky'' tags, +dates, or \fB\-k\fP options. (``Sticky'' options will restrict how +.` "cvs update" +operates until you reset them; see the +description of +.` "cvs update \-A\|.\|.\|.".) +.SP +You can also use this command to anticipate the potential impact of a +.` "cvs update" +on your working source directory. If you do +not specify any \fIfiles\fP explicitly, reports are shown for all +files that \fBcvs\fP has placed in your working directory. You can +limit the scope of this search to the current directory itself (not +its subdirectories) with the standard \fB\-l\fP option flag; or you +can explicitly request recursive status reports with the \fB\-R\fP +option. +.SP +The +.B \-v +option causes the symbolic tags for the +.SM RCS +file to be displayed as well. +.TP +\fBtag\fP [\fB\-lQqR\fP] [\fB\-F\fP] [\fB\-b\fP] [\fB\-d\fP] [\fB\-r\fP \fItag\fP | \fB\-D\fP \fIdate\fP] [\fB\-f\fP] \fIsymbolic_tag\fP [\fIfiles\fP\|.\|.\|.\|] +.I Requires: +working directory, repository. +.br +.I Changes: +repository. +.br +.I Synonym: +.B freeze +.br +Use this command to assign symbolic tags to the nearest repository +versions to your working sources. The tags are applied immediately to +the repository, as with \fBrtag\fP. +.SP +One use for tags is to record a ``snapshot'' of the current sources +when the software freeze date of a project arrives. As bugs are fixed +after the freeze date, only those changed sources that are to be part +of the release need be re-tagged. +.SP +The symbolic tags are meant to permanently record which revisions of which +files were used in creating a software distribution. +The +.BR checkout , +.B export +and +.B update +commands allow you to extract an exact copy of a tagged release at any time in +the future, regardless of whether files have been changed, added, or removed +since the release was tagged. +.SP +You can use the standard \fB\-r\fP and \fB\-D\fP options to tag only those +files that already contain a certain tag. This method would be used +to rename a tag: tag only the files identified by the old tag, then delete the +old tag, leaving the new tag on exactly the same files as the old tag. +.SP +Specifying the \fB\-f\fP flag in addition to the \fB\-r\fP or \fB\-D\fP +flags will tag those files named on the command line even if they do not +contain the old tag or did not exist on the specified date. +.SP +By default (without a \fB\-r\fP or \fB\-D\fP flag) +the versions to be tagged are supplied +implicitly by the \fBcvs\fP records of your working files' history +rather than applied explicitly. +.SP +If you use +.` "cvs tag \-d \fIsymbolic_tag\fP\|.\|.\|.", +the +symbolic tag you specify is +.I deleted +instead of being added. \fIWarning\fP: Be very certain of your ground +before you delete a tag; doing this effectively discards some +historical information, which may later turn out to have been valuable. +.SP +.` "cvs tag" +will not move a tag that already exists. With the \fB\-F\fP option, +however, +.` "cvs tag" +will re-locate any instance of \fIsymbolic_tag\fP that already exists +on that file to the new repository versions. Without the \fB\-F\fP +option, attempting to use +.` "cvs tag" +to apply a tag that already exists on that file will produce an error +message. +.SP +The \fB-b\fP option makes the tag a ``branch'' tag, allowing +concurrent, isolated development. +This is most useful for creating a patch to a previously released software +distribution. +.SP +Normally, +.B tag +executes recursively through subdirectories; you can prevent this by +using the standard \fB\-l\fP option, or specify the recursion +explicitly by using \fB\-R\fP. +.TP +\fBupdate\fP [\fB\-Adf\|lPpQqR\fP] [\fB\-d\fP] [\fB\-r\fP \fItag\fP|\fB\-D\fP \fIdate\fP] \fIfiles\|.\|.\|.\fP +.I Requires: +repository, working directory. +.br +.I Changes: +working directory. +.br +After you've run +.B checkout +to create your private copy of source from the common repository, +other developers will continue changing the central source. From time +to time, when it is convenient in your development process, you can +use the +.B update +command +from within your working directory to reconcile your work with any +revisions applied to the source repository since your last +.B checkout +or +.BR update . +.SP +.B update +keeps you informed of its progress by printing a line for each file, +prefaced with one of the characters +.` "U A R M C ?" +to indicate the status of the file: +.TP 1i +\fBU\fP \fIfile\fP +The file was brought \fIup to date\fP with respect to the repository. +This is done for any file that exists in the repository but not in +your source, and for files that you haven't changed but are not the most +recent versions available in the repository. +.TP 1i +\fBA\fP \fIfile\fP +The file has been \fIadded\fP to your private copy of the sources, and +will be added to the +.SM RCS +source repository when you run +.` "cvs commit" +on the file. +This is a reminder to you that the file needs to be committed. +.TP 1i +\fBR\fP \fIfile\fP +The file has been \fIremoved\fP from your private copy of the sources, and +will be removed from the +.SM RCS +source repository when you run +.` "cvs commit" +on the file. +This is a reminder to you that the file needs to be committed. +.TP 1i +\fBM\fP \fIfile\fP +The file is \fImodified\fP in your working directory. +.` "M" +can indicate one of two states for a file you're working on: either +there were no modifications to the same file in the repository, so +that your file remains as you last saw it; or there were modifications +in the repository as well as in your copy, but they were +\fImerged\fP successfully, without conflict, in your working +directory. +.TP 1i +\fBC\fP \fIfile\fP +A \fIconflict\fP was detected while trying to merge your changes to +\fIfile\fP with changes from the source repository. \fIfile\fP (the +copy in your working directory) is now the output of the +.BR rcsmerge ( 1 ) +command on the two versions; an unmodified copy of your file is also +in your working directory, with the name `\fB.#\fP\fIfile\fP\fB.\fP\fIversion\fP', +where +.I version +is the +.SM RCS +revision that your modified file started from. +(Note that some systems automatically purge files that begin with +\& +.` ".#" +if they have not been accessed for a few days. +If you intend to keep a copy of your original file, it is a very good +idea to rename it.) +.TP 1i +\fB?\fP \fIfile\fP +\fIfile\fP is in your working directory, but does not correspond to +anything in the source repository, and is not in the list of files +for \fBcvs\fP to ignore (see the description of the \fB\-I\fP option). +.PP +.RS .5i +.SP +Use the +.B \-A +option to reset any sticky tags, dates, or +.B \-k +options. (If you get a working copy of a file by using one of the +\fB\-r\fP, \fB\-D\fP, or \fB\-k\fP options, \fBcvs\fP remembers the +corresponding tag, date, or \fIkflag\fP and continues using it on +future updates; use the \fB\-A\fP option to make \fBcvs\fP forget these +specifications, and retrieve the ``head'' version of the file). +.SP +The \fB\-j\fP\fIbranch\fP option +merges the changes made between the +resulting revision and the revision that it is based on (e.g., if +the tag refers to a branch, +.B cvs +will merge all changes made in +that branch into your working file). +.SP +With two \fB-j\fP options, +.B cvs +will merge in the changes between the two respective revisions. +This can be used to ``remove'' a certain delta from your working file. +E.g., If the file foo.c is based on +revision 1.6 and I want to remove the changes made between 1.3 and +1.5, I might do: +.SP +.in +1i +.ft B +.nf +example% cvs update -j1.5 -j1.3 foo.c # note the order... +.fi +.ft P +.in -1i +.SP +In addition, each \fB-j\fP option can contain on optional date +specification which, when used with branches, can limit the chosen +revision to one within a specific date. +An optional date is specified by adding a colon (:) to the tag. +.SP +.in +1i +.ft B +.nf +-jSymbolic_Tag:Date_Specifier +.fi +.ft P +.in -1i +.SP +Use the +.B \-d +option to create any directories that exist in the repository if they're +missing from the working directory. (Normally, update acts only on +directories and files that were already enrolled in your +working directory.) This is useful for updating directories +that were created in the repository since the initial +\fBcheckout\fP; but it has an unfortunate side effect. If you +deliberately avoided certain directories in the repository when you +created your working directory (either through use of a module name or by +listing explicitly the files and directories you wanted on the +command line), then updating with +.B \-d +will create those directories, which may not be what you want. +.SP +Use \fB\-I\fP \fIname\fP to ignore files whose names match \fIname\fP +(in your working directory) during the update. You can specify +\fB\-I\fP more than once on the command line to specify several files +to ignore. By default, +\fBupdate\fP ignores files whose names match any of the following: +.SP +.in +1i +.ft B +.nf +RCSLOG RCS SCCS +CVS* cvslog.* +tags TAGS +\&.make.state .nse_depinfo +*~ #* .#* ,* +*.old *.bak *.BAK *.orig *.rej .del\-* +*.a *.o *.so *.Z *.elc *.ln core +.fi +.ft P +.in -1i +.SP +Use +.` "\-I !" +to avoid ignoring any files at all. +.SP +The standard \fBcvs\fP command options \fB\-f\fP, \fB\-k\fP, +\fB\-l\fP, \fB\-P\fP, \fB\-p\fP, and \fB\-r\fP +are also available with \fBupdate\fP. +.RE +.SH "FILES" +For more detailed information on +.B cvs +supporting files, see +.BR cvs ( 5 ). +.LP +.I +Files in home directories: +.TP +\&.cvsrc +The +.B cvs +initialisation file. Lines in this file can be used to specify default +options for each +.B cvs +command. For example the line +.` "diff \-c" +will ensure that +.` "cvs diff" +is always passed the +.B \-c +option in addition to any other options passed on the command line. +.TP +\&.cvswrappers +Specifies wrappers to be used in addition to those specified in the +CVSROOT/cvswrappers file in the repository. +.LP +.I +Files in working directories: +.TP +CVS +A directory of \fBcvs\fP administrative files. +.I +Do not delete. +.TP +CVS/Entries +List and status of files in your working directory. +.TP +CVS/Entries.Backup +A backup of +.` "CVS/Entries". +.TP +CVS/Entries.Static +Flag: do not add more entries on +.` "cvs update". +.TP +CVS/Root +Pathname to the repository ( +.SM CVSROOT +) location at the time of checkout. This file is used instead +of the +.SM CVSROOT +environment variable if the environment variable is not +set. A warning message will be issued when the contents of this +file and the +.SM CVSROOT +environment variable differ. The file may be over-ridden by the +presence of the +.SM CVS_IGNORE_REMOTE_ROOT +environment variable. +.TP +CVS/Repository +Pathname to the corresponding directory in the source repository. +.TP +CVS/Tag +Contains the per-directory ``sticky'' tag or date information. +This file is created/updated when you specify +.B \-r +or +.B \-D +to the +.B checkout +or +.B update +commands, and no files are specified. +.TP +CVS/Checkin.prog +Name of program to run on +.` "cvs commit". +.TP +CVS/Update.prog +Name of program to run on +.` "cvs update". +.LP +.I +Files in source repositories: +.TP +$CVSROOT/CVSROOT +Directory of global administrative files for repository. +.TP +CVSROOT/commitinfo,v +Records programs for filtering +.` "cvs commit" +requests. +.TP +CVSROOT/cvswrappers,v +Records +.B cvs +wrapper commands to be used when checking files into and out of the +repository. Wrappers allow the file or directory to be processed +on the way in and out of CVS. The intended uses are many, one +possible use would be to reformat a C file before the file is checked +in, so all of the code in the repository looks the same. +.TP +CVSROOT/editinfo,v +Records programs for editing/validating +.` "cvs commit" +log entries. +.TP +CVSROOT/history +Log file of \fBcvs\fP transactions. +.TP +CVSROOT/loginfo,v +Records programs for piping +.` "cvs commit" +log entries. +.TP +CVSROOT/modules,v +Definitions for modules in this repository. +.TP +CVSROOT/rcsinfo,v +Records pathnames to templates used during a +.` "cvs commit" +operation. +.TP +CVSROOT/taginfo,v +Records programs for validating/logging +.` "cvs tag" +and +.` "cvs rtag" +operations. +.TP +MODULE/Attic +Directory for removed source files. +.TP +#cvs.lock +A lock directory created by +.B cvs +when doing sensitive changes to the +.SM RCS +source repository. +.TP +#cvs.tfl.\fIpid\fP +Temporary lock file for repository. +.TP +#cvs.rfl.\fIpid\fP +A read lock. +.TP +#cvs.wfl.\fIpid\fP +A write lock. +.SH "ENVIRONMENT VARIABLES" +.TP +.SM CVSROOT +Should contain the full pathname to the root of the +.B cvs +source repository (where the +.SM RCS +files are kept). This information must be available to \fBcvs\fP for +most commands to execute; if +.SM CVSROOT +is not set, or if you wish to override it for one invocation, you can +supply it on the command line: +.` "cvs \-d \fIcvsroot cvs_command\fP\|.\|.\|." +You may not need to set +.SM CVSROOT +if your \fBcvs\fP binary has the right path compiled in; use +.` "cvs \-v" +to display all compiled-in paths. +.TP +.SM CVSREAD +If this is set, +.B checkout +and +.B update +will try hard to make the files in your working directory read-only. +When this is not set, the default behavior is to permit modification +of your working files. +.TP +.SM RCSBIN +Specifies the full pathname where to find +.SM RCS +programs, such as +.BR co ( 1 ) +and +.BR ci ( 1 ). +If not set, a compiled-in value is used; see the display from +.` "cvs \-v". +.TP +.SM CVSEDITOR +Specifies the program to use for recording log messages during +.BR commit . +If not set, the +.SM EDITOR +environment variable is used instead. +If +.SM EDITOR +is not set either, the default is +.BR /usr/ucb/vi . +.TP +.SM CVS_IGNORE_REMOTE_ROOT +If this variable is set then +.B cvs +will ignore all references to remote repositories in the CVS/Root file. +.TP +.SM CVS_RSH +.B cvs +uses the contents of this variable to determine the name of the +remote shell command to use when starting a +.B cvs +server. If this variable is not set then +.` "rsh" +is used. +.TP +.SM CVS_SERVER +.B cvs +uses the contents of this variable to determine the name of the +.B cvs +server command. If this variable is not set then +.` "cvs" +is used. +.TP +.SM CVSWRAPPERS +This variable is used by the +.` "cvswrappers" +script to determine the name of the wrapper file, in addition to the +wrappers defaults contained in the repository +.SM (CVSROOT/cvswrappers) +and the user's home directory (~/.cvswrappers). +.SH "AUTHORS" +.TP +Dick Grune +Original author of the +.B cvs +shell script version posted to +.B comp.sources.unix +in the volume6 release of December, 1986. +Credited with much of the +.B cvs +conflict resolution algorithms. +.TP +Brian Berliner +Coder and designer of the +.B cvs +program itself in April, 1989, based on the original work done by Dick. +.TP +Jeff Polk +Helped Brian with the design of the +.B cvs +module and vendor branch support and author of the +.BR checkin ( 1 ) +shell script (the ancestor of +.` "cvs import"). +.SH "SEE ALSO" +.BR ci ( 1 ), +.BR co ( 1 ), +.BR cvs ( 5 ), +.BR cvsbug ( 8 ), +.BR diff ( 1 ), +.BR grep ( 1 ), +.BR patch ( 1 ), +.BR rcs ( 1 ), +.BR rcsdiff ( 1 ), +.BR rcsmerge ( 1 ), +.BR rlog ( 1 ), diff --git a/contrib/cvs/man/cvs.5 b/contrib/cvs/man/cvs.5 new file mode 100644 index 0000000..fb2bcec --- /dev/null +++ b/contrib/cvs/man/cvs.5 @@ -0,0 +1,325 @@ +.TH cvs 5 "12 February 1992" +.\" Full space in nroff; half space in troff +.de SP +.if n .sp +.if t .sp .5 +.. +.SH NAME +cvs \- Concurrent Versions System support files +.SH SYNOPSIS +.hy 0 +.na +.TP +.B $CVSROOT/CVSROOT/commitinfo,v +.TP +.B $CVSROOT/CVSROOT/cvsignore,v +.TP +.B $CVSROOT/CVSROOT/cvswrappers,v +.TP +.B $CVSROOT/CVSROOT/editinfo,v +.TP +.B $CVSROOT/CVSROOT/history +.TP +.B $CVSROOT/CVSROOT/loginfo,v +.TP +.B $CVSROOT/CVSROOT/modules,v +.TP +.B $CVSROOT/CVSROOT/rcsinfo,v +.TP +.B $CVSROOT/CVSROOT/taginfo,v +.ad b +.hy 1 +.SH DESCRIPTION +.B cvs +is a system for providing source control to hierarchical collections +of source directories. Commands and procedures for using \fBcvs\fP +are described in +.BR cvs ( 1 ). +.SP +.B cvs +manages \fIsource repositories\fP, the directories containing master +copies of the revision-controlled files, by copying particular +revisions of the files to (and modifications back from) developers' +private \fIworking directories\fP. In terms of file structure, each +individual source repository is an immediate subdirectory of +\fB$CVSROOT\fP. +.SP +The files described here are supporting files; they do not have to +exist for \fBcvs\fP to operate, but they allow you to make \fBcvs\fP +operation more flexible. +.SP +You can use the `\|modules\|' file to define symbolic names for +collections of source maintained with \fBcvs\fP. If there is no +`\|modules\|' file, developers must specify complete path names +(absolute, or relative to \fB$CVSROOT\fP) for the files they wish to +manage with \fBcvs\fP commands. +.SP +You can use the `\|commitinfo\|' file to define programs to execute +whenever `\|\fBcvs commit\fP\|' is about to execute. +These programs are used for ``pre-commit'' checking to verify that the +modified, added, and removed files are really ready to be committed. +Some uses for this check might be to turn off a portion (or all) of the +source repository from a particular person or group. +Or, perhaps, to verify that the changed files conform to the site's +standards for coding practice. +.SP +You can use the `\|cvswrappers\|' file to record +.B cvs +wrapper commands to be used when checking files into and out of the +repository. Wrappers allow the file or directory to be processed +on the way in and out of CVS. The intended uses are many, one +possible use would be to reformat a C file before the file is checked +in, so all of the code in the repository looks the same. +.SP +You can use the `\|loginfo\|' file to define programs to execute after +any +.BR commit , +which writes a log entry for changes in the repository. +These logging programs might be used to append the log message to a file. +Or send the log message through electronic mail to a group of developers. +Or, perhaps, post the log message to a particular newsgroup. +.SP +You can use the `\|taginfo\|' file to define programs to execute after +any +.BR tag or rtag +operation. These programs might be used to append a message to a file +listing the new tag name and the programmer who created it, or send mail +to a group of developers, or, perhaps, post a message to a particular +newsgroup. +.SP +You can use the `\|rcsinfo\|' file to define forms for log messages. +.SP +You can use the `\|editinfo\|' file to define a program to execute for +editing/validating `\|\fBcvs commit\fP\|' log entries. +This is most useful when used with a `\|rcsinfo\|' forms specification, as +it can verify that the proper fields of the form have been filled in by the +user committing the change. +.SP +You can use the `\|cvsignore\|' file to specify the default list of +files to ignore during \fBupdate\fP. +.SP +You can use the `\|history\|' file to record the \fBcvs\fP commands +that affect the repository. +The creation of this file enables history logging. +.SH FILES +.TP +.B modules +The `\|modules\|' file records your definitions of names for +collections of source code. \fBcvs\fP will use these definitions if +you use \fBcvs\fP to check in a file with the right format to +`\|\fB$CVSROOT/CVSROOT/modules,v\fP\|'. +.SP +The `\|modules\|' file may contain blank lines and comments (lines +beginning with `\|\fB#\fP\|') as well as module definitions. +Long lines can be continued on the next line by specifying a backslash +(``\e'') as the last character on the line. +.SP +A \fImodule definition\fP is a single line of the `\|modules\|' file, +in either of two formats. In both cases, \fImname\fP represents the +symbolic module name, and the remainder of the line is its definition. +.SP +\fImname\fP \fB\-a\fP \fIaliases\fP\|.\|.\|. +.br +This represents the simplest way of defining a module \fImname\fP. +The `\|\fB\-a\fP\|' flags the definition as a simple alias: \fBcvs\fP +will treat any use of \fImname\fP (as a command argument) as if the list +of names \fIaliases\fP had been specified instead. \fIaliases\fP may +contain either other module names or paths. When you use paths in +\fIaliases\fP, `\|\fBcvs checkout\fP\|' creates all intermediate +directories in the working directory, just as if the path had been +specified explicitly in the \fBcvs\fP arguments. +.SP +.nf +\fImname\fP [ \fIoptions\fP ] \fIdir\fP [ \fIfiles\fP\|.\|.\|. ] [ \fB&\fP\fImodule\fP\|.\|.\|. ] +.fi +.SP +In the simplest case, this form of module definition reduces to +`\|\fImname dir\fP\|'. This defines all the files in directory +\fIdir\fP as module \fImname\fP. \fIdir\fP is a relative path (from +\fB$CVSROOT\fP) to a directory of source in one of the source +repositories. In this case, on \fBcheckout\fP, a single directory +called \fImname\fP is created as a working directory; no intermediate +directory levels are used by default, even if \fIdir\fP was a path +involving several directory levels. +.SP +By explicitly specifying \fIfiles\fP in the module definition after +\fIdir\fP, you can select particular files from directory +\fIdir\fP. The sample definition for \fBmodules\fP is an example of +a module defined with a single file from a particular directory. Here +is another example: +.SP +.nf +.ft B +m4test unsupported/gnu/m4 foreach.m4 forloop.m4 +.ft P +.fi +.SP +With this definition, executing `\|\fBcvs checkout m4test\fP\|' +will create a single working directory `\|m4test\|' containing the two +files listed, which both come from a common directory several levels +deep in the \fBcvs\fP source repository. +.SP +A module definition can refer to other modules by including +`\|\fB&\fP\fImodule\fP\|' in its definition. \fBcheckout\fP creates +a subdirectory for each such \fImodule\fP, in your working directory. +.br +.I +New in \fBcvs\fP 1.3; +avoid this feature if sharing module definitions with older versions +of \fBcvs\fP. +.SP +Finally, you can use one or more of the following \fIoptions\fP in +module definitions: +.SP +\&`\|\fB\-d\fP \fIname\fP\|', to name the working directory something +other than the module name. +.br +.I +New in \fBcvs\fP 1.3; +avoid this feature if sharing module definitions with older versions +of \fBcvs\fP. +.SP +\&`\|\fB\-i\fP \fIprog\fP\|' allows you to specify a program \fIprog\fP +to run whenever files in a module are committed. \fIprog\fP runs with a +single argument, the full pathname of the affected directory in a +source repository. The `\|commitinfo\|', `\|loginfo\|', and +`\|editinfo\|' files provide other ways to call a program on \fBcommit\fP. +.SP +`\|\fB\-o\fP \fIprog\fP\|' allows you to specify a program \fIprog\fP +to run whenever files in a module are checked out. \fIprog\fP runs +with a single argument, the module name. +.SP +`\|\fB\-e\fP \fIprog\fP\|' allows you to specify a program \fIprog\fP +to run whenever files in a module are exported. \fIprog\fP runs +with a single argument, the module name. +.SP +`\|\fB\-t\fP \fIprog\fP\|' allows you to specify a program \fIprog\fP +to run whenever files in a module are tagged. \fIprog\fP runs with two +arguments: the module name and the symbolic tag specified to \fBrtag\fP. +.SP +`\|\fB\-u\fP \fIprog\fP\|' allows you to specify a program \fIprog\fP +to run whenever `\|\fBcvs update\fP\|' is executed from the top-level +directory of the checked-out module. \fIprog\fP runs with a +single argument, the full path to the source repository for this module. +.TP +\&\fBcommitinfo\fP, \fBloginfo\fP, \fBrcsinfo\fP, \fBeditinfo\fP +These files all specify programs to call at different points in the +`\|\fBcvs commit\fP\|' process. They have a common structure. +Each line is a pair of fields: a regular expression, separated by +whitespace from a filename or command-line template. +Whenever one of the regular expression matches a directory name in the +repository, the rest of the line is used. +If the line begins with a \fB#\fP character, the entire line is considered +a comment and is ignored. +Whitespace between the fields is also ignored. +.SP +For `\|loginfo\|', the rest of the +line is a command-line template to execute. +The templates can include not only +a program name, but whatever list of arguments you wish. If you write +`\|\fB%s\fP\|' somewhere on the argument list, \fBcvs\fP supplies, at +that point, the list of files affected by the \fBcommit\fP. +The first entry in the list is the relative path within the source +repository where the change is being made. +The remaining arguments list the files that are being modified, added, or +removed by this \fBcommit\fP invocation. +.SP +For `\|taginfo\|', the rest of the +line is a command-line template to execute. +The arguments passed to the command are, in order, the +.I tagname , +.I operation +(i.e. +.B add +for `tag', +.B mov +for `tag -F', and +.B del +for `tag -d`), +.I repository , +and any remaining are pairs of +.B "filename revision" . +A non-zero exit of the filter program will cause the tag to be aborted. +.SP +For `\|commitinfo\|', the rest of the line is a command-line template to +execute. +The template can include not only a program name, but whatever +list of arguments you wish. +The full path to the current source repository is appended to the template, +followed by the file names of any files involved in the commit (added, +removed, and modified files). +.SP +For `\|rcsinfo\|', the rest of the line is the full path to a file that +should be loaded into the log message template. +.SP +For `\|editinfo\|', the rest of the line is a command-line template to +execute. +The template can include not only a program name, but whatever +list of arguments you wish. +The full path to the current log message template file is appended to the +template. +.SP +You can use one of two special strings instead of a regular +expression: `\|\fBALL\fP\|' specifies a command line template that +must always be executed, and `\|\fBDEFAULT\fP\|' specifies a command +line template to use if no regular expression is a match. +.SP +The `\|commitinfo\|' file contains commands to execute \fIbefore\fP any +other \fBcommit\fP activity, to allow you to check any conditions that +must be satisfied before \fBcommit\fP can proceed. The rest of the +\fBcommit\fP will execute only if all selected commands from this file +exit with exit status \fB0\fP. +.SP +The `\|rcsinfo\|' file allows you to specify \fIlog templates\fP for +the \fBcommit\fP logging session; you can use this to provide a form +to edit when filling out the \fBcommit\fP log. The field after the +regular expression, in this file, contains filenames (of files +containing the logging forms) rather than command templates. +.SP +The `\|editinfo\|' file allows you to execute a script \fIbefore the +commit starts\fP, but after the log information is recorded. These +"edit" scripts can verify information recorded in the log file. If +the edit script exits wth a non-zero exit status, the commit is aborted. +.SP +The `\|loginfo\|' file contains commands to execute \fIat the end\fP +of a commit. The text specified as a commit log message is piped +through the command; typical uses include sending mail, filing an +article in a newsgroup, or appending to a central file. +.TP +\&\fBcvsignore\fP, \fB.cvsignore\fP +The default list of files (or +.BR sh ( 1 ) +file name patterns) to ignore during `\|\fBcvs update\fP\|'. +At startup time, \fBcvs\fP loads the compiled in default list of file name +patterns (see +.BR cvs ( 1 )). +Then the per-repository list included in \fB$CVSROOT/CVSROOT/cvsignore\fP +is loaded, if it exists. +Then the per-user list is loaded from `\|$HOME/.cvsignore\|'. +Finally, as \fBcvs\fP traverses through your directories, it will load any +per-directory `\|.cvsignore\|' files whenever it finds one. +These per-directory files are only valid for exactly the directory that +contains them, not for any sub-directories. +.TP +.B history +Create this file in \fB$CVSROOT/CVSROOT\fP to enable history logging +(see the description of `\|\fBcvs history\fP\|'). +.SH "SEE ALSO" +.BR cvs ( 1 ), +.SH COPYING +Copyright \(co 1992 Cygnus Support, Brian Berliner, and Jeff Polk +.PP +Permission is granted to make and distribute verbatim copies of +this manual provided the copyright notice and this permission notice +are preserved on all copies. +.PP +Permission is granted to copy and distribute modified versions of this +manual under the conditions for verbatim copying, provided that the +entire resulting derived work is distributed under the terms of a +permission notice identical to this one. +.PP +Permission is granted to copy and distribute translations of this +manual into another language, under the above conditions for modified +versions, except that this permission notice may be included in +translations approved by the Free Software Foundation instead of in +the original English. diff --git a/contrib/cvs/man/cvsbug.8 b/contrib/cvs/man/cvsbug.8 new file mode 100644 index 0000000..496ef14 --- /dev/null +++ b/contrib/cvs/man/cvsbug.8 @@ -0,0 +1,269 @@ +.\" -*- nroff -*- +.\" --------------------------------------------------------------------------- +.\" man page for send-pr (by Heinz G. Seidl, hgs@cygnus.com) +.\" updated Feb 1993 for GNATS 3.00 by Jeffrey Osier, jeffrey@cygnus.com +.\" +.\" This file is part of the Problem Report Management System (GNATS) +.\" Copyright 1992 Cygnus Support +.\" +.\" 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 of the License, 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. +.\" +.\" You should have received a copy of the GNU Library General Public +.\" License along with this program; if not, write to the Free +.\" Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA +.\" +.\" --------------------------------------------------------------------------- +.nh +.TH CVSBUG 1 xVERSIONx "February 1993" +.SH NAME +cvsbug \- send problem report (PR) about CVS to a central support site +.SH SYNOPSIS +.B cvsbug +[ +.I site +] +[ +.B \-f +.I problem-report +] +[ +.B \-t +.I mail-address +] +.br +.in +0.8i +[ +.B \-P +] +[ +.B \-L +] +[ +.B \-\-request-id +] +[ +.B \-v +] +.SH DESCRIPTION +.B cvsbug +is a tool used to submit +.I problem reports +.\" SITE ADMINISTRATORS - change this if you use a local default +(PRs) to a central support site. In most cases the correct +.I site +will be the default. This argument indicates the support site which +is responsible for the category of problem involved. Some sites may +use a local address as a default. +.I site +values are defined by using the +.BR aliases (5). +.LP +.B cvsbug +invokes an editor on a problem report template (after trying to fill +in some fields with reasonable default values). When you exit the +editor, +.B cvsbug +sends the completed form to the +.I Problem Report Management System +(\fBGNATS\fR) at a central support site. At the support site, the PR +is assigned a unique number and is stored in the \fBGNATS\fR database +according to its category and submitter-id. \fBGNATS\fR automatically +replies with an acknowledgement, citing the category and the PR +number. +.LP +To ensure that a PR is handled promptly, it should contain your (unique) +\fIsubmitter-id\fR and one of the available \fIcategories\fR to identify the +problem area. (Use +.B `cvsbug -L' +to see a list of categories.) +.LP +The +.B cvsbug +template at your site should already be customized with your +submitter-id (running `\|\fBinstall-sid\fP \fIsubmitter-id\fP\|' to +accomplish this is part of the installation procedures for +.BR cvsbug ). +If this hasn't been done, see your system administrator for your +submitter-id, or request one from your support site by invoking +.B `cvsbug \-\-request\-id'. +If your site does not distinguish between different user sites, or if +you are not affiliated with the support site, use +.B `net' +for this field. +.LP +The more precise your problem description and the more complete your +information, the faster your support team can solve your problems. +.SH OPTIONS +.TP +.BI \-f " problem-report" +specify a file (\fIproblem-report\fR) which already contains a +complete problem report. +.B cvsbug +sends the contents of the file without invoking the editor. If +the value for +.I problem-report +is +.BR `\|\-\|' , +then +.B cvsbug +reads from standard input. +.TP +.BI \-t " mail-address" +Change mail address at the support site for problem reports. The +default +.I mail-address +is the address used for the default +.IR site . +Use the +.I site +argument rather than this option in nearly all cases. +.TP +.B \-P +print the form specified by the environment variable +.B PR_FORM +on standard output. If +.B PR_FORM +is not set, print the standard blank PR template. No mail is sent. +.TP +.B -L +print the list of available categories. No mail is sent. +.TP +.B \-\-request\-id +sends mail to the default support site, or +.I site +if specified, with a request for your +.IR submitter-id . +If you are +not affiliated with +.IR site , +use a +.I submitter-id +of +.BR net \|'. +.TP +.B \-v +Display the +.B cvsbug +version number. +.LP +Note: use +.B cvsbug +to submit problem reports rather than mailing them directly. Using +both the template and +.B cvsbug +itself will help ensure all necessary information will reach the +support site. +.SH ENVIRONMENT +The environment variable +.B EDITOR +specifies the editor to invoke on the template. +.br +default: +.B vi +.sp +If the environment variable +.B PR_FORM +is set, then its value is used as the file name of the template for +your problem-report editing session. You can use this to start with a +partially completed form (for example, a form with the identification +fields already completed). +.SH "HOW TO FILL OUT A PROBLEM REPORT" +Problem reports have to be in a particular form so that a program can +easily manage them. Please remember the following guidelines: +.IP \(bu 3m +describe only +.B one problem +with each problem report. +.IP \(bu 3m +For follow-up mail, use the same subject line as the one in the automatic +acknowledgent. It consists of category, PR number and the original synopsis +line. This allows the support site to relate several mail messages to a +particular PR and to record them automatically. +.IP \(bu 3m +Please try to be as accurate as possible in the subject and/or synopsis line. +.IP \(bu 3m +The subject and the synopsis line are not confidential. This is +because open-bugs lists are compiled from them. Avoid confidential +information there. +.LP +See the GNU +.B Info +file +.B cvsbug.info +or the document \fIReporting Problems With cvsbug\fR\ for detailed +information on reporting problems +.SH "HOW TO SUBMIT TEST CASES, CODE, ETC." +Submit small code samples with the PR. Contact the support site for +instructions on submitting larger test cases and problematic source +code. +.SH FILES +.ta \w'/tmp/pbad$$ 'u +/tmp/p$$ copy of PR used in editing session +.br +/tmp/pf$$ copy of empty PR form, for testing purposes +.br +/tmp/pbad$$ file for rejected PRs +.SH EMACS USER INTERFACE +An Emacs user interface for +.B cvsbug +with completion of field values is part of the +.B cvsbug +distribution (invoked with +.BR "M-x cvsbug" ). +See the file +.B cvsbug.info +or the ASCII file +.B INSTALL +in the top level directory of the distribution for configuration and +installation information. The Emacs LISP template file is +.B cvsbug-el.in +and is installed as +.BR cvsbug.el . +.SH INSTALLATION AND CONFIGURATION +See +.B cvsbug.info +or +.B INSTALL +for installation instructions. +.SH SEE ALSO +.I Reporting Problems Using cvsbug +(also installed as the GNU Info file +.BR cvsbug.info ). +.LP +.BR gnats (l), +.BR query-pr (1), +.BR edit-pr (1), +.BR gnats (8), +.BR queue-pr (8), +.BR at-pr (8), +.BR mkcat (8), +.BR mkdist (8). +.SH AUTHORS +Jeffrey Osier, Brendan Kehoe, Jason Merrill, Heinz G. Seidl (Cygnus +Support) +.SH COPYING +Copyright (c) 1992, 1993 Free Software Foundation, Inc. +.PP +Permission is granted to make and distribute verbatim copies of +this manual provided the copyright notice and this permission notice +are preserved on all copies. +.PP +Permission is granted to copy and distribute modified versions of this +manual under the conditions for verbatim copying, provided that the +entire resulting derived work is distributed under the terms of a +permission notice identical to this one. +.PP +Permission is granted to copy and distribute translations of this +manual into another language, under the above conditions for modified +versions, except that this permission notice may be included in +translations approved by the Free Software Foundation instead of in +the original English. + diff --git a/contrib/cvs/mkinstalldirs b/contrib/cvs/mkinstalldirs new file mode 100755 index 0000000..91f6d04 --- /dev/null +++ b/contrib/cvs/mkinstalldirs @@ -0,0 +1,32 @@ +#!/bin/sh +# mkinstalldirs --- make directory hierarchy +# Author: Noah Friedman <friedman@prep.ai.mit.edu> +# Created: 1993-05-16 +# Last modified: 1994-03-25 +# Public domain + +errstatus=0 + +for file in ${1+"$@"} ; do + set fnord `echo ":$file" | sed -ne 's/^:\//#/;s/^://;s/\// /g;s/^#/\//;p'` + shift + + pathcomp= + for d in ${1+"$@"} ; do + pathcomp="$pathcomp$d" + case "$pathcomp" in + -* ) pathcomp=./$pathcomp ;; + esac + + if test ! -d "$pathcomp"; then + echo "mkdir $pathcomp" 1>&2 + mkdir "$pathcomp" || errstatus=$? + fi + + pathcomp="$pathcomp/" + done +done + +exit $errstatus + +# mkinstalldirs ends here diff --git a/contrib/cvs/src/ChangeLog b/contrib/cvs/src/ChangeLog new file mode 100644 index 0000000..99161b0 --- /dev/null +++ b/contrib/cvs/src/ChangeLog @@ -0,0 +1,1373 @@ +Sun May 5 21:39:02 1996 Jim Kingdon <kingdon@harvey.cyclic.com> + + * vers_ts.c (Version_TS): If sdtp is NULL, go ahead and check + RCS_getexpand for options. Fixes binaries and non-unix clients. + * sanity.sh: Fix binfiles-5.5 to test for the correct behavior + rather than the buggy behavior which existed when the binfiles-5.5 + test was written. + (binfiles-14c,binfiles-14f): Likewise. + +Sun May 5 17:38:21 1996 Benjamin J. Lee <benjamin@cyclic.com> + + Integrated changes submitted by Ian Taylor <ian@cygnus.com> + + * update.c (update_dirent_proc): cvs co -p doesn't print + anything when run from an empty directory. + + * import.c (import_descend_dir): Check for a file in the + repository which will be checked out to the same name as the + directory. + +Thu May 2 13:34:37 1996 Benjamin J. Lee <benjamin@cyclic.com> + + * Version 1.7.88 + +Thu May 2 01:40:55 1996 Benjamin J. Lee <benjamin@cyclic.com> + + * server.c (HAVE_INITGROUPS): Use initgroups() only if + located by configure, in the event a system has crypt(), but + no initgroups() + +Wed May 1 18:05:02 1996 Jim Kingdon <kingdon@harvey.cyclic.com> + + * sanity.sh (basica): When testing rejection of reserved tag name, + use BASE instead of RESERVED. + +Wed May 1 15:15:11 1996 Tom Jarmolowski <tjj@booklink.com> + + * rcs.c (linevector_delete): Only copy up to vec->nlines - nlines, + not to vec->nlines. + +Wed May 1 15:43:21 1996 Jim Kingdon <kingdon@harvey.cyclic.com> + + * rcscmds.c (RCS_settag): Instead of reserving all tag names + containing only uppercase letters, reserve only BASE and HEAD. + * sanity.sh (mflag): Revert 26 Mar change; use all-uppercase tag + name again. + +Wed May 1 15:15:11 1996 Tom Jarmolowski <tjj@booklink.com> + + * rcs.c (linevector_add): Move increment of i out of larger + statement, to avoid assumptions about evaluation order. + +Tue Apr 30 15:46:03 1996 Jim Kingdon <kingdon@harvey.cyclic.com> + + * Version 1.7.87. + + * server.c (check_password): Don't use ANSI string concatenation. + Reindent function. + +Wed Apr 24 17:27:53 1996 Norbert Kiesel <nk@col.sw-ley.de> + + * vers_ts.c (Version_TS): xmalloc enough space (1 more + byte). Thanks to purify! + +Fri Apr 19 11:22:35 1996 Benjamin J. Lee <benjamin@cyclic.com> + + * Version 1.7.86 + +Thu Apr 18 1996 Jim Kingdon <kingdon@cyclic.com> + + * client.c (try_read_from_server): Compare return value from fwrite + with a size_t not an int (Visual C++ lint). + +Wed Apr 17 11:56:32 1996 Jim Kingdon <kingdon@harvey.cyclic.com> + + * client.c (try_read_from_server): New function. + (read_from_server): Use it. + (read_counted_file): New function. + * client.c, server.c: Add Template response. + * cvs.h (CVSADM_TEMPLATE): Added. + * logmsg.c (do_editor): If repository is NULL, use CVSADM_TEMPLATE + file in place of rcsinfo. + * server.c, server.h (server_template): New function. + * create_adm.c (Create_Admin): Call it. + +Tue Apr 16 13:56:06 1996 Jim Kingdon <kingdon@harvey.cyclic.com> + + * repos.c (Name_Repository): Fix comments. + * create_adm.c (Create_Admin): Fix indentation. + +Wed Apr 10 16:46:54 1996 Jim Kingdon <kingdon@harvey.cyclic.com> + + * options.h.in: Include relevant information here rather than + citing (former) FAQ. + + * ChangeLog-9395: Fix typo in introductory paragraph. + +Wed Apr 10 14:55:10 1996 code by Mike Spengler mks@msc.edu + comments by Jim Kingdon <kingdon@harvey.cyclic.com> + + * filesubr.c (unlink_file_dir,deep_remove_dir): Don't call unlink + on something which might be a directory; check using isdir instead. + +Wed Apr 10 14:55:10 1996 Jim Kingdon <kingdon@harvey.cyclic.com> + + * checkout.c (build_dirs_and_chdir): Pass path, not cp, to + Create_Admin. The former is the correct update dir. + * sanity.sh (modules): New tests modules-155* test, for above fix. + +Mon Apr 8 13:53:27 1996 Samuel Tardieu <sam@inf.enst.fr> + + * rcs.c (annotate_fileproc): If the file is not under CVS control, + return instead of dumping a core. Don't bug on files with an empty + first revision. + +Fri Mar 29 16:08:28 1996 Jim Kingdon <kingdon@harvey.cyclic.com> + + * rcs.c (annotate_fileproc): If last line of add-chunk is not + newline terminated, end the loop when we find that out. + +Fri Mar 29 16:59:34 1996 Norbert Kiesel <nk@col.sw-ley.de> + + * rcs.c (annotate_fileproc): allow last line of add-chunk not to + be newline terminated + +Thu Mar 28 10:56:36 1996 Jim Kingdon <kingdon@harvey.cyclic.com> + + Add more diff tests: + * sanity.sh (basic2): Use dotest for test 61. + (basica): Add test basica-6.2. + (branches): Add tests branches-14.4 and branches-14.5. + (basic1): Remove tests 19, 20, 25, and 26. The only thing this + might miss out on is diff's interaction with added and removed + files, but those tests didn't test that very well anyway. + + * rcs.c (RCS_getrevtime): Add comment regarding years after 1999. + + * rcs.c: Add "cvs annotate" command and related code. + (getrcskey): Move special handling of RCSDESC from here to + callers. Handle those keys (desc, log, text) which do not + end in a semicolon. + * rcs.h (RCSVers): Add author field. + * rcs.c (RCS_reparsercsfile): Set it. + * cvs.h (annotate), main.c (cmd_usage, cmds), client.h client.c + (client_annotate), server.c (serve_annotate, requests): Usual + machinery to add a new command. + * sanity.sh (basica): Test cvs annotate. + + * sanity.sh (branches): More tests, of things like adding files on + the trunk after a branch has been made. + +Tue Mar 26 09:48:49 1996 Jim Kingdon <kingdon@harvey.cyclic.com> + + * expand_path.c: Don't declare free and xmalloc; cvs.h already + takes care of that. + + * sanity.sh (mflag): Don't use tag name reserved to CVS. + + NT local changes plus miscellaneous things noticed in the process: + * import.c (add_rcs_file): Use binary mode to write RCS file. Use + \012 where linefeed is intended. Copy data a small block at a + time, until we hit EOF, rather than trying to read the whole file + into memory at once. + * client.c (send_modified): Add comments regarding st_size. + * commit.c (commit): Add comments regarding binary mode and read(). + * logmsg.c (do_editor): Add comments regarding st_size. + * server.c (server_updated): Use binary mode to read file we are + sending. + + * rcscmds.c (RCS_settag): Complain if user tries to add a tag name + reserved to CVS. + * sanity.sh (basica): Test for this behavior. + + * sanity.sh (binfiles): New tests test ability to change keyword + expansion. + +Mon Mar 25 1996 Jim Kingdon <kingdon@cyclic.com> + + * cvs.h, filesubr.c (expand_wild): New function. + * recurse.c (start_recursion): Call expand_wild at beginning and + free its results at the end. + * cvs.h, subr.c (xrealloc): Make argument and return value void *. + * client.h, client.c (send_file_names): Add flags argument. If + SEND_EXPAND_WILD flag is passed, call expand_wild at beginning and + free its results at the end. + * admin.c, add.c, log.c, tag.c, status.c, edit.c, watch.c, + update.c, commit.c, remove.c, client.c, diff.c: Update callers. + +Fri Mar 22 10:09:55 1996 Jim Kingdon <kingdon@harvey.cyclic.com> + + * error.c (error, fperror): Exit with status EXIT_FAILURE rather + than STATUS. We had been neglecting to check for 256, and the + value of providing a count of errors is probably minimal anyway. + * add.c, modules.c, mkmodules.c, tag.c, server.c, main.c, + import.c, client.c, scramble.c, recurse.c: Exit with status + EXIT_FAILURE rather than 1. On VMS, 1 is success, not failure. + * main.c (main): Return EXIT_FAILURE or 0. The value of providing + a count of errors is minimal. + + * client.c (init_sockaddr): Exit with status 1 rather than + EXIT_FAILURE. The latter apparently doesn't exist on SunOS4. + Reindent function. + +Mon Mar 18 14:28:00 1996 Jim Kingdon <kingdon@harvey.cyclic.com> + + * cvs.h, ignore.c: New variable ign_case. + * ignore.c (ign_name): If it is set, match in a case-insensitive + fashion. + * server.c (serve_case): New function. + (requests): Add Case request. + * client.c (start_server): If FILENAMES_CASE_INSENSITIVE is + defined, send Case request. + +Sat Mar 16 08:20:01 1996 Jim Kingdon <kingdon@harvey.cyclic.com> + + For reference, this change takes cvs's text segment from 315392 + bytes to 311296 bytes (one 4096 byte page). + * cvs.h (struct file_info): Add fullname field. + * recurse.c (do_file_proc): Set it. + * commit.c (find_fileproc), client.c (send_fileproc), commit.c + (check_fileproc), diff.c (diff_fileproc), edit.c + (unedit_fileproc), patch.c (patch_fileproc), remove.c + (remove_fileproc), rtag.c (rtag_fileproc), tag.c (tag_fileproc), + update.c (update_fileproc), watch.c (watchers_fileproc): Use it + instead of computing it each time. + * diff.c (diff_fileproc), remove.c (remove_fileproc): Use fullname + where we had been (bogusly) omitting the directory from user + messages. + * edit.c (unedit_fileproc, edit_fileproc): If we cannot close + CVSADM_NOTIFY, mention CVSADM_NOTIFY rather than finfo->file in + error message. + * rtag.c (rtag_fileproc), tag.c (tag_fileproc): Reindent. + +Fri Mar 15 15:12:11 1996 Norbert Kiesel <nk@col.sw-ley.de> + + * server.h: fix prototype of server_pause_check (was + server_check_pause) + +Thu Mar 14 1996 Jim Kingdon <kingdon@cyclic.com> + + * vers_ts.c (Version_TS), entries.c (Scratch_Entry, AddEntryNode): + Change findnode to findnode_fn. + + * main.c: Depending on HAVE_WINSOCK_H, include winsock.h or + declare gethostname. + * cvs.h: Don't declare it here. + +Thu Mar 14 07:06:59 1996 Jim Kingdon <kingdon@harvey.cyclic.com> + + * commit.c (find_fileproc): If vn_user is NULL and ts_user is not, + print an error rather than silently succeeding. + * sanity.sh (basica-notadded): New test, for above fix. + (dotest_internal): New function. + (dotest,dotest_fail): Call it instead of duplicating code between + these two functions. + + * sanity.sh: Skip tests binfiles-9 through binfiles-13 for remote. + + * options.h.in: Adjust comment to reflect kfogel change. + +Thu Mar 14 01:38:30 1996 Karl Fogel <kfogel@floss.red-bean.com> + + * options.h.in (AUTH_CLIENT_SUPPORT): turn on by default. + +Wed Mar 13 09:25:56 1996 Jim Kingdon <kingdon@harvey.cyclic.com> + + * vers_ts.c (Version_TS): Don't try to override options from rcs + file if there isn't an rcs file (e.g. called from send_fileproc). + This fixes a bug detected by test 59 in "make remotecheck". + + * rcs.c (RCS_reparsercsfile, RCS_getexpand): Assert that argument + is not NULL. + + Fix a gcc -Wall warning: + * rcs.c, rcs.h (RCS_getexpand): New function. + * vers_ts.c (Version_TS): Call it. + * rcs.c (RCS_reparsercsfile): Make static. + + Add a "cvs init" command. This is needed because cvsinit.sh + invoked mkmodules which doesn't exist any more. + * mkmodules.c: Break filelist out of mkmodules function, rename + struct _checkout_file to struct admin_file (for namespace + correctness), and add contents field. + (init,mkdir_if_needed): New functions. + * cvs.h (init): Declare. + * main.c (cmds): Add init. + (main): If command is init, don't require cvsroot to exist. + * client.c, client.h (client_init, send_init_command): New functions. + * client.c (start_server): Don't send Root request if command is init. + * server.c (serve_init): New function. + (requests): Add "init". + +Wed Mar 13 09:51:03 MET 1996 Norbert Kiesel <nk@col.sw-ley.de> + + * vers_ts.c (Version_TS): set options to default option if the + file if no -k option but -A was given. This avoids the (wrong) + update message for binary files which are up-to-date when + running 'cvs -A'. + + * update.c (checkout_file): remove test of -k option stored in the + file itself because it was moved to vers_ts.c + + * sanity.sh: added tests for the above fix. + +Tue Mar 12 13:47:09 1996 Jim Kingdon <kingdon@harvey.cyclic.com> + + * hash.c (findnode): Adjust comment regarding errors. + + * hash.c (findnode, findnode_fn): Assert that key != NULL. This + way the check still happens even if the function is later + rewritten to not start out by calling hashp. + +Mon Mar 11 10:21:05 1996 Jim Kingdon <kingdon@harvey.cyclic.com> + + * sanity.sh: If expr accepts multi-line patterns but is too + liberal in matching them, print a warning but keep going. + + * sanity.sh: Add QUESTION variable, analogous to PLUS. Use it + instead of \? to match a question mark. + + * cvs.h (CVSMODULE_OPTS, CVSMODULE_SPEC): Move from here... + * modules.c: ...to here. They are only used here and the code to + handle the syntax of modules files should not be scattered all over. + * modules.c (CVSMODULE_OPTS): Add "+" as first character. + * sanity.sh (modules): New tests 148a0 and 148a1 test for + above-fixed bug. + +Mon Mar 11 13:11:04 1996 Samuel Tardieu <sam@inf.enst.fr> + + * modules.c (cat_module): set optind to 0 to force getopt() to + reinitialize its internal nextchar + +Mon Mar 11 00:09:14 1996 Jim Kingdon <kingdon@harvey.cyclic.com> + + * hash.c (findnode, findnode_fn): Revert changes of 7-8 Mar 1996. + The correct style is to assert() that key != NULL (see HACKING), + which is already done in the hashp function. + * fileattr.c (fileattr_delproc): Likewise, assert() that + node->data != NULL rather than trying to deal with it being NULL. + +Fri Mar 8 01:31:04 1996 Greg A. Woods <woods@most.weird.com> + + * hash.c (findnode_fn): one more place to avoid calling hashp() + with a NULL key + +Thu Mar 7 17:30:01 1996 Greg A. Woods <woods@most.weird.com> + + * hash.c (findnode): also return NULL if key is not set + [[ reported by Chris_Eich@optilink.optilink.dsccc.com, and + supposedly in a PR that should be marked "fixed"..... ]] + + * fileattr.c (fileattr_set): set node->data to NULL after freeing + it to prevent subsequent accesses + (fileattr_delproc): don't free node->data if it's NULL, and set it + to NULL after freeing + [[ reported by Chris_Eich@optilink.optilink.dsccc.com, and + supposedly in a PR that should be marked "fixed"..... ]] + +Fri Mar 1 14:56:08 1996 Jim Kingdon <kingdon@harvey.cyclic.com> + + * sanity.sh (basica): New test basica-4a tests for bug fixed by + sam@inf.enst.fr on 1 Mar 96. + +Fri Mar 1 18:10:49 1996 Samuel Tardieu <sam@inf.enst.fr> + + * tag.c (check_fileproc): Check for file existence before trying + to tag it. + +Fri Mar 1 07:51:29 1996 Jim Kingdon <kingdon@harvey.cyclic.com> + + * client.c (update_entries): If command is export, set options to + NULL. + +Thu Feb 29 16:54:14 1996 Jim Kingdon <kingdon@harvey.cyclic.com> + + * lock.c (write_lock, Reader_Lock): Remove + BOGUS_UNLESS_PROVEN_OTHERWISE code. It was pretty bogus, and has + been ifdeffed out for a long time. + * cvs.h (CVSTFL): Removed; no longer used. + + * cvsrc.c, cvs.h (read_cvsrc): Pass in command name rather than + using global variable command_name. + * main.c (command_name): Initialize to "", not "cvs" so that error + messages don't say "cvs cvs". Update calls to read_cvsrc to pass + in command_name or "cvs" as appropriate. + * sanity.sh (basica): New test basica-9 tests for above-fixed bug. + + * lock.c: Rename unlock to lock_simple_remove to avoid conflict + with builtin function on QNX. + +Thu Feb 29 17:02:22 1996 Samuel Tardieu <sam@inf.enst.fr> + + * fileattr.c (fileattr_get): Removed NULL pointer dereference + which occurred in the absence of default attribute. + +Thu Feb 29 07:36:57 1996 J.T. Conklin <jtc@rtl.cygnus.com> + + * rcs.c (RCS_isbranch, RCS_whatbranch): Remove no longer used file + argument, swap order of remaining two arguments to be like other + RCS_* functions. + (RCS_nodeisbranch): swap order of arguments to be like other RCS_* + functions. + * rcs.h (RCS_isbranch, RCS_whatbranch, RCS_nodeisbranch): Update + prototypes for above changes. + * commit.c, rtag.c, status.c, tag.c: Update for above calling + convention changes. + +Thu Feb 29 08:39:03 1996 Jim Kingdon <kingdon@harvey.cyclic.com> + + * client.c (start_server): Revert changes which claimed to fall + back to a different way of connecting. Add comments explaining + why. (I don't think the changes did what they claimed, anyway). + Use indentation rather than comments to line up #if, #else, and + #endif. + + * patch.c (patch, patch_fileproc): Revert change to add optional + arguments to -c and -u. Optional arguments are evil and in + violation of the POSIX argument syntax guidelines. The correct + way to do this is -C and -U. Also change DIFF back to "diff" in + output (see comments). + + gcc -Wall lint: + * client.c (copy_a_file): Declare p inside the #ifdef in which is + it used. + * commit.c (remove_file): Remove unused variable p. + * commit.c (checkaddfile): Remove unused variables p. + * rcs.c (RCS_isbranch): Remove unused variable p. + * rcs.c: Remove unused declarations and definitions of + parse_rcs_proc, rcsnode_delproc, rcslist, and repository. + * rtag.c (rtag_fileproc): Remove unused variable p. + * patch.c (patch_fileproc): Remove unused variable p. + * tag.c (val_fileproc): Remove unused variable node. + * client.c, import.c, lock.c, server.c: Cast pid_t to long before + passing it to %ld. + + * cvs.h: Don't prototype gethostname; merely declare it (on linux, + second argument is size_t not int). + +Thu Feb 29 10:29:25 MET 1996 Norbert Kiesel (nk) <nk@col.sw-ley.de> + + * sanity.sh: added "cat > /dev/null" to loginfo entry to avoid the + SIGPIPE signal + +Thu Feb 29 10:28:25 MET 1996 Norbert Kiesel (nk) <nk@col.sw-ley.de> + + * patch.c: added new variable diff_opt + (patch): allow optional parameter to -c and -u option, send it to + server + (patch_fileproc): cleaned up the code which prints the current + filename. For "-s" option, print the pathname relative to CVSROOT + instead of just the filename. + + * filesubr.c (xchmod): added cast to shut up gcc + + * cvs.h: added prototype for gethostname + +Thu Feb 29 10:27:25 MET 1996 Norbert Kiesel (nk) <nk@col.sw-ley.de> + + * lock.c (write_lock), (Reader_Lock), import.c (update_rcs_file), + client.c (update_entries), (send_modified), server.c (server), + (receive_file), (server_updated): use %ld for printing pid_t + variables + +Thu Feb 29 02:22:12 1996 Benjamin J. Lee <benjamin@cyclic.com> + + * run.c (run_exec): Added VMS return status support. + +Thu Feb 29 01:07:43 1996 Benjamin J. Lee <benjamin@cyclic.com> + + * client.c (send_to_server): wrtn wasn't being declared under + VMS for some reason. + +Wed Feb 28 23:27:04 1996 Benjamin J. Lee <benjamin@cyclic.com> + + * client.c: Changed #ifdef VMS && NO_SOCKET_TO_FD to + #if defined(VMS) && defined(NO_SOCKET_TO_FD) + +Wed Feb 28 22:28:43 1996 Benjamin J. Lee <benjamin@cyclic.com> + + * build_src.com: Added DCL command procedure to build + and link CVS client for VMS. + +Wed Feb 28 22:07:20 1996 Benjamin J. Lee <benjamin@cyclic.com> + + * client.c: VMS CVS client specific changes. + + Added USE_DIRECT_TCP to allow CVS_PORT to be used to specify + a TCP connection port (no Kerberos). Changed + start_kerberos_server() to start_tcp_server(). + + In copy_a_file(): transform a backup file to have a + VMS-friendly name. + + Added HAVE_CONFIG_H to include "config.h". + + start_server() will starts the first successful of any + mutually exclusive methods of starting the CVS server + which might be enabled. + + Initialized use_socket_style and server_sock for VMS in + start_server(). + +Wed Feb 28 21:49:48 1996 Benjamin J. Lee <benjamin@cyclic.com> + + * find_names.c, recurse.c, cvs.h: Changed Find_Dirs() to + Find_Directories(). + * cvs.h: Added VMS filenames enabled through USE_VMS_FILENAMES + VMS POSIX will require to use the regular CVS filenames + while VMS is #define'd. + +Wed Feb 28 21:26:22 1996 Benjamin J. Lee <benjamin@cyclic.com> + + * ignore.c: Added the patterns *.olb *.exe _$* *$ to default + ignore list for VMS. + +Wed Feb 28 13:32:28 1996 Jim Kingdon <kingdon@harvey.cyclic.com> + + * logmsg.c (do_editor): Fix indentation. + +Wed Feb 28 12:56:49 1996 Benjamin J. Lee <benjamin@cyclic.com> + + * logmsg.c (do_editor): If no editor is defined, exit and print + a message. + +Wed Feb 28 10:40:25 1996 Jim Kingdon <kingdon@harvey.cyclic.com> + + * vers_ts.c (time_stamp, time_stamp_server): Reindent and revise + comments. + +Tue Feb 27 23:57:55 1996 Benjamin J. Lee <benjamin@cyclic.com> + + * vers_ts.c: gmtime() returns NULL on some systems (VMS) + revert to local time via ctime() if GMT is not avaiable. + +Tue Feb 27 13:07:45 1996 J.T. Conklin <jtc@rtl.cygnus.com> + + The changes listed below cause cvs to parse each rcs file (and + free the associated rcsnode after the file has been processed) + sequentially. cvs used to parse all files in a directory, an + approach that does not scale to huge repositories with lots + of revisions/branches/tags/etc. + + * cvs.h (struct file_info): Removed srcfiles field. Added rcs + (node) field. + * recurse.c (do_recursion): Removed code that pre-parsed all + rcs files in the directory. + (do_file_proc): Parse current rcs file. + * rcs.c (RCS_parsefiles, parse_rcs_proc, RCS_addnode): Removed. + (RCS_isbranch, RCS_whatbranch): Changed srcfiles argument to + rcs (node). + * rcs.h (RCS_parsefiles, RCS_addnode): Removed prototypes. + (RCS_isbranch, RCS_whatbranch): Updated prototypes. + * add.c, admin.c, checkin.c, checkout.c, classify.c, client.c, + commit.c, diff.c, history.c, import.c, log.c, patch.c, remove.c, + rtag.c, status.c, tag.c, update.c, vers_ts: Updated for above + calling convention / data structure changes. + +Mon Feb 26 16:07:56 1996 Jim Kingdon <kingdon@harvey.cyclic.com> + + * Version 1.7.3. + + * Version 1.7.2. + +Mon Feb 26 1996 Jim Kingdon <kingdon@cyclic.com> + + * recurse.c (start_recursion): Use last_component rather than + checking for '/' directly. + (do_dir_proc): Likewise. + + Visual C++ lint: + * client.c (send_to_server): Change wrtn to size_t. + (connect_to_pserver): Put tofd and fromfd declarations inside + #ifndef NO_SOCKET_TO_FD. + * scramble.c (shifts): Change from array of char to array of + unsigned char. + +Mon Feb 26 13:31:25 1996 Jim Kingdon <kingdon@harvey.cyclic.com> + + * server.c (check_repository_password): Remove unused variables + linelen, ch. + + * client.c (send_file_names): Translate ISDIRSEP characters to '/'. + +Sat Feb 24 21:25:46 1996 Jim Kingdon <kingdon@harvey.cyclic.com> + + * checkout.c (safe_location): Re-indent one line. + +Sat Feb 24 10:50:42 1996 Karl Fogel <kfogel@floss.red-bean.com> + + * checkout.c (safe_location): put assignment to hardpath[x] in an + `else'-clause, so we don't do it when x == -1. + +Sat Feb 24 01:40:28 1996 Marcus Daniels <marcus@sayre.sysc.pdx.edu> + via Karl Fogel <kfogel@floss.red-bean.com> + + * server.c (check_repository_password): Return by reference an + optional username, the `host_user', from the passwd file. The + host_user will be the user-id under which the cvs repository is + run. + (check_repository_password): Use `read_line' instead of fgets to + allow for passwords larger than 32 characters, as well as the + optional host user argument. + (check_password): Modify to use host_user. + (authenticate_connection): Modify to use host_user. + +Sat Feb 24 01:05:21 1996 Karl Fogel <kfogel@floss.red-bean.com> + + * scramble.c (descramble): just shift descrambled string to get + rid of tag char, instead of allocating a whole new copy. + (scramble): cast return value of xmalloc to avoid unsightly + compiler warnings. + + * options.h.in (RCSBIN_DFLT): don't refer to AUTH_SERVER_SUPPORT + in comment anymore, now that it's not defined in this file. + +Fri Feb 23 1996 Jim Kingdon <kingdon@cyclic.com> + + * client.c: Ifdef HAVE_WINSOCK_H, include winsock.h + instead of sys/socket.h and friends. + * login.c: Don't include sys/socket.h and friends. + * login.c (login): Only fclose fp in the case where it was + successfully fopen'd. + * login.c: Declare getpass. + * filesubr.c, cvs.h (get_homedir): New function. + * cvsrc.c, expand_path.c, history.c, login.c: Call it instead + of getenv ("HOME"). + +Fri Feb 23 09:23:20 1996 Jim Kingdon <kingdon@harvey.cyclic.com> + + * client.c (connect_to_pserver): Remove unused variable host. + * login.c: Include getline.h. + (login): Remove unused variables i and username. + (get_cvs_password): Move free of linebuf to where it actually will + be called. Add a "return NULL" at the end of the function to shut + up gcc -Wall. + + * options.h.in: Remove AUTH_SERVER_SUPPORT. + * client.h (authenticate_connection): Declare. + * scramble.c (scramble): Cast char to unsigned char before using + it to look up in table (char might be signed). + * server.c [AUTH_SERVER_SUPPORT]: Include grp.h + (authenticate_connection): Remove unused variables len and + server_user. + + * sanity.sh (basica): Add comments regarding creating a top-level + directory. + (basic1): Don't try to remove first-dir and + ${CVSROOT_DIRNAME}/first-dir at start of test; tests are now + responsible for cleaning up at the end. + (PLUS,DOTSTAR,ENDANCHOR): Add comments regarding fixed GNU expr. + +Thu Feb 22 22:34:11 1996 Jim Kingdon <kingdon@harvey.cyclic.com> + + * cvs.h: Remove alloca cruft. + +Wed Feb 21 07:30:16 1996 J.T. Conklin <jtc@rtl.cygnus.com> + + * modules.c (do_module): call free_cwd before exiting. + + * recurse.c: Removed entries global variable. + (do_recursion): Declare entries. Moved call to Entries_Close so + entries list is closed on all code paths. + (start_recursion): Removed call to Entries_Close, entries list has + been moved to do_recursion only. + +Tue Feb 20 22:10:05 1996 Jim Kingdon <kingdon@harvey.cyclic.com> + + * update.c (update_dirent_proc): If dir lacks a CVS subdirectory, + don't recurse into it. + * sanity.sh (conflicts): Test for above-fixed bug. + + * update.c (merge_file): Use write_letter not printf. + +Tue Feb 20 12:34:07 EST 1996: Gary Oberbrunner <garyo@avs.com> + and Jim Kingdon <kingdon@cyclic.com> + + * history.c (history_write): Change username to char * and call + getcaller() to set it. Setting username accidentally got deleted + 8 Feb 96. + * sanity.sh: Revise test 64 to test for above-fixed bug. + * sanity.sh (PLUS): New variable, work around yet another GNU expr + bug. + +Tue Feb 20 14:07:50 1996 Jim Kingdon <kingdon@harvey.cyclic.com> + + * sanity.sh: Merge test rtags into test basic2. They never were + capable of running separately of each other. + + * sanity.sh (deep): New test, to test ability to operate in deeply + nested directories (more quickly than basic2 test did). + (basic2,rtags): Remove directories dir3 and dir4. Remove file8, + file10, file12, file9, file11, file13, file15, file16, file17. + These additional files slowed down the tests considerably without + significantly increasing coverage. + + * sanity.sh (PROG): New variable. Use it instead of "cvs" + to match the name cvs prints out for itself. + +Mon Feb 19 09:00:29 1996 Jim Kingdon <kingdon@harvey.cyclic.com> + + This fixes a bug whereby old default attributes would get + clobbered instead of added to on "cvs watch add". + * hash.c (findnode): Don't check for key == NULL; let the + assertion in hashp take care of it. + * fileattr.h, fileattr.c (fileattr_get): If filename is NULL, + return default attributes. + + * client.c (send_repository): Fix indentation. + +Mon Feb 19 01:10:01 1996 Karl Fogel <kfogel@floss.red-bean.com> + + * login.c (login): print out full repos so user knows which server + she's logging into. + + * client.c (send_repository): die if `repos' is NULL. This is a + lame solution; see comments in code. + +Thu Feb 15 15:04:01 1996 Jim Kingdon <kingdon@harvey.cyclic.com> + + * error.c (error): Free entire and mess when done with them. + + * sanity.sh (info): Correct syntax of .cvsrc file. + + * cvs.h, expand_path.c, edit.c, parseinfo.c, wrapper.c: + expand_path now takes arguments containing file and line for error + message, and it prints the error message itself. + * sanity.sh (info-6a): Test printing of error message. + + * expand_path.c (expand_variable): Add USER internal variable. + * sanity.sh (info): Test USER and CVSROOT internal variables too. + +Wed Feb 14 19:11:08 1996 Jim Kingdon <kingdon@harvey.cyclic.com> + + * main.c (usg): Add -s option. + +Tue Feb 13 20:26:06 1996 Jim Kingdon <kingdon@harvey.cyclic.com> + + gcc -Wall lint: + * mkmodules.c (mkmodules_usage): Remove declaration of + non-existent function. + * cvs.h (mkmodules): Declare. + +Mon Feb 12 12:20:04 1996 Jim Kingdon <kingdon@harvey.cyclic.com> + + * mkmodules.c: Rename main to mkmodules and remove various pieces + of scaffolding which it used to emulate non-existent parts of CVS. + Change calling convention to just take a char * not argc,argv. + Save and restore working directory. + * commit.c (commit_filesdoneproc): Call it if checking files into + CVSROOT. + * Makefile.in (SOURCES): Add mkmodules.c. + (OBJECTS): Add mkmodules.o. + (MSOURCES,MOBJECTS): Removed. + (COMMON_OBJECTS): Removed; move former contents into OBJECTS. + Update other rules accordingly. + * sanity.sh: Adjust to reflect nonexistence of mkmodules. + + These changes introduce functions cvs_output and cvs_outerr; + eventually all server output will go through them rather than + stdio directly. + * server.c (saved_output, saved_outerr): New variables. + (do_cvs_command): Initialize them. + (buf_output): Don't require that buf->output be set; saved_* use + this to shove some data in a buffer which buf_copy_lines will + later want to get data from. + * server.c, cvs.h (cvs_output, cvs_outerr): New functions. + * mkmodules.c (cvs_outerr): New function, so error() works. + * error.c: Reindent. Don't declare program_name and command_name; + cvs.h declares them. + (error): Use vasprintf and cvs_outerr (or fputs in the + error_use_protocol case) rather than stdio directly. + * import.c (import_descend_dir): Remove kludge which had prevented + messages from error() from being out of order with respect to + messages from printf; cvs_output and cvs_outerr are a cleaner + solution to the problem. + (add_log, import): Use cvs_output not printf. + * update.c (write_letter): Use cvs_output not printf. + (checkout_file): Use write_letter not printf. + * sanity.sh: Use dotest for test 56 (test that output is actually + correct). In theory should test that the import.c bug is fixed, + but I was unable to reproduce the bug (it is timing dependent). + +Mon Feb 12 16:07:45 1996 Norbert Kiesel <nk@col.sw-ley.de> + + * commit.c: define last_register_time + (commit): make sure cvs doesn't exit in the same second it wrote + the last timestamp + (commit_fileproc): set last_register_time + (finaladd): set last_register_time + + * run.c, cvs.h: Changed more Popen() to run_popen() + +Mon Feb 12 03:06:50 1996 Benjamin J. Lee <benjamin@cyclic.com> + + * release.c, rtag.c, tag.c: changed 'delete' to 'delete_flag' + to avoid symbol collision with DEC C RTL function delete() + +Mon Feb 12 03:01:48 1996 Benjamin J. Lee <benjamin@cyclic.com> + + * mkmodules.c: changed 'void Lock_Cleanup()' to 'void static + Lock_Cleanup() to avoid conflict with more substantial + Lock_Cleanup() in lock.c + +Mon Feb 12 02:50:19 1996 Benjamin J. Lee <benjamin@cyclic.com> + + * edit.c, logmsg.c, release.c, run.c: Changed Popen() to + run_popen(). VMS' linker is not case sensitive and considered + popen() and Popen() to be identical symbols. + +Sun Feb 11 10:51:14 1996 Jim Kingdon <kingdon@harvey.cyclic.com> + + * main.c (main) [!CLIENT_SUPPORT]: Silently ignore gzip level + rather than printing usage message. + + * cvs.h, expand_path.c (variable_list): New variable. + (variable_set): New function. + * hash.h (enum ntype), hash.c (nodetypestring): Add VARIABLE. + * expand_path.c (expand_path, expand_variable): Reindent. + (expand_variable): Use user variables not environment variables + for ${=VAR} syntax. The environment variables didn't work + client/server. + * main.c (main): Process new -s global option. + * client.c (send_variable_proc): New function. + (start_server): Call it, to send user variables. + * server.c (serve_set): New function. + (requests): Add Set request. + * sanity.sh: Revise info test to use user variables rather than + environment variables. + +Sat Feb 10 16:55:37 1996 Jim Kingdon <kingdon@harvey.cyclic.com> + + By itself this is only a small cleanup, but in the long run it + will be way cool (for reference, it takes CVS's text segment from + 290816 bytes to 294912, which I expect will be made up by future + changes which this enables): + * cvs.h (struct file_info): Added. + (FILEPROC): Replace 5 args with single struct file_info *. + * recurse.c (do_file_proc): Adjust args to fileproc; passed in + instead of from globals. + (do_recursion): Call do_file_proc accordingly. Remove srcfiles + global variable. + * update.c (update_fileproc): Renamed from update_file_proc. + * admin.c, client.c, commit.c, diff.c, edit.c, log.c, patch.c, + remove.c, rtag.c, status.c, tag.c, update.c, watch.c: Update + fileprocs to new calling convention. + +Fri Feb 9 15:30:32 1996 Jim Kingdon <kingdon@harvey.cyclic.com> + + * expand_path.c (expand_variable): Accept a variable name starting + with '=' as a way to specify an environment variable. + * sanity.sh (info): New tests, for above behavior. + + * Makefile.in (clean): Also remove check.log check.plog. + + * import.c (comtable): Remove SYSTEM_COMMENT_TABLE; the table + should *not* depend on what kind of machine the server happens to + be. Add "mak", "rc", "dlg", "frm", and "bas" types which were + formerly included via SYSTEM_COMMENT_TABLE. + + * cvs.h, rcs.h, add.c, checkin.c, classify.c, commit.c, diff.c, + import.c, patch.c, rcs.c, update.c, vers_ts.c: Remove + DEATH_SUPPORT ifdefs. They were introduced to facilitate merging + between Cygnus and Berliner variants of CVS, not because it was + intended to subset CVS this way. And they clutter up the code + quite a bit. + * cvs.h, create_adm.c, main.c, update.c: Likewise, remove + CVSADM_ROOT ifdefs (it is still a #define, of course). I believe + they had a more-or-less similar motivation. + + * sanity.sh: Move setting of HOME from ignore test to the start of + the tests so it applies to all tests. + (CVS): Remove -f; the above change takes care of it. + + * rcs.h (RCS_MERGE): Removed; unused. + + * commit.c (checkaddfile): Fix memory leak. + + * admin.c, commit.c, diff.c, log.c, mkmodules.c: Pass -x,v/ to RCS + commands. + + * rcscmds.c, cvs.h (RCS_checkin): New function. + * checkin.c, commit.c, import.c: Call it, rather than run_*. + * cvs.h, commit.c: Remove DEATH_STATE define; the behavior + which used to be the default (DEATH_STATE) is now the only one. + Failing to define DEATH_STATE has been commented as obsolete at + least since CVS 1.5. We still can read repositories created with + such a CVS, however. + * rcs.h, rcs.c: Adjust comments regarding DEATH_STATE. + * subr.c (make_message_rcslegal): Add comment, describing + allocation of returned value. + +Fri Feb 9 09:53:44 MET 1996 Norbert Kiesel <nk@col.sw-ley.de> + + * sanity.sh: use "${testcvs}" instead of "cvs" in devcom tests + + * hash.c: fix "dereferencing a NULL pointer" bug triggered with + "cvs watch add" + (findnode): return NULL if key == NULL + (hashp): assert (key != NULL) + +Fri Feb 9 00:46:47 1996 Jim Kingdon <kingdon@harvey.cyclic.com> + + * rcs.c (RCS_reparsercsfile): Remove unused variable date. + + * myndbm.c (mydbm_load_file): Fix typo ('015' -> '\015'). + +Thu Feb 8 13:00:00 1996 Jim Kingdon <kingdon@peary.cyclic.com> + + * rcs.c (RCS_parse, RCS_parsercsfile, RCS_reparsercsfile), + fileattr.c (fileattr_read), myndbm.c (myndbm_open): + Use FOPEN_BINARY_READ. + * fileattr.c (fileattr_write), myndbm.c (myndbm_close): + Use FOPEN_BINARY_WRITE. + * history.c (history_write, read_hrecs): Specify OPEN_BINARY. + * rcs.c: Remove calls to abort. + * myndbm.c (myndbm_load_file): Ignore CRs from ends of lines + if present. + * myndbm.c, fileattr.c: While I am at it, change \n to \012 + a few places where LF is intended. + * history.c (history_write): Use getenv ("HOME"), not getpwnam, + to find home directory. If it isn't set, just keep going; don't + print a message. + * rcscmds.c, cvs.h (RCS_checkout): New function. + * update.c, checkin.c, commit.c, diff.c, import.c, no_diff.c, + patch.c: Call it instead of run_*. + * patch.c (patch_fileproc): Clean up inconsistent handling of + noexec flag. + * rcscmds.c (RCS_*): Pass -x,v/ to RCS commands; elsewhere in + CVS it is assumed that ,v is a suffix. + +Fri Feb 2 14:07:32 1996 J.T. Conklin <jtc@rtl.cygnus.com> + + * rcs.h (struct rcsnode): Remove dates field (list of rcsversnodes + indexed by date). CVS maintained this list for each RCS file even + though it was never used. This resulted in higher then necessary + memory requirements (and run time too). Even if revision info was + needed, CVS' List data structure is inappropriate because can't + handle duplicate keys. The above was discovered by tracking down + a memory leak. + * rcs.c (RCS_reparsercsfile): Don't build dates list. + (freercsnode): Don't delete dates list. + (rcsvers_delproc): Free date field. + (null_delproc): Removed. + +Thu Feb 1 12:28:33 1996 Jim Kingdon <kingdon@harvey.cyclic.com> + + * remove.c (cvsremove): Don't tell user the name of the program + which they use to remove files; we don't have any way of knowing + that, and besides which they might use a GUI or emacs 'dired' anyway. + * update.c (update_filesdone_proc, update_dirleave_proc): Call + unlink_file_dir instead of rm -rf. + * options.h.in: Remove RM; no longer used. + + * sanity.sh: New tests devcom-a* test "cvs watch add", + "cvs watch remove", and "cvs watchers". + + * sanity.sh: New test 171a0 tests for watch.c bug just fixed by kfogel. + + * Most .c files: Remove rcsids. + * cvs.h: Remove USE macro. + +Thu Feb 1 13:07:15 1996 J.T. Conklin <jtc@rtl.cygnus.com> + + * tag.c, rtag.c: Update various comments to reflect function name + changes. + +Thu Feb 1 14:14:31 1996 Karl Fogel <kfogel@floss.red-bean.com> + + * recurse.c (do_recursion): comment #endif. + + * edit.c (notify_check): surround with #ifdef CLIENT_SUPPORT; else + CVS won't compile if CLIENT_SUPPORT is undefined. + + * edit.h (notify_check): surround declaration with #ifdef + CLIENT_SUPPORT. + + * watch.c (watch): if argc <= 1, then just give usage (previously + was "argc == -1"). + +Thu Feb 1 12:28:33 1996 Jim Kingdon <kingdon@harvey.cyclic.com> + + * README-rm-add: Remove information which is now in cvs.texinfo. + + * sanity.sh: Remove basic0 tests. Move a few to new tests + basica-1a* (but there is no need to test that *every* command + gracefully does nothing on an empty directory; exhaustive testing + is impractical and the generic recursion processor handles this + anyway). + + * sanity.sh: New tests 69a* test use of update -p to restore old + version of dead file. + +Wed Jan 31 18:32:34 1996 Jim Kingdon <kingdon@harvey.cyclic.com> + + * ChangeLog-9395: Remove duplicate entries from 1996 which + accidentally got into this file. + + * client.c (read_line, read_from_server): Change "premature end of + file from server" message to "end of file from server (consult + above messages if any)" because 99% of the time it means rsh has + printed an error message and exited. + +Wed Jan 31 15:09:51 1996 J.T. Conklin <jtc@rtl.cygnus.com> + + * edit.c (ncheck_fileproc): Fix memory leak; free line before + returning. + +Tue Jan 30 18:06:12 1996 Jim Kingdon <kingdon@harvey.cyclic.com> + + * recurse.c (do_recursion): Add comment about the fact that we + don't have locks in place at certain points. + +Tue Jan 30 09:43:34 1996 Vince Demarco <vdemarco@bou.shl.com> + + * edit.c (notify_proc): have notify_proc call expand_path with + the name of the filter program. The user may have used a + cvs environmental variable. (Popen will expand it, but it may not + use the correct value) + +Tue Jan 30 09:43:34 1996 Jim Kingdon <kingdon@harvey.cyclic.com> + + * ChangeLog: take the pre-1996 changes and put them in a new file + ChangeLog-9395. + * ChangeLog-9194: Renamed from ChangeLog.fsf. + * ChangeLog-9194, ChangeLog-9395, ChangeLog: Add additional text + explaining the difference between all these logs and pointing to + older logs. + * Makefile.in (DISTFILES): Add ChangeLog-9194 and ChangeLog-9395; + remove ChangeLog.fsf. + + * modules.c (do_module): Don't fall through from 'l' to 'o' case + of option processing switch statement. + +Tue Jan 30 06:50:19 1996 J.T. Conklin <jtc@rtl.cygnus.com> + + * client.c (send_repository): Fix memory leak; free adm_name + before returning. + * diff.c (diff_file_nodiff): Fix memory leak; free xvers before + returning. + * rtag.c (rtag_fileproc): Fix memory leak; if branch_mode is set, + free rev before returning. + * status.c (status_fileproc, tag_list_proc): Fix memory leak; free + return value of RCS_whatbranch. + * tag.c (tag_fileproc): Fix memory leak; free vers before + returning. + (val_fileproc): Fix memory leak; free return value of RCS_gettag. + * watch.c (watch_modify_watchers): Fix memory leak; free mynewattr + before returning. + +Tue Jan 30 09:43:34 1996 Jim Kingdon <kingdon@harvey.cyclic.com> + + * lock.c (readers_exist): If stat gave an error, print an error + message saying it was from stat, rather than from "reading + directory". Skip the message completely if it was an + existence_error. + + * sanity.sh (branches): New tests (branches off of branches, etc.). + +Tue Jan 30 11:55:34 MET 1996 Norbert Kiesel <nk@col.sw-ley.de> + + * main.c (main): Add change to run getopt_long twice again. + +Mon Jan 29 15:59:31 1996 Jim Kingdon <kingdon@harvey.cyclic.com> + + gcc -Wall lint: + * client.c: Include edit.h + +Sun Jan 28 09:45:53 1996 Jim Kingdon <kingdon@harvey.cyclic.com> + + * edit.c, edit.h (mark_up_to_date): New function, to remove file + in CVS/Base. + * client.c (update_entries): Call it if file is up to date. + * checkin.c (Checkin): Call it in non-server (local) case. + * sanity.sh: New test 182.5, tests for above-fixed bug. + +Sun Jan 28 01:07:22 1996 Jim Kingdon (kingdon@beezley) + + * client.c (change_mode): Separate out CHMOD_BROKEN code to parse + mode_string, rather than going through a mode_t. Cleaner than + the previous CHMOD_BROKEN code (which also had a typo of && not &). + +Sat Jan 27 23:29:46 1996 Jim Kingdon (kingdon@beezley) + + * edit.c (edit_fileproc): Check for EACCESS as well as EEXIST. + +Sat Jan 27 16:26:30 1996 Karl Fogel (kfogel@floss.cyclic.com) + + * client.c (notified_a_file): use rename_file() instead of + rename() (but temporarily set `noexec' to 0 so it runs + unconditionally). + (change_mode): deal with CHMOD_BROKEN. + +Fri Jan 26 00:14:00 1996 Karl Fogel <kfogel@floss.red-bean.com> + + * server.c: renamed `dirname' to `dir_name', to avoid conflicts + with system headers. + + * client.c: renamed `dirname' and `last_dirname' to `dir_name' and + last_dir_name' (see above). Not strictly necessary, but + consistency is nice -- as long as you do it all the time. + +Thu Jan 25 00:41:59 1996 Karl Fogel <kfogel@floss.red-bean.com> + + * options.h.in (AUTH_SERVER_SUPPORT, AUTH_CLIENT_SUPPORT): change + comment now that no longer under construction. + +Wed Jan 24 15:25:22 1996 Jim Kingdon <kingdon@harvey.cyclic.com> + + * Version 1.7.1. + + * Version 1.7. + +Sat Jan 20 00:05:08 1996 Jim Kingdon <kingdon@harvey.cyclic.com> + + * Version 1.6.87. + +Mon Jan 15 18:14:55 1996 Gary Oberbrunner <garyo@avs.com> + and Jim Kingdon <kingdon@harvey.cyclic.com> + + * tag.c (val_direntproc): New function to ignore + nonexistent dirs when recursing to check tag validity. + (tag_check_valid): Pass it to start_recursion. + * sanity.sh (death): New tests 65a0-65a6 cause test 74 to test for + above-fixed bug. + +Mon Jan 15 12:55:37 1996 Jim Kingdon <kingdon@harvey.cyclic.com> + + * main.c: Revert change to run getopt_long twice. This can go in + after 1.7. + +Mon Jan 15 13:03:28 1996 Norbert Kiesel <nk@col.sw-ley.de> + + * filesubr.c (deep_remove_dir): added test of EEXIST for nonempty + directory (Posix states that both ENOTEMPTY (BSD) and EEXIST + (SYSV) are valid) + + * main.c (main): run getopt_long twice to allow command-line + suppression of reading the cvsrc file + +Fri Jan 12 10:02:43 1996 Jim Kingdon <kingdon@harvey.cyclic.com> + + * Version 1.6.86. + +Thu Jan 11 23:28:05 1996 J.T. Conklin <jtc@rtl.cygnus.com> + and Jim Kingdon <kingdon@harvey.cyclic.com> + + * fileattr.h (fileattr_startdir): Add comment about REPOS == NULL. + * fileattr.c (fileattr_read, fileattr_write): Assert that + fileattr_stored_repos != NULL. + (fileattr_free): If fileattr_stored_repos is NULL, don't free it. + +Thu Jan 11 18:03:21 1996 Karl Fogel <kfogel@floss.red-bean.com> + + * scramble.c (descramble): deal with DIAGNOSTIC better. + +Thu Jan 11 12:04:42 1996 Norbert Kiesel <nk@col.sw-ley.de> + + * main.c: remove CVS_NOADMIN. + + * options.h.in: remove CVS_NOADMIN + +Thu Jan 11 10:28:44 1996 Karl Fogel <kfogel@floss.red-bean.com> + + * scramble.c (descramble): make sure the string returned is safe + to free(). + +Wed Jan 10 01:11:23 1996 Jim Kingdon <kingdon@harvey.cyclic.com> + + * server.c (serve_notify): Cast return value from malloc. + + * edit.c (notify_do): Use struct assignment, not struct + initialization (which SunOS4 /bin/cc doesn't have). + +Tue Jan 9 09:41:29 1996 Jim Kingdon <kingdon@harvey.cyclic.com> + + * Version 1.6.85. + + We use version numbers instead of patchlevels. But there was some + confusing patchlevel stuff lying around. Nuke it: + * Makefile.in (HEADERS): Remove patchlevel.h + * patchlevel.h: Removed. + * main.c: Don't include patchlevel.h. + (main): Don't print patch level. + + * server.c (check_repository_password): Check for errors from + system calls; reindent function. + +Tue Jan 9 23:15:30 1996 Karl Fogel <kfogel@floss.red-bean.com> + + * expand_path.c: fix comments (explain expand_path()'s behavior + correctly). + +Tue Jan 9 09:41:29 1996 Jim Kingdon <kingdon@harvey.cyclic.com> + + * edit.c (notify_proc): After copying in string following %s, + don't clobber it. Instead set up q to end of string. + + * watch.c (watch_modify_watchers), edit.c (editor_set): Fix sense + of test in trying to decide whether attributes are changed. + + * cvs.h (CVSROOTADM_USERS): New macro. + * edit.c (notify_do): Look up notifyee in CVSROOTADM_USERS if it + exists. + +Tue Jan 9 21:39:45 1996 Karl Fogel <kfogel@floss.red-bean.com> + + * expand_path.c: don't redundantly #include things that cvs.h + already #includes (i.e., stdio.h, ctype.h, string[s].h). + +Tue Jan 9 09:41:29 1996 Jim Kingdon <kingdon@harvey.cyclic.com> + + * ignore.c (ign_default): Add *.obj. + + * server.c: Put /* */ around #endif comment. + +Mon Jan 8 20:37:17 1996 Karl Fogel <kfogel@floss.red-bean.com> + + * client.c (connect_to_pserver): check return value of recv(). + +Mon Jan 8 11:37:57 1996 Jim Kingdon <kingdon@harvey.cyclic.com> + + * client.c (connect_to_pserver): Check for error from connect; + reindent function. + + * sanity.sh (4.75): Use dotest, so we get a PASS if test passes. + + * sanity.sh (dotest): New argument OUTPUT2. + (188a): Use it instead of \|. + + * sanity.sh (import): Avoid using string $ followed by Id followed + by $ in sanity.sh source, in case sanity.sh itself is under CVS. + I hate keyword expansion. + + * sanity.sh: If expr cannot handle multiline expressions, fail and + tell the user to get one which can. + + * release.c (release_delete): Remove unused variable retcode. + +Fri Jan 5 13:30:00 1996 Jim Kingdon <kingdon@peary.cyclic.com> + + * release.c (release_delete): Call unlink_file_dir rather + than "rm -rf". + +Thu Jan 4 09:58:30 1996 Jim Kingdon <kingdon@harvey.cyclic.com> + + * commit.c (find_fileproc): Print "nothing known about foo" and + return 1 if the file doesn't exist and isn't in CVS/Entries. + (commit): If the recursion over find_fileproc returns an error, + print "correct above errors first!" just like local CVS. + * sanity.sh (basica): Test for above-fixed bug. + + * release.c (release): If we are the client, only unedit if the + server supports it. + + * sanity.sh: Remove STARTANCHOR stuff; expr patterns are + automatically anchored to the start. ENDANCHOR remains. + + * commit.c (commit): Don't start the server until we have + determined that there is something to commit. + +Thu Jan 4 09:48:33 1996 Ben Laurie <ben@gonzo.ben.algroup.co.uk> + and Jim Kingdon <kingdon@harvey.cyclic.com> + + * client.c (start_server): dup the file descriptor before + fdopening it. + +Wed Jan 3 18:25:25 1996 Jim Kingdon <kingdon@harvey.cyclic.com> + + * sanity.sh: Remove tests 5, 5.5, and 5.75. All that stuff is + tested elsewhere. + + * ignore.c (ign_default): Change CVS* to CVS CVS.adm. CVS* is too + broad, especially in a case-insensitive filesystem. + + * Makefile.in (cvsbug): version.c is in srcdir. + +Wed Jan 3 17:30:45 1996 Phi-Long Tran <ptran@autodesk.com> + + * modules.c (do_module): Honor error_use_protocol in printing trace. + * server.c (server_register): Move check for options NULL to above + printing of the trace. + +Wed Jan 3 01:19:53 1996 Mark Immel <immel@centerline.com> + and Jim Kingdon <kingdon@harvey.cyclic.com> + + * update.c (checkout_file): Do not resurrect file on join if it + doesn't contain the revisions we are joining. Probably not a + perfect test, but should be an improvement. + * sanity.sh (death): New death-file4-* tests, for bug fixed above. + +Wed Jan 3 01:19:53 1996 Jim Kingdon <kingdon@harvey.cyclic.com> + + * add.c, admin.c, checkout.c, client.c, commit.c, diff.c, edit.c, + history.c, import.c, log.c, patch.c, release.c, remove.c, rtag.c, + status.c, tag.c, update.c, watch.c: In calling send_to_server, + pass \012 not \n. On the Mac \n is CR, not LF, and we want to + send LF. I didn't try to deal with whether files in CVSADM should + contain CR or LF--in fact there is some code in client.c which + reads \n from CVSADM files and passes it to send_to_server; it + needs to be cleaned up one way or the other. + + * entries.c (Entries_Open): Don't try to close fpin twice. + + * client.c (update_entries): Fix typo ("strlen (filename + 10)" + -> "strlen (filename) + 10"). + + * commit.c (checkaddfile): Remove arbitrary limit. + +Tue Jan 2 11:25:22 1996 Jim Kingdon <kingdon@harvey.cyclic.com> + + * commit.c (commit): Only pass files which were modified, added, + or removed to send_file_names. This has as a side effect a + semantic change--the up-to-date check is now skipped for other + files--but probably a good one, or at least not a bad one. + * sanity.sh (basica): New test; tests for bug fixed above. + * sanity.sh (187a3): Adjust for new 'cvs commit' output. Set up + DOTSTAR to match arbitrary text (another GNU expr bug/misfeature, + sigh). + + * sanity.sh: Test that the commit in test 43 actually worked. + Merge tests basic2 and basic3 and make them independent of basic1. + (pass,fail): Don't insert spurious space. + (45.5): Fix typo in directory name. + +Tue Jan 2 13:00:00 1996 Jim Kingdon <kingdon@peary.cyclic.com> + + Visual C++ lint: + * myndbm.c: Prototype write_item. + +Tue Jan 2 11:25:22 1996 Jim Kingdon <kingdon@harvey.cyclic.com> + + gcc -Wall lint: + * client.c (client_expand_modules): Pass error message not "" to error. + * client.c (supported_request), server.c (supported_response): + Return a value (gcc -Wall can't know that error doesn't return). + * commit.c (copy_ulist): Return a value. + * history.c (fill_hrec): Don't make assumptions about whether + time_t is "int" or "long" or what. + * cvs.h: Declare link_file. + * server.c: Include fileattr.h. + * server.c (server_notify): Remove unused variable val. + * tag.c (val_fileproc): Remove unused variable foundtag. + +Mon Jan 1 09:49:16 1996 Jim Kingdon <kingdon@harvey.cyclic.com> + + * Version 1.6.5. + + * Version 1.6.4. + + * filesubr.c (link_file): Add comment about link vs. copy semantics. + + * cvs.h (struct vers_ts): Fix comments. + * commit.c (commit): Before we ask for a log message, figure out + what is modified and what is not and pass the information to + do_editor. + (copy_ulist,find_fileproc): New helper functions for above code. + + * client.c (read_line): When writing to from_server_logfile, write + the \n too. + + * client.c (send_files): No longer call send_file_names. + * client.h: Update comment. + * add.c, admin.c, commit.c, diff.c, edit.c, log.c, remove.c, + status.c, tag.c, update.c, watch.c: Call send_file_names before + send_files. + * client.c: New variables module_argc, module_argv. + (client_expand_modules): Set them, to arguments. + (client_send_expansions): Use them instead of modules_vector to + send arguments. + * sanity.sh (modules): Add test of modules -d flag. + + +For older changes see ChangeLog-9395. diff --git a/contrib/cvs/src/ChangeLog-9194 b/contrib/cvs/src/ChangeLog-9194 new file mode 100644 index 0000000..eb79efc --- /dev/null +++ b/contrib/cvs/src/ChangeLog-9194 @@ -0,0 +1,524 @@ +Thu Sep 15 08:20:23 1994 Jim Kingdon (kingdon@lioth.cygnus.com) + + * subr.c (run_setup, run_args): Check USE_PROTOTYPES if defined + instead of __STDC__, just like cvs.h does. + +Thu Sep 15 00:14:58 1994 david d `zoo' zuhn <zoo@monad.armadillo.com> + + * main.c: rename nocvsrc to use_cvsrc, don`t read ~/.cvsrc when -H + has been seen + +Wed Sep 14 21:55:17 1994 david d `zoo' zuhn <zoo@monad.armadillo.com> + + * cvs.h, subr.c: use size_t for xmalloc, xrealloc, and xstrdup + parameters + + * cvsrc.c: optimize away two calls of getenv + + * commit.c, subr.c: use mode_t for file mode values (Thanks to jtc@cygnus.com) + + * main.c: update copyrights in -v message + +Tue Sep 6 10:29:13 1994 J.T. Conklin (jtc@rtl.cygnus.com) + + * hash.c (hashp): Replace hash function with one from p436 of the + Dragon book (via libg++'s hash.cc) which has *much* better + behavior. + +Wed Aug 17 09:37:44 1994 J.T. Conklin (jtc@cygnus.com) + + * find_names.c (find_dirs): Use 4.4BSD filesystem feature (it + contains the file type in the dirent structure) to avoid + stat'ing each file. + +Tue Aug 16 11:15:12 1994 J.T. Conklin (jtc@cygnus.com) + + * rcs.h (struct rcsnode): add symbols_data field. + * rcs.c (RCS_parsercsfile_i): store value of rcs symbols in + symbols_data instead of parsing it. + (RCS_symbols): New function used for lazy symbols parsing. + Build a list out of symbols_data and store it in symbols if it + hasn't been done already, and return the list of symbols. + (RCS_gettag, RCS_magicrev, RCS_nodeisbranch, RCS_whatbranch): + Use RCS_symbols. + * status.c: (status_fileproc): Use RCS_symbols. + +Thu Jul 14 13:02:51 1994 david d `zoo' zuhn (zoo@monad.armadillo.com) + + * src/diff.c (diff_fileproc): add support for "cvs diff -N" which + allows for adding or removing files via patches. (from + K. Richard Pixley <rich@cygnus.com>) + +Wed Jul 13 10:52:56 1994 J.T. Conklin (jtc@phishhead.cygnus.com) + + * cvs.h: Add macro CVSRFLPAT, a string containing a shell wildcard + expression that matches read lock files. + * lock.c (readers_exist): Reorganized to use CVSRFLPAT and to not + compute the full pathname unless the file matches. + + * rcs.h: Add macro RCSPAT, a string containing a shell wildcard + expression that matches RCS files. + * find_names.c (find_rcs, find_dirs): Use RCSPAT. + +Fri Jul 8 07:02:08 1994 Jim Kingdon (kingdon@lioth.cygnus.com) + + * entries.c (Register): Pass two arguments to write_ent_proc, in + accordance with its declaration. + +Thu Jun 30 09:08:57 1994 Jim Kingdon (kingdon@lioth.cygnus.com) + + * logmsg.c (do_editor): Fix typo ("c)continue" -> "c)ontinue"). + +Thu Jun 23 18:28:12 1994 J.T. Conklin (jtc@phishhead.cygnus.com) + + * find_names.c (find_rcs, find_dirs): use fnmatch instead of + re_comp/re_exec for wildcard matching. + * lock.c (readers_exist): Likewise. + +Fri May 20 08:13:10 1994 Jim Kingdon (kingdon@lioth.cygnus.com) + + * modules.c (do_module): If something is aliased to itself, print + an error message rather than recursing. + +Fri May 6 19:25:28 1994 david d zuhn (zoo@monad.armadillo.com) + + * cvsrc.c (read_cvsrc): use open_file for error checking + +Sat Feb 26 10:59:37 1994 david d zuhn (zoo@monad.armadillo.com) + + * import.c: use $TMPDIR if available, instead of relying on /tmp + +Mon Jan 24 19:10:03 1994 david d zuhn (zoo@monad.armadillo.com) + + * update.c (joining): compare join_rev1 with NULL instead of + casting pointer to an int + + * options.h: remove S_IWRITE, S_IWGRP, S_IWOTH macros + + * logmsg.c: #if 0 around gethostbyname prototype + + * hash.c (printnode), find_names.c (add_entries_proc), + entries.c (write_ent_proc): correct declaration for function + (added void *closure) + + * cvs.h: header include order reorganization: First include the + program config headers (config.h, options.h). Then include any + system headers (stdio.h, unistd.h). Last, get the program + headers and any cvs supplied library support + + * commit.c: use xstrdup instead of strdup + + * cvs.h: redefined USE(var) macro; comment after an #endif + + * all .c files: remove the semicolon from after the USE(var) + +Sat Dec 18 00:17:27 1993 david d zuhn (zoo@monad.armadillo.com) + + * cvs.h: include errno.h if available, otherwise declare errno if + it's not somehow else defined + + * commit.c (checkaddfile): remove unused file argument from + RCS_nodeisbranch call + + * rcs.c (RCS_nodeisbranch): remove file from arguments (was unused) + + * rcs.h (RCS_nodeisbranch): remove file from prototype + + * main.c: don't use rcsid when printing version number (the CVS + version number is independent of the repository that it comes + from) + + * hash.c (printlist, printnode): use %p to print pointers, not %x + (avoids gcc format warnings) + + * cvs.h: define USE if GCC 2, to avoid unused variable warning + + * all .c files: use USE(rcsid) + + * Makefile.in (VPATH): don't use $(srcdir), but @srcdir@ instead + (COMMON_OBJECTS): define, and use in several places + (OBJECTS): reorder alphabetically + + * hash.c (nodetypestring): handle default return value better + + * modules.c (do_module): remove extra argument to ign_dir_add + + * main.c (main): initialize cvs_update_env to 0 (zero) + + * modules.c (do_module): return error code when ignoring directory + (instead of a bare return). error code should be zero here + + * cvs.h: add prototypes for ignore_directory, ign_dir_add + + * ignore.c: add comments about ignore_directory + + * root.c (Name_Root): remove unused variables has_cvsadm and path + + * checkin.c (Checkin): only use -m<message> when message is non-NULL + + * cvsrc.c (read_cvsrc): make sure homeinit is never used while + uninitialized (could have happened if getenv("HOME") had failed) + + * cvs.h: include unistd.h if available + +Fri Dec 17 23:54:58 1993 david d zuhn (zoo@monad.armadillo.com) + + * all files: now use strchr, strrchr, and memset instead of index, + rindex, and bzero respectively + +Sat Dec 11 09:50:03 1993 david d zuhn (zoo@monad.armadillo.com) + + * version.c (version_string): bump to +104z + + * Makefile.in: set standard directory variables, CC, and other + variables needed to be able to do 'make all' in this directory + + * import.c: implement -k<subst> options, for setting the RCS + keyword expansion mode + + * all files: use PROTO() macro for ANSI function prototypes + instead of #ifdef __STDC__/#else/#endif around two sets of + declarations + +Thu Nov 18 19:02:51 1993 K. Richard Pixley (rich@sendai.cygnus.com) + + * add.c (add), import.c (import), commit.c (commit): change + xmalloc & strcpy to xstrdup. + + * commit.c (remove_file): correct another static buffer problem. + +Wed Nov 10 15:01:34 1993 K. Richard Pixley (rich@sendai.cygnus.com) + + * recurse.c (start_recursion): directories in repository but not + in working directory should be added to dirlist. Fixes "update + -d dir" case. + + * version.c (version_string): bump to +103r. + + * commit.c (checkaddfile): mkdir attic only if it does not already + exist. comment changes. changed diagnostic about adding on a + branch. if a file is added on a branch, remove and replace the + internal representation of that rcs file. + +Tue Nov 9 18:02:01 1993 K. Richard Pixley (rich@sendai.cygnus.com) + + * add.c (add): if a file is being added on a branch, then say so; + add quotes around file names in error messages. + +Thu Nov 4 16:58:33 1993 K. Richard Pixley (rich@sendai.cygnus.com) + + * version.c (version_string): bump to +102r. + + * recurse.c (unroll_files_proc, addfile): new files, forward + decls, and prototypes. + (recursion_frame): new struct. + (start_recursion): rewrite to handle the case of "file1 file2 + dir1/file3". + + * rcs.c (RCS_parsercsfile): trap and error out on the case where + getrcskey tells us it hit an error while reading the file. + + * commit.c (lock_filesdoneproc): add comment about untrapped error + condition. + + * hash.c (addnode): comment change. + + * subr.c: add comment about caching. + + * sanity.sh: updated copyright. + +Wed Nov 3 14:49:15 1993 K. Richard Pixley (rich@sendai.cygnus.com) + + * version.c (version_string): bump to +101r. + + * hash.c (walklist): add a closure for called routines. All + callers, callees, and prototypes changed. + + * hash.c (nodetypestring, printnode, printlist): new functions for + dumping lists & nodes. + + * tag.c (tag_fileproc): fatal out on failure to set tag. + +Tue Nov 2 14:26:38 1993 K. Richard Pixley (rich@sendai.cygnus.com) + + * version.c (version_string): bump version to +99. + +Mon Nov 1 15:54:51 1993 K. Richard Pixley (rich@sendai.cygnus.com) + + Change buffer allocation for check in messages from static to + dynamic. + * add.c (add): dynamically allocate message. + (build_entry): check (message != NULL) now that message is a + pointer. + * commit.c (got_message, commit, commit_fileproc, + commit_filesdoneproc, commit_direntproc): removed. Replaced by + (message != NULL). Dynamically allocate message. + * cvs.h: adjust do_editor prototype and forward decl. + (MAXMESGLEN): removed. + * import.c (import): dynamically allocate message. + * logmsg.c (do_editor): change return type to char *. Remove + message parameter. Slight optimization to algorythm for + removing CVSEDITPREFIX lines. Add comment about fgets lossage. + + * subr.c (xmalloc): change error message to print number of bytes + we were attempting to allocate. + +Fri Oct 29 14:22:02 1993 K. Richard Pixley (rich@sendai.cygnus.com) + + * add.c (add): prevent adding a directory if there exists a dead + file of the same name. + + * sanity.sh: update argument to diff from "+ignore-file" to + "--exclude=". + + * Makefile.in (TAGS): extend to work from an objdir. + +Mon Oct 18 18:45:45 1993 david d `zoo' zuhn (zoo@rtl.cygnus.com) + + * tag.c, rtag.c: change the default actions to make writing over + existing tags harder (but not impossible) + +Thu Oct 14 18:00:53 1993 david d `zoo' zuhn (zoo@rtl.cygnus.com) + + CVS/Root changes from Mark Baushke (mdb@cisco.com) + + * Makefile.in: added new file called root.c + + * create_adm.c: will create CVS/Root at the same time that the + other CVS files are being created + + * cvs.h: new CVSADM_ROOT define plus new function externs + + * main.c: default to using CVS/Root contents for CVSROOT + if neither the environment variable or the command line + "-d" switch is given. If either are given, perform a + sanity check that this directory belongs to that repository. + + * update.c: if CVS/Root does not exist, then create it + during an update -- this may be removed if CVS/Root becomes a + standard feature + + * root.c: implement new functions to manipulate CVS/Root + [this may be integrated with other utility functions in + a future revision if CVS/Root becomes a standard feature.] + +Wed Sep 29 17:01:40 1993 david d `zoo' zuhn (zoo@rtl.cygnus.com) + + * patch.c (patch_fileproc): output an Index: line for each file + +Mon Sep 6 18:40:22 1993 david d `zoo' zuhn (zoo@rtl.cygnus.com) + + * cvs.h: wrap definition of PATH_MAX in #ifndef PATH_MAX/#endif + +Tue Aug 9 21:52:10 1994 Mark Eichin (eichin@cygnus.com) + + * commit.c (remove_file): actually allocate space for the + filename, not just the directory. + +Tue Jul 6 19:05:37 1993 david d `zoo' zuhn (zoo@cygnus.com) + + * diff.c: patches to print an Index: line + +Mon Jun 14 12:19:35 1993 david d `zoo' zuhn (zoo at rtl.cygnus.com) + + * Makefile.in: update install target + +Tue Jun 1 17:03:05 1993 david d `zoo' zuhn (zoo at cirdan.cygnus.com) + + * Makefile.in: link cvs against libiberty + +Wed May 19 14:10:34 1993 david d `zoo' zuhn (zoo at cirdan.cygnus.com) + + * ignore.c: add code for keeping lists of directories to ignore. + + * modules.c: new syntax for modules file, !dirname is added to + the list of directories to ignore + + * update.c: don't process directories on the ignore list + +Tue Apr 6 14:22:48 1993 Ian Lance Taylor (ian@cygnus.com) + + * cvs.h: Removed gethostname prototype, since it is unnecessary + and does not match prototype in <unistd.h> on HP/UX. + +Mon Mar 22 23:25:16 1993 david d `zoo' zuhn (zoo at cirdan.cygnus.com) + + * Makefile.in: rename installtest to installcheck + +Mon Feb 1 12:53:34 1993 K. Richard Pixley (rich@rtl.cygnus.com) + + * Makefile.in (check, installtest): set RCSBIN so that we + explicitly test the appropriate version of rcs as well. + +Fri Jan 29 13:37:35 1993 K. Richard Pixley (rich@rtl.cygnus.com) + + * version.c: bump version to +2. + +Thu Jan 28 18:11:34 1993 K. Richard Pixley (rich@rtl.cygnus.com) + + * import.c (update_rcs_file): if a file was dead, be sure to check + in the new version. + + * update.c (checkout_file): if file_is_dead and we *did* have an + entry, scratch it. + +Tue Jan 26 16:16:48 1993 K. Richard Pixley (rich@rtl.cygnus.com) + + * sanity.sh: parcel into pieces for easier truncation when + debugging. + + * update.c (checkout_file): print the "no longer pertinent" + message only if there was a user file. + +Wed Jan 20 17:08:09 1993 K. Richard Pixley (rich@rtl.cygnus.com) + + * update.c (checkout_file): remove unused variable s. + (join_file): remove unused variables rev & baserev. Fix a typo. + + * commit.c (commit_fileproc): remove unused variable magicbranch. + + * sanity.sh: bring back test 45 even though it fails. Update + tests against imported files. + + * add.c (add_directory): move declaration of unused variable. + + * Makefile.in (xxx): when building in this directory, pass CC for + the recursion. + +Mon Jan 18 13:48:33 1993 K. Richard Pixley (rich@cygnus.com) + + * commit.c (remove_file): fix for files removed in trunk + immediately after import. + + * commit.c (remove_file): initialize some variables. Otherwise we + end up free'ing some rather inconvenient things. + +Wed Jan 13 15:55:36 1993 K. Richard Pixley (rich@rtl.cygnus.com) + + * Makefile.in (check, install, installtest): use the sanity test. + + * sanity.el: make into real functions and bind to sun keys. + + * sanity.sh: bring back to working order. Add test for death + after import. + +Tue Dec 22 17:45:19 1992 K. Richard Pixley (rich@cygnus.com) + + * commit.c (remove_file): when checking in a dead revision to a + branch as we are creating the branch, do not lock the underlying + revision. Also free some malloc'd memory. + +Wed Dec 2 13:09:48 1992 K. Richard Pixley (rich@cygnus.com) + + * RCS-patches: new file. + +Fri Nov 27 20:12:48 1992 K. Richard Pixley (rich@rtl.cygnus.com) + + Added support for adding previously removed files, as well as + adding and removing files in branches. + + * add.c (build_entry): add new argument, tag, so as to store in + Entries the per directory sticky tag under which a file is + added. Changed prototype and caller. + (build_entry): Do not prevent file additions if the file exists + in the Attic. + (add): if the file being adding was previously dead, say so, and + mark the Entries file with the addition. + * checkin.c (Checkin): adding with a tag no longer means to add, + then tag. Hence, remove the tagging operation. + * classify.c (Classify_File): if the base RCS version is dead, + then the file is being added. If a file being added already + exists in the attic, and the base RCS version is NOT dead, then + we have a conflict. + * commit.c (checkaddfile): add the list of srcfiles to calling + convention. Change prototype and callers. + (remove_file): add message and list of srcfiles to calling + convention. Change prototype and callers. When removing a file + with a tag, remove the tag only when the tag does not represent + a branch. Remove files by committing dead revisions in the + appropriate branch. When removing files from the trunk, also + move the RCS file into the Attic. + (check_fileproc): when adding, and looking for previously + existing RCS files, do not look in the Attic. + (commit_fileproc): adding files with tags now implies adding the + file on a branch with that tag. + (checkaddfile): When adding a file on a branch, in addition to + creating the rcs file in the Attic, also create a dead, initial + revision on the trunk and stub in a magic branch tag. + * cvs.h (joining, gca): added prototypes. + * rcs.c (RCS_getbranch): now global rather than static. + remove prototype and forward decl. + (parse_rcs_proc): use RCS_addnode. + (RCS_addnode): new function. + (RCS_parsercsfile): recognize the new RCS revision + newphrase, "dead". Mark the node for the revision. + (RCS_gettag): requesting the head of a file in the attic now + returns the head of the file in the attic rather than NULL. + (RCS_isbranch): use RCS_nodeisbranch. + (RCS_nodeisbranch): new function. + (RCS_isdead): new function. + * rcs.h (RCSDEAD): new macro for new rcs keyword. + (struct rcsversnode): new field to flag dead revisions. + (RCS_nodeisbranch, RCS_isdead, RCS_addnode): new functions, + new prototypes, new externs. + (RCS_getbranch): now global, so prototype and extern moved + to here. + * subr.c (gca): new function. + * update.c (join_file): add entries list to calling + convention. Caller changed. + (update): also search the Attic when joining. + (checkout_file): when joining, checkout dead revisions too. If + a file has died across an update then say so. + (join_file): support joins of dead files against live ones, live + files against dead ones, and added files. Change the semantic + of a join with only rev specified to mean join specified rev + against checked out files via the greatest common ancestor of + the specified rev and the base rev of the checked out files. + (joining): new function. + * vers_ts.c (Version_TS): ALWAYS get the rcs version number. + + * update.c (update): write the 'C' letter for conflicts. + + * cvs.h (ParseTag): remove duplicate extern. + + * add.c (add_directory): do not prompt for interactive + verification before adding a directory. Doing so prevents + scripted testing. + +Wed Feb 26 18:04:40 1992 K. Richard Pixley (rich@cygnus.com) + + * Makefile.in, configure.in: removed traces of namesubdir, + -subdirs, $(subdir), $(unsubdir), some rcs triggers. Forced + copyrights to '92, changed some from Cygnus to FSF. + +Tue Dec 10 01:24:40 1991 K. Richard Pixley (rich at cygnus.com) + + * diff.c: do not pass an empty -r option to rcsdiff. + + * update.c: fix bug where return code from rcsmerge wasn't being + handled properly. + + * main.c: "rm" and "delete" now synonyms for "remove". + + * commit.c: abort if editor session fails, but remember to clear + locks. + + * Makefile.in: remove conf.h and checkin.configured on clean. + infodir belongs in datadir. + +Thu Dec 5 22:46:03 1991 K. Richard Pixley (rich at rtl.cygnus.com) + + * Makefile.in: idestdir and ddestdir go away. Added copyrights + and shift gpl to v2. Added ChangeLog if it didn't exist. docdir + and mandir now keyed off datadir by default. + +Wed Nov 27 02:47:13 1991 K. Richard Pixley (rich at sendai) + + * brought Makefile.in's up to standards.text. + + * fresh changelog. + + +For older changes, there might be some relevant stuff in the bottom of +the NEWS file, but I'm afraid probably a lot of them are lost in the +mists of time. diff --git a/contrib/cvs/src/ChangeLog-9395 b/contrib/cvs/src/ChangeLog-9395 new file mode 100644 index 0000000..c2d2111 --- /dev/null +++ b/contrib/cvs/src/ChangeLog-9395 @@ -0,0 +1,3731 @@ +Note: this log overlaps in time with ChangeLog-9194. There was a time +during which changes which had been merged into the official CVS +(which produced releases such as 1.4A1 and 1.4A2) went into what has +become ChangeLog-9194, and changes which existed only at Cygnus went +into this file (ChangeLog-9395). Eventually the Cygnus release became +Cyclic CVS (as it was then called), which became CVS 1.5, so probably +all the changes in both (what are now) ChangeLog-9194 and +ChangeLog-9395 made it into 1.5. + +Sun Dec 31 17:33:47 1995 Jim Kingdon <kingdon@harvey.cyclic.com> + + * import.c (add_rev): Revert portion of 31 Aug 95 change which + passes -u to ci instead of using a hard link. + * sanity.sh (import): Add test for above-fixed bug. + +Sun Dec 31 16:40:41 1995 Peter Chubb <peterc@bookworm.sw.oz.au> + and Jim Kingdon <kingdon@cyclic.com> + + * admin.c (admin_fileproc): Call freevers_ts before returning. + +Mon Dec 25 12:20:06 1995 Peter Wemm <peter@haywire.DIALix.COM> + + * logmsg.c (rcsinfo_proc): initialise line and + line_chars_allocated so they dont cause malloc problems within + getline(). This was causing rcsinfo templates to not work. + +Sun Dec 24 01:38:36 1995 Karl Fogel <kfogel@floss.cyclic.com> + + * server.c (authenticate_connection): clarify protocol. + + * login.c (login): deprolixify the password prompt. + +Sat Dec 23 10:46:41 1995 Jim Kingdon <kingdon@harvey.cyclic.com> + + * myndbm.h, myndbm.c (dbm_store): New function. + * myndbm.h (DBM): Add modified and filename fields. + * myndbm.c (dbm_open, dbm_close): Manipulate new fields. dbm_open + no longer fails if the file doesn't exist and O_CREAT is set. + * cvs.h (CVSROOTADM_VALTAGS): Added. + * tag.c, cvs.h (tag_check_valid): New function. + * update.c (update), checkout.c (checkout_proc), commit.c (commit), + diff.c (diff), patch.c (patch_proc), rtag.c (rtag_proc), tag.c (tag): + Call it. + * sanity.sh: Test for rejection of invalid tagname. + +Fri Dec 22 18:21:39 1995 Karl Fogel <kfogel@csxt.cs.oberlin.edu> + + * client.c (start_server): don't use kerberos if authenticating + server was specified. + +Fri Dec 22 16:35:57 1995 Karl Fogel <kfogel@csxt.cs.oberlin.edu> + + * login.c (login): deal with new scramble methods. + (get_cvs_password): same. + + * server.c (check_repository_password): remove arbitrary limit on + line length. + (authenticate_connection): use a separate variable for the + descrambled password, now that we no longer scramble in place. + Set `error_use_protocol' to 1 and just use error() where used to + do its job inline. + + * cvs.h (scramble, descramble): adjust prototype. + + * scramble.c (scramble, descramble): return char *. + +Fri Dec 22 13:00:00 1995 Jim Kingdon <kingdon@peary.cyclic.com> + + * release.c (release): If SERVER_SUPPORT is not defined, still + set up arg_start_idx. + + * release.c (release): When calling unedit, set argv[1] to + NULL (since argc is only 1). + + * edit.c: Pass dosrcs 0 to all calls to start_recursion. + None of the fileprocs were using it, so it just slowed things + down and caused potentially harmful checks for rcs files. + + * edit.c (send_notifications): In client case, do not readlock. + +Thu Dec 21 16:00:00 1995 Jim Kingdon <kingdon@peary.cyclic.com> + + Clean up Visual C++ lint: + * client.c (read_line): Change input_index and result_size to size_t. + (update_entries): Remove unused variables buf2, size_left, size_read. + (handle_mode): Prototype. + * client.c, client.h (send_to_server, read_from_server): Change + len to size_t. + * client.c (send_to_server): Change wrtn to size_t. + (read_from_server): Change red to size_t. + * client.c, myndbm.c, edit.c, fileattr.c: Include getline.h. + * checkin.c, commit.c, update.c: Include fileattr.h. + * commit.c, update.c: Include edit.h. + * edit.c (onoff_filesdoneproc): Prototype. + (ncheck_fileproc,edit_fileproc): Change "return" to "return 0". + (notify_do): Cast a signed value to unsigned before comparing + with unsigned value. + +Thu Dec 21 15:24:37 1995 Karl Fogel <kfogel@occs.cs.oberlin.edu> + + * client.c: don't include socket headers twice just because + both HAVE_KERBEROS and AUTH_CLIENT_SUPPORT are set. + (start_kerberos_server): if fail to connect to kerberos, print out + a more specific error message, mainly so pcl-cvs can know what + happened and not panic. + (start_server): don't assume sprintf() returns len + written (only some systems provide this); instead, have + send_to_server() calculate the length itself. + (send_modified): same. + (send_fileproc): same. + (send_file_names): same. + +Wed Dec 20 14:00:28 1995 Jim Kingdon <kingdon@harvey.cyclic.com> + + * update.c (ignore_files): Move from here... + * ignore.c (ignore_files): ...to here. No longer static. Take + new argument PROC. + * cvs.h (ignore_files): Declare. + * client.c (send_filesdoneproc): Split off from + update_filesdone_proc. Pass new function send_ignproc to + ignore_files (to ask server about ignored file before printing + "?"). + * server.c: Rename outbuf from but_to_net and take it from + do_cvs_command to a global. Move initialization accordingly. + (serve_questionable): New function. + (requests): Add it. + * update.c (update_filesdone_proc): Remove client stuff. Pass new + function update_ignproc to ignore_files. + * cvs.h (joining, do_update): Move declarations from here... + * update.h: ...to here. + * cvs.h: Include update.h. + * update.c, client.c: Don't include update.h + * ignore.c, cvs.h: New variable ign_inhibit_server, set on -I !. + * import.c (import): Pass -I ! to server if specified. + (import_descend): If server, ignore CVS directories even if -I !. + * update.c (update), import.c (import): Only call ign_setup before + argument processing; don't call it again afterwards in client case. + * sanity.sh (ignore): Test above-fixed bugs and other ignore behaviors. + (dotest): New function. + Move modules checkin from modules test to start, so that other + tests can use mkmodules without a warning message. + +Wed Dec 20 13:06:17 1995 Karl Fogel <kfogel@floss.cyclic.com> + + * client.c (send_to_server): don't check string's length twice. + +Wed Dec 20 02:05:19 1995 Karl Fogel <kfogel@floss.cyclic.com> + + * login.c (login): took out debugging printf's. + (login): Removed unused variable `p'. + +Wed Dec 20 00:27:36 1995 Karl Fogel <kfogel@floss.cyclic.com> + + * login.c (login): prefix scrambled password with 'A', so we know + which version of scrambling was used. This may be useful in the + future. + (get_cvs_password): skip past the leading 'A'. + Scramble $CVS_PASSWORD before returning it. + + * scramble.c: made this work. + +Tue Dec 19 17:45:11 1995 Karl Fogel <kfogel@floss.cyclic.com> + + * login.c (cvs_password): new static var, init to NULL. + (login): scramble() the password before using it. + Verify the password with the server. + Check CVSroot more carefully to insure that it is + "fully-qualified". + (get_cvs_password): if cvs_password is not NULL, just return it. + Never prompt -- just tell user why failed, then exit. + Try CVS_PASSWORD environment variable first. + (construct_cvspass_filename): try CVS_PASSFILE environment + variable first. + + * client.h (connect_to_pserver): update prototype. + + * client.c (cvsroot_parsed): new static var. + (parse_cvsroot): set `cvsroot_parsed' to 1 when done. + (connect_to_pserver): return int. + Take `verify_only' arg. If it is non-zero, perform password + verification with the server and then shut down the connection and + return. + Call parse_cvsroot() before doing anything. + + * server.c (authenticate_connection): deal with verification + requests as well as authorization requests. + descramble() the password before hashing it. + + * cvs.h: prototype scramble() and descramble(). + + * Makefile.in: build scramble.o. + + * scramble.c: new file, provides trivial encoding but NOT real + encryption. + +Mon Dec 18 20:57:58 1995 Karl Fogel <kfogel@floss.cyclic.com> + + * login.c (login): don't insert extra newlines. They were + harmless, but confusing. + +Mon Dec 18 15:32:32 1995 Jim Kingdon <kingdon@harvey.cyclic.com> + + * hash.c, hash.h (findnode_fn): New function. + * hash.c (hashp): Tweak hash function so that findnode_fn works. + * update.c (ignore_files): Call findnode_fn, not findnode. + +Mon Dec 18 09:34:56 1995 Jim Kingdon <kingdon@harvey.cyclic.com> + + * myndbm.c: Remove arbitrary limit. + + * client.c: Fix comment--Windows 95 requires NO_SOCKET_TO_FD, not + Windows NT. + +Mon Dec 18 01:06:20 1995 Karl Fogel <kfogel@floss.cyclic.com> + + * client.c (server_sock): replaces `server_socket'. + (start_kerberos_server): added FIXME comment about how + NO_SOCKET_TO_FD is not dealt with in the kerberos case. + (connect_to_pserver): deal with NO_SOCKET_TO_FD case. + (read_line): deal with NO_SOCKET_TO_FD case. + (read_from_server): deal with NO_SOCKET_TO_FD case. + (send_to_server): deal with NO_SOCKET_TO_FD case. + (get_responses_and_close): deal with NO_SOCKET_TO_FD case. + + * client.c (send_to_server): error check logging. + (start_server): error check opening of logfiles. + (read_from_server): error check logging. + (read_line): use fwrite() to log, & error_check it. + Don't log if using socket style, because read_from_server() + already logged for us. + +Mon Dec 18 00:52:26 1995 Karl Fogel <kfogel@floss.cyclic.com> + + * client.c (use_socket_style): new static var, init to 0. + (server_socket): new static var. + (connect_to_pserver): don't deal with logging here. + Caller changed. + (start_kerberos_server): don't deal with logging here either. + Caller changed. + +Mon Dec 18 00:40:46 1995 Karl Fogel <kfogel@floss.cyclic.com> + + * client.c (send_modified): don't error-check `to_server'; + send_to_server() does that now. + +Mon Dec 18 00:19:16 1995 Karl Fogel <kfogel@floss.cyclic.com> + + * login.c (get_cvs_password): Init `linebuf' to NULL. + free() `linebuf' and reset it for each new line. + (login): same as above. + + * client.c: Removed all the varargs prototyping gunk. + (to_server, from_server): make these static. + (from_server_logfile, to_server_logfile): new vars. + (start_server): init above two new vars to NULL. + (send_to_server): return void. + Correct bug in which amount to be written would be too high if the + loop ever ran more than once. + Log to `to_server_logfile' if it's non-NULL. + (read_from_server): new func, does raw reading from server. + Logs to `from_server_logfile' if it's non-NULL. + (update_entries): just use read_from_server() instead of looping + to fread() directly from `from_server'. + (read_line): Log to `from_server_logfile' if it's non-NULL. + + * client.h: send_to_server() returns void now. + (read_from_server): prototype. + +Sun Dec 17 19:38:03 1995 Jim Kingdon <kingdon@harvey.cyclic.com> + + * checkout.c (checkout_proc), client.c, lock.c (readers_exist), + login.c, modules.c (cat_module, do_module): Remove arbitrary limits. + + * client.c (send_to_server): Fix typo (NULL -> '\0'). + (get_responses_and_close): Set server_started to 0 instead of + setting to_server and from_server to NULL. + * client.c: Make to_server and from_server static. + +Sun Dec 17 17:59:04 1995 Karl Fogel <kfogel@floss.cyclic.com> + + * client.h (to_server, from_server): don't declare these anymore. + They are now entirely private to client.c (and in fact will go + away soon there too). + +Sun Dec 17 15:40:58 1995 Karl Fogel <kfogel@floss.cyclic.com> + + * client.h: update prototype of send_to_server(). + + * client.c, watch.c, update.c, tag.c, status.c, rtag.c, remove.c, + release.c, patch.c, log.c, import.c, history.c, edit.c, diff.c, + commit.c, client.c, checkout.c, admin.c, add.c: + Convert all send_to_server() calls that used formatting to send + pre-formatted strings instead. And don't error check + send_to_server(), because it does its own error checking now. + + * client.c (send_to_server): don't use vasprintf(), just fwrite a + certain number of bytes to the server. And do error checking + here, so our callers don't have to. + (send_arg): use send_to_server() instead of putc()'ing + directly to `to_server'. + +Sun Dec 17 14:37:52 1995 Karl Fogel <kfogel@floss.cyclic.com> + + * options.h.in (AUTH_CLIENT_SUPPORT, AUTH_SERVER_SUPPORT): + Define to 1 but leave commented out, instead of #undef'ing them. + This treats them like everything else in this file. + + * client.c: define server_started, init to 0. + (start_server): set server_started to 1. + + * client.h: declare `server_started', extern. + AUTH_CLIENT_SUPPORT moved here from cvs.h. + + * cvs.h: moved AUTH_CLIENT_SUPPORT stuff to client.h. + + * edit.c (notify_check): use new var server_started. + +Sun Dec 17 00:44:17 1995 Jim Kingdon <kingdon@harvey.cyclic.com> + + * client.c (get_responses_and_close): Really stop ignoring ECHILD + errors. The Nov 30 1995 change claimed to do this, but the code + was not actually changed. + + * update.c (ignore_files): Revert H.J. Lu change; it was wrong for + directories and sometimes looked at sb.st_mode when it wasn't set. + * import.c (import_descend): Revert H.J. Lu change; it was wrong + for directories and the extra lstat call was an unnecessary + performance hit. + * sanity.sh (import): Add test for the second of these two bugs. + +Sat Dec 16 17:26:08 1995 Jim Kingdon <kingdon@harvey.cyclic.com> + + * client.c (send_to_server): Remove arbitrary limit. Also remove + !HAVE_VPRINTF code; all relevant systems have vprintf these days. + +Sat Dec 16 21:35:31 1995 Karl Fogel <kfogel@floss.cyclic.com> + + * checkout.c (checkout): use send_to_server() now. + +Sat Dec 16 21:18:16 1995 H.J. Lu (hjl@gnu.ai.mit.edu) + (applied by kfogel@cyclic.com) + + * import.c (import_descend): We ignore an entry if it is + 1. not a file, nor a link, nor a directory, or + 2. a file and on the ignore list. + + * update.c (ignore_files): We ignore any thing which is + 1. not a file, or + 2. it is a file on the ignore list. + +Sat Dec 16 00:14:19 1995 Karl Fogel <kfogel@floss.cyclic.com> + + * client.c (send_to_server): corrected comment. + + * client.h: prototype new func send_to_server(). + + * add.c, admin.c, client.c, commit.c, diff.c, edit.c, history.c, + import.c, log.c, patch.c, release.c, remove.c, rtag.c, status.c, + tag.c, update.c, watch.c: + Use send_to_server() instead of writing directly to to_server. + + * client.c: conditionally include the right stuff for variable arg + lists. + (send_to_server): new func. + +Fri Dec 15 23:10:22 1995 Karl Fogel <kfogel@floss.cyclic.com> + + * error.c: expanded comments. + + * client.c (connect_to_pserver): verbosify errors. + (connect_to_pserver): use send() and recv(), not write() and + read(). Sockets are not file descriptors on all systems. + +Fri Dec 15 22:36:05 1995 Karl Fogel <kfogel@floss.cyclic.com> + + * client.c (connect_to_pserver): oops, removed old debugging + printf. + +Fri Dec 15 18:21:16 1995 Karl Fogel (kfogel@floss.cyclic.com) + + * client.c (auth_server_port_number): don't call htons(); + init_sockaddr() does that for us. + (init_sockaddr): zero the sockadder_in struct before doing + anything with it. IBM TCP/IP docs recommend this, and it can't + hurt. + +Fri Dec 15 15:21:53 1995 Karl Fogel <kfogel@floss.cyclic.com> + + * client.c (connect_to_pserver): new var `port_number', initialize + with new func auth_server_port_number() and pass to + init_sockaddr(). + (auth_server_port_number): new func. Right now it just returns + `htons (CVS_AUTH_PORT)'. We'll probably add the ability to + specify the port at run time soon, anyway, so having this function + will make that easier. + +Wed Dec 6 18:08:40 1995 Jim Kingdon <kingdon@harvey.cyclic.com> + + * cvs.h: Add CVSREP. + * find_names.c (find_dirs): Skip CVSREP too. + * fileattr.h, fileattr.c: New files, to manipulate file attributes. + * hash.c (nodetypestring), hash.h (enum ntype): Add FILEATTR. + * hash.c, hash.h (list_isempty): New function. + * recurse.c (do_recursion): Call fileattr_startdir before + processing files in a directory and fileattr_write and + fileattr_free (after files, before recursing). + * watch.c, watch.h: New files, to handle notification features. + * edit.c, edit.h: New file, to handle new read-only checkout features. + * client.c, server.c: Add "Mode" request, to change the mode of a file + when it is checked in. + * main.c (cmds): Add "watch", "edit", "unedit", "watchers", "editors". + * main.c: Split command help from usg into new variable cmd_usage, + which. + (main): Add --help-commands option to print out cmd_usage. + * cvs.h: Declare watch, edit, unedit, watchers, editors. + * client.c, client.h: Add client_watch, client_edit, client_unedit, + client_watchers, client_editors. + * client.c, server.c: Add notification stuff. + * update.c (checkout_file, patch_file), checkin.c (Checkin): Check + _watched attribute when deciding read-only or read-write. + * commit.c (checkaddfile): Call fileattr_newfile to set attributes + on newly created files. + * release.c (release): + * cvs.h: Add CVSADM_NOTIFY and CVSADM_NOTIFYBAK. + * recurse.c (do_recursion): Call notify_check. + * commit.c (commit_fileproc): Call notify_do after committing file. + * client.c (get_responses_and_close): Set to_server and from_server + to NULL so that it is possible to tell whether we are speaking to + the server. + * cvs.h: Add CVSROOTADM_NOTIFY. + * mkmodules.c (main): Add CVSROOTADM_NOTIFY to filelist. + * Makefile.in (SOURCES,OBJECTS,HEADERS): Add new files mentioned above. + * lock.c, cvs.h (lock_tree_for_write, lock_tree_cleanup): New + functions, taken from old commit.c writelock code. As part of + this, fsortcmp and lock_filesdoneproc go from commit.c to lock.c. + So does locklist but it gets renamed to lock_tree_list. + * commit.c: Use lock_tree_*. + +Fri Dec 15 10:37:00 1995 J.T. Conklin <jtc@slave.cygnus.com> + + * tag.c (tag_usage): Added -r and -D flags to usage string. + (tag): Detect when user specifies both -r and -D arguments. + Pass -r and -D arguments to server. + +Thu Dec 14 11:56:13 1995 Karl Fogel <kfogel@floss.cyclic.com> + + * client.c (start_rsh_server): use RSH_NEEDS_BINARY_FLAG to + conditionalize "-b" option to "rsh". + + * run.c (filter_stream_through_program): document return value and + error behavior. + + * client.c (filter_through_gunzip): pass the supposedly + superfluous "-d" option to gunzip, to avoid stimulating what seems + to be an argument-passing bug in spawn() under OS/2 with IBM + C/C++. Yucko. + +Wed Dec 13 20:08:37 1995 Jim Kingdon <kingdon@harvey.cyclic.com> + + * options.h.in (RCSBIN_DFLT): Recommend specifying -b in + inetd.conf for pserver. That is a pretty good solution. + +Wed Dec 13 18:29:59 1995 Preston L. Bannister <pbannister@ca.mdis.com> + and Karl Fogel <kfogel@floss.cyclic.com> + + * client.c (send_modified): make sure that vers and vers->options + are non-NULL before strcmp()'ing them with "-kb". + Initialize `bin' near where it is used, not at beginning of + function. + (update_entries): make sure `options' is non-NULL before + strcmp()'ing with "-kb". + Initialize `bin' near where it is used, not at beginning of + function. + +Tue Dec 12 18:56:38 1995 Karl Fogel <kfogel@totoro.cyclic.com> + + * options.h.in (RCSBIN_DFLT): document the probable need for this + to be set in the authenticating server. + +Tue Dec 12 11:56:43 1995 Jim Kingdon <kingdon@harvey.cyclic.com> + + * server.c (expand_proc): If mfile is non-NULL, return it too as + part of the expansion. + * sanity.sh (modules): Add tests for above-fixed bug. + +Mon Dec 11 21:39:07 1995 Karl Fogel <kfogel@floss.cyclic.com> + + * dog.c (flea_bath): Take `suds' arg. + All collars changed. + +Mon Dec 11 15:58:47 1995 Karl Fogel <kfogel@floss.cyclic.com> + + * login.c (login): if client password file doesn't exist, create + it, duh. + + * main.c (main): die if CVSroot has access-method but no + username. + + * root.c: added some comments. + + * main.c: removed all code pertaining to the "-a" option. We + specify access-method in CVSroot now. + + * client.c (parse_cvsroot): new var, `access_method'. If CVSroot + is prepended with an access method (i.e., + ":pserver:user@host:/path"), then handle it. + + * login.c (login): use || when checking if CVSroot is "fully + qualified". + Prepend ":pserver:" before writing to ~/.cvspass. + (get_cvs_password): Take no parameters; we'll just use CVSroot to + get the password. + +Mon Dec 11 12:43:35 1995 adamg <adamg@microsoft.com> + + * error.c, client.c, remove.c, main.c: Add explicit casts for some + function pointers to remove warnings under MS VC. + * main.c (main): remove use of NEED_CALL_SOCKINIT in favor of the + more generic INITIALIZE_SOCKET_SUBSYSTEM. Note that the code assumes + that if INITIALIZE_SOCKET_SUBSYSTEM() returns, socket subsystem + initialization has been successful. + +Sat Dec 9 22:01:41 1995 Dan O'Connor <doconnor@tii.com> + + * commit.c (check_fileproc): pass RUN_REALLY flag to run_exec, + because it's okay to examine the file with noexec set. + +Sat Dec 9 20:28:01 1995 Karl Fogel <kfogel@floss.cyclic.com> + + * client.c (update_entries): new var, `bin, init to 0. + Use it in determining whether to convert the file. + (send_modified): same as above. + +Fri Dec 8 17:47:39 1995 Karl Fogel <kfogel@floss.cyclic.com> + + * server.c (downcase_string): removed. + (check_repository_password): don't deal with case-insensitivity + anymore. + + * options.h.in (CVS_PASSWORDS_CASE_SENSITIVE): deleted this. No + need for it anymore. + +Thu Dec 7 21:08:39 1995 Karl Fogel <kfogel@floss.cyclic.com> + + * server.c (check_repository_password): when checking for false + prefix-matches, look for ':', not '@'. Duh. + +Thu Dec 7 18:44:51 1995 Karl Fogel <kfogel@floss.cyclic.com> + + * options.h.in (CVS_PASSWORDS_CASE_SENSITIVE): replaces + CVS_PASSWORDS_CASE_INSENSITIVE; passwords are now insensitive by + default. Expanded explanatory comment. + + * login.c (get_cvs_password): Use memset(), not bzero(). I + botched this change earlier. + + * server.c (check_repository_password): no need to check + xmalloc()'s return value. + (check_repository_password): check for false prefix-matches (for + example, username is "theo" and linebuf contains user + "theocracy"). + +Thu Dec 7 14:49:16 1995 Jim Meyering (meyering@comco.com) + + * filesubr.c (isaccessible): Rename from isaccessable. + Update callers. + * cvs.h: Update prototype. + * main.c (main): Update callers. + * server.c (main): Update callers. + +Thu Dec 7 12:50:20 1995 Adam Glass <glass@NetBSD.ORG> + + * cvs.h: "isaccessible" is the correct spelling. + Also add "const" to second arg to make prototype match + declaration. + +Thu Dec 7 11:06:51 1995 Karl Fogel <kfogel@floss.cyclic.com> + + * client.c, login.c: memset() instead of bzero(). + +Thu Dec 7 00:08:53 1995 Karl Fogel <kfogel@floss.cyclic.com> + + * server.c (authenticate_connection): document server's side of + the Authentication Protocol too. + + * client.c (connect_to_pserver): when printing out "unrecognized + response", also print out the offending response. + + * server.c (check_password): take `repository' arg too now. + Call check_repository_password() before checking /etc/passwd. + (check_repository_password): new func. + + * options.h.in (CVS_PASSWORDS_CASE_INSENSITIVE): new define, unset + by default. + +Wed Dec 6 18:51:16 1995 Karl Fogel <kfogel@floss.cyclic.com> + + * server.c (check_password): If user has a null password, then + return 1 if arg is also null. + Reverse sense of return value. Caller changed. + +Wed Dec 6 14:42:57 1995 Karl Fogel <kfogel@floss.cyclic.com> + + * server.c (check_password): new func. + (authenticate_connection): call above new func. + + * login.c (login): use construct_cvspass_filename(). + If CVSroot is not "fully-qualified", then insist the user qualify + it before going on. + (get_cvs_password): fleshed out. Now reads from ~/.cvspass, or + prompts if no appropriate password found. + (construct_cvspass_filename): new func. + + * server.c (authenticate_connection): send ACK or NACK to client. + + * client.c (connect_to_pserver): check for ACK vs NACK response + from server after sending authorization request. + + * login.c (get_cvs_password): new func. + + * client.c (connect_to_pserver): use new func get_cvs_password(). + Prototype it at top of file. Hmmm. + +Wed Dec 6 13:29:22 1995 Karl Fogel <kfogel@floss.cyclic.com> + + * server.c: same as below (AUTH_SERVER_SUPPORT). + + * main.c: same as below (AUTH_SERVER_SUPPORT where appropriate). + + * login.c: same same as below. + + * cvs.h: same as below. + + * client.c: use AUTH_CLIENT_SUPPORT, not CVS_LOGIN. + + * options.h.in (AUTH_CLIENT_SUPPORT, AUTH_SERVER_SUPPORT): these + replace CVS_LOGIN. + +Wed Dec 6 00:04:58 1995 Karl Fogel <kfogel@floss.cyclic.com> + + * server.c (authenticate_connection): expanded comment. + +Tue Dec 5 23:37:39 1995 Karl Fogel <kfogel@floss.cyclic.com> + + * client.c (connect_to_pserver): read password from prompt for + now. + + * server.c (authenticate_connection): if the password passes + muster, then don't abort. + +Tue Dec 5 22:46:37 1995 Karl Fogel <kfogel@floss.cyclic.com> + + * subr.c (strip_trailing_newlines): new func. + + * client.c (connect_to_pserver): took out print statements. + + * server.c (authenticate_connection): removed print statments. + Use new func strip_trailing_newlines() to purify `repository', + `username', and `password'. + Run a primitive password check, just for testing. + + * client.c (connect_to_pserver): use CVS_AUTH_PORT. + Take tofdp, fromfdp, and log args. Caller changed. + (get_responses_and_close): either kerberos and CVS_LOGIN might + have one fd for both directions, so adjust #ifdef accordingly. + + * cvs.h (CVS_AUTH_PORT): new define, default to 2401. + Prototype strip_trailing_newlines(). + +Tue Dec 5 16:53:35 1995 Karl Fogel <kfogel@floss.cyclic.com> + + * server.c (authenticate_connection): new func. + + * client.c (init_sockaddr): func moved here from login.c. + (connect_to_pserver): same as above. Take no args, now. + Include <sys/socket.h>, <netinet/in.h>, <netdb.h>, if CVS_LOGIN. + + * cvs.h: Declare use_authenticating_server, as extern int. + Declare connect_to_pserver(). + + * main.c (main): call authenticate_connection(). Removed testing + code. + Add 'a' to the short-option string in the getopt() call. + + * login.c (connect_to_pserver): moved to client.c. + +Tue Dec 5 16:01:42 1995 Peter Chubb <peterc@bookworm.sw.oz.au> + (patch applied by Karl Fogel <kfogel@cyclic.com>) + + * update.c (join_file): if vers->vn_user is "0", file has been + removed on the current branch, so print an error and return. + +Mon Dec 4 14:27:42 1995 Jim Kingdon <kingdon@harvey.cyclic.com> + + * Version 1.6.3. + +Mon Dec 4 16:28:25 1995 Norbert Kiesel <nk@col.sw-ley.de> + + * release.c (release): add return (0) as last line + + * cvs.h: declare program_path + + * main.c define program_path + (main): set program_path + + * release.c (release): use program_path for update_cmd + +Mon Dec 4 11:22:42 1995 Jim Kingdon <kingdon@harvey.cyclic.com> + + * Version 1.6.2. + +Sun Dec 3 20:02:29 1995 Jim Kingdon <kingdon@harvey.cyclic.com> + + * rcs.h (struct rcsnode), rcs.c (freercsnode): Add expand field. + * rcs.h (RCSEXPAND): New #define. + * rcs.c (RCS_reparsercsfile): Record keyword expansion in expand + field of struct rcsnode. + * update.c (checkout_file): Set keyword expansion in Entries file + from rcs file if there is nowhere else to set it from. + * client.c (send_modified, update_entries) [LINES_CRLF_TERMINATED]: + If -kb is in effect, don't convert. + + * update.c (update_file_proc), commit.c (check_fileproc), + rcscmds.c (RCS_merge): Direct stdout to DEVNULL rather than + passing -s option to grep. This avoids trouble with respect to + finding a grep which support -s and whether we should use the (GNU + grep) -q option if it exists. + * options.h.in: Change "@ggrep_path@" to "grep". + +Fri Dec 1 11:53:19 1995 Norbert Kiesel <nk@col.sw-ley.de> + + * rcs.c (RCS_gettag): new parameter return_both force return both + tags: the symbolic and the numeric one. + (RCS_getversion): new parameter return_both is forwarded to + RCS_gettag. + + * rtag.c, tag.c, commit.c, patch.c, update.c: pass 0 as additional + last parameter to RCS_getversion and RCS_gettag + + * rcs.h (RCS_gettag): new parameter return_both. + (RCS_getversion): new parameter return_both. + + * cvs.h (struct vers_ts): add vn_tag slot for symbolic tag name + + * vers_ts.c (Version_TS): call RCS_getversion with 1 for + return_both and split output into vn_rcs and vn_tag + (freevers_ts): free vn_tag + + * update.c (checkout_file): use vn_tag instead of vn_rcs when + calling 'rcs co' to allow rcs expansion of :$Name : + +Thu Nov 30 20:44:30 1995 Karl Fogel <kfogel@floss.cyclic.com> + + * client.c (get_responses_and_close): undo previous change + regarding waitpid(). The problem has been solved by modifying + os2/waitpid.c instead of its callers. + +Thu Nov 30 16:37:10 1995 Karl Fogel <kfogel@floss.cyclic.com> + + * client.c: All these changes are for OS/2, which will no longer have + a separate client.c: + (start_kerberos_server): new func, contains code that + used to be in start_server(). + (start_server): moved kerberos code to above function, reorganized + the rest. Added authentication clause. + (call_in_directory): test errno against EACCESS, if EACCESS is + defined (this is for OS/2's oddball mkdir). + (change_mode): don't set execute permission on anything if + EXECUTE_PERMISSION_LOSES is defined. + (get_responses_and_close): if START_RSH_WITH_POPEN_RW, then use + pclose() instead of fclose(). + If waitpid errors with ECHILD, don't die. This is okay. + (start_rsh_server): alternate definition if + START_RSH_WITH_POPEN_RW. + + * main.c: [all these changes conditional on CVS_LOGIN: ] + Don't prototype connect_to_pserver, don't enter it in cmds[] + (actually, it was never in there, I don't know why my previous + change said it was). + (use_authenticating_server): new global var. + (main): if "-a", then set above new var to TRUE. + (usg): document "-a" option. + +Wed Nov 29 12:55:10 1995 Karl Fogel <kfogel@floss.cyclic.com> + + * main.c: Prototype connect_to_pserver(), and enter it in cmds[]. + (main): test some extremely primitive authentication. + + * login.c: Include <sys/socket.h> + (connect_to_pserver): new func. + (init_sockaddr): new func. + +Mon Nov 20 14:07:41 1995 Jim Kingdon <kingdon@harvey.cyclic.com> + + * Makefile.in (TAGFILES): Separate out from DISTFILES, for C code. + (TAGS,tags): Use TAGFILES not DISTFILES. + +Sun Nov 19 11:22:43 1995 Jim Kingdon <kingdon@harvey.cyclic.com> + + * recurse.c (do_recursion): Don't call server_pause_check if there + are writelocks around. Revise comment to reflect fact we are no + longer relying on a writelock'd operations being "unable" to + generate enough data to pause. + +Sun Nov 19 10:04:50 1995 Peter Wemm <peter@haywire.DIALix.COM> + + * server.c, server.h, options.h.in: Implement hooks for doing + simple flow control on the server to prevent VM exhaustion on a + slow network with a fast server. + * recurse.c: Call the flow control check at a convenient location + while no locks are active. This is a convenience tradeoff against + accurate flow control - if you have a large directory it will all + be queued up, bypassing the flow control check until the next + directory is processed. + +Sat Nov 18 16:22:06 1995 Karl Fogel <kfogel@floss.cyclic.com> + + * client.c, update.c, vers_ts.c, server.c, rcs.c, lock.c, + ignore.c, entries.c, diff.c, commit.c, checkin.c: + Use new macro `existence_error', instead of comparing errno to + ENOENT directly. + +Fri Nov 17 14:56:12 1995 Karl Fogel <kfogel@floss.cyclic.com> + + * client.c (start_server): removed alternate version of this func, + since os2/client.c will now be used under OS/2. + +Thu Nov 16 22:57:12 1995 Karl Fogel <kfogel@floss.cyclic.com> + + * client.c (start_server): ifdef HAVE_POPEN_RW, use a different + version of start_server(). This is maybe not the cleanest cut to + make, but it's better than mucking around with yet more #ifdefs in + the middle of the old start_server() function. Once things are + up, I may reposition this code. + +Wed Nov 15 15:33:37 1995 Karl Fogel <kfogel@floss.cyclic.com> + + * main.c (main): ifdef NEED_CALL_SOCKINIT, then call SockInit(). + Only OS/2 needs this initialization. + +Tue Nov 14 18:54:01 1995 Greg A. Woods <woods@most.weird.com> + + * patch.c: + - fix orientation of test for result of getline() call + - use fputs() not printf() when just copying file out + + * cvsbug.sh: + - add space after #! + - new rcs id + - allow version to be edited by Makefile. + + * Makefile.in: + - make Makefile a dependent of all (this might not be perfect, but + it at least gives you a chance to catch up on the second + go-around). + - filter cvsbug.sh in a manner similar to cvsinit.sh to get the + version number set from version.c + +Tue Nov 14 13:28:17 1995 Jim Kingdon <kingdon@harvey.cyclic.com> + + * sanity.sh: Call old log file check.plog, not check.olog. + + * sanity.sh: Convert remaining tests from old-style ('***' on fail + and nothing on pass), to new-style (FAIL on fail and PASS on pass). + + * sanity.sh: Fix ability to run only some of the tests (always run + tests 1-4.75 to set up repository, document better how it works). + + * sanity.sh: Change "completed successfully" to "completed" in + message--many tests, but not all, exit if they fail. + +Tue Nov 14 15:10:00 1995 Greg A. Woods <woods@most.weird.com> + + * sanity.sh: test 63 doesn't work and probably can't + +Tue Nov 14 12:22:00 1995 Greg A. Woods <woods@most.weird.com> + + * sanity.sh: many minor tweaks: + - make the optional arguments almost work + - use a function 'directory_cmp' instead of 'diff -r' + - fix up a few more tests that weren't working.... + +Mon Nov 13 07:33:55 1995 Karl Fogel <kfogel@floss.cyclic.com> + + * cvs.h: ifdef USE_OWN_POPEN, #include "popen.h". Only OS/2 has + its own popen()/pclose() right now. + +Mon Nov 13 04:06:10 1995 Karl Fogel <kfogel@floss.cyclic.com> + + * cvs.h: conform to 80 column standard (yes, I'm a pedant). + +Sat Nov 11 13:45:13 1995 Karl Fogel <kfogel@floss.cyclic.com> + + * client.c (process_prune_candidates): use unlink_file_dir() to + remove the directory, instead of invoking "rm" via run_exec(). + +Fri Nov 10 14:38:56 1995 Karl Fogel <kfogel@floss.cyclic.com> + + * main.c (main): removed "#define KF_GETOPT_LONG 1", since that + change is no longer in testing. + +Thu Nov 9 20:32:12 1995 Karl Fogel <kfogel@floss.cyclic.com> + + * release.c (release): Use Popen(), not popen(). + +Wed Nov 8 10:20:20 1995 Jim Meyering (meyering@comco.com) + + * entries.c (ParseTag): Remove dcl of unused local. + + * patch.c: Include getline.h. + +Wed Nov 8 11:57:31 1995 Norbert Kiesel <nk@col.sw-ley.de> + + * options.h.in: add configuration option STEXID_SUPPORT (default + is off i.e. old semantics) + + * filesubr.c (isaccessable): new function. Checks access-rights + for files like access(), but is getxid-safe. Falls back to + access() if SETXID_SUPPORT is not enabled. + (isfile): replace stat() by isaccessable(file, F_OK) + (isreadable): replace access() by isaccessable() + (iswritable): ditto + (make_directory): rename local variable buf to sb + + * cvs.h: add prototype for new function isaccessable. + + * server.c (serve_root): replace access() by isaccessable() + + * cvsrc.c (read_cvsrc): replace access() by isreadable() + + * main.c (main): replace access() by isaccessable() + +Wed Nov 8 10:22:41 1995 Greg A. Woods <woods@most.weird.com> + + * entries.c (fgetentent): change definition to static to match the + declaration at the top of the file + +Tue Nov 7 16:59:25 1995 J.T. Conklin <jtc@lestat.cygnus.com> + + * rcs.c (RCS_getbranch, RCS_getdate, RCS_getrevtime, RCS_gettag, + RCS_getversion, RCS_head): Use assert() instead of attempting to + "do the right thing" with a bogus RCSNode argument. + +Mon Nov 6 14:24:34 1995 Jim Kingdon <kingdon@harvey.cyclic.com> + + * vers_ts.c: Remove ctime define. It is just asking for trouble. + +Mon Nov 6 11:58:26 1995 Karl Fogel <kfogel@floss.cyclic.com> + + * vers_ts.c: ifdef ctime, undef it before redefining it. It is a + macro on some systems. + + * lock.c: don't prototype ctime() here. (See note below about + fgetentent() in entries.c.) + +Sun Nov 5 16:06:01 1995 Karl Fogel <kfogel@floss.cyclic.com> + + * entries.c (fgetentent): don't prototype ctime here; we include + cvs.h, which includes system.h, which includes <time.h> + unconditionally (either as <time.h> or <sys/time.h>). Anyway, IBM + C/C++ chokes on mid-function, or even mid-file, prototypes. Sigh. + +Thu Nov 2 21:51:04 1995 Dan Wilder <dan@gasboy.com> + + * rtag.c (rtag): Fix typo ("-T" -> "-F"). + +Tue Oct 31 19:09:11 1995 Dan Wilder <dan@gasboy.com> + + * diff.c (diff_dirproc): just return R_SKIP_ALL if dir not exist. + (diff_file_nodiff): don't complain if file doesn't exist, just + ignore. + +Tue Oct 31 09:25:10 1995 Norbert Kiesel <nk@col.sw-ley.de> + + * sanity.sh: Use absolute pathname for mkmodules. + +Sat Oct 28 01:01:41 1995 Jim Meyering (meyering@comco.com) + + * entries.c (ParseTag): Use getline instead of fgets. + +Fri Oct 27 13:44:20 1995 Karl Fogel <kfogel@floss.cyclic.com> + + * cvs.h: do nothing about alloca ifdef ALLOCA_IN_STDLIB. I am + rather suspicious of this solution, and will not be surprised to + find out that there's a Right Way to handle this situation ("this + situation" being that OS/2 simply declares alloca in <stdlib.h>). + Suggestions are welcome; see src/cvs.h and lib/system.h to see why + I was getting a conflict in the first place. + +Wed Oct 25 16:03:20 1995 J.T. Conklin <jtc@slave.cygnus.com> + + * cvs.h (struct entnode): Add user field. + * entries.c (fputentent): New function, write entries line. + (write_ent_proc): Call fputentent to write entries line. + (Entnode_Create): New function, construct new Entnode. + (Entnode_Destroy): New function, destruct old Entnode. + (AddEntryNode): Changed to take an Entnode argument instead of + separate user, version, timestamp, etc. arguments. + (fgetentent): Changed to return Entnode. + (struct entent, free_entent): Removed. + +Wed Oct 25 12:44:32 1995 Jim Kingdon <kingdon@harvey.cyclic.com> + + * admin.c (admin): Don't rely on ANSI C string concatenation; + SunOS 4.1.3 /bin/cc doesn't support it. + +Tue Oct 24 22:34:22 1995 Anthony J. Lill <ajlill@ajlc.waterloo.on.ca> + + * import.c (expand_at_signs): Check errno as well as return value + from putc. Some systems bogusly return EOF when successfully + writing 0xff. + +Tue Oct 24 14:32:45 1995 Norbert Kiesel <nk@col.sw-ley.de> + + * admin.c (admin): use getcaller() instead of getpwuid + + * subr.c (getcaller): prefer getlogin() to $USER and $LOGNAME + (especially useful for NT where getuid always returns 0) + +Tue Oct 24 06:22:08 1995 Jim Meyering (meyering@comco.com) + + * cvsrc.c (read_cvsrc): Use getline instead of fgets. + * patch.c (patch_fileproc): Use getline instead of fgets. + + * entries.c (fgetentent): Use getline instead of fgets. + Use xmalloc to allocate space for each returned entry. + Since LINE is no longer static, save it in struct entent. + (struct entent): New member, line. + (free_entent): New function. + (Entries_Open): Call it after each call to fgetentent. + +Tue Oct 24 11:13:15 1995 Norbert Kiesel <nk@col.sw-ley.de> + + * cvs.h: Declare valloc again, but this time with the right + signature (also changed in libs/valloc.c) + +Mon Oct 23 12:17:03 1995 Jim Kingdon <kingdon@harvey.cyclic.com> + + * logmsg.c (do_editor): Check for errors from stdio calls. + +Mon Oct 23 12:37:06 1995 Jim Kingdon <kingdon@harvey.cyclic.com> + + * cvs.h: Don't declare valloc. Some systems (e.g. linux) declare + it in stdlib.h in a conflicting way. + +Mon Oct 23 08:41:25 1995 Jim Meyering (meyering@comco.com) + + * commit.c (commit_filesdoneproc): Use getline instead of fgets. + + * logmsg.c (do_editor): Use getline instead of fgets. + (rcsinfo_proc): Likewise. + + * logmsg.c (do_editor): Lose if fclose of temp file output + stream fails. + +Mon Oct 23 11:59:41 1995 Norbert Kiesel <nk@col.sw-ley.de> + + * cvs.h: add valloc declaration + + * server.h: add server_cleanup prototype + + * server.c: remove server_cleanup prototype + + * mkmodules.c (server_cleanup): fix parameter type + + * server.c: encapsulate wait_sig in #ifdef sun (it's only used in + code which is also encapsulated in #ifdef sun) + + * rcscmds.c (RCS_deltag, RCS_lock): add definition of noerr + parameter + + * error.c: include cvs.h instead of config.h, add USE(rcsid) + + * error.c (error): fix parameter type + + * update.c (join_file): encapsulate recent changes from garyo + within #ifdef SERVER_SUPPORT + +Sun Oct 22 13:47:53 1995 J.T. Conklin <jtc@slave.cygnus.com> + + * client.c (update_entries): Fix memory leak; free mode_string and + file_timestamp. + (send_fileproc): Fix memory leak; call freevers_ts before exiting. + + * module.c (do_module): Partially fix memory leak; added + variable so that the address of memory allocated by line2argv + is retained, but comment out the call to free_names. Freeing + the vector at that point loses because some of the elements + may be used later in the function. + (cat_module): fix memory leak. + + * recurse.c (start_recursion): Fix memory leak; free return + value of Name_Repository after it has been used. + +Sat Oct 21 23:24:26 1995 Jim Meyering (meyering@comco.com) + + * client.c (send_modified) [LINES_CRLF_TERMINATED]: Comment text + after #endif. + +Fri Oct 20 14:41:49 1995 Jim Kingdon <kingdon@harvey.cyclic.com> + + * sanity.sh: Add test 87a, to test for bug fixed by garyo in + change below. + +Fri Oct 20 10:59:58 1995 Gary Oberbrunner <garyo@darkstar.avs.com> + + * update.c (join_file): send file back to client even if no + conflicts were detected, by calling Register(). + +Fri Oct 20 10:46:45 1995 Norbert Kiesel <nk@col.sw-ley.de> + + * lock.c: Add prototype for Check_Owner + +Thu Oct 19 16:38:14 1995 Jim Meyering (meyering@comco.com) + + * lock.c (Check_Owner): Declare function `static int'. + +Thu Oct 19 14:58:40 1995 Jim Kingdon <kingdon@harvey.cyclic.com> + + * expand_path.c (expand_variable): Fix typo ('*'->'('). + +Thu Oct 19 14:58:40 1995 Jim Kingdon <kingdon@harvey.cyclic.com> + + * commit.c (commit_filesdoneproc): Check for errors from fopen, + fgets, and fclose. + + * rcscmds.c (RCS_merge): Remove comment about rcsmerge -E. + Hacking CVS was never a very good solution; the situation is fixed + in RCS 5.7, and is documented in ../INSTALL. + +Thu Oct 19 15:06:15 1995 Jim Meyering (meyering@comco.com) + + * filesubr.c (xchmod): Parenthesize arithmetic in operand of | + to placate gcc -Wall. + + * expand_path.c (expand_path): Parenthesize assignments used as + truth values to placate gcc -Wall. + + * commit.c (checkaddfile): Remove dcls of unused variables. + * lock.c (unlock): Remove dcl of unused variable. + +Thu Oct 19 14:58:40 1995 Jim Kingdon <kingdon@harvey.cyclic.com> + + * root.c (Create_Root): If noexec, don't create CVS/Root. + +Wed Oct 18 11:19:40 1995 J.T. Conklin <jtc@slave.cygnus.com> + + * lock.c (unlock): Change order of comparison so that Check_Owner + is called only if other conditions are true. This performance + enhancement was broken when the AFS support was added. + +Wed Oct 18 12:51:33 1995 Karl Fogel <kfogel@floss.cyclic.com> + + * main.c (main): check if argv[0] is "pserver" with else-if, not + if, since we've already asked if it's "kserver". + +Tue Oct 17 18:09:23 1995 Warren Jones <wjones@tc.fluke.com> + and Jim Kingdon <kingdon@harvey.cyclic.com> + + * sanity.sh: Deal with supplying a relative cvs filename, or + with a cvs filename which doesn't have basename "cvs". + +Mon Oct 16 15:58:31 1995 Vince Demarco <vdemarco@bou.shl.com> + + * parseinfo.c (Parse_Info): if the Keyword isn't ALL the current + version doesn't use the expanded variable, It should. + +Mon Oct 16 15:58:31 1995 Gary Oberbrunner <garyo@avs.com> + and Jim Kingdon <kingdon@harvey.cyclic.com> + + * server.c (server_register): Don't pass NULL to printf if tag, + date, or conflict is NULL. + +Thu Oct 12 12:13:42 1995 Karl Fogel <kfogel@floss.cyclic.com> + + * main.c (main): begin to handle "pserver"; support not complete + yet, however. + +Thu Oct 12 02:52:13 1995 Roland McGrath <roland@churchy.gnu.ai.mit.edu> + + * expand_path.c: Don't #include <pwd.h>, since cvs.h already does, + and not all systems' <pwd.h>s are protected from multiple inclusion. + * login.c: Likewise. + +Wed Oct 11 15:23:24 1995 Karl Fogel <kfogel@floss.cyclic.com> + + * login.c (login): handle everything correctly now. + +Wed Oct 11 12:02:48 1995 Norbert Kiesel <nk@col.sw-ley.de> + + * rcs.c (RCS_gettag): support RCS keyword Name + +Tue Oct 10 19:11:16 1995 Karl Fogel <kfogel@floss.cyclic.com> + + * options.h.in (CVS_LOGIN): discuss, but leave commented out. + The "cvs login" command is still under construction; however, the + repository was changing so fast that instead of creating a branch + and dealing with the attendant hair, I'm just developing on the + trunk, making sure that everything is surrounded by "#ifdef + CVS_LOGIN ... #endif" so I don't get in anyone's way. + + * login.c: include cvs.h before checking CVS_LOGIN, so it has a + chance to get defined before we ask if it's defined. + (login): oops, use semi not comma in `for' loop init. + + * Makefile.in (SOURCES, OBJECTS): include login.c, login.o. + + * main.c: added protoype for login(). + Added "login" entry to cmds[]. + (usg): added line about "login". + + * login.c: new file. + +Tue Oct 10 18:33:47 1995 Karl Fogel <kfogel@totoro.cyclic.com> + + * Makefile.in (COMMON_OBJECTS): added error.o. + (OBJECTS): took error.o out; it's in COMMON_OBJECTS now. + +Tue Oct 10 12:02:37 1995 Thorsten Lockert <tholo@sigmasoft.com> + + * cvsbug.sh: Cater to lame versions of sh (4.4BSD ash) by using + ${foo-bar} instead of `if....`. + +Tue Oct 10 12:02:37 1995 Jim Kingdon <kingdon@harvey.cyclic.com> + + * remove.c (remove_fileproc): If noexec, don't remove file. Check + for error when removing file. + +Sun Oct 8 12:32:15 1995 Peter Wemm <peter@haywire.DIALix.COM> + + * run.c: detect/use POSIX/BSD style reliable signals for critical + section masking etc. Helps prevent stray locks on interruption. + +Sat Oct 7 23:26:54 1995 Norbert Kiesel <nk@col.sw-ley.de> + + * admin.c (admin): If group CVS_ADMIN_GROUP exists, allow only + users in that group to use "cvs admin". + * options.h.in: Default CVS_ADMIN_GROUP to "cvsadmin". + +Sat Oct 7 23:05:24 1995 Norbert Kiesel <nk@col.sw-ley.de> + + * add.c, checkout.c, commit.c, cvs.h, filesubr.c, import.c, + lock.c, main.c, modules.c, options.h.in: New variable cvsumask + which is used to set mode of files in repository (regardless of + umask in effect when cvs is run). + +Sat Oct 7 22:40:17 1995 Stephen Bailey <sjbailey@sand.npl.washington.edu> + + * lock.c: Include AFSCVS ifdefs to deal with AFS's lack of + correspondance between userid's from stat and from geteuid. + +Sat Oct 7 22:28:49 1995 Scott Carson <sdc@TracerTech.COM> + + * add.c (add): Pass -ko, not -k -ko, to set keyword expansion options. + + * admin.c (admin): Don't skip first argument when sending to server. + +Fri Oct 6 21:45:03 1995 Jim Kingdon <kingdon@harvey.cyclic.com> + + * version.c: Version 1.6.1. + +Fri Oct 6 21:31:28 1995 Jeff Johnson <jbj@brewster.jbj.org> + + * cvs.h, admin.c, client.c, commit.c, log.c, modules.c, + parseinfo.c, patch.c, recurse.c, rtag.c, status.c, tag.c: + Prototype when dealing in pointers to functions. + +Fri Oct 6 21:07:22 1995 Mark H. Wilkinson <mhw@minster.york.ac.uk> + + * cvsrc.c (read_cvsrc): fix look up of command names in cvsrc file + to use full name from command table rather than possible nickname + in argv. Fixes errors with things like `cvs di' when cvsrc has + `diff -u5' in it. + +Thu Aug 3 01:03:52 1995 Vince DeMarco <vdemarco@bou.shl.com> + + * parseinfo.c (Parse_Info): Add code to call expand_path function + instead of using built in code. + + * wrapper.c (wrap_add): Add code to call expand_path function to + expand all built in variables. + + * expand_path.c (New file): expand things that look like + environmental variables (only expand local CVS environmental + variables) and user names like ~/. + * cvs.h: Declare expand_path. + + * Makefile.in (SOURCES, OBJECTS): Added expand_path.c, + expand_path.o. + +Fri Oct 6 14:03:09 1995 Jim Kingdon <kingdon@harvey.cyclic.com> + + * ignore.c (ign_setup): Don't try to look for a file in CVSroot if + client. (The recent tightening of the error checking detects this). + + * commit.c (checkaddfile): Don't try to pass options if it is "". + +Thu Oct 5 18:04:46 1995 Karl Fogel <kfogel@totoro.cyclic.com> + + * sanity.sh: unset CVSREAD, since it causes the script to bomb. + +Thu Oct 5 18:29:17 1995 Jim Kingdon <kingdon@harvey.cyclic.com> + + * remove.c, add.c, commit.c, cvs.h: Remove CVSEXT_OPT stuff; it + has been broken for ages and the options are already stored in the + Entries file. + +Thu Oct 5 18:20:13 1995 Norbert Kiesel <nk@col.sw-ley.de> + + * commit.c (checkaddfile): New argument options; pass it to RCS. + (commit_fileproc): Pass it. + +Tue Oct 3 09:26:00 1995 Karl Fogel <kfogel@totoro.cyclic.com> + + * version.c: upped to 1.6. + +Mon Oct 2 18:10:35 1995 Larry Jones <larry.jones@sdrc.com> + + * server.c: if HAVE_SYS_BSDTYPES_H, include <sys/bsdtypes.h>. + +Mon Oct 2 10:34:53 1995 Karl Fogel <kfogel@totoro.cyclic.com> + + * version.c: Upped version to 1.5.95. + +Mon Oct 2 15:16:47 1995 Norbert Kiesel <nk@col.sw-ley.de> + + * tag.c, rtag.c: pass "mov" instead of "add" if tag will be moved + (i.e. invoked with -F) + +Sun Oct 1 18:36:34 1995 Karl Fogel <kfogel@totoro.cyclic.com> + + * version.c: upped to 1.5.94. + + * server.c: reverted earlier ISC change (of Sep. 28). + + * version.c: upped to 1.5.93, for Peter Wemm's new SVR4 patch. + +Sun Oct 1 14:51:59 1995 Harlan Stenn <Harlan.Stenn@pfcs.com> + + * main.c: don't #include <pwd.h>; cvs.h does that already. + +Fri Sep 29 15:21:35 1995 Karl Fogel <kfogel@floss.cyclic.com> + + * version.c: upped to 1.5.91 for another pre-1.6 release. + +Fri Sep 29 14:41:14 1995 <bmeier@rzu.unizh.ch> + + * root.c: start rcsid[] with "CVSid". + +Fri Sep 29 13:22:44 1995 Jim Blandy <jimb@totoro.cyclic.com> + + * diff.c (diff): Doc fix. + +Fri Sep 29 14:32:36 1995 Norbert Kiesel <nk@col.sw-ley.de> + + * repos.c (Short_Repository): chop superfluous "/". + + * tag.c (pretag_proc): correct user-visible string. + + * rtag.c (pretag_proc): correct user-visible string. + +Fri Sep 29 13:45:36 1995 Karl Fogel <kfogel@floss.cyclic.com> + + * cvs.h (USE): if __GNUC__ != 2, expand to a dummy var instead of + nothing. + +Thu Sep 28 13:37:05 1995 Larry Jones <larry.jones@sdrc.com> + + * server.c: ifdef ISC, include <sys/bsdtypes.h>. + +Fri Sep 29 07:54:22 1995 Mike Sutton <mws115@llcoolj.dayton.saic.com> + + * filesubr.c (last_component): Don't use ANSI style declaration. + +Wed Sep 27 15:24:00 1995 Del <del@matra.com.au> + + * tag.c, rtag.c: Pass a few extra options to the script + named in taginfo (del/add, and revision number). + + * tag.c: Support a -r option (at long last). Also needs + a -f option to tag the head if there is no matching -r tag. + +Tue Sep 26 11:41:08 1995 Karl Fogel <kfogel@totoro.cyclic.com> + + * version.c: Upped version to 1.5.89 for test release preceding + 1.6. + +Wed Sep 20 15:32:49 1995 Jim Kingdon <kingdon@harvey.cyclic.com> + + * ignore.c (ign_add_file): Check for errors from fopen and fclose. + +Tue Sep 19 18:02:16 1995 Jim Blandy <jimb@totoro.cyclic.com> + + * Makefile.in (DISTFILES): Remove sanity.el from this list; the + file has been deleted. + +Thu Sep 14 14:17:52 1995 Peter Wemm <peter@haywire.dialix.com> + + * import.c: Recover from being unable to open the user file. + + * update.c (join_file): Print a message in the case where the file + was added. + + * mkmodules.c: Deal with .db as well as .pag/.dir (for use with + BSD 4.4 and real dbm support). + +Mon Sep 11 15:44:13 1995 Jim Kingdon <kingdon@harvey.cyclic.com> + + * release.c (release): Revise comment regarding why and how we + skip argv[0]. + +Mon Sep 11 10:03:59 1995 Karl Fogel <kfogel@floss.cyclic.com> + + * release.c (release): use return value of pclose to determine + success of update. + +Mon Sep 11 09:56:33 1995 Jim Kingdon <kingdon@harvey.cyclic.com> + + * release.c (release_delete): Fix comment. + +Sun Sep 10 18:48:35 1995 Karl Fogel <kfogel@floss.cyclic.com> + + * release.c (release): made work with client/server. + Don't ask if <arg> is mentioned in `modules'. + +Fri Sep 8 13:25:55 1995 Jim Kingdon <kingdon@harvey.cyclic.com> + + * sanity.sh: When committing a removal, send stdout to LOGFILE; + this is no longer a silent operation. + + * sanity.sh: Remove OUTPUT variable; it is unused. + + * client.c: Add comment regarding deleting temp file. + * main.c: Add comment regarding getopt REQUIRE_ORDER. + +Thu Sep 7 20:24:46 1995 Karl Fogel <kfogel@floss.cyclic.com> + + * main.c (main): use getopt_long(), accept "--help" and + "--version". + Don't assume EOF is -1. + +Thu Sep 7 19:18:00 1995 Jim Blandy <jimb@cyclic.com> + + * cvs.h (unlink_file_dir): Add prototype for this. + +Thu Sep 7 14:38:06 1995 Karl Fogel <kfogel@floss.cyclic.com> + + * ALL FILES: add semicolon, as indicated below. + + * cvs.h (USE): don't provide semicolon in the expansion of the USE + macro; we'd rather the callers provided it themselves because that + way etags doesn't get fooled. + +Mon Sep 4 23:30:41 1995 Magnus Hyllander <mhy@os.se> + + * checkout.c: cvs export now takes -k option and does not default + to -kv. + * checkout.c, cvs.h, modules.c: Modules file now takes -e option + for cvs export. + +Mon Sep 4 23:30:41 1995 Kirby Koster <koster@sctc.com> + + * commit.c: When committing a removal, print a message saying what + we are doing. + +Wed Aug 2 10:06:51 1995 Vince DeMarco <vdemarco@bou.shl.com> + + * server.c: fix compiler warnings (on NeXT) (declare functions as + static inline instead of just static) functions: get_buffer_date, + buf_append_char, and buf_append_data + +Mon Sep 4 22:31:28 1995 Jim Kingdon <kingdon@harvey.cyclic.com> + + * client.c (update_entries), import.c (expand_at_signs): Check for + errors from fread and putc. + +Fri Sep 1 00:03:17 1995 Jim Kingdon <kingdon@harvey.cyclic.com> + + * sanity.sh: Fix TODO item pathname. + + * sanity.el: Removed. It was out of date, didn't do much, and I + doubt anyone was using it. + + * no_diff.c (No_Difference): Don't change the modes of the files. + +Thu Aug 31 13:14:34 1995 Jim Kingdon <kingdon@harvey.cyclic.com> + + * version.c: Change version to 1.5.1. + + * client.c (start_rsh_server): Don't pass -d to "cvs server" + invocation via rsh (restore change which was lost when NT stuff + was merged in). + * sanity.sh: Add TODO item suggesting test for bug which this fixes. + +Wed Aug 30 12:36:37 1995 Jim Blandy <jimb@totoro.cyclic.com> + + * sanity.sh (basic1): Make sure first-dir is deleted before + running this set of tests. + + * subr.c: Extract file twiddling functions to a different file, + because we want to use different versions of many of these + routines under Windows NT. + (copy_file, isdir, islink, isfile, isreadable, iswritable, + open_file, make_directory, make_directories, xchmod, + rename_file, link_file, unlink_file, xcmp, tmpnam, + unlink_file_dir, deep_remove_dir): Moved to... + * filesubr.c: ...this file, which is new. + * Makefile.in (SOURCES): Mention filesubr.c. + (COMMON_OBJECTS): Mention filesubr.o. + + * subr.c: Extract process execution guts to a different file, + because we want to replace these routines entirely under + Windows NT. + (VA_START, va_alist, va_dcl): Move this stuff... + (run_add_arg, run_init_prog): and these declarations... + (run_prog, run_argv, run_argc, run_argc_allocated): and these + variables... + (run_setup, run_arg, run_args, run_add_arg, run_init_prog, + run_exec, run_print, Popen): and these functions... + * run.c: To this file, which is new. + * Makefile.in (SOURCES): Mention run.c. + (COMMON_OBJECTS): Mention run.o. + + * status.c (status): Call ign_setup, if client_active. Otherwise, + we don't end up ignoring CVS directories and such. + + * server.c (mkdir_p, dirswitch): Use CVS_MKDIR instead of mkdir. + + * repos.c (Name_Repository): Use the isabsolute function instead of + checking the first character of the path. + * root.c (Name_Root): Same. + + * release.c (release): Use fncmp instead of strcmp to compare + filenames. + + * rcs.c (RCS_parse, RCS_parsercsfile) [LINES_CRLF_TERMINATED]: + Abort, because we have strong reason to believe this code is + wrong. + + * patch.c (patch): Register signal handlers iff the signal name is + #defined. + + * no_diff.c (No_Difference): Don't try to include server_active in + trace message unless SERVER_SUPPORT is #defined. + + * modules.c (do_module): Use CVS_MKDIR instead of mkdir. + + * mkmodules.c (main): Call last_component instead of writing it out. + + * main.c (main): Call last_component instead of writing it out. + Break up the long copyright string into several strings; Microsoft + Visual C++ can't handle a line that long. Feh. + Use fncmp instead of strcmp to compare filenames. + Register signal handlers iff the signal name is #defined. + + * lock.c (readers_exist): Don't check return value of closedir. + Most of the rest of the code doesn't, and some systems don't + provide a return value anyway. + (set_lock): Use CVS_MKDIR instead of mkdir. + + * import.c (import): Use the isabsolute function instead of + checking the first character of the path. + Try to delete the temporary file again after we close it, so it'll + get deleted on systems that don't let you delete files that are + open. + (add_rev): Instead of making a hard link to the working file and + checking in the revision with ci -r, use ci -u and restore the + permission bits. + (comtable): Include lines from SYSTEM_COMMENT_TABLE, if it is + #defined. + (add_rcs_file) [LINES_CRLF_TERMINATED]: Abort, because we have + strong reason to believe this code is wrong. + (import_descend_dir): Use CVS_MKDIR instead of mkdir. + + * history.c (read_hrecs): Open the file with OPEN_BINARY. + + * find_names.c (add_entries_proc, fsortcmp): Add prototypes. + * entries.c (write_ent_proc): Add prototype. + * hash.c (walklist): Add prototype for PROC argument. + (sortlist): Add prototype for COMP argument. + (printnode): Add a prototype, and make it static. + + * cvs.h (wrap_add_file, wrap_add): Add extern decls for these; + they're used in import.c and update.c. + * wrapper.c (wrap_add_file, wrap_add): Remove them from here. + + * cvs.h (RUN_NORMAL, RUN_COMBINED, RUN_REALLY, RUN_STDOUT_APPEND, + RUN_STDERR_APPEND, RUN_SIGNIGNORE, RUN_TTY, run_arg, run_print, + run_setup, run_args, run_exec, Popen, piped_child, close_on_exec, + filter_stream_through_program, waitpid): Move all these + declarations and definitions to the same section. + + * cvs.h (error_set_cleanup): Fix prototype. + + * cvs.h (isabsolute, last_component): New extern decls. + + * cvs.h (link_file): Function is deleted; remove extern decl. + + * cvs.h (DEATH_STATE, DEATH_SUPPORT): Move #definitions of these + above the point where we #include rcs.h, since rcs.h tests them + (or DEATH_SUPPORT, at least). + + * cvs.h (DEVNULL): #define this iff it isn't already #defined. + config.h may want to override it. + + * cvs.h (SERVER_SUPPORT, CLIENT_SUPPORT): Don't #define these + here; let config.h do that. On some systems, we don't have any + server support. + + * cvs.h: Don't #include <io.h> or <direct.h>; we take care of + those in lib/system.h. + + * commit.c (commit): Open logfile with the OPEN_BINARY flag. + (precommit_proc): Use the isabsolute function, instead of + comparing the first character with /. + (remove_file, checkaddfile): Use CVS_MKDIR instead of mkdir. + + * client.c (send_repository): Use larger line buffers. + + * client.c [LINES_CRLF_TERMINATED] (update_entries): If we've just + received a gzipped file, copy it over, converting LF to CRLF, + instead of just renaming it into place. + [LINES_CRLF_TERMINATED] (send_modified): Convert file to LF format + before sending with gzip. + (send_modified): Don't be disturbed if we get fewer than + sb.st_size characters when we read. The read function may be + collapsing CRLF to LF for us. + + * client.c: Add forward declarations for all the cvs command + functions we call. + + * client.c: Add forward static declarations for all the + handle_mumble functions. + + On some systems, RSH converts LF to CRLF; this screws us up. + * client.c (rsh_pid): Declare this iff RSH_NOT_TRANSPARENT is not + #defined. + (get_responses_and_close): Use SHUTDOWN_SERVER if it is #defined. + Only wait for rsh process to exit if RSH_NOT_TRANSPARENT is not + #defined. + (start_rsh_server): Declare and define only if + RSH_NOT_TRANSPARENT is not #defined. Use piped_child, instead of + writing all that out. + (start_server): Only try to call start_rsh_server if + RSH_NOT_TRANSPARENT is not #defined. Use START_SERVER if it is + #defined. Convert file descriptors to stdio file pointers using + the FOPEN_BINARY_WRITE and FOPEN_BINARY_READ strings. + + * client.h (rsh_pid): Don't declare this; it's never used elsewhere. + (supported_request): Add external declaration for this; + it's used in checkout.c. + + Move process-running functions to run.c; we need to totally + replace these on other systems, like Windows NT. + * client.c (close_on_exec, filter_stream_through_program): Moved + to run.c. + * run.c (close_on_exec, filter_stream_through_program): Here they + are. + + * add.c (add_directory): Use CVS_MKDIR instead of straight mkdir. + * checkout.c (checkout, build_dirs_and_chdir): Same. + (checkout_proc): Use fncmp instead of strcmp. + * client.c (call_in_directory): Use CVS_MKDIR instead of straight + mkdir. + + * client.c (handle_checksum): Cast return value of strtol. + +Wed Aug 30 10:35:46 1995 Stefan Monnier <stefan.monnier@epfl.ch> + + * main.c (main): Allow -d to override CVSROOT_ENV. + +Thu Aug 24 18:57:49 1995 Jim Kingdon <kingdon@harvey.cyclic.com> + + * cvs.h, rcscmds.c (RCS_unlock, RCS_deltag, RCS_lock): Add extra + parameter for whether to direct stderr to DEVNULL. + * checkin.c, tag.c, rtag.c, import.c, commit.c: Pass extra + argument. 1 if stderr had been directed to DEVNULL before + rcscmds.c was in use, 0 if it was RUN_TTY. + + * cvs.h: Add comment regarding attic. + +Tue Aug 22 10:09:29 1995 Alexander Dupuy <dupuy@smarts.com> + + * rcs.c (whitespace): Cast to unsigned char in case char is signed + and value is negative. + +Tue Aug 22 10:09:29 1995 Kirby Koster <koster@sctc.com> + and Jim Kingdon <kingdon@harvey.cyclic.com> + + * update.c (join_file): If vers->vn_user is NULL, just return. + +Tue Aug 22 10:09:29 1995 Jim Kingdon <kingdon@harvey.cyclic.com> + + * server.c, client.c: Add comments about modes and umasks. + +Mon Aug 21 12:54:14 1995 Rick Sladkey <jrs@world.std.com> + + * update.c (update_filesdone_proc): If pipeout, don't try to + create CVS/Root. + +Mon Aug 21 12:54:14 1995 Jim Kingdon <kingdon@harvey.cyclic.com> + + * client.c (start_rsh_server): Don't pass -d to "cvs server" + invocation via rsh. + + * server.c (serve_root): Report errors via pending_error_text. + (serve_valid_requests): Check for pending errors. + +Sun Aug 20 00:59:46 1995 Jim Kingdon <kingdon@harvey.cyclic.com> + + * options.h.in: Document usage of DIFF in update.c + * update.c: Use DIFF -c, not DIFF -u. The small improvement in + diff size is not worth the hassle in terms of everyone having to + make sure that DIFF is GNU diff (IMHO). + +Sat Aug 19 22:05:46 1995 Jim Blandy <jimb@totoro.cyclic.com> + + * recurse.c (start_recursion): Doc fix. + + * server.c (do_cvs_command): Clear error_use_protocol in the + child. + (server): Set error_use_protocol. + +Sun Aug 13 15:33:37 1995 Jim Kingdon <kingdon@harvey.cyclic.com> + + * server.c (do_cvs_command): Don't select on exceptions. + +Fri Aug 4 00:13:47 1995 Jim Meyering (meyering@comco.com) + + * Makefile.in (LDFLAGS): Set to @LDFLAGS@. + (options.h): Depend on ../config.status and options.h.in. + Add rule to build it from dependents. + + * add.c: Include save-cwd.h. + (add_directory): Use save_cwd and restore_cwd instead of + explicit getwd then chdir. + * import.c (import_descend_dir): Likewise. + * modules.c (do_module): Likewise. + + * recurse.c (save_cwd, restore_cwd, free_cwd): Remove functions. + New versions have been broken out into save-cwd.c. + (do_dir_proc): Adapt to handle status code returned by new versions + of save_cwd and restore_cwd -- and one fewer argument to restore_cwd. + (unroll_files_proc): Likewise. + + * wrapper.c (wrap_name_has): Add default: abort () to switch + statement to avoid warning from gcc -Wall. + (wrap_matching_entry): Remove dcl of unused TEMP. + (wrap_tocvs_process_file): Remove dcl of unused ERR. + (wrap_fromcvs_process_file): Likewise. + + * cvs.h: Remove prototype for error. Instead, include error.h. + Also, remove trailing white space. + +Thu Aug 3 10:12:20 1995 Jim Meyering (meyering@comco.com) + + * import.c (import_descend_dir): Don't print probably-bogus CWD + in error messages saying `cannot get working directory'. + +Sun Jul 30 20:52:04 1995 James Kingdon <kingdon@harvey.cyclic.com> + + * parseinfo.c (Parse_Info): Revise comments and indentation. + +Sun Jul 30 15:30:16 1995 Vince DeMarco <vdemarco@bou.shl.com> + + * history.c: put ifdef SERVER_SUPPORT around tracing code incase + the client/server code is not compiled into the program. + +Sat Jul 29 16:59:49 1995 James Kingdon <kingdon@harvey.cyclic.com> + + * subr.c (deep_remove_dir): Use struct dirent, not struct direct. + +Sat Jul 29 18:32:06 1995 Vince DeMarco <vdemarco@bou.shl.com> + + * add.c: Check wrap_name_has. + + * diff.c, checkin.c, import.c: have code call unlink_file_dir in + the appropriate places instead of just calling unlink_file. + + * checkin.c: Remove one unlink call. + + * import.c (comtable): Add .m .psw .pswm. + + * import.c (add_rcs_file): Remove tocvsPath before returning. + + * subr.c (unlink_file_dir): Add new function. unlinks the file if + it is a file. or will do a recursive delete if the path is + actually a directory. + (deep_remove_dir): New function, helps unlink_file_dir. + + * mkmodules.c: Added CVSROOTADM_WRAPPER (cvswrappers file) to the + checkout file list. + +Fri Jul 28 16:27:56 1995 James Kingdon <kingdon@harvey.cyclic.com> + + * checkout.c (safe_location): Use PATH_MAX not MAXPATHLEN. + +Fri Jul 28 19:37:03 1995 Paul Eggert <eggert@twinsun.com> + + * log.c (cvslog, log_fileproc): Pass all options (except -l) + to rlog as-is, so that users can put spaces in options, + can specify multiple -d options, etc. + (ac, av): New variables. + (log_option_with_arg, options): Remove. + + (log_fileproc): Don't prepend `/' to file name if update_dir is empty. + +Tue Jul 25 00:52:26 1995 James Kingdon <kingdon@harvey.cyclic.com> + + * checkout.c (safe_location): Don't use PROTO in function definition. + +Mon Jul 24 18:32:06 1995 Vince DeMarco <vdemarco@bou.shl.com> + + * checkout.c (safe_location): fix a compiler warning. (Declare + safe_location). Changed code in safe_location to call getwd + instead of getcwd. getwd is declared in the ../lib directory and + used exclusively thoughout the code. (this helps portability on + non POSIX systems). + + * wrapper.c: updated Andrew Athan's email address. + + * main.c: fix an ifdef so the code will compile. syntax error in + the ifdef for CVS_NOADMIN. + +Mon Jul 24 13:25:00 1995 Del <del@babel.dialix.oz.au> + + * checkout.c: New procedure safe_location. + Ensures that you don't check out into the repository + itself. + + * tag.c, rtag.c, cvs.h, mkmodules.c: Added a "taginfo" file in + CVSROOT to perform pre-tag checks. + + * main.c, options.h.in: Added a compile time option to + disable the admin command. + +Fri Jul 21 17:07:42 1995 James Kingdon <kingdon@harvey.cyclic.com> + + * update.c, status.c, patch.c, checkout.c, import.c, release.c, + rtag.c, tag.c: Now -q and -Q options just print an error message + telling you to use global -q and -Q options. The non-global + options were a mess because some commands accepted them and some + did not, and they were redundant with -q and -Q global options. + + * rcs.c, cvs.h, commit.c, log.c, find_names.c: Remove CVS.dea + stuff. It is slower than the alternatives and I don't think + anyone ever actually used it. + +Fri Jul 21 10:35:10 1995 Vince DeMarco <vdemarco@bou.shl.com> + + * Makefile.in (SOURCES, OBJECTS): Add wrapper.c, wrapper.o. + * add.c, admin.c, checkout.c, commit.c, diff.c, import.c, log.c, + remove.c, status.c: Call wrap_setup at start of commands. + * add.c (add): Check for wrapper, as well as directory, in repository. + * checkin.c: Add tocvsPath variable and associated handling. + * cvs.h: Add wrapper declarations. + * diff.c: Add tocvsPath variable and associated handling. + * import.c: Add -W option, CVSDOTWRAPPER handling. + (import_descend): check wrap_name_has. + (update_rcs_file, add_rev, add_rcs_file): add tocvsPath + variable and associated handling. + * no_diff.c: Add tocvsPath variable and associated handling. + * recurse.c (start_recursion): Check wrap_name_has. + * update.c: Copy, don't merge, copy-by-merge files. Attempt to + use -j on a copy-by-merge file generates a warning and no further + action. + * update.c: Add CVSDOTWRAPPER handling. + * wrapper.c: Added. + +Fri Jul 21 00:20:52 1995 James Kingdon <kingdon@harvey.cyclic.com> + + * client.c: Revert David Lamkin patch, except for the bits about + removing temp_filename and the .rej file. + * sanity.sh (errmsg1): Test for the underlying bug which Lamkin + kludged around. + * client.c (call_in_directory): Set short_pathname to include the + filename, not just the directory. Improve comments regarding what + is passed to FUNC. + +Thu Jul 20 17:51:54 1995 David Lamkin <drl@net-tel.co.uk> + + * client.c (short_pathname): Fixes the fetching of the whole file + after a patch to bring it up to date has failed: + - failed_patches[] now holds short path to file that failed + - patch temp files are unlinked where the patch is done + +Thu Jul 20 12:37:10 1995 James Kingdon <kingdon@harvey.cyclic.com> + + * cvs.h: Declare error_set_cleanup + * main.c: Call it. + (error_cleanup): New function. + +Thu Jul 20 12:17:16 1995 Mark H. Wilkinson <mhw@minster.york.ac.uk> + + * add.c, admin.c, checkin.c, checkout.c, classify.c, client.c, + client.h, commit.c, create_adm.c, cvs.h, diff.c, entries.c, + history.c, import.c, log.c, main.c, modules.c, no_diff.c, patch.c, + release.c, remove.c, repos.c, rtag.c, server.c, server.h, + status.c, subr.c, tag.c, update.c, vers_ts.c, version.c: Put + client code inside #ifdef CLIENT_SUPPORT, server code inside + #ifdef SERVER_SUPPORT. When reporting version, report whether + client and/or server are compiled in. + +Wed Jul 19 18:00:00 1995 Jim Blandy <jimb@cyclic.com> + + * subr.c (copy_file): Declare local var n to be an int, + not a size_t. size_t is unsigned, and the return values + of read and write are definitely not unsigned. + + * cvs.h [HAVE_IO_H]: #include <io.h>. + [HAVE_DIRECT_H]: #include <direct.h>. + +Fri Jul 14 22:28:46 1995 Jim Blandy <jimb@totoro.cyclic.com> + + * server.c (dirswitch, serve_static_directory, serve_sticky, + serve_lost, server_write_entries, serve_checkin_prog, + serve_update_prog): Include more information in error messages. + (Thanks, DJM.) + + * cvsbug.sh: Use /usr/sbin/sendmail, unless it doesn't + exist, in which case use /usr/lib/sendmail. (Thanks, DJM.) + + * server.c (server, server_cleanup): Use "/tmp" instead of + "/usr/tmp" when the TMPDIR environment variable isn't set. This + is what the rest of the code uses. + +Thu Jul 13 11:03:17 1995 Jim Meyering (meyering@comco.com) + + * recurse.c (free_cwd): New function. + (save_cwd, restore_cwd): Use it instead of simply freeing any + string. The function also closes any open file descriptor. + + * import.c (comtable): Now static. + (comtable): Put braces around each element of initializer. + + * cvs.h: Add prototype for xgetwd. + * recurse.c (save_cwd, restore_cwd): New functions to encapsulate + run-time solution to secure-SunOS vs. fchown problem. + (do_dir_proc, unroll_files_proc): Use new functions instead of + open-coded fchdir/chdir calls with cpp directives. + + * sanity.sh: Change out of TESTDIR before removing it. + Some versions of rm fail when asked to delete the current directory. + +Wed Jul 12 22:35:04 1995 Jim Meyering (meyering@comco.com) + + * client.c (get_short_pathname): Add const qualifier to parameter dcl. + (copy_a_file): Remove set-but-not-used variable, LEN. + (handle_clear_static_directory): Likewise: SHORT_PATHNAME. + (set_sticky): Likewise: LEN. + (handle_set_sticky): Likewise: SHORT_PATHNAME. + (handle_clear_sticky): Likewise: SHORT_PATHNAME. + (start_rsh_server): Convert perl-style `cond || stmt' to more + conventional C-style `if (cond) stmt.' Sheesh. + Remove dcl of unused file-static, SEND_CONTENTS. + + * history.c: Remove dcls of set-but-not-used file-statics, + HISTSIZE, HISTDATA. + (read_hrecs): Don't set them. + + * import.c (add_rev): Remove dcl of set-but-not-used local, RETCODE. + + * repos.c (Name_Repository): Remove dcl of set-but-not-used local, + HAS_CVSADM. + + * cvsrc.c (read_cvsrc): Parenthesize assignment used as truth value. + +Tue Jul 11 16:49:41 1995 J.T. Conklin <jtc@rtl.cygnus.com> + + * hash.h (struct entnode, Entnode): moved from here... + * cvs.h: to here. + +Wed Jul 12 19:45:24 1995 Dominik Westner (dominik@gowest.ppp.informatik.uni-muenchen.de) + + * client.c (server_user): new var. + (parse_cvsroot): set above if repo is "user@host:/dir". + (start_rsh_server): if server_user set, then use it. + +Wed Jul 12 10:53:36 1995 Karl Fogel <kfogel@floss.cyclic.com> + + * sanity.sh: remove the TESTDIR after done. + + * cvsbug.sh (GNATS_ADDR): now bug-cvs@prep.ai.mit.edu again. + +Tue Jul 11 15:53:08 1995 Greg A. Woods <woods@most.weird.com> + + * options.h.in: depend on configure for grep and diff, now that + changes to configure.in are applied. + +Tue Jul 11 14:32:14 1995 Michael Shields <shields@tembel.org> + + * Makefile.in (LDFLAGS): Pick up from configure. + +Tue Jul 11 14:20:00 1995 Loren James Rittle <rittle@supra.comm.mot.com> + + * import.c (add_rev), commit.c (remove_file, ci_new_rev), + checkin.c (Checkin), subr.c (make_message_rcslegal), cvs.h: + Always perform sanity check and fix-up on messages to be passed + directly to RCS via the '-m' switch. RCS 5.7 requires that a + non-total-whitespace, non-null message be provided or it will + abort with an error. CVS is not setup to handle any returned + error from 'ci' gracefully and, thus, the repository entered a + trashed state. + + * sanity.sh: Add regression tests for new code and interactions + with RCS 5.7. + +Sun Jul 9 19:03:00 1995 Greg A. Woods <woods@most.weird.com> + + * .cvsignore: added new backup file + + * options.h.in: our new configure.in finds the right diff and + grep paths now.... + + * subr.c: quote the string in run_print() for visibility + - indent a comment + - Jun Hamano's xchmod() patch to prevent writable files + (from previous local changes) + + * logmsg.c: fix a NULL pointer de-reference + - clean up some string handling code... + (from previous local changes) + + * parseinfo.c: add hack to expand $CVSROOT in an *info file. + - document "ALL" and "DEFAULT" in opening comment for Parse_Info() + - fix the code to match the comments w.r.t. callbacks for "ALL" + - add a line of trace output... + (from previous local changes) + + * mkmodules.c: add support for comments in CVSROOT/checkoutlist + - add CVSroot used by something other .o, ala main.c + (from previous local changes) + + * main.c, cvs.h: add support for $VISUAL as log msg editor + (from previous local changes) + + * status.c: add support for -q and -Q (from previous local changes) + + +Sun Jul 9 18:44:32 1995 Karl Fogel <kfogel@floss.cyclic.com> + + * log.c: trivial change to test ChangeLog stuff. + +Sat Jul 8 20:33:57 1995 Paul Eggert <eggert@twinsun.com> + + * history.c: (history_write): Don't assume that fopen(..., "a") + lets one interleave writes to the history file from different processes + without interlocking. Use open's O_APPEND option instead. + Throw in an lseek to lessen the race bugs on non-Posix hosts. + * cvs.h, subr.c (Fopen): Remove. + + * log.c (log_fileproc): Pass working file name to rlog, so that + the name is reported correctly. + +Fri Jul 7 18:29:37 1995 Michael Hohmuth <hohmuth@inf.tu-dresden.de> + + * client.c, client.h (client_import_setup): New function. + (client_import_done, client_process_import_file): Add comments + regarding now-redundant code. + * import.c (import): Call client_import_setup. + +Tue Jul 4 09:21:26 1995 Bernd Leibing <bernd.leibing@rz.uni-ulm.de> + + * rcs.c (RCS_parsercsfile_i): Rename error to l_error; SunOS4 /bin/cc + doesn't like a label and function with the same name. + +Sun Jul 2 12:51:33 1995 Fred Appelman <Fred.Appelman@cv.ruu.nl> + + * logmsg.c: Rename strlist to str_list to avoid conflict with + Unixware 2.01. + +Thu Jun 29 17:37:22 1995 Paul Eggert <eggert@twinsun.com> + + * rcs.c (RCS_check_kflag): Allow RCS 5.7's new -kb option. + +Wed Jun 28 09:53:14 1995 James Kingdon <kingdon@harvey.cyclic.com> + + * Makefile.in (HEADERS): Remove options.h.in. + (DISTFILES): Add options.h.in. + Depend on options.h in addition to HEADERS. + +Tue Jun 27 22:37:28 1995 Vince Demarco <vdemarco@bou.shl.com> + + * subr.c: Don't try to do fancy waitstatus stuff for NeXT, + lib/wait.h is sufficient. + +Mon Jun 26 15:17:45 1995 James Kingdon <kingdon@harvey.cyclic.com> + + * Makefile.in (DISTFILES): Remove RCS-patches and convert.sh. + +Fri Jun 23 13:38:28 1995 J.T. Conklin (jtc@rtl.cygnus.com) + + * server.c (dirswitch, serve_co): Use CVSADM macro instead of + literal "CVS". + +Fri Jun 23 00:00:51 1995 James Kingdon <kingdon@harvey.cyclic.com> + + * README-rm-add: Do not talk about patching RCS, that only + confuses people. + * RCS-patches, convert.sh: Removed (likewise). + +Thu Jun 22 10:41:41 1995 James Kingdon <kingdon@harvey.cyclic.com> + + * subr.c: Change -1 to (size_t)-1 when comparing against a size_t. + +Wed Jun 21 16:51:54 1995 nk@ipgate.col.sw-ley.de (Norbert Kiesel) + + * create_adm.c, entries.c, modules.c: Avoid coredumps if + timestamps, tags, etc., are NULL. + +Tue Jun 20 15:52:53 1995 Jim Meyering (meyering@comco.com) + + * checkout.c (checkout): Remove dcl of unused variable. + * client.c (call_in_directory, handle_clear_static_directory, + handle_set_sticky, handle_clear_sticky, send_a_repository, + send_modified, send_dirent_proc): Remove dcls of unused variables. + * server.c (receive_file, serve_modified, server_cleanup): + Remove dcls of unused variables. + * subr.c (copy_file): Remove dcl of unused variable. + * vers_ts.c (time_stamp_server): Remove dcl of unused variable. + +Mon Jun 19 13:49:35 1995 Jim Blandy <jimb@totoro.cyclic.com> + + * sanity.sh: Fix commencement message --- the test suite says + "Ok." when it's done. + +Fri Jun 16 11:23:44 1995 Jim Meyering (meyering@comco.com) + + * entries.c (fgetentent): Parenthesize assignment in if-conditional. + +Thu Jun 15 17:33:28 1995 J.T. Conklin <jtc@rtl.cygnus.com> + + * server.c (get_buffer_data, buf_append_char, buf_append_data): + Don't conditionalize use of "inline". Autoconf takes care of + defining it away on systems that don't grok it. + +Thu Jun 15 13:43:38 1995 Jim Kingdon (kingdon@cyclic.com) + + * options.h.in (DIFF): Default to "diff" not "diff -a" since diff + might not support the -a option. + +Wed Jun 14 11:29:42 1995 J.T. Conklin <jtc@rtl.cygnus.com> + + * import.c (import_descend): Initialize dirlist to NULL. + + * subr.c (copy_file): Fix infinite loop. + + * server.c (serve_directory): fix a memory leak. + + * checkout.c, commit.c, diff.c, history.c, import.c, log.c, + patch.c, release.c, remove.c, rtag.c, status.c, tag.c, update.c: + Use send_arg() to send command line arguments to server. + + * commit.c (fsortcmp), find_names (fsortcmp), hash.c (hashp, + findnode), hash.h (findnode), rcs.c (RCS_addnode, + RCS_check_kflag, RCS_check_tag, RCS_isdead, RCS_parse, + RCS_parsercsfile_i), rcs.h (RCS_addnode, RCS_check_kflag, + RCS_check_tag, RCS_parse): Added const qualifiers as + appropriate. + * rcs.h (RCS_isdead): Added prototype. + + * hash.h (walklist, sortlist): correct function prototypes. + + * ignore.c (ign_setup): don't bother checking to see if file + exists before calling ign_add_file. + +Fri Jun 9 11:24:06 1995 J.T. Conklin <jtc@rtl.cygnus.com> + + * all source files (rcsid): Added const qualifer. + * ignore.c (ign_default): Added const qualifier. + * subr.c (numdots): Added const qualifier to function argument. + * cvs.h (numdots): Added const qualifier to prototype argument. + + * client.c (change_mode): Tied consecutive if statements testing + the same variable together with else if. + + * import.c (import_descend): Build list of subdirectories when + reading directory, and then process the subdirectories in that + list. This change avoids I/O overhead of rereading directory + and reloading ignore list (.cvsignore) for each subdirectory. + +Thu Jun 8 11:54:24 1995 J.T. Conklin <jtc@rtl.cygnus.com> + + * import.c (import_descend): Use 4.4BSD d_type field if it is + present. + + * lock.c (set_lockers_name): Use %lu in format and cast st_uid + field to unsigned long. + + * import.c (import): Use RCS_check_kflag() to check -k options. + (keyword_usage, str2expmode, strn2expmode, expand_names): + Removed. + * rcs.c (RCS_check_kflag): Added keyword_usage array from import.c + for more descriptive error messages. + + * subr.c (run_setup, run_args): Changed variable argument + processing to work on machines that use <varargs.h>. + + * subr.c (copy_file, xcmp): Changed to read the file(s) by blocks + rather than by reading the whole file into a huge buffer. The + claim that this was reasonable because source files tend to be + small does not hold up in real world situations. CVS is used + to manage non-source files, and mallocs of 400K+ buffers (x2 + for xcmp) can easily fail due to lack of available memory or + even memory pool fragmentation. + (block_read): New function, taken from GNU cmp and slightly + modified. + + * subr.c (xcmp): Added const qualifier to function arguments. + * cvs.h (xcmp): Added const qualifer to prototype arguments. + +Wed Jun 7 11:28:31 1995 J.T. Conklin <jtc@rtl.cygnus.com> + + * cvs.h (Popen): Added prototype. + (Fopen, open_file, isreadable, iswritable, isdir, isfile, + islink, make_directory, make_directories, rename_file, + link_file, unlink_file, copy_file): Added const qualifer to + prototype arguments. + * subr.c (Fopen, Popen, open_file, isreadable, iswritable, isdir, + isfile, islink, make_directory, make_directories, rename_file, + link_file, unlink_file, copy_file): Added const qualifier to + function arguments. + + * logmsg.c (logfile_write), recurse.c (do_recursion, addfile): + Don't cast void functions to a void expression. There is at + least one compiler (MPW) that balks at this. + + * rcs.c (keysize, valsize): Change type to size_t. + + * add.c (add_directory): Don't cast umask() argument to int. + + * import.c (add_rcs_file): Changed type of mode to mode_t. + + * rcscmds.c (RCS_merge): New function. + * cvs.h (RCS_merge): Declare. + * update.c (merge_file, join_file): Call RCS_merge instead of + invoking rcsmerge directly. + + * cvs.h: Include <stdlib.h> if HAVE_STDC_HEADERS, otherwise + declared getenv(). + * cvsrc.c, ignore.c, main.c: Removed getenv() declaration. + + * client.c (mode_to_string): Changed to take mode_t instead of + struct statb argument. Simplified implementation, no longer + overallocates storage for returned mode string. + * client.h (mode_to_string): Updated declaration. + * server.c (server_updated): Updated for new calling conventions, + pass st_mode instead of pointer to struct statb. + + * cvs.h (CONST): Removed definition, use of const qualifier is + determined by autoconf. + * history.c, modules.c, parseinfo.c: Use const instead of CONST. + + * add.c, admin.c, checkout.c, commit.c, diff.c, import.c, log.c, + main.c, mkmodules.c, patch.c, recurse.c, remove.c, rtag.c, + server.c, status.c, subr.c, tag.c, update.c: Changed function + arguments "char *argv[]" to "char **argv" to silence lint + warnings about performing arithmetic on arrays. + +Tue Jun 6 18:57:21 1995 Jim Blandy <jimb@totoro.cyclic.com> + + * version.c: Fix up version string, to say that this is Cyclic + CVS. + +Tue Jun 6 15:26:16 1995 J.T. Conklin <jtc@rtl.cygnus.com> + + * subr.c (run_setup, run_args, run_add_arg, xstrdup): Add const + qualifier to format argument. + * cvs.h (run_setup, run_args, xstrdup): Likewise. + + * Makefile.in (SOURCES): Added rcscmds.c. + (OBJECTS): Added rcscmds.o. + + * rcscmds.c: New file, with new functions RCS_settag, RCS_deltag, + RCS_setbranch, RCS_lock, RCS_unlock. + * checkin.c, commit.c, import.c, rtag.c, tag.c: Call above + functions instead of exec'ing rcs commands. + * cvs.h: Declare new functions. + +Mon May 29 21:40:54 1995 J.T. Conklin (jtc@rtl.cygnus.com) + + * recurse.c (start_recursion, do_recursion): Set entries to NULL + after calling Entries_Close(). + +Sat May 27 08:08:18 1995 Jim Meyering (meyering@comco.com) + + * Makefile.in (check): Export RCSBIN only if there exists an + `rcs' executable in ../../rcs/src. Before, tests would fail when + the directory existed but contained no executables. + (distclean): Remove options.h, now that it's generated. + (Makefile): Regenerate only *this* file when Makefile.in is + out of date. Depend on ../config.status. + +Fri May 26 14:34:28 1995 J.T. Conklin <jtc@rtl.cygnus.com> + + * entries.c (Entries_Open): Added missing fclose(). + (Entries_Close): Don't write Entries unless Entries.Log exists. + + * entries.c (Entries_Open): Renamed from ParseEntries; changed to + process Entries Log files left over from previous crashes or + aborted runs. + (Entries_Close): New function, write out Entries file if + neccessary and erase Log file. + (Register): Append changed records to Log file instead of + re-writing file. + (fgetentent): New function, parse one Entry record from a file. + (AddEntryNode): It's no longer an error for two records with the + same name to be added to the list. New records replace older + ones. + * cvs.h (Entries_Open, Entries_Close): Add prototypes. + (CVSADM_ENTLOG): New constant, name of Entries Log file. + * add.c, checkout.c, client.c, find_names.c, recurse.c: Use + Entries_Open()/Entries_Close() instead of ParseEntries()/dellist(). + + * add.c, admin.c, checkout.c, client.c, commit.c, diff.c, + history.c, import.c, log.c, patch.c, release.c, remove.c, + rtag.c, server.c, status.c, tag.c, update.c: Changed + conditionals so that return value of *printf is tested less than + 0 instead of equal to EOF. + +Thu May 25 08:30:12 1995 Jim Kingdon (kingdon@lioth.cygnus.com) + + * subr.c (xmalloc): Never try to malloc zero bytes; if the user + asks for zero bytes, malloc one instead. + +Wed May 24 12:44:25 1995 Ken Raeburn <raeburn@cujo.cygnus.com> + + * subr.c (xmalloc): Don't complain about NULL if zero bytes were + requested. + +Tue May 16 21:49:05 1995 Jim Blandy <jimb@totoro.bio.indiana.edu> + + * subr.c (xmalloc): Never try to malloc zero bytes; if the user + asks for zero bytes, malloc one instead. + +Mon May 15 14:35:11 1995 J.T. Conklin <jtc@rtl.cygnus.com> + + * lock.c (L_LOCK_OWNED): Removed. + + * add.c, checkout.c, client.c, create_adm.c, cvs.h, entries.c, + find_names.c modules.c, recurse.c, release.c, repos.c, update.c: + removed CVS 1.2 compatibility/upgrade code. + +Mon May 8 11:25:07 1995 J.T. Conklin <jtc@rtl.cygnus.com> + + * lock.c (write_lock): Missed one instance where rmdir(tmp) should + have been changed to clear_lock(). + +Wed May 3 11:08:32 1995 J.T. Conklin <jtc@rtl.cygnus.com> + + * create_adm.c, entries.c, import.c, root.c: Changed conditionals + so that return value of *printf is tested less than 0 instead of + equal to EOF --- That's all Standard C requires. + +Wed May 3 18:03:37 1995 Samuel Tardieu <tardieu@emma.enst.fr> + + * rcs.h: removed #ifdef CVS_PRIVATE and #endif because cvs didn't + compile anymore. + +Mon May 1 13:58:53 1995 J.T. Conklin <jtc@rtl.cygnus.com> + + * rcs.c, rcs.h: Implemented lazy parsing of rcs files. + RCS_parsercsfile_i modified to read only the first two records + of rcs files, a new function RCS_reparsercsfile is called only + when additional information (tags, revision numbers, dates, + etc.) is required. + +Mon May 1 12:20:02 1995 Jim Kingdon (kingdon@lioth.cygnus.com) + + * Makefile.in (INCLUDES): Include -I. for options.h. + +Fri Apr 28 16:16:33 1995 Jim Blandy <jimb@totoro.bio.indiana.edu> + + * Makefile.in (SOURCES, HEADERS, DISTFILES): Updated. + (dist-dir): Renamed from dist; changed to work with DISTDIR + variable passed from parent. + + We don't want to include a file the user has to edit in the + distribution. + * options.h: No longer distributed. + * options.h.in: Distribute this instead. + * ../INSTALL, ../README: Installation instructions updated. + + * client.c (start_rsh_server): Send the remote command to rsh as a + single string. + +Fri Apr 28 00:29:49 1995 Noel Cragg <noel@vo.com> + + * commit.c: Added initializer for FORCE_CI + + * sanity.sh: Fix tests added 25 Apr -- they were expecting the + server to make noise, but the CVS_SERVER variable had been + accidentally set with the `-Q' flag. Ran all tests -- both + locally and remotely -- to verify that the change didn't break + anything. + +Thu Apr 27 12:41:52 1995 Jim Kingdon (kingdon@lioth.cygnus.com) + + * Makefile.in: Revise comment regarding check vs. remotecheck. + +Thu Apr 27 12:52:28 1995 Bryan O'Sullivan <bos@cyclic.com> + + * client.c (start_rsh_server): If the CVS_RSH environment variable + is set, use its contents as the name of the program to invoke + instead of `rsh'. + +Thu Apr 27 12:18:38 1995 Noel Cragg <noel@vo.com> + + * checkout.c (checkout): To fix new bug created by Apr 23 change, + re-enabled "expand-module" functionality, because it has the side + effect of setting the checkin/update programs for a directory. To + solve the local/remote checkout problem that prompted this change + in the first place, I performed the next change. + * server.c (expand_proc): Now returns expansions for aliases only. + +Wed Apr 26 12:07:42 1995 J.T. Conklin <jtc@rtl.cygnus.com> + + * rcs.c (getrcskey): Rewritten to process runs of whitespace chars + and rcs @ strings instead of using state variables "white" and + "funky". + +Fri Apr 7 15:49:25 1995 J.T. Conklin <jtc@rtl.cygnus.com> + + * lock.c (unlock): Only call stat if we need to. + +Wed Apr 26 10:48:44 1995 Jim Kingdon (kingdon@lioth.cygnus.com) + + * server.c (new_entries_line): Don't prototype. + +Tue Apr 25 22:19:16 1995 Jim Blandy <jimb@totoro.bio.indiana.edu> + + * sanity.sh: Add new tests to catch bugs in Apr 23 change. + +Tue Apr 25 17:10:55 1995 Roland McGrath <roland@baalperazim.frob.com> + + * create_adm.c (Create_Admin): Use getwd instead of getcwd. + +Sun Apr 23 20:58:32 1995 Noel Cragg <noel@vo.com> + + * checkout.c (checkout): Disabled "expand-module" functionality on + remote checkout, since it makes modules behave like aliases (see + longer note there). This change necessitated the change below. + Also merged the like parts of a conditional. + + * client.c (call_in_directory): Changed the algorithm that created + nested and directories and the "CVS" administration directories + therein. The algoithm wrongly assumed that the name of the + directory that that was to be created and the repository name were + the same, which breaks modules. + + * create_adm.c (Create_Admin), module.c (do_module), server.c + (server_register), subr.c, entries.c: Added fprintfs for trace-mode + debugging. + + * client.c (client_send_expansions): Argument to function didn't + have a type -- added one. + + * server.c (new_entries_line): Arguments to this function are + never used -- reoved them and fixed callers. + +Sat Apr 22 11:17:20 1995 Jim Kingdon (kingdon@lioth.cygnus.com) + + * rcs.c (RCS_parse): If we can't open the file, give an error + message (except for ENOENT in case callers rely on that). + +Wed Apr 19 08:52:37 1995 Jim Kingdon (kingdon@lioth.cygnus.com) + + * client.c (send_repository): Check for CVSADM_ENTSTAT in `dir', not + in `.'. + + * sanity.sh: Add TODO list. Revise some comments. Add tests of + one working directory adding a file and other updating it. + +Sat Apr 8 14:52:55 1995 Jim Blandy <jimb@totoro.bio.indiana.edu> + + * Makefile.in (CFLAGS): Let configure set the default for CFLAGS. + Under GCC, we want -g -O. + +Fri Apr 7 15:49:25 1995 J.T. Conklin <jtc@rtl.cygnus.com> + + * root.c (Name_Root): merge identical adjacent conditionals. + + * create_admin.c (Create_Admin): Rearranged check for CVSADM and + OCVSADM directories so that CVSADM pathname is only built once. + + * update.c (update_dirleave_proc): Removed code to remove CVS + administration directory if command_name == "export" and to + create CVS/Root file if it is not present. Identical code + in update_filesdone_proc() will perform these same actions. + Also removed code that read and verfied CVS/Root. This is + expensive, and if it is necessary should happen in the + general recursion processor rather than in the update + callbacks. + + * lock.c (masterlock): New variable, pathname of master lockdir. + (set_lock): removed lockdir argument, now constructs it itself + and stores it in masterlock. + (clear_lock): new function, removes master lockdir. + (Reader_Lock, write_lock): call clear_lock instead of removing + master lockdir. + (Reader_Lock, write_lock): #ifdef'd out CVSTFL code. + + * main.c (main): register Lock_Cleanup signal handler. + * lock.c (Reader_Lock, write_lock): no longer register + Lock_Cleanup. + + * main.c (main): initialize new array hostname. + * lock.c (Reader_Lock, write_lock): Use global hostname array. + * logmsg.c (logfile_write): Likewise. + + * recurse.c (do_dir_proc, unroll_files_proc): Use open()/fchdir() + instead of getwd()/chdir() on systems that support the fchdir() + system call. + +Fri Apr 7 06:57:20 1995 Jim Kingdon (kingdon@lioth.cygnus.com) + + * server.c: Include the word "server" in error message for memory + exhausted, so the user knows which machine ran out of memory. + + * sanity.sh: For remote, set CVS_SERVER to test the right server, + rather than a random one from the PATH. + + * commit.c [DEATH_STATE]: Pass -f to `ci'. + +Thu Apr 6 13:05:15 1995 Jim Blandy <jimb@totoro.bio.indiana.edu> + + * commit.c (checkaddfile): If we didn't manage to fopen the file, + don't try to fclose it. + + * client.c (handle_m, handle_e): Use fwrite, rather than a loop of + putc's. Sometimes these streams are unbuffered. + +Tue Apr 4 11:33:56 1995 Jim Blandy <jimb@totoro.bio.indiana.edu> + + * (DISTFILES): Include cvsbug.sh, ChangeLog, NOTES, RCS-patches, + README-rm-add, ChangeLog.fsf, sanity.sh, sanity.el, and + .cvsignore. + +Mon Mar 27 08:58:42 1995 Jim Kingdon (kingdon@lioth.cygnus.com) + + * rcs.c (RCS_parsercsfile_i): Accept `dead' state regardless of + DEATH_STATE define. Revise comments regarding DEATH_STATE versus + CVSDEA versus the scheme which uses a patched RCS. + * README-rm-add, RCS-patches: Explain what versions of CVS need + RCS patches. + +Sat Mar 25 18:51:39 1995 Roland McGrath <roland@churchy.gnu.ai.mit.edu> + + * server.c (server_cleanup): Only do the abysmal kludge of waiting + for command and draining the pipe #ifdef sun. The code makes + assumptions not valid on all systems, and is only there to + workaround a SunOS bug. + +Wed Mar 22 21:55:56 1995 Jim Kingdon (kingdon@lioth.cygnus.com) + + * server.c (mkdir_p): Call stat only if we get the EACCES. Faster + and more elegant. + +Tue Jan 31 20:59:19 1995 Ken Raeburn <raeburn@cujo.cygnus.com> + + * server.c: Try to avoid starting the "rm -rf" at cleanup time + until after subprocesses have finished. + (command_fds_to_drain, max_command_fd): New variables. + (do_cvs_command): Set them. + (command_pid_is_dead): New variable. + (wait_sig): New function. + (server_cleanup): If command_pid is nonzero, wait for it to die, + draining output from it in the meantime. If nonzero SIG was + passed, send a signal to the subprocess, to encourage it to die + soon. + + * main.c (usage): Argument is now `const char *const *'. + * cvs.h (usage): Changed prototype. + (USE): Make new variable `const'. + * add.c (add_usage), admin.c (admin_usage), checkout.c + (checkout_usage, export_usage, checkout), commit.c (commit_usage), + diff.c (diff_usage), history.c (history_usg), import.c + (import_usage, keyword_usage), log.c (log_usage), main.c (usg), + patch.c (patch_usage), release.c (release_usage), remove.c + (remove_usage), rtag.c (rtag_usage), server.c (server), status.c + (status_usage), tag.c (tag_usage), update.c (update_usage): Usage + messages are now const arrays of pointers to const char. + + * import.c (comtable): Now const. + * main.c (rcsid): Now static. + (cmd): Now const. + (main): Local variable CM now points to const. + * server.c (outbuf_memory_error): Local var MSG now const. + + * client.c (client_commit_usage): Deleted. + +Sat Dec 31 15:51:55 1994 Jim Kingdon (kingdon@lioth.cygnus.com) + + * logmsg.c (do_editor): Allocate enough space for trailing '\0'. + +Fri Mar 3 11:59:49 1995 Jim Blandy <jimb@totoro.bio.indiana.edu> + + * cvsbug.sh: Call it "Cyclic CVS" now, not "Remote CVS". Call it + version C1.4A, not 1.4A2-remote. Send bugs to cyclic-cvs, not + remote-cvs. + + * classify.c (Classify_File): Put check for dead file inside + "#ifdef DEATH_SUPPORT". + +Thu Feb 23 23:03:43 1995 Jim Blandy <jimb@totoro.bio.indiana.edu> + + * update.c (join_file): Don't pass the -E option to rcsmerge here, + either (see Jan 22 change). + +Mon Feb 13 13:28:46 1995 Jim Blandy <jimb@totoro.bio.indiana.edu> + + * cvsbug.sh: Send bug reports to remote-cvs@cyclic.com, rather + than to the ordinary CVS bug address. This does mean we'll have + to wade through GNATS-style bug reports, sigh. + +Wed Feb 8 06:42:27 1995 Roland McGrath <roland@churchy.gnu.ai.mit.edu> + + * server.c: Don't include <sys/stat.h>; system.h already does, and + 4.3BSD can't take it twice. + + * subr.c [! HAVE_VPRINTF] (run_setup, run_args): Don't use va_dcl + in declaration. Declare the a1..a8 args which are used in the + sprintf call. + * cvs.h [! HAVE_VPRINTF] (run_setup, run_args): Don't prototype + args, to avoid conflicting with the function definitions + themselves. + +Tue Feb 7 20:10:00 1995 Jim Blandy <jimb@totoro.bio.indiana.edu> + + * client.c (update_entries): Pass the patch subprocess the switch + "-b ~", not "-b~"; the latter form seems not to work with patch + version 2.0 and earlier --- it takes the next argv element as the + backup suffix, and thus doesn't notice that the patch file's name + has been specified, thus doesn't find the patch, thus... *aargh* + +Fri Feb 3 20:28:21 1995 Jim Blandy <jimb@totoro.bio.indiana.edu> + + * log.c (log_option_with_arg): New function. + (cvslog): Use it and send_arg to handle the rlog options that take + arguments. The code used to use send_option_string for + everything, which assumes that "-d1995/01/02" is equivalent to + "-d -1 -9 -9 -5 ...". + +Tue Jan 31 15:02:01 1995 Jim Blandy <jimb@floss.life.uiuc.edu> + + * server.c: #include <sys/stat.h> for the new stat call in mkdir_p. + (mkdir_p): Don't try to create the intermediate directory if it + exists already. Some systems return EEXIST, but others return + EACCES, which we can't otherwise distinguish from a real access + problem. + +Sun Jan 22 15:25:45 1995 Jim Blandy <jimb@totoro.bio.indiana.edu> + + * update.c (merge_file): My rcsmerge doesn't accept a -E option, + and it doesn't look too important, so don't pass it. + +Fri Jan 20 14:24:58 1995 Ian Lance Taylor <ian@sanguine.cygnus.com> + + * client.c (do_deferred_progs): Don't try to chdir to toplevel_wd + if it has not been set. + (process_prune_candidates): Likewise. + +Mon Nov 28 09:59:14 1994 Jim Kingdon (kingdon@lioth.cygnus.com) + + * client.c (client_commit): Move guts of function from here... + * commit.c (commit): ...to here. + +Mon Nov 28 15:14:36 1994 Ken Raeburn <raeburn@cujo.cygnus.com> + + * server.c (buf_input_data, buf_send_output): Start cpp directives + in column 1, otherwise Sun 4 pcc complains. + +Mon Nov 28 09:59:14 1994 Jim Kingdon (kingdon@lioth.cygnus.com) + + * client.c (add_prune_candidate): Don't try to prune ".". + +Tue Nov 22 05:27:10 1994 Jim Kingdon (kingdon@lioth.cygnus.com) + + * server.c, client.c: More formatting cleanups. + + * client.h, client.c: New variable client_prune_dirs. + * update.c (update), checkout.c (checkout): Set it. + * client.c (add_prune_candidate, process_prune_candidates): New + functions. + (send_repository, call_in_directory, get_responses_and_close): + Call them. + +Wed Nov 23 01:17:32 1994 Ian Lance Taylor (ian@tweedledumb.cygnus.com) + + * server.c (do_cvs_command): Don't select on STDOUT_FILENO unless + we have something to write. + +Tue Nov 22 05:27:10 1994 Jim Kingdon (kingdon@lioth.cygnus.com) + + * remove.c (remove_fileproc): Only call server_checked_in if we + actually are changing the entries file. + + * server.c (server_write_entries): New function. + (dirswitch, do_cvs_command): Call it. + (serve_entry, serve_updated): Just update in-memory data + structures, don't mess with CVS/Entries file. + +Mon Nov 21 10:15:11 1994 Jim Kingdon (kingdon@lioth.cygnus.com) + + * server.c (server_checked_in): Set scratched_file to NULL after + using it. + + * checkin.c (Checkin): If the file was changed by the checkin, + call server_updated not server_checked_in. + +Sun Nov 20 08:01:51 1994 Jim Kingdon (kingdon@lioth.cygnus.com) + + * client.c (send_repository): Move check for update_dir NULL to + before where we check last_update_dir. Check for "" here too. + + * client.c (send_repository): Use new argument dir. + + * client.c: Pass new argument dir to send_repository and + send_a_repository. + + * server.c, server.h (server_prog): New function. + * modules.c (do_modules): Call it if server_expanding. + * client.c: Support Set-checkin-prog and Set-update-prog responses. + * server.c, client.c: Add Checkin-prog and Update-prog requests. + +Fri Nov 18 14:04:38 1994 Jim Kingdon (kingdon@lioth.cygnus.com) + + * client.c (get_short_pathname, is_cvsroot_level, + call_in_directory): Base whether this is new-style or + old-style based on whether we actually used the Directory request, + not based on whether the pathname is absolute. Rename + directory_supported to use_directory. + * server.c: Rename use_relative_pathnames to use_dir_and_repos. + * client.c (send_a_repository): If update_dir is absolute, don't + use it to try to reconstruct how far we have recursed. + + * server.c, server.h, client.c, client.h, vers_ts.c, update.h: + More cosmetic changes (identation, PARAMS vs. PROTO, eliminate + alloca, etc.) to remote CVS to make it more like the rest of CVS. + + * server.c: Make server_temp_dir just the dir name, not the name + with "%s" at the end. + * server.c, client.c: Add "Max-dotdot" request, and use it to make + extra directories in server_temp_dir if needed. + +Thu Nov 17 09:03:28 1994 Jim Kingdon <kingdon@cygnus.com> + + * client.c: Fix two cases where NULL was used and 0 was meant. + +Mon Nov 14 08:48:41 1994 Jim Kingdon (kingdon@lioth.cygnus.com) + + * server.c (serve_unchanged): Set noexec to 0 when calling Register. + + * update.c (merge_file): Don't call xcmp if noexec. + +Fri Nov 11 13:58:22 1994 Jim Kingdon (kingdon@lioth.cygnus.com) + + * client.c (call_in_directory): Deal with it if reposdirname is + not a subdirectory of toplevel_repos. + +Mon Nov 7 09:12:01 1994 Jim Kingdon (kingdon@lioth.cygnus.com) + + * patch.c: If file is removed and we don't have a tag or date, + just print "current release". + + * classify.c (Classify_File): Treat dead files appropriately. + +Fri Nov 4 07:33:03 1994 Jim Kingdon (kingdon@lioth.cygnus.com) + + * main.c (main) [SERVER_SUPPORT]: Move call to getwd past where we + know whether we are the server or not. Set CurDir to "<remote>" + if we are the server. + + * client.c: Remove #if 0'd function option_with_arg. + Remove #if 0'd code pertaining to the old way of logging the + session. + + * client.c (start_rsh_server): Don't invoke the server with the + -d option. + * server.c (serve_root): Test root for validity, just like main.c + does for non-remote CVS. + * main.c (main): If `cvs server' happens with a colon in the + CVSroot, just handle it normally; don't make it an error. + +Wed Nov 2 11:09:38 1994 Jim Kingdon (kingdon@lioth.cygnus.com) + + * client.c (send_dirent_proc): If dir does not exist, just return + R_SKIP_ALL. + + * server.c, client.c: Add Directory request and support for + local relative pathnames (along with the repository absolute + pathnames). + * update.c, add.c, checkout.c, checkin.c, cvs.h, create_adm.c, + commit.c, modules.c, server.c, server.h, remove.c, client.h: + Pass update_dir to server_* functions. Include update_dir in + more error messages. + +Fri Oct 28 08:54:00 1994 Jim Kingdon (kingdon@lioth.cygnus.com) + + * client.c: Reformat to bring closer to cvs standards for brace + position, comment formatting, etc. + + * sanity.sh: Remove wrong "last mod" line. Convert more tests to + put PASS or FAIL in log file. Change it so arguments to the + script specify which tests to run. + + * client.c, client.h, server.c, checkout.c: Expand modules in + separate step from the checkout itself. + +Sat Oct 22 20:33:35 1994 Ken Raeburn (raeburn@kr-pc.cygnus.com) + + * update.c (join_file): When checking for null return from + RCS_getversion, still do return even if quiet flag is set. + +Thu Oct 13 07:36:11 1994 Jim Kingdon (kingdon@lioth.cygnus.com) + + * client.c (send_files): Call send_repository even if + toplevel_repos was NULL. + + * server.c (server_updated): If joining, don't remove file. + + * update.c (join_file): If server and file is unmodified, check it + out before joining. After joining, call server_updated. New + argument repository. + + * server.c, server.h (server_copy_file): New function. + * update.c (update_file_proc, join_file): Call it. + * client.c (copy_file, handle_copy_file): New functions. + * client.c (responses): Add "Copy-file". + + * client.c, client.h: Make toplevel_wd, failed_patches and + failed_patches_count extern. + * client.c (client_update): Move guts of function from here... + * update.c (update): ...to here. + + * client.c, checkout.c: Likewise for checkout. + + * client.c (is_cvsroot_level): New function. + (handle_set_sticky, handle_clear_sticky, + handle_clear_static_directory): Call it, instead of checking + short_pathname for a slash. + + * client.c, client.h (client_process_import_file, + client_import_done): New functions. + * import.c (import, import_descend): Use them. + * import.c (import_descend): If server, don't mention ignored CVS + directories. + * import.c (import_descend_dir): If client, don't print warm + fuzzies, or make directories in repository. If server, print warm + fuzzies to stdout not stderr. + * client.c (send_modified): New function, broken out from + send_fileproc. + (send_fileproc): Call it. + + * client.c (handle_clear_sticky, handle_set_sticky, + handle_clear_static_directory, handle_set_static_directory): If + command is export, just return. + (call_in_directory, update_entries): If command is export, don't + create CVS directories, CVS/Entries files, etc. + * update.c (update_filesdone_proc): Don't remove CVS directories if + client_active. + + * client.c (send_a_repository): Instead of insisting that + repository end with update_dir, just strip as many pathname + components from the end as there are in update_dir. + + * Makefile.in (remotecheck): New target, pass -r to sanity.sh. + * sanity.sh: Accept -r argument which means to test remote cvs. + + * tag.c (tag), rtag.c (rtag), patch.c (patch), import.c (import), + admin.c (admin), release.c (release): If client_active, connect to + the server and send the right requests. + * main.c (cmds): Add these commands. + (main): Remove code which would strip hostname off cvsroot and try + the command locally. There are no longer any commands which are + not supported. + * client.c, client.h (client_rdiff, client_tag, client_rtag, + client_import, client_admin, client_export, client_history, + client_release): New functions. + * server.c (serve_rdiff, serve_tag, serve_rtag, serve_import, + serve_admin, serve_export, serve_history, serve_release): New + functions. + (requests): List them. + * server.c: Declare cvs commands (add, admin, etc.). + * cvs.h, server.h: Don't declare any of them here. + * main.c: Restore declarations of cvs commands which were + previously removed. + + * cvs.h: New define DEATH_STATE, commented out for now. + * rcs.c (RCS_parsercsfile_i), commit.c (remove_file, checkaddfile) + [DEATH_STATE]: Use RCS state to record a dead file. + +Mon Oct 3 09:44:54 1994 Jim Kingdon (kingdon@lioth.cygnus.com) + + * status.c (status_fileproc): Now that ts_rcs is just one time, + don't try to print the second time from it. (Same as raeburn 20 + Aug change, it accidentally got lost in 1.4 Alpha-1 merge). + + * cvs.h (CVSDEA): Added (but commented out for now). + * rcs.c (RCS_parsercsfile_i) [CVSDEA]: Also look in CVSDEA to see if + something is dead. + * commit.c (ci_new_rev, mark_file) [CVSDEA]: New functions. + (remove_file, checkaddfile) [CVSDEA]: Use them instead of ci -K. + * find_names.c (find_dirs) [CVSDEA]: Don't match CVSDEA directories. + * update.c (checkout_file): Check RCS_isdead rather than relying + on co to not create the file. + + * sanity.sh: Direct output to logfile, not /dev/null. + + * subr.c (run_exec): Print error message if we are unable to exec. + + * commit.c (remove_file): Call Scratch_Entry when removing tag + from file. The DEATH_SUPPORT ifdef was erroneous. + +Sun Oct 2 20:33:27 1994 Jim Kingdon (kingdon@lioth.cygnus.com) + + * commit.c (checkaddfile): Instead of calling isdir before + attempting to create the directory, just ignore EEXIST errors from + mkdir. (This removes some DEATH_SUPPORT ifdefs which actually had + nothing to do with death support). + +Thu Sep 29 09:23:57 1994 Jim Kingdon (kingdon@lioth.cygnus.com) + + * diff.c (diff): Search attic too if we have a second tag/date. + (diff_fileproc): If we have a second tag/date, don't do all the + checking regarding the user file. + +Mon Sep 26 12:02:15 1994 Jim Kingdon (kingdon@lioth.cygnus.com) + + * checkin.c (Checkin): Check for error from unlink_file. + +Mon Sep 26 08:51:10 1994 Anthony J. Lill (ajlill@ajlc.waterloo.on.ca) + + * rcs.c (getrcskey): Allocate space for terminating '\0' if + necessary. + +Sat Sep 24 09:07:37 1994 Jim Kingdon (kingdon@lioth.cygnus.com) + + * commit.c (commit_fileproc): Set got_message = 1 when calling + do_editor (accidentally omitted from last change). + +Fri Sep 23 11:59:25 1994 Jim Kingdon (kingdon@lioth.cygnus.com) + + Revert buggy parts of Rich's change of 1 Nov 1993 (keeping the + dynamic buffer allocation, which was the point of that change). + * logmsg.c (do_editor): Reinstate message arg, but make it char + **messagep instead of char *message. Change occurances of message + to *messagep. Char return type from char * back to void. + * cvs.h: Change do_editor declaration. + * commit.c: Reinstate got_message variable + (commit_filesdoneproc, commit_fileproc, commit_direntproc): Use it. + * import.c (import), commit.c (commit_fileproc, + commit_direntproc): Pass &message to do_editor; don't expect it to + return a value. + * client.c (client_commit): Likewise. + * import.c (import): Deal with it if message is NULL. + +Wed Sep 21 09:43:25 1994 Jim Kingdon (kingdon@lioth.cygnus.com) + + * server.c (server_updated): If the file doesn't exist, skip it. + + * diff.c, client.h, client.c: Rename diff_client_senddate to + client_senddate and move from diff.c to client.c. + * client.c (client_update, client_checkout): Use it. + +Sat Sep 17 08:36:58 1994 Jim Kingdon (kingdon@lioth.cygnus.com) + + * checkout.c (checkout_proc): Don't pass NULL to Register for + version. (should fix "cvs co -r <nonexistent-tag> <file>" + coredump on Solaris). + +Fri Sep 16 08:38:02 1994 Jim Kingdon (kingdon@lioth.cygnus.com) + + * diff.c (diff_fileproc): Set top_rev from vn_user, not vn_rcs. + Rename it to user_file_rev because it need not be the head of any + branch. + (diff_file_nodiff): After checking user_file_rev, if we have both + use_rev1 and use_rev2, compare them instead of going on to code + which assumes use_rev2 == NULL. + +Thu Sep 15 08:20:23 1994 Jim Kingdon (kingdon@lioth.cygnus.com) + + * status.c (status): Return a value in client_active case. + +Thu Sep 15 15:02:12 1994 Ian Lance Taylor (ian@sanguine.cygnus.com) + + * server.c (serve_modified): Create the file even if the size is + zero. + +Thu Sep 15 08:20:23 1994 Jim Kingdon (kingdon@lioth.cygnus.com) + + * lock.c (readers_exist): Clear errno each time around the loop, + not just the first time. + + * client.c (start_server): Don't send Global_option -q twice. + + * no_diff.c (No_Difference): Check for error from unlink. + + * no_diff.c, cvs.h (No_Difference): New args repository, + update_dir. Call server_update_entries if needed. Use update_dir + in error message. + * classify.c (Classify_File): Pass new args to No_Difference. + + * server.c (server_update_entries, server_checked_in, + server_updated): Don't do anything if noexec. + + * client.c (send_fileproc): Rather than guessing how big the gzip + output may be, just realloc the buffer as needed. + +Tue Sep 13 13:22:03 1994 Jim Kingdon (kingdon@lioth.cygnus.com) + + * lock.c: Check for errors from unlink, readdir, and closedir. + + * classify.c (Classify_File): Pass repository and update_dir to + sticky_ck. + (sticky_ck): New args repository and update_dir. + * server.c, server.h (server_update_entries): New function. + * classify.c (sticky_ck): Call it. + * client.c: New response "New-entry". + * client.c (send_fileproc): Send tag/date from vers->entdata, not + from vers itself. + +Mon Sep 12 07:07:05 1994 Jim Kingdon (kingdon@lioth.cygnus.com) + + * server.c: Clean up formatting ("= (errno)" -> "= errno"). + + * cvs.h: Declare strerror. + + * client.c: Add code to deal with Set-sticky and Clear-sticky + responses, and Sticky request. + * server.c: Add code to deal with Sticky request. + * server.c, server.h (server_set_sticky): New function. + * create_adm.c (Create_Admin), update.c (update, update_dirent_proc), + commit.c (commit_dirleaveproc): Call it. + * client.c, client.h (send_files): Add parameter aflag. + * add.c (add), diff.c (diff), log.c (cvslog), remove.c (cvsremove), + status.c (status), + client.c (client_commit, client_update, client_checkout): Pass it. + * client.c (client_update): Add -A flag. + +Fri Sep 9 07:05:35 1994 Jim Kingdon (kingdon@lioth.cygnus.com) + + * entries.c (WriteTag): Check for error from unlink_file. + + * server.c (server_updated): Initialize size to 0. Previously if + the file was zero length, the variable size got used without being + set. + +Thu Sep 8 14:23:05 1994 Jim Kingdon (kingdon@lioth.cygnus.com) + + * server.c (serve_repository): Check for error from fopen on + CVSADM_ENT. + + * update.c (update, update_dirent_proc): Check for errors when + removing Entries.Static. + + * client.c: Add code to deal with Set-static-directory and + Clear-static-directory responses, and Static-directory request. + * server.c, server.h (server_clear_entstat, server_set_entstat): + New functions. + * update.c, checkout.c, modules.c: Call them. + * server.c: Add code to deal with Static-directory request. + + * server.c, client.c: Use strchr and strrchr instead of index and + rindex. + + * server.c (serve_unchanged, serve_lost): Change comments which + referred to changing timestamp; we don't always change the + timestamp in those cases anymore. + +Wed Sep 7 10:58:12 1994 J.T. Conklin (jtc@rtl.cygnus.com) + + * cvsrc.c (read_cvsrc): Don't call getenv() three times when one + time will do. + + * subr.c (xmalloc, xrealloc): Change type of bytes argument from + int to size_t and remove the test that checks if it is less than + zero. + * cvs.h (xmalloc, xrealloc): Update prototype. + +Thu Sep 1 12:22:20 1994 Jim Kingdon (kingdon@cygnus.com) + + * update.c (merge_file, join_file): Pass -E to rcsmerge. + (merge_file): If rcsmerge doesn't change the file, say so. + + * recurse.c, cvs.h (start_recursion): New argument wd_is_repos. + * recurse.c (start_recursion): Use it instead of checking whether + command_name is rtag to find out if we are cd'd to the repository. + * client.c, update.c, commit.c, status.c, diff.c, log.c, admin.c, + remove.c, tag.c: Pass 0 for wd_is_repos. + * rtag.c, patch.c: Pass 1 for wd_is_repos. + + * classify.c, cvs.h (Classify_File): New argument pipeout. + * classify.c (Classify_File): If pipeout, don't complain if the + file is already there. + * update.c, commit.c, status.c: Change callers. + + * mkmodules.c (main): Don't print "reminders" if commitinfo, + loginfo, rcsinfo, or editinfo files are missing. + +Mon Aug 22 23:22:59 1994 Ken Raeburn (raeburn@kr-pc.cygnus.com) + + * server.c (strerror): Static definition replaced by extern + declaration. + +Sun Aug 21 07:16:27 1994 Ken Raeburn (raeburn@kr-pc.cygnus.com) + + * client.c (update_entries): Run "patch" with input from + /dev/null, so if it's the wrong version, it fails quickly rather + than waiting for EOF from terminal before failing. + +Sat Aug 20 04:16:33 1994 Ken Raeburn (raeburn@cujo.cygnus.com) + + * server.c (serve_unchanged): Instead of creating a file with a + zero timestamp, rewrite the entries file to have "=" in the + timestamp field. + * vers_ts.c (mark_lost, mark_unchanged): New macros. + (time_stamp_server): Use them, for clarity. Interpret "=" + timestamp as an unchanged file. A zero-timestamp file should + never be encountered now in use_unchanged mode. + + * client.c (start_server): If CVS_CLIENT_PORT indicates a + non-positive port number, skip straight to rsh connection. + + * status.c (status_fileproc): Fix ts_rcs reference when printing + version info, to correspond to new Entries file format. Don't + print it at all if server_active, because it won't have any useful + data. + +Thu Aug 18 14:38:21 1994 Ken Raeburn (raeburn@cujo.cygnus.com) + + * cvs.h (status): Declare. + * client.c (client_status): New function. + + * client.h (client_status): Declare. + * main.c (cmds): Include it. + * server.c (serve_status): New function. + (requests): Add it. + * status.c (status): Do the remote thing if client_active. + + * client.c (supported_request): New function. + (start_server): Use it. + + * server.c (receive_partial_file): New function, broken out from + serve_modified. Operate with fixed-size local buffer, instead of + growing stack frame by entire file size. + (receive_file): New function, broken out from serve_modified. + (serve_modified): Call it. + (server): Print out name of unrecognized request. + + More generic stream-filtering support: + * client.c (close_on_exec, filter_stream_through_program): New + functions. + (server_fd): New variable. + (get_responses_and_close): Direct non-rsh connection is now + indicated by server_fd being non-negative. File descriptors for + to_server and from_server may now be different in case "tee" + filtering is being done. Wait for rsh_pid specifically. + (start_server): Use filter_stream_through_program for "tee" + filter, and enable it for direct Kerberos-authenticated + connections. Use dup to create new file descriptors for server + connection if logging is enabled. + (start_rsh_server): Disable code that deals with logging. + + Per-file compression support: + * cvs.h (gzip_level): Declare. + * main.c (usg): Describe new -z argument. + (main): Recognize it and set gzip_level. + * client.c (filter_through_gzip, filter_through_gunzip): New + functions to handle compression. + (update_entries): If size starts with "z", uncompress + (start_server): If gzip_level is non-zero and server supports it, + issue gzip-file-contents request. + (send_fileproc): Optionally compress file contents. Use a + slightly larger buffer, anticipating the worst case. + * server.c (gzip_level): Define here. + (receive_file): Uncompress file contents if needed. + (serve_modified): Recognize "z" in file size and pass receive_file + appropriate flag. + (buf_read_file_to_eof, buf_chain_length): New functions. + (server_updated): Call them when sending a compressed file. + (serve_gzip_contents): New function; set gzip_level. + (requests): Added gzip-file-contents request. + +Wed Aug 17 09:37:44 1994 J.T. Conklin (jtc@cygnus.com) + + * find_names.c (find_dirs): Use 4.4BSD filesystem feature (it + contains the file type in the dirent structure) to avoid + stat'ing each file. + + * commit.c (remove_file,checkaddfile): Change type of umask + variables from int to mode_t. + * subr.c (): Likewise. + +Tue Aug 16 19:56:34 1994 Mark Eichin (eichin@cygnus.com) + + * diff.c (diff_fileproc): Don't use diff_rev* because they're + invariant across calls -- add new variable top_rev. + (diff_file_nodiff): After checking possible use_rev* values, if + top_rev is set drop it in as well (if we don't already have two + versions) and then clear it for next time around. + +Wed Aug 10 20:50:47 1994 Mark Eichin (eichin@cygnus.com) + + * diff.c (diff_fileproc): if ts_user and ts_rcs match, then the + file is at the top of the tree -- so we might not even have a + copy. Put the revision into diff_rev1 or diff_rev2. + +Wed Aug 10 14:55:38 1994 Ken Raeburn (raeburn@cujo.cygnus.com) + + * server.c (do_cvs_command): Use waitpid. + + * subr.c (run_exec): Always use waitpid. + + * Makefile.in (CC, LIBS): Define here, in case "make" is run in + this directory instead of top level. + +Wed Aug 10 13:57:06 1994 Mark Eichin (eichin@cygnus.com) + + * client.c (krb_get_err_text): use HAVE_KRB_GET_ERR_TEXT to + determine if we need to use the array or the function. + * main.c: ditto. + +Tue Aug 9 16:43:30 1994 Ken Raeburn (raeburn@cujo.cygnus.com) + + * entries.c (ParseEntries): If timestamp is in old format, rebuild + it in the new format. Fudge an unmatchable entry that won't + trigger this code next time around, if the file is modified. + + * vers_ts.c (time_stamp): Only put st_mtime field into timestamp, + and use GMT time for it. With st_ctime or in local time, copying + trees between machines in different time zones makes all the files + look modified. + (time_stamp_server): Likewise. + +Tue Aug 9 19:40:51 1994 Mark Eichin (eichin@cygnus.com) + + * main.c (main): use krb_get_err_text function instead of + krb_err_txt array. + +Thu Aug 4 15:37:50 1994 Ian Lance Taylor (ian@sanguine.cygnus.com) + + * main.c (main): When invoked as kserver, set LOGNAME and USER + environment variables to the remote user name. + +Thu Aug 4 07:44:37 1994 Mark Eichin (eichin@cygnus.com) + + * client.c: (handle_valid_requests): if we get an option that has + rq_enableme set, then send that option. If it is UseUnchanged, set + use_unchanged so that the rest of the client knows about + it. (Could become a more general method for dealing with protocol + upgrades.) + (send_fileproc): if use_unchanged didn't get set, send an + old-style "Lost" request, otherwise send an "Unchanged" request. + * server.c (serve_unchanged): new function, same as serve_lost, + but used in the opposite case. + (requests): add new UseUnchanged and Unchanged requests, and make + "Lost" optional (there isn't a good way to interlock these.) + * server.h (request.status): rq_enableme, new value for detecting + compatibility changes. + * vers_ts.c (time_stamp_server): swap meaning of zero timestamp if + use_unchanged is set. + +Tue Jul 26 10:19:30 1994 Jim Kingdon (kingdon@lioth.cygnus.com) + + * sanity.sh: Separate CVSROOT_FILENAME, which must be the filename + of the root, from CVSROOT, which can include a hostname for + testing remote CVS. (but the tests aren't yet prepared to deal + with the bugs in remote CVS). + + * import.c (update_rcs_file): Change temporary file name in TMPDIR + from FILE_HOLDER to cvs-imp<process-id>. + + * sanity.sh: Add ">/dev/null" and "2>/dev/null" many places to + suppress spurious output. Comment out tests which don't work (cvs + add on top-level directory, cvs diff when non-committed adds or + removes have been made, cvs release, test 53 (already commented as + broken), retagging without deleting old tag, test 63). Now 'make + check' runs without any failures. + +Fri Jul 15 12:58:29 1994 Ian Lance Taylor (ian@sanguine.cygnus.com) + + * Makefile.in (install): Do not depend upon installdirs. + +Thu Jul 14 15:49:42 1994 Ian Lance Taylor (ian@sanguine.cygnus.com) + + * client.c, server.c: Don't try to handle alloca here; it's + handled by cvs.h. + +Tue Jul 12 13:32:40 1994 Ian Lance Taylor (ian@sanguine.cygnus.com) + + * client.c (update_entries): Reset stored_checksum_valid if we + quit early because of a patch failure. + +Fri Jul 8 11:13:05 1994 Ian Lance Taylor (ian@sanguine.cygnus.com) + + * client.c (responses): Mark "Remove-entry" as optional. + +Thu Jul 7 14:07:58 1994 Ian Lance Taylor (ian@sanguine.cygnus.com) + + * server.c (server_updated): Add new checksum argument. If it is + not NULL, and the client supports the "Checksum" response, send + it. + * server.h (server_updated): Update prototype. + * update.c: Include md5.h. + (update_file_proc): Pass new arguments to patch_file and + server_updated. + (patch_file): Add new checksum argument. Set it to the MD5 + checksum of the version of the file being checked out. + (merge_file): Pass new argument to server_updated. + * client.c: Include md5.h. + (stored_checksum_valid, stored_checksum): New static variables. + (handle_checksum): New static function. + (update_entries): If a checksum was received, check it against the + MD5 checksum of the final file. + (responses): Add "Checksum". + (start_server): Clear stored_checksum_valid. + * commit.c (commit_fileproc): Pass new argument to server_updated. + + * client.h (struct response): Move definition in from client.c, + add status field. + (responses): Declare. + * client.c (struct response): Remove definition; moved to + client.h. + (responses): Make non-static. Initialize status field. + * server.c (serve_valid_responses): Check and record valid + responses, just as in handle_valid_requests in client.c. + + * diff.c (diff_client_senddate): New function. + (diff): Use it to send -D arguments to server. + +Wed Jul 6 12:52:37 1994 J.T. Conklin (jtc@phishhead.cygnus.com) + + * rcs.c (RCS_parsercsfile_i): New function, parse RCS file + referenced by file ptr argument. + (RCS_parsercsfile): Open file and pass its file ptr to above function. + (RCS_parse): Likewise. + +Wed Jul 6 01:25:38 1994 Ian Lance Taylor (ian@tweedledumb.cygnus.com) + + * client.c (update_entries): Print message indicating that an + unpatchable file will be refetched. + (client_update): Print message when refetching unpatchable files. + +Fri Jul 1 07:16:29 1994 Jim Kingdon (kingdon@lioth.cygnus.com) + + * client.c (send_dirent_proc): Don't call send_a_repository if + repository is "". + +Fri Jul 1 13:58:11 1994 Ian Lance Taylor (ian@sanguine.cygnus.com) + + * client.c (last_dirname, last_repos): Move out of function. + (failed_patches, failed_patches_count): New static variables. + (update_entries): If patch program fails, save short_pathname in + failed_patches array, only exit program if retcode is -1, and + return out of the function rather than update the Entries line. + (start_server): Clear toplevel_repos, last_dirname, last_repos. + (client_update): If failed_patches is not NULL after doing first + update, do another update, but remove all the failed files first. + +Thu Jun 30 09:08:57 1994 Jim Kingdon (kingdon@lioth.cygnus.com) + + * server.c (requests): Add request "Global_option". + (serve_global_option): New function, to handle it. + * client.c (start_server): Deal with global options. Check for + errors from fprintf. + + * client.c (send_fileproc): Split out code which sends repository + into new function send_a_repository. Also, deal with update_dir + being ".". + (send_dirent_proc): Call send_a_repository. + * add.c (add): If client_active, do special processing for + directories. + (add_directory): If server_active, don't try to create CVSADM + directory. + +Thu Jun 30 11:58:52 1994 Ian Lance Taylor (ian@sanguine.cygnus.com) + + * client.c (update_entries): If patch succeeds, remove the backup + file. + * server.c (server_updated): Add new argument file_info. If it is + not NULL, use it rather than sb to get the file mode. + * server.h (server_updated): Update prototype for new argument. + * update.c (update_file_proc): Pass new arguments to patch_file + and server_updated. + (patch_file): Add new argument file_info. Don't use -p to check + out new version, check it out into file and rename that to file2. + If result is not readable, assume file is dead and set docheckout. + Call xchmod on file2. Close the patch file after checking for a + binary diff. Set file_info to the results of stat on file2. + (merge_file): Pass new argument to server_updated. + * commit.c (commit_fileproc): Pass new argument to server_updated. + +Wed Jun 29 13:00:41 1994 Ian Lance Taylor (ian@sanguine.cygnus.com) + + * client.c (krb_realmofhost): Declare, since it's not the current + <krb.h>. + (start_server): Save the name returned by gethostbyname. Call + krb_realmofhost to get the realm. Pass the resulting realm to + krb_sendauth. Pass the saved real name to krb_sendauth, rather + than server_host. + + * update.c (update_file_proc): Pass &docheckout to patch_file. If + it is set to 1, fall through to T_CHECKOUT case. + (patch_file): Add docheckout argument. Set it to 1 if we can't + make a patch. Check out the files and run diff rather than + rcsdiff. If either file does not end in a newline, we can't make + a patch. If the patch starts with the string "Binary", assume + one or the other is a binary file, and that we can't make a patch. + +Tue Jun 28 11:57:29 1994 Ian Lance Taylor (ian@sanguine.cygnus.com) + + * client.c (update_entries): If the patch file is empty, don't run + patch program; avoids error message. + + * classify.c (Classify_File): Return T_CHECKOUT, not T_PATCH, if + the file is in the Attic. + + * cvs.h (enum classify_type): Add T_PATCH. + * config.h (PATCH_PROGRAM): Define. + * classify.c (Classify_File): If user file exists and is not + modified, and using the same -k options, return T_PATCH instead of + T_CHECKOUT. + * update.c (patches): New static variable. + (update): Add u to gnu_getopt argument. Handle it. + (update_file_proc): Handle T_PATCH. + (patch_file): New static function. + * server.h (enum server_updated_arg4): Add SERVER_PATCHED. + * server.c (server_updated): Handle SERVER_PATCHED by sending + "Patched" command. + (serve_ignore): New static function. + (requests): Add "update-patches". + (client_update): If the server supports "update-patches", send -u. + * client.c (struct update_entries_data): Change contents field + from int to an unnamed enum. + (update_entries): Correponding change. If contents is + UPDATE_ENTRIES_PATCH, pass the input to the patch program. + (handle_checked_in): Initialize contents to enum value, not int. + (handle_updated, handle_merged): Likewise. + (handle_patched): New static function. + (responses): Add "Patched". + * commit.c (check_fileproc): Handle T_PATCH. + * status.c (status_fileproc): Likewise. + + * client.c (start_server): If CVS_CLIENT_PORT is set in the + environment, connect to that port, rather than looking up "cvs" in + /etc/services. For debugging. + +Tue Jun 21 12:48:16 1994 Ken Raeburn (raeburn@cujo.cygnus.com) + + * update.c (joining): Return result of comparing pointer with + NULL, not result of casting (truncating, on Alpha) pointer to int. + + * main.c (main) [HAVE_KERBEROS]: Impose a umask if starting as + Kerberos server, so temp directories won't be world-writeable. + + * update.c (update_filesdone_proc) [CVSADM_ROOT]: If environment + variable CVS_IGNORE_REMOTE_ROOT is set and repository is remote, + don't create CVS/Root file. + * main.c (main): If env var CVS_IGNORE_REMOTE_ROOT is set, don't + check CVS/Root. + +Fri Jun 10 18:48:32 1994 Mark Eichin (eichin@cygnus.com) + + * server.c (O_NDELAY): use POSIX O_NONBLOCK by default, unless it + isn't available (in which case substitute O_NDELAY.) + +Thu Jun 9 19:17:44 1994 Mark Eichin (eichin@cygnus.com) + + * server.c (server_cleanup): chdir out of server_temp_dir before + deleting it (so that it works on non-BSD systems.) Code for choice + of directory cloned from server(). + +Fri May 27 18:16:01 1994 Ian Lance Taylor (ian@tweedledumb.cygnus.com) + + * client.c (update_entries): Add return type of void. + (get_responses_and_close): If using Kerberos and from_server and + to_server are using the same file descriptor, use shutdown, not + fclose. Close from_server. + (start_server): New function; most of old version renamed to + start_rsh_server. + (start_rsh_server): Mostly renamed from old start_server. + (send_fileproc): Use %lu and cast sb.st_size in fprintf call. + (send_files): Remove unused variables repos and i. + (option_no_arg): Comment out; unused. + * main.c (main): Initialize cvs_update_env to 0. If command is + "kserver", authenticate and change command to "server". If + command is "server", don't call Name_Root, don't check access to + history file, and don't assume that CVSroot is not NULL. + * server.c (my_memmove): Removed. + (strerror): Change check from STRERROR_MISSING to HAVE_STRERROR. + (serve_root): Likewise for putenv. + (serve_modified): Initialize buf to NULL. + (struct output_buffer, buf_try_send): Remove old buffering code. + (struct buffer, struct buffer_data, BUFFER_DATA_SIZE, + allocate_buffer_datas, get_buffer_data, buf_empty_p, + buf_append_char, buf_append_data, buf_read_file, buf_input_data, + buf_copy_lines): New buffering code. + (buf_output, buf_output0, buf_send_output, set_nonblock, + set_block, buf_send_counted, buf_copy_counted): Rewrite for new + buffering code. + (protocol, protocol_memory_error, outbuf_memory_error, + do_cvs_command, server_updated): Rewrite for new buffering code. + (input_memory_error): New function. + (server): Put Rcsbin at start of PATH in environment. + * Makefile.in: Add @includeopt@ to DEFS. + +Fri May 20 08:13:10 1994 Jim Kingdon (kingdon@lioth.cygnus.com) + + * cvs.h, classify.c (Classify_File): New argument update_dir. + Include it in user messages. + * commit.c (check_fileproc), status.c (status_fileproc), update.c + (update_file_proc): Pass update_dir to Classify_File. + * commit.c (check_fileproc), update.c (checkout_file): + Include update_dir in user messages. + * commit.c (check_fileproc) update.c (update_file_proc): Re-word + "unknown status" message. + + * server.c (server_checked_in): Deal with the case where + scratched_file is set rather than entries_line. + + * entries.c (Register): Write file even if server_active. + * add.c (add): Add comment about how we depend on above behavior. + +Tue May 17 08:16:42 1994 Jim Kingdon (kingdon@lioth.cygnus.com) + + * mkmodules.c: Add dummy server_active and server_cleanup, to go + with the dummy Lock_Cleanup already there. + + * server.c (server_cleanup): No longer static. + +Sat May 7 10:17:17 1994 Jim Kingdon (kingdon@lioth.cygnus.com) + + Deal with add and remove: + * commit.c (checkaddfile): If CVSEXT_OPT or CVSEXT_LOG file does + not exist, just silently keep going. + (remove_file): If server_active, remove file before creating + temporary file with that name. + * server.c (serve_remove, serve_add): New functions. + (requests): Add them. + * server.c (server_register): If options is NULL, it means there + are no options. + * server.c, server.h (server_scratch_entry_only): New function. + New variable kill_scratched_file. + (server_scratch, server_updated): Deal with kill_scratched_file. + * commit.c (commit_fileproc): If server_active, call + server_scratch_entry_only and server_updated. + * add.c (add): Add client_active code. + (add): If server_active, call server_checked_in for each file added. + * remove.c (remove): Add client_active code. + (remove_fileproc): If server_active, call server_checked_in. + * main.c (cmds), client.c, client.h: New functions client_add and + client_remove. + * Move declarations of add, cvsremove, diff, and cvslog from + main.c to cvs.h. + * client.c (call_in_directory): Update comment regarding Root and + Repository files. + (send_fileproc): Only send Entries line if Version_TS really finds + an entry. If it doesn't find one, send Modified. + (update_entries): If version is empty or starts with 0 or -, + create a dummy timestamp. + +Thu May 5 19:02:51 1994 Per Bothner (bothner@kalessin.cygnus.com) + + * recurse/c (start_recursion): If we're doing rtag, and thus + have cd'd to the reporsitory, add ,v to a file name before stat'ing. + +Wed Apr 20 15:01:45 1994 Ian Lance Taylor (ian@tweedledumb.cygnus.com) + + * client.c (client_commit): Call ign_setup. + (client_update, client_checkout): Likewise. + * diff.c (diff): If client, call ign_setup. + * log.c (cvslog): Likewise. + * update.h (ignlist): Change definition to declaration to avoid + depending upon common semantics (not required by ANSI C, and not + the default on Irix 5). + * update.c (ignlist): Define. + +Tue Apr 19 00:02:54 1994 John Gilmore (gnu@cygnus.com) + + Add support for remote `cvs log'; clean up `cvs diff' a bit. + + * client.c (send_arg): Make external. + (send_option_string): New function. + (client_diff_usage): Remove, unused. + (client_diff): Just call diff, not do_diff. + (client_log): Add. + * client.h (client_log, send_arg, send_option_string): Declare. + * cvs.h (cvslog): Declare. + * diff.c (do_diff): Fold back into diff(), distinguish by checking + client_active. + (diff): Remove `-*' arg parsing crud; use send_option_string. + * log.c (cvslog): If a client, start the server, pass options + and files, and handle server responses. + * main.c (cmds): Add client_log. + (main): Remove obnoxious message every time CVS/Root is used. + Now CVS will be quiet about it -- unless there is a conflict + between $CVSROOT or -d value versus CVS/Root. + * server.c (serve_log): Add. + (requests): Add "log". + +Mon Apr 18 22:07:53 1994 John Gilmore (gnu@cygnus.com) + + Add support for remote `cvs diff'. + + * diff.c (diff): Break guts out into new fn do_diff. + Add code to handle starting server, writing args, + sending files, and retrieving responses. + (includes): Use PARAMS for static function declarations. + * client.c (to_server, from_server, rsh_pid, + get_responses_and_close, start_server, send_files, + option_with_arg): Make external. + (send_file_names): New function. + (client_diff): New function. + * client.h (client_diff, to_server, from_server, + rsh_pid, option_with_arg, get_responses_and_close, start_server, + send_file_names, send_files): Declare. + * cvs.h (diff): Declare. + * main.c (cmds): Add client_diff to command table. + * server.c (serve_diff): New function. + (requests): Add serve_diff. + (server): Bug fix: avoid free()ing incremented cmd pointer. + * update.h (update_filesdone_proc): Declare with PARAMS. + +Sat Apr 16 04:20:09 1994 John Gilmore (gnu@cygnus.com) + + * root.c (Name_root): Fix tyop (CVSroot when root meant). + +Sat Apr 16 03:49:36 1994 John Gilmore (gnu@cygnus.com) + + Clean up remote `cvs update' to properly handle ignored + files (and files that CVS can't identify), and to create + CVS/Root entries on the client side, not the server side. + + * client.c (send_fileproc): Handle the ignore list. + (send_dirent_proc): New function for handling ignores. + (send_files): Use update_filesdone_proc and send_dirent_proc + while recursing through the local filesystem. + * update.h: New file. + * update.c: Move a few things into update.h so that client.c + can use them. + +Fri Mar 11 13:13:20 1994 Ian Lance Taylor (ian@tweedledumb.cygnus.com) + + * server.c: If O_NDELAY is not defined, but O_NONBLOCK is, define + O_NDELAY to O_NONBLOCK. + +Wed Mar 9 21:08:30 1994 Jim Kingdon (kingdon@lioth.cygnus.com) + + Fix some spurious remote CVS errors caused by the CVS/Root patches: + * update.c (update_filesdone_proc): If server_active, don't try to + create CVS/Root. + * root.c (Name_Root): Make error messages which happen if root is + not an absolute pathname or if it doesn't exist a bit clearer. + Skip them if root contains a colon. + +Mon Nov 1 15:54:51 1993 K. Richard Pixley (rich@sendai.cygnus.com) + + * client.c (client_commit): dynamically allocate message. + +Tue Jun 1 17:03:05 1993 david d `zoo' zuhn (zoo at cirdan.cygnus.com) + + * server.h: remove alloca cruft + + * server.c: replace with better alloca cruft + +Mon May 24 11:25:11 1993 Jim Kingdon (kingdon@lioth.cygnus.com) + + * entries.c (Scratch_Entry): Update our local Entries file even if + server_active. + + * server.c (server_scratch, server_register): If both Register + and Scratch_Entry happen, use whichever one happened later. + If neither happen, silently continue. + + * client.c (client_checkout): Initialize tag and date (eichin and + I independently discovered this bug at the same time). + +Wed May 19 10:11:51 1993 Mark Eichin (eichin@cygnus.com) + + * client.c (update_entries): handle short reads over the net + (SVR4 fread is known to be broken, specifically for short + reads off of streams.) + +Tue May 18 15:53:44 1993 Jim Kingdon (kingdon@lioth.cygnus.com) + + * server.c (do_cvs_command): Fix fencepost error in setting + num_to_check. + + * server.c (do_cvs_command): If terminated with a core dump, print + message and set dont_delete_temp. + (server_cleanup): If dont_delete_temp, don't delete it. + + * client.c (get_server_responses): Don't change cmd since we + are going to "free (cmd)". + + * server.c: Rename memmove to my_memmove pending a real fix. + + * server.c (do_cvs_command): Set num_to_check to largest descriptor + we try to use, rather than using (non-portable) getdtablesize. + +Wed May 12 15:31:40 1993 Jim Kingdon (kingdon@lioth.cygnus.com) + + Add CVS client feature: + * client.{c,h}: New files. + * cvs.h: Include client.h. + * main.c: If CVSROOT has a colon, use client commands instead. + * vers_ts.c (Version_TS): If repository arg is NULL, don't worry + about the repository. + * logmsg.c (do_editor): If repository or changes is NULL, just don't + use those features. + * create_adm.c (Create_Admin), callers: Move the test for whether + the repository exists from here to callers. + * repos.c (Name_Repository): Don't test whether the repository exists + if client_active set (might be better to move test to callers). + + Add CVS server feature: + * server.{c,h}: New files. + * cvs.h: Include server.h. + * checkin.c (Checkin): Call server_checked_in. + * update.c (update_file_proc, merge_files): Call server_updated. + * entries.c (Register): Call server_register. + (Scratch_Entry): Call server_scratch. + * main.c: Add server to cmds. + * vers_ts.c (Version_TS): If server_active, call new function + time_stamp_server to set ts_user. + + +For older changes, there might be some relevant stuff in the bottom of +the NEWS file, but I'm afraid probably a lot of them are lost in the +mists of time. diff --git a/contrib/cvs/src/Makefile.in b/contrib/cvs/src/Makefile.in new file mode 100644 index 0000000..acb3343 --- /dev/null +++ b/contrib/cvs/src/Makefile.in @@ -0,0 +1,198 @@ +# Makefile for GNU CVS program. +# Do not use this makefile directly, but only from `../Makefile'. +# Copyright (C) 1986, 1988-1990 Free Software Foundation, Inc. + +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2, or (at your option) +# any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +# $CVSid: @(#)Makefile.in 1.19 94/09/29 $ + +SHELL = /bin/sh + +srcdir = @srcdir@ +top_srcdir = @top_srcdir@ +VPATH = @srcdir@ + +prefix = @prefix@ +exec_prefix = @exec_prefix@ + +# Where to install the executables. +bindir = $(exec_prefix)/bin + +# Where to put the system-wide .cvsrc file +libdir = $(prefix)/lib + +# Where to put the manual pages. +mandir = $(prefix)/man + +# Use cp if you don't have install. +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ + +LIBS = @LIBS@ + +SOURCES = add.c admin.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 \ +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 \ +tag.c update.c watch.c wrapper.c vers_ts.c version.c + +OBJECTS = add.o admin.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 \ +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 + +HEADERS = cvs.h rcs.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) + +DISTFILES = .cvsignore Makefile.in ChangeLog ChangeLog-9395 ChangeLog-9194 \ + NOTES README-rm-add \ + sanity.sh cvsbug.sh $(TAGFILES) + +PROGS = cvs cvsbug + +DEFS = @DEFS@ @includeopt@ + +CC = @CC@ +CFLAGS = @CFLAGS@ +CPPFLAGS = +LDFLAGS = @LDFLAGS@ + +INCLUDES = -I. -I.. -I$(srcdir) -I$(top_srcdir)/lib +.c.o: + $(CC) $(CPPFLAGS) $(INCLUDES) $(DEFS) $(CFLAGS) -c $< + +all: Makefile $(PROGS) +.PHONY: all + +saber_cvs: + @cd ..; $(MAKE) saber SUBDIRS=src + +lint: + @cd ..; $(MAKE) lint SUBDIRS=src + +# CYGNUS LOCAL: Do not depend upon installdirs +install: + @for prog in $(PROGS); do \ + echo Installing $$prog in $(bindir); \ + $(INSTALL) $$prog $(bindir)/$$prog ; \ + done + +installdirs: + $(SHELL) $(top_srcdir)/mkinstalldirs $(bindir) + +.PHONY: install installdirs + +installcheck: + RCSBIN=$(bindir) ; export RCSBIN ; $(SHELL) $(srcdir)/sanity.sh $(bindir)/cvs +.PHONY: installcheck + +check: all + if [ -x ../../rcs/src/rcs ] ; then \ + RCSBIN=`pwd`/../../rcs/src ; \ + export RCSBIN ; \ + fi ; \ + $(SHELL) $(srcdir)/sanity.sh `pwd`/cvs +.PHONY: check + +# I'm not sure there is any remaining reason for this to be separate from +# `make check'. +remotecheck: all + $(SHELL) $(srcdir)/sanity.sh -r `pwd`/cvs +.PHONY: remotecheck + +tags: $(TAGFILES) + ctags $(TAGFILES) + +TAGS: $(TAGFILES) + etags `for i in $(TAGFILES); do echo $(srcdir)/$$i; done` + +ls: + @echo $(DISTFILES) +.PHONY: ls + +clean: + /bin/rm -f $(PROGS) *.o core check.log check.plog +.PHONY: clean + +distclean: clean + rm -f tags TAGS Makefile options.h +.PHONY: distclean + +realclean: distclean +.PHONY: realclean + +dist-dir: + mkdir ${DISTDIR} + for i in ${DISTFILES}; do \ + ln $(srcdir)/$${i} ${DISTDIR}; \ + done +.PHONY: dist-dir + +# Linking rules. + +$(PROGS): ../lib/libcvs.a + +cvs: $(OBJECTS) + $(CC) $(OBJECTS) ../lib/libcvs.a $(LIBS) $(LDFLAGS) -o $@ + +xlint: $(SOURCES) + files= ; \ + for i in $(SOURCES) ; do \ + files="$$files $(srcdir)/$$i" ; \ + done ; \ + sh -c "lint $(DEFS) $(INCLUDES) $$files | grep -v \"possible pointer alignment problem\" \ + | grep -v \"argument closure unused\"" + +saber: $(SOURCES) + # load $(CFLAGS) $(SOURCES) + # load ../lib/libcvs.a $(LIBS) + +cvsbug: cvsbug.sh + echo > .fname \ + cvs-`sed < $(srcdir)/version.c \ + -e '/version_string/!d' \ + -e 's/[^0-9.]*\([0-9.]*\).*/\1/' \ + -e q` + sed -e 's,xLIBDIRx,$(libdir)/cvs,g' \ + -e "s,xVERSIONx,`cat .fname`,g" $(srcdir)/$@.sh > $@-t + rm -f .fname + mv $@-t $@ + chmod a+x $@ + +# Compilation rules. + +$(OBJECTS): $(HEADERS) options.h + +subdir = src +Makefile: ../config.status Makefile.in + cd .. && CONFIG_FILES=$(subdir)/$@ CONFIG_HEADERS= ./config.status + +options.h: ../config.status options.h.in + cd .. && CONFIG_FILES=$(subdir)/$@ CONFIG_HEADERS= ./config.status + +#../config.status: ../configure +# cd .. ; $(SHELL) config.status --recheck + +#../configure: ../configure.in +# cd $(top_srcdir) ; autoconf diff --git a/contrib/cvs/src/NOTES b/contrib/cvs/src/NOTES new file mode 100644 index 0000000..646ebdf --- /dev/null +++ b/contrib/cvs/src/NOTES @@ -0,0 +1,60 @@ +wishlist - Tue Nov 2 15:22:58 PST 1993 + +* bcopy -> memcpy & friends. + ** done 12/18/93 + +* remove static buffers. +* replace list & node cache with recursive obstacks, (xmalloc, + getnode, getlist) +* check all io functions for error return codes. also check all + system calls. +* error check mkdir. + +--- +Old notes... + +* All sizing limits are gone. The rest of these items were incidental + in that effort. + +* login name from history was duplicated. taught existing routine to + cache and use that instead. Also add routines to cache uid, pid, + etc. + +* ign strings were never freed. Now they are. + +* there was a printf("... %s ...", cp) vs *cp bug in history.c. Now + fixed. + +* The environment variables TMPDIR, HOME, and LOGNAME were not + honored. Now they are. + +* extra line inserted by do_editor() is gone. Then obviated. Editor + is now called exactly once per checkin. + +* revised editor behaviour. Never use /dev/tty. If the editor + session fails, we haven't yet done anything. Therefor the user can + safely rerun cvs and we should just fail. Also use the editor for + initial log messages on added files. Also omit the confirmation + when adding directories. Adding directories will require an + explicit "commit" step soon. Make it possible to prevent null login + messages using #define REQUIRE_LOG_MESSAGES + +* prototypes for all callbacks. + +* all callbacks get ref pointers. + +* do_recursion/start_recursion now use recusion_frame's rather than a + list of a lot of pointers and global variables. + +* corrected types on status_dirproc(). + +* CONFIRM_DIRECTORY_ADDS + +* re_comp was innappropriate in a few places. I've eliminated it. + +* FORCE_MESSAGE_ON_ADD + +* So I built a regression test. Let's call it a sanity check to be + less ambitious. It exposed that cvs is difficult to call from + scripts. + diff --git a/contrib/cvs/src/README-rm-add b/contrib/cvs/src/README-rm-add new file mode 100644 index 0000000..87fd7c6 --- /dev/null +++ b/contrib/cvs/src/README-rm-add @@ -0,0 +1,31 @@ +WHAT THE "DEATH SUPPORT" FEATURES DO: + +(Some of the death support stuff is documented in the main manual, but +this file is for stuff which noone has gotten around to adding to the +main manual yet). + +CVS with death support can record when a file is active, or alive, and +when it is removed, or dead. With this facility you can record the +history of a file, including the fact that at some point in its life +the file was removed and then later added. + +Files can now be added or removed in a branch and later merged +into the trunk. + + cvs update -A + touch a b c + cvs add a b c ; cvs ci -m "added" a b c + cvs tag -b branchtag + cvs update -r branchtag + touch d ; cvs add d + rm a ; cvs rm a + cvs ci -m "added d, removed a" + cvs update -A + cvs update -jbranchtag + +Added and removed files may also be merged between branches. + +Files removed in the trunk may be merged into branches. + +Files added on the trunk are a special case. They cannot be merged +into a branch. Instead, simply branch the file by hand. diff --git a/contrib/cvs/src/add.c b/contrib/cvs/src/add.c new file mode 100644 index 0000000..82efefe --- /dev/null +++ b/contrib/cvs/src/add.c @@ -0,0 +1,514 @@ +/* + * Copyright (c) 1992, Brian Berliner and Jeff Polk + * Copyright (c) 1989-1992, Brian Berliner + * + * You may distribute under the terms of the GNU General Public License as + * specified in the README file that comes with the CVS 1.4 kit. + * + * Add + * + * Adds a file or directory to the RCS source repository. For a file, + * the entry is marked as "needing to be added" in the user's own CVS + * directory, and really added to the repository when it is committed. + * For a directory, it is added at the appropriate place in the source + * repository and a CVS directory is generated within the directory. + * + * The -m option is currently the only supported option. Some may wish to + * supply standard "rcs" options here, but I've found that this causes more + * trouble than anything else. + * + * The user files or directories must already exist. For a directory, it must + * not already have a CVS file in it. + * + * An "add" on a file that has been "remove"d but not committed will cause the + * file to be resurrected. + */ + +#include "cvs.h" +#include "savecwd.h" + +static int add_directory PROTO((char *repository, char *dir)); +static int build_entry PROTO((char *repository, char *user, char *options, + char *message, List * entries, char *tag)); + +static const char *const add_usage[] = +{ + "Usage: %s %s [-k rcs-kflag] [-m message] files...\n", + "\t-k\tUse \"rcs-kflag\" to add the file with the specified kflag.\n", + "\t-m\tUse \"message\" for the creation log.\n", + NULL +}; + +int +add (argc, argv) + int argc; + char **argv; +{ + char *message = NULL; + char *user; + int i; + char *repository; + int c; + int err = 0; + int added_files = 0; + char *options = NULL; + List *entries; + Vers_TS *vers; + + if (argc == 1 || argc == -1) + usage (add_usage); + + wrap_setup (); + + /* parse args */ + optind = 1; + while ((c = getopt (argc, argv, "k:m:")) != -1) + { + switch (c) + { + case 'k': + if (options) + free (options); + options = RCS_check_kflag (optarg); + break; + + case 'm': + message = xstrdup (optarg); + break; + case '?': + default: + usage (add_usage); + break; + } + } + argc -= optind; + argv += optind; + + if (argc <= 0) + usage (add_usage); + + /* find the repository associated with our current dir */ + repository = Name_Repository ((char *) NULL, (char *) NULL); + +#ifdef CLIENT_SUPPORT + if (client_active) + { + int i; + start_server (); + ign_setup (); + if (options) send_arg(options); + option_with_arg ("-m", message); + for (i = 0; i < argc; ++i) + /* FIXME: Does this erroneously call Create_Admin in error + conditions which are only detected once the server gets its + hands on things? */ + if (isdir (argv[i])) + { + char *tag; + char *date; + char *rcsdir = xmalloc (strlen (repository) + + strlen (argv[i]) + 10); + + /* before we do anything else, see if we have any + per-directory tags */ + ParseTag (&tag, &date); + + sprintf (rcsdir, "%s/%s", repository, argv[i]); + + Create_Admin (argv[i], argv[i], rcsdir, tag, date); + + if (tag) + free (tag); + if (date) + free (date); + free (rcsdir); + } + send_file_names (argc, argv, SEND_EXPAND_WILD); + send_files (argc, argv, 0, 0); + send_to_server ("add\012", 0); + return get_responses_and_close (); + } +#endif + + entries = Entries_Open (0); + + /* walk the arg list adding files/dirs */ + for (i = 0; i < argc; i++) + { + int begin_err = err; + int begin_added_files = added_files; + + user = argv[i]; + strip_trailing_slashes (user); + if (strchr (user, '/') != NULL) + { + error (0, 0, + "cannot add files with '/' in their name; %s not added", user); + err++; + continue; + } + + vers = Version_TS (repository, options, (char *) NULL, (char *) NULL, + user, 0, 0, entries, (RCSNode *) NULL); + if (vers->vn_user == NULL) + { + /* No entry available, ts_rcs is invalid */ + if (vers->vn_rcs == NULL) + { + /* There is no RCS file either */ + if (vers->ts_user == NULL) + { + /* There is no user file either */ + error (0, 0, "nothing known about %s", user); + err++; + } + else if (!isdir (user) || wrap_name_has (user, WRAP_TOCVS)) + { + /* + * See if a directory exists in the repository with + * the same name. If so, blow this request off. + */ + char dname[PATH_MAX]; + (void) sprintf (dname, "%s/%s", repository, user); + if (isdir (dname)) + { + error (0, 0, + "cannot add file `%s' since the directory", + user); + error (0, 0, "`%s' already exists in the repository", + dname); + error (1, 0, "illegal filename overlap"); + } + + /* There is a user file, so build the entry for it */ + if (build_entry (repository, user, vers->options, + message, entries, vers->tag) != 0) + err++; + else + { + added_files++; + if (!quiet) + { + if (vers->tag) + error (0, 0, "\ +scheduling %s `%s' for addition on branch `%s'", + (wrap_name_has (user, WRAP_TOCVS) + ? "wrapper" + : "file"), + user, vers->tag); + else + error (0, 0, "scheduling %s `%s' for addition", + (wrap_name_has (user, WRAP_TOCVS) + ? "wrapper" + : "file"), + user); + } + } + } + } + else if (RCS_isdead (vers->srcfile, vers->vn_rcs)) + { + if (isdir (user) && !wrap_name_has (user, WRAP_TOCVS)) + { + error (0, 0, "the directory `%s' cannot be added because a file of the", user); + error (1, 0, "same name already exists in the repository."); + } + else + { + if (vers->tag) + error (0, 0, "file `%s' will be added on branch `%s' from version %s", + user, vers->tag, vers->vn_rcs); + else + error (0, 0, "version %s of `%s' will be resurrected", + vers->vn_rcs, user); + Register (entries, user, "0", vers->ts_user, NULL, + vers->tag, NULL, NULL); + ++added_files; + } + } + else + { + /* + * There is an RCS file already, so somebody else must've + * added it + */ + error (0, 0, "%s added independently by second party", user); + err++; + } + } + else if (vers->vn_user[0] == '0' && vers->vn_user[1] == '\0') + { + + /* + * An entry for a new-born file, ts_rcs is dummy, but that is + * inappropriate here + */ + error (0, 0, "%s has already been entered", user); + err++; + } + else if (vers->vn_user[0] == '-') + { + /* An entry for a removed file, ts_rcs is invalid */ + if (vers->ts_user == NULL) + { + /* There is no user file (as it should be) */ + if (vers->vn_rcs == NULL) + { + + /* + * There is no RCS file, so somebody else must've removed + * it from under us + */ + error (0, 0, + "cannot resurrect %s; RCS file removed by second party", user); + err++; + } + else + { + + /* + * There is an RCS file, so remove the "-" from the + * version number and restore the file + */ + char *tmp = xmalloc (strlen (user) + 50); + + (void) strcpy (tmp, vers->vn_user + 1); + (void) strcpy (vers->vn_user, tmp); + (void) sprintf (tmp, "Resurrected %s", user); + Register (entries, user, vers->vn_user, tmp, vers->options, + vers->tag, vers->date, vers->ts_conflict); + free (tmp); + + /* XXX - bugs here; this really resurrect the head */ + /* Note that this depends on the Register above actually + having written Entries, or else it won't really + check the file out. */ + if (update (2, argv + i - 1) == 0) + { + error (0, 0, "%s, version %s, resurrected", user, + vers->vn_user); + } + else + { + error (0, 0, "could not resurrect %s", user); + err++; + } + } + } + else + { + /* The user file shouldn't be there */ + error (0, 0, "%s should be removed and is still there (or is back again)", user); + err++; + } + } + else + { + /* A normal entry, ts_rcs is valid, so it must already be there */ + error (0, 0, "%s already exists, with version number %s", user, + vers->vn_user); + err++; + } + freevers_ts (&vers); + + /* passed all the checks. Go ahead and add it if its a directory */ + if (begin_err == err + && isdir (user) + && !wrap_name_has (user, WRAP_TOCVS)) + { + err += add_directory (repository, user); + continue; + } +#ifdef SERVER_SUPPORT + if (server_active && begin_added_files != added_files) + server_checked_in (user, ".", repository); +#endif + } + if (added_files) + error (0, 0, "use 'cvs commit' to add %s permanently", + (added_files == 1) ? "this file" : "these files"); + + Entries_Close (entries); + + if (message) + free (message); + + return (err); +} + +/* + * The specified user file is really a directory. So, let's make sure that + * it is created in the RCS source repository, and that the user's directory + * is updated to include a CVS directory. + * + * Returns 1 on failure, 0 on success. + */ +static int +add_directory (repository, dir) + char *repository; + char *dir; +{ + char rcsdir[PATH_MAX]; + struct saved_cwd cwd; + char message[PATH_MAX + 100]; + char *tag, *date; + + if (strchr (dir, '/') != NULL) + { + error (0, 0, + "directory %s not added; must be a direct sub-directory", dir); + return (1); + } + if (strcmp (dir, CVSADM) == 0) + { + error (0, 0, "cannot add a `%s' directory", CVSADM); + return (1); + } + + /* before we do anything else, see if we have any per-directory tags */ + ParseTag (&tag, &date); + + /* now, remember where we were, so we can get back */ + if (save_cwd (&cwd)) + return (1); + if (chdir (dir) < 0) + { + error (0, errno, "cannot chdir to %s", dir); + return (1); + } +#ifdef SERVER_SUPPORT + if (!server_active && isfile (CVSADM)) +#else + if (isfile (CVSADM)) +#endif + { + error (0, 0, "%s/%s already exists", dir, CVSADM); + goto out; + } + + (void) sprintf (rcsdir, "%s/%s", repository, dir); + if (isfile (rcsdir) && !isdir (rcsdir)) + { + error (0, 0, "%s is not a directory; %s not added", rcsdir, dir); + goto out; + } + + /* setup the log message */ + (void) sprintf (message, "Directory %s added to the repository\n", rcsdir); + if (tag) + { + (void) strcat (message, "--> Using per-directory sticky tag `"); + (void) strcat (message, tag); + (void) strcat (message, "'\n"); + } + if (date) + { + (void) strcat (message, "--> Using per-directory sticky date `"); + (void) strcat (message, date); + (void) strcat (message, "'\n"); + } + + if (!isdir (rcsdir)) + { + mode_t omask; + Node *p; + List *ulist; + +#if 0 + char line[MAXLINELEN]; + + (void) printf ("Add directory %s to the repository (y/n) [n] ? ", + rcsdir); + (void) fflush (stdout); + clearerr (stdin); + if (fgets (line, sizeof (line), stdin) == NULL || + (line[0] != 'y' && line[0] != 'Y')) + { + error (0, 0, "directory %s not added", rcsdir); + goto out; + } +#endif + + omask = umask (cvsumask); + if (CVS_MKDIR (rcsdir, 0777) < 0) + { + error (0, errno, "cannot mkdir %s", rcsdir); + (void) umask (omask); + goto out; + } + (void) umask (omask); + + /* + * Set up an update list with a single title node for Update_Logfile + */ + ulist = getlist (); + p = getnode (); + p->type = UPDATE; + p->delproc = update_delproc; + p->key = xstrdup ("- New directory"); + p->data = (char *) T_TITLE; + (void) addnode (ulist, p); + Update_Logfile (rcsdir, message, (char *) NULL, (FILE *) NULL, ulist); + dellist (&ulist); + } + +#ifdef SERVER_SUPPORT + if (!server_active) + Create_Admin (".", dir, rcsdir, tag, date); +#else + Create_Admin (".", dir, rcsdir, tag, date); +#endif + if (tag) + free (tag); + if (date) + free (date); + + (void) printf ("%s", message); +out: + if (restore_cwd (&cwd, NULL)) + exit (EXIT_FAILURE); + free_cwd (&cwd); + return (0); +} + +/* + * Builds an entry for a new file and sets up "CVS/file",[pt] by + * interrogating the user. Returns non-zero on error. + */ +static int +build_entry (repository, user, options, message, entries, tag) + char *repository; + char *user; + char *options; + char *message; + List *entries; + char *tag; +{ + char fname[PATH_MAX]; + char line[MAXLINELEN]; + FILE *fp; + + if (noexec) + return (0); + + /* + * The requested log is read directly from the user and stored in the + * file user,t. If the "message" argument is set, use it as the + * initial creation log (which typically describes the file). + */ + (void) sprintf (fname, "%s/%s%s", CVSADM, user, CVSEXT_LOG); + fp = open_file (fname, "w+"); + if (message && fputs (message, fp) == EOF) + error (1, errno, "cannot write to %s", fname); + if (fclose(fp) == EOF) + error(1, errno, "cannot close %s", fname); + + /* + * Create the entry now, since this allows the user to interrupt us above + * without needing to clean anything up (well, we could clean up the + * ,t file, but who cares). + */ + (void) sprintf (line, "Initial %s", user); + Register (entries, user, "0", line, options, tag, (char *) 0, (char *) 0); + return (0); +} diff --git a/contrib/cvs/src/admin.c b/contrib/cvs/src/admin.c new file mode 100644 index 0000000..214318a --- /dev/null +++ b/contrib/cvs/src/admin.c @@ -0,0 +1,168 @@ +/* + * Copyright (c) 1992, Brian Berliner and Jeff Polk + * Copyright (c) 1989-1992, Brian Berliner + * + * You may distribute under the terms of the GNU General Public License as + * specified in the README file that comes with the CVS 1.4 kit. + * + * Administration + * + * For now, this is basically a front end for rcs. All options are passed + * directly on. + */ + +#include "cvs.h" +#ifdef CVS_ADMIN_GROUP +#include <grp.h> +#endif + +static Dtype admin_dirproc PROTO((char *dir, char *repos, char *update_dir)); +static int admin_fileproc PROTO((struct file_info *finfo)); + +static const char *const admin_usage[] = +{ + "Usage: %s %s rcs-options files...\n", + NULL +}; + +static int ac; +static char **av; + +int +admin (argc, argv) + int argc; + char **argv; +{ + int err; +#ifdef CVS_ADMIN_GROUP + struct group *grp; +#endif + if (argc <= 1) + usage (admin_usage); + +#ifdef CVS_ADMIN_GROUP + grp = getgrnam(CVS_ADMIN_GROUP); + /* skip usage right check if group CVS_ADMIN_GROUP does not exist */ + if (grp != NULL) + { + char *me = getcaller(); + char **grnam = grp->gr_mem; + int denied = 1; + + while (*grnam) + { + if (strcmp(*grnam, me) == 0) + { + denied = 0; + break; + } + grnam++; + } + + if (denied) + error (1, 0, "usage is restricted to members of the group %s", + CVS_ADMIN_GROUP); + } +#endif + + wrap_setup (); + + /* skip all optional arguments to see if we have any file names */ + for (ac = 1; ac < argc; ac++) + if (argv[ac][0] != '-') + break; + argc -= ac; + av = argv + 1; + argv += ac; + ac--; + if (ac == 0 || argc == 0) + usage (admin_usage); + +#ifdef CLIENT_SUPPORT + if (client_active) + { + int i; + + /* We're the client side. Fire up the remote server. */ + start_server (); + + ign_setup (); + + for (i = 0; i <= ac; ++i) /* XXX send -ko too with i = 0 */ + send_arg (av[i]); + + send_file_names (argc, argv, SEND_EXPAND_WILD); + /* FIXME: We shouldn't have to send current files, but I'm not sure + whether it works. So send the files -- + it's slower but it works. */ + send_files (argc, argv, 0, 0); + send_to_server ("admin\012", 0); + return get_responses_and_close (); + } +#endif /* CLIENT_SUPPORT */ + + /* start the recursion processor */ + err = start_recursion (admin_fileproc, (FILESDONEPROC) NULL, admin_dirproc, + (DIRLEAVEPROC) NULL, argc, argv, 0, + W_LOCAL, 0, 1, (char *) NULL, 1, 0); + return (err); +} + +/* + * Called to run "rcs" on a particular file. + */ +/* ARGSUSED */ +static int +admin_fileproc (finfo) + struct file_info *finfo; +{ + Vers_TS *vers; + char *version; + char **argv; + int argc; + int retcode = 0; + int status = 0; + + vers = Version_TS (finfo->repository, (char *) NULL, (char *) NULL, (char *) NULL, + finfo->file, 0, 0, finfo->entries, finfo->rcs); + + version = vers->vn_user; + if (version == NULL) + goto exitfunc; + else if (strcmp (version, "0") == 0) + { + error (0, 0, "cannot admin newly added file `%s'", finfo->file); + goto exitfunc; + } + + run_setup ("%s%s -x,v/", Rcsbin, RCS); + for (argc = ac, argv = av; argc; argc--, argv++) + run_arg (*argv); + run_arg (vers->srcfile->path); + if ((retcode = run_exec (RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL)) != 0) + { + if (!quiet) + error (0, retcode == -1 ? errno : 0, + "%s failed for `%s'", RCS, finfo->file); + status = 1; + goto exitfunc; + } + exitfunc: + freevers_ts (&vers); + return status; +} + +/* + * Print a warm fuzzy message + */ +/* ARGSUSED */ +static Dtype +admin_dirproc (dir, repos, update_dir) + char *dir; + char *repos; + char *update_dir; +{ + if (!quiet) + error (0, 0, "Administrating %s", update_dir); + return (R_PROCESS); +} diff --git a/contrib/cvs/src/checkin.c b/contrib/cvs/src/checkin.c new file mode 100644 index 0000000..e1ce9cb --- /dev/null +++ b/contrib/cvs/src/checkin.c @@ -0,0 +1,188 @@ +/* + * Copyright (c) 1992, Brian Berliner and Jeff Polk + * Copyright (c) 1989-1992, Brian Berliner + * + * You may distribute under the terms of the GNU General Public License as + * specified in the README file that comes with the CVS 1.4 kit. + * + * Check In + * + * Does a very careful checkin of the file "user", and tries not to spoil its + * modification time (to avoid needless recompilations). When RCS ID keywords + * get expanded on checkout, however, the modification time is updated and + * there is no good way to get around this. + * + * Returns non-zero on error. + */ + +#include "cvs.h" +#include "fileattr.h" +#include "edit.h" + +int +Checkin (type, file, update_dir, repository, + rcs, rev, tag, options, message, entries) + int type; + char *file; + char *update_dir; + char *repository; + char *rcs; + char *rev; + char *tag; + char *options; + char *message; + List *entries; +{ + char fname[PATH_MAX]; + Vers_TS *vers; + int set_time; + char *fullname; + + char *tocvsPath = NULL; + + fullname = xmalloc (strlen (update_dir) + strlen (file) + 10); + if (update_dir[0] == '\0') + strcpy (fullname, file); + else + sprintf (fullname, "%s/%s", update_dir, file); + + (void) printf ("Checking in %s;\n", fullname); + (void) sprintf (fname, "%s/%s%s", CVSADM, CVSPREFIX, 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 (fullname); + + if (!noexec) + { + if (tocvsPath) + { + copy_file (tocvsPath, fname); + if (unlink_file_dir (file) < 0) + if (! existence_error (errno)) + error (1, errno, "cannot remove %s", file); + copy_file (tocvsPath, file); + } + else + { + copy_file (file, fname); + } + } + + switch (RCS_checkin (rcs, NULL, message, rev, 0, 0)) + { + 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. + */ + + if (strcmp (options, "-V4") == 0) /* upgrade to V5 now */ + options[0] = '\0'; + + /* FIXME: should be checking for errors. */ + (void) RCS_checkout (rcs, "", rev, options, RUN_TTY, 0, 0); + + xchmod (file, 1); + if (xcmp (file, fname) == 0) + { + rename_file (fname, file); + /* the time was correct, so leave it alone */ + 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 */ + set_time = 1; + } + + wrap_fromcvs_process_file (file); + + /* + * If we want read-only files, muck the permissions here, before + * getting the file time-stamp. + */ + if (cvswrite == FALSE || fileattr_get (file, "_watched")) + xchmod (file, 0); + + /* re-register with the new data */ + vers = Version_TS (repository, (char *) NULL, tag, (char *) NULL, + file, 1, set_time, entries, (RCSNode *) NULL); + if (strcmp (vers->options, "-V4") == 0) + vers->options[0] = '\0'; + Register (entries, file, vers->vn_rcs, vers->ts_user, + vers->options, vers->tag, vers->date, (char *) 0); + history_write (type, (char *) 0, vers->vn_rcs, file, repository); + freevers_ts (&vers); + + if (tocvsPath) + if (unlink_file_dir (tocvsPath) < 0) + error (0, errno, "cannot remove %s", tocvsPath); + + break; + + case -1: /* fork failed */ + if (tocvsPath) + if (unlink_file_dir (tocvsPath) < 0) + error (0, errno, "cannot remove %s", tocvsPath); + + if (!noexec) + error (1, errno, "could not check in %s -- fork failed", + fullname); + 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 + */ + if (tocvsPath) + if (unlink_file_dir (tocvsPath) < 0) + error (0, errno, "cannot remove %s", tocvsPath); + + if (!noexec) + { + rename_file (fname, file); + error (0, 0, "could not check in %s", fullname); + } + return (1); + } + + /* + * When checking in a specific revision, we may have locked the wrong + * branch, so to be sure, we do an extra unlock here before + * returning. + */ + if (rev) + { + (void) RCS_unlock (rcs, NULL, 1); + } + +#ifdef SERVER_SUPPORT + if (server_active) + { + if (set_time) + /* Need to update the checked out file on the client side. */ + server_updated (file, update_dir, repository, SERVER_UPDATED, + NULL, NULL); + else + server_checked_in (file, update_dir, repository); + } + else +#endif + mark_up_to_date (file); + + return (0); +} diff --git a/contrib/cvs/src/checkout.c b/contrib/cvs/src/checkout.c new file mode 100644 index 0000000..64aa10e --- /dev/null +++ b/contrib/cvs/src/checkout.c @@ -0,0 +1,889 @@ +/* + * Copyright (c) 1992, Brian Berliner and Jeff Polk + * Copyright (c) 1989-1992, Brian Berliner + * + * You may distribute under the terms of the GNU General Public License as + * specified in the README file that comes with the CVS 1.4 kit. + * + * Create Version + * + * "checkout" creates a "version" of an RCS repository. This version is owned + * totally by the user and is actually an independent copy, to be dealt with + * as seen fit. Once "checkout" has been called in a given directory, it + * never needs to be called again. The user can keep up-to-date by calling + * "update" when he feels like it; this will supply him with a merge of his + * own modifications and the changes made in the RCS original. See "update" + * for details. + * + * "checkout" can be given a list of directories or files to be updated and in + * the case of a directory, will recursivley create any sub-directories that + * exist in the repository. + * + * When the user is satisfied with his own modifications, the present version + * can be committed by "commit"; this keeps the present version in tact, + * usually. + * + * The call is cvs checkout [options] <module-name>... + * + * "checkout" creates a directory ./CVS, in which it keeps its administration, + * in two files, Repository and Entries. The first contains the name of the + * repository. The second contains one line for each registered file, + * consisting of the version number it derives from, its time stamp at + * derivation time and its name. Both files are normal files and can be + * edited by the user, if necessary (when the repository is moved, e.g.) + */ + +#include "cvs.h" + +static char *findslash PROTO((char *start, char *p)); +static int build_dirs_and_chdir PROTO((char *dir, char *prepath, char *realdir, + int sticky)); +static int checkout_proc PROTO((int *pargc, char **argv, char *where, + char *mwhere, char *mfile, int shorten, + int local_specified, char *omodule, + char *msg)); +static int safe_location PROTO((void)); + +static const char *const checkout_usage[] = +{ + "Usage:\n %s %s [-ANPcflnps] [-r rev | -D date] [-d dir] [-k kopt] modules...\n", + "\t-A\tReset any sticky tags/date/kopts.\n", + "\t-N\tDon't shorten module paths if -d specified.\n", + "\t-P\tPrune empty directories.\n", + "\t-c\t\"cat\" the module database.\n", + "\t-f\tForce a head revision match if tag/date not found.\n", + "\t-l\tLocal directory only, not recursive\n", + "\t-n\tDo not run module program (if any).\n", + "\t-p\tCheck out files to standard output.\n", + "\t-s\tLike -c, but include module status.\n", + "\t-r rev\tCheck out revision or tag. (implies -P)\n", + "\t-D date\tCheck out revisions as of date. (implies -P)\n", + "\t-d dir\tCheck out into dir instead of module name.\n", + "\t-k kopt\tUse RCS kopt -k option on checkout.\n", + "\t-j rev\tMerge in changes made between current revision and rev.\n", + NULL +}; + +static const char *const export_usage[] = +{ + "Usage: %s %s [-NPfln] [-r rev | -D date] [-d dir] [-k kopt] module...\n", + "\t-N\tDon't shorten module paths if -d specified.\n", + "\t-f\tForce a head revision match if tag/date not found.\n", + "\t-l\tLocal directory only, not recursive\n", + "\t-n\tDo not run module program (if any).\n", + "\t-r rev\tCheck out revision or tag.\n", + "\t-D date\tCheck out revisions as of date.\n", + "\t-d dir\tCheck out into dir instead of module name.\n", + "\t-k kopt\tUse RCS kopt -k option on checkout.\n", + NULL +}; + +static int checkout_prune_dirs; +static int force_tag_match = 1; +static int pipeout; +static int aflag; +static char *options = NULL; +static char *tag = NULL; +static int tag_validated = 0; +static char *date = NULL; +static char *join_rev1 = NULL; +static char *join_rev2 = NULL; +static char *preload_update_dir = NULL; + +int +checkout (argc, argv) + int argc; + char **argv; +{ + int i; + int c; + DBM *db; + int cat = 0, err = 0, status = 0; + int run_module_prog = 1; + int local = 0; + int shorten = -1; + char *where = NULL; + char *valid_options; + const char *const *valid_usage; + enum mtype m_type; + + /* + * A smaller subset of options are allowed for the export command, which + * is essentially like checkout, except that it hard-codes certain + * options to be default (like -kv) and takes care to remove the CVS + * directory when it has done its duty + */ + if (strcmp (command_name, "export") == 0) + { + m_type = EXPORT; + valid_options = "Nnk:d:flRQqr:D:"; + valid_usage = export_usage; + } + else + { + m_type = CHECKOUT; + valid_options = "ANnk:d:flRpQqcsr:D:j:P"; + valid_usage = checkout_usage; + } + + if (argc == -1) + usage (valid_usage); + + ign_setup (); + wrap_setup (); + + optind = 1; + while ((c = getopt (argc, argv, valid_options)) != -1) + { + switch (c) + { + case 'A': + aflag = 1; + break; + case 'N': + shorten = 0; + break; + case 'k': + if (options) + free (options); + options = RCS_check_kflag (optarg); + break; + case 'n': + run_module_prog = 0; + break; + case 'Q': + case 'q': +#ifdef SERVER_SUPPORT + /* The CVS 1.5 client sends these options (in addition to + Global_option requests), so we must ignore them. */ + if (!server_active) +#endif + error (1, 0, + "-q or -Q must be specified before \"%s\"", + command_name); + break; + case 'l': + local = 1; + break; + case 'R': + local = 0; + break; + case 'P': + checkout_prune_dirs = 1; + break; + case 'p': + pipeout = 1; + run_module_prog = 0; /* don't run module prog when piping */ + noexec = 1; /* so no locks will be created */ + break; + case 'c': + cat = 1; + break; + case 'd': + where = optarg; + if (shorten == -1) + shorten = 1; + break; + case 's': + status = 1; + break; + case 'f': + force_tag_match = 0; + break; + case 'r': + tag = optarg; + checkout_prune_dirs = 1; + break; + case 'D': + date = Make_Date (optarg); + checkout_prune_dirs = 1; + break; + case 'j': + if (join_rev2) + error (1, 0, "only two -j options can be specified"); + if (join_rev1) + join_rev2 = optarg; + else + join_rev1 = optarg; + break; + case '?': + default: + usage (valid_usage); + break; + } + } + argc -= optind; + argv += optind; + + if (shorten == -1) + shorten = 0; + + if ((!(cat + status) && argc == 0) || ((cat + status) && argc != 0) + || (tag && date)) + usage (valid_usage); + + if (where && pipeout) + error (1, 0, "-d and -p are mutually exclusive"); + + if (strcmp (command_name, "export") == 0) + { + if (!tag && !date) + { + error (0, 0, "must specify a tag or date"); + usage (valid_usage); + } + if (tag && isdigit (tag[0])) + error (1, 0, "tag `%s' must be a symbolic tag", tag); +/* + * mhy 950615: -kv doesn't work for binaries with RCS keywords. + * Instead use the default provided in the RCS file (-ko for binaries). + */ +#if 0 + if (!options) + options = RCS_check_kflag ("v");/* -kv is default */ +#endif + } + + if (!safe_location()) { + error(1, 0, "Cannot check out files into the repository itself"); + } + +#ifdef CLIENT_SUPPORT + if (client_active) + { + int expand_modules; + + start_server (); + + ign_setup (); + + /* We have to expand names here because the "expand-modules" + directive to the server has the side-effect of having the + server send the check-in and update programs for the + various modules/dirs requested. If we turn this off and + simply request the names of the modules and directories (as + below in !expand_modules), those files (CVS/Checking.prog + or CVS/Update.prog) don't get created. Grrr. */ + + expand_modules = (!cat && !status && !pipeout + && supported_request ("expand-modules")); + + if (expand_modules) + { + /* This is done here because we need to read responses + from the server before we send the command checkout or + export files. */ + + client_expand_modules (argc, argv, local); + } + + if (!run_module_prog) send_arg ("-n"); + if (local) send_arg ("-l"); + if (pipeout) send_arg ("-p"); + if (!force_tag_match) send_arg ("-f"); + if (aflag) + send_arg("-A"); + if (!shorten) + send_arg("-N"); + if (checkout_prune_dirs && strcmp (command_name, "export") != 0) + send_arg("-P"); + client_prune_dirs = checkout_prune_dirs; + if (cat) + send_arg("-c"); + if (where != NULL) + { + option_with_arg ("-d", where); + } + if (status) + send_arg("-s"); + if (strcmp (command_name, "export") != 0 + && options != NULL + && options[0] != '\0') + send_arg (options); + option_with_arg ("-r", tag); + if (date) + client_senddate (date); + if (join_rev1 != NULL) + option_with_arg ("-j", join_rev1); + if (join_rev2 != NULL) + option_with_arg ("-j", join_rev2); + + if (expand_modules) + { + client_send_expansions (local); + } + else + { + int i; + for (i = 0; i < argc; ++i) + send_arg (argv[i]); + client_nonexpanded_setup (); + } + + send_to_server (strcmp (command_name, "export") == 0 ? + "export\012" : "co\012", + 0); + + return get_responses_and_close (); + } +#endif /* CLIENT_SUPPORT */ + + if (cat || status) + { + cat_module (status); + return (0); + } + db = open_module (); + + /* + * if we have more than one argument and where was specified, we make the + * where, cd into it, and try to shorten names as much as possible. + * Otherwise, we pass the where as a single argument to do_module. + */ + if (argc > 1 && where != NULL) + { + char repository[PATH_MAX]; + + (void) CVS_MKDIR (where, 0777); + if (chdir (where) < 0) + error (1, errno, "cannot chdir to %s", where); + preload_update_dir = xstrdup (where); + where = (char *) NULL; + if (!isfile (CVSADM)) + { + (void) sprintf (repository, "%s/%s/%s", CVSroot, CVSROOTADM, + CVSNULLREPOS); + if (!isfile (repository)) + { + mode_t omask; + omask = umask (cvsumask); + (void) CVS_MKDIR (repository, 0777); + (void) umask (omask); + } + + /* I'm not sure whether this check is redundant. */ + if (!isdir (repository)) + error (1, 0, "there is no repository %s", repository); + + Create_Admin (".", where, repository, + (char *) NULL, (char *) NULL); + if (!noexec) + { + FILE *fp; + + fp = open_file (CVSADM_ENTSTAT, "w+"); + if (fclose(fp) == EOF) + error(1, errno, "cannot close %s", CVSADM_ENTSTAT); +#ifdef SERVER_SUPPORT + if (server_active) + server_set_entstat (preload_update_dir, repository); +#endif + } + } + } + + /* + * if where was specified (-d) and we have not taken care of it already + * with the multiple arg stuff, and it was not a simple directory name + * but rather a path, we strip off everything but the last component and + * attempt to cd to the indicated place. where then becomes simply the + * last component + */ + if (where != NULL && strchr (where, '/') != NULL) + { + char *slash; + + slash = strrchr (where, '/'); + *slash = '\0'; + + if (chdir (where) < 0) + error (1, errno, "cannot chdir to %s", where); + + preload_update_dir = xstrdup (where); + + where = slash + 1; + if (*where == '\0') + where = NULL; + } + + for (i = 0; i < argc; i++) + err += do_module (db, argv[i], m_type, "Updating", checkout_proc, + where, shorten, local, run_module_prog, + (char *) NULL); + close_module (db); + return (err); +} + +static int +safe_location () +{ + char current[PATH_MAX]; + char hardpath[PATH_MAX+5]; + int x; + + x = readlink(CVSroot, hardpath, sizeof hardpath - 1); + if (x == -1) + { + strcpy(hardpath, CVSroot); + } + else + { + hardpath[x] = '\0'; + } + getwd (current); + if (strncmp(current, hardpath, strlen(hardpath)) == 0) + { + return (0); + } + return (1); +} + +/* + * process_module calls us back here so we do the actual checkout stuff + */ +/* ARGSUSED */ +static int +checkout_proc (pargc, argv, where, mwhere, mfile, shorten, + local_specified, omodule, msg) + int *pargc; + char **argv; + char *where; + char *mwhere; + char *mfile; + int shorten; + int local_specified; + char *omodule; + char *msg; +{ + int err = 0; + int which; + char *cp; + char *cp2; + char repository[PATH_MAX]; + char xwhere[PATH_MAX]; + char *oldupdate = NULL; + char *prepath; + char *realdirs; + + /* + * OK, so we're doing the checkout! Our args are as follows: + * argc,argv contain either dir or dir followed by a list of files + * where contains where to put it (if supplied by checkout) + * mwhere contains the module name or -d from module file + * mfile says do only that part of the module + * shorten = TRUE says shorten as much as possible + * omodule is the original arg to do_module() + */ + + /* set up the repository (maybe) for the bottom directory */ + (void) sprintf (repository, "%s/%s", CVSroot, argv[0]); + + /* save the original value of preload_update_dir */ + if (preload_update_dir != NULL) + oldupdate = xstrdup (preload_update_dir); + + /* fix up argv[] for the case of partial modules */ + if (mfile != NULL) + { + char file[PATH_MAX]; + + /* if mfile is really a path, straighten it out first */ + if ((cp = strrchr (mfile, '/')) != NULL) + { + *cp = 0; + (void) strcat (repository, "/"); + (void) strcat (repository, mfile); + + /* + * Now we need to fill in the where correctly. if !shorten, tack + * the rest of the path onto where if where is filled in + * otherwise tack the rest of the path onto mwhere and make that + * the where + * + * If shorten is enabled, we might use mwhere to set where if + * nobody set it yet, so we'll need to setup mwhere as the last + * component of the path we are tacking onto repository + */ + if (!shorten) + { + if (where != NULL) + (void) sprintf (xwhere, "%s/%s", where, mfile); + else + (void) sprintf (xwhere, "%s/%s", mwhere, mfile); + where = xwhere; + } + else + { + char *slash; + + if ((slash = strrchr (mfile, '/')) != NULL) + mwhere = slash + 1; + else + mwhere = mfile; + } + mfile = cp + 1; + } + + (void) sprintf (file, "%s/%s", repository, mfile); + if (isdir (file)) + { + + /* + * The portion of a module was a directory, so kludge up where to + * be the subdir, and fix up repository + */ + (void) strcpy (repository, file); + + /* + * At this point, if shorten is not enabled, we make where either + * where with mfile concatenated, or if where hadn't been set we + * set it to mwhere with mfile concatenated. + * + * If shorten is enabled and where hasn't been set yet, then where + * becomes mfile + */ + if (!shorten) + { + if (where != NULL) + (void) sprintf (xwhere, "%s/%s", where, mfile); + else + (void) sprintf (xwhere, "%s/%s", mwhere, mfile); + where = xwhere; + } + else if (where == NULL) + where = mfile; + } + else + { + int i; + + /* + * The portion of a module was a file, so kludge up argv to be + * correct + */ + for (i = 1; i < *pargc; i++)/* free the old ones */ + free (argv[i]); + argv[1] = xstrdup (mfile); /* set up the new one */ + *pargc = 2; + + /* where gets mwhere if where isn't set */ + if (where == NULL) + where = mwhere; + } + } + + /* + * if shorten is enabled and where isn't specified yet, we pluck the last + * directory component of argv[0] and make it the where + */ + if (shorten && where == NULL) + { + if ((cp = strrchr (argv[0], '/')) != NULL) + { + (void) strcpy (xwhere, cp + 1); + where = xwhere; + } + } + + /* if where is still NULL, use mwhere if set or the argv[0] dir */ + if (where == NULL) + { + if (mwhere) + where = mwhere; + else + { + (void) strcpy (xwhere, argv[0]); + where = xwhere; + } + } + + if (preload_update_dir != NULL) + { + char tmp[PATH_MAX]; + + (void) sprintf (tmp, "%s/%s", preload_update_dir, where); + free (preload_update_dir); + preload_update_dir = xstrdup (tmp); + } + else + preload_update_dir = xstrdup (where); + + /* + * At this point, where is the directory we want to build, repository is + * the repository for the lowest level of the path. + */ + + /* + * If we are sending everything to stdout, we can skip a whole bunch of + * work from here + */ + if (!pipeout) + { + + /* + * We need to tell build_dirs not only the path we want it to build, + * but also the repositories we want it to populate the path with. To + * accomplish this, we pass build_dirs a ``real path'' with valid + * repositories and a string to pre-pend based on how many path + * elements exist in where. Big Black Magic + */ + prepath = xstrdup (repository); + cp = strrchr (where, '/'); + cp2 = strrchr (prepath, '/'); + while (cp != NULL) + { + cp = findslash (where, cp - 1); + cp2 = findslash (prepath, cp2 - 1); + } + *cp2 = '\0'; + realdirs = cp2 + 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 (where, prepath, realdirs, *pargc <= 1) != 0) + { + error (0, 0, "ignoring module %s", omodule); + free (prepath); + free (preload_update_dir); + preload_update_dir = oldupdate; + return (1); + } + + /* clean up */ + free (prepath); + + /* set up the repository (or make sure the old one matches) */ + if (!isfile (CVSADM)) + { + FILE *fp; + + if (!noexec && *pargc > 1) + { + /* I'm not sure whether this check is redundant. */ + if (!isdir (repository)) + error (1, 0, "there is no repository %s", repository); + + Create_Admin (".", where, repository, + (char *) NULL, (char *) NULL); + fp = open_file (CVSADM_ENTSTAT, "w+"); + if (fclose(fp) == EOF) + error(1, errno, "cannot close %s", CVSADM_ENTSTAT); +#ifdef SERVER_SUPPORT + if (server_active) + server_set_entstat (where, repository); +#endif + } + else + { + /* I'm not sure whether this check is redundant. */ + if (!isdir (repository)) + error (1, 0, "there is no repository %s", repository); + + Create_Admin (".", where, repository, tag, date); + } + } + else + { + char *repos; + + /* get the contents of the previously existing repository */ + repos = Name_Repository ((char *) NULL, preload_update_dir); + if (fncmp (repository, repos) != 0) + { + error (0, 0, "existing repository %s does not match %s", + repos, repository); + error (0, 0, "ignoring module %s", omodule); + free (repos); + free (preload_update_dir); + preload_update_dir = oldupdate; + return (1); + } + free (repos); + } + } + + /* + * If we are going to be updating to stdout, we need to cd to the + * repository directory so the recursion processor can use the current + * directory as the place to find repository information + */ + if (pipeout) + { + if (chdir (repository) < 0) + { + error (0, errno, "cannot chdir to %s", repository); + free (preload_update_dir); + preload_update_dir = oldupdate; + return (1); + } + which = W_REPOS; + if (tag != NULL && !tag_validated) + { + tag_check_valid (tag, *pargc - 1, argv + 1, 0, aflag, NULL); + tag_validated = 1; + } + } + else + { + which = W_LOCAL | W_REPOS; + if (tag != NULL && !tag_validated) + { + tag_check_valid (tag, *pargc - 1, argv + 1, 0, aflag, + repository); + tag_validated = 1; + } + } + + if (tag != NULL || date != NULL) + which |= W_ATTIC; + + /* FIXME: We don't call tag_check_valid on join_rev1 and join_rev2 + yet (make sure to handle ':' correctly if we do, though). */ + + /* + * if we are going to be recursive (building dirs), go ahead and call the + * update recursion processor. We will be recursive unless either local + * only was specified, or we were passed arguments + */ + if (!(local_specified || *pargc > 1)) + { + if (strcmp (command_name, "export") != 0 && !pipeout) + history_write ('O', preload_update_dir, tag ? tag : date, where, + repository); + err += do_update (0, (char **) NULL, options, tag, date, + force_tag_match, 0 /* !local */ , + 1 /* update -d */ , aflag, checkout_prune_dirs, + pipeout, which, join_rev1, join_rev2, + preload_update_dir); + free (preload_update_dir); + preload_update_dir = oldupdate; + return (err); + } + + if (!pipeout) + { + int i; + List *entries; + + /* we are only doing files, so register them */ + entries = Entries_Open (0); + for (i = 1; i < *pargc; i++) + { + char *line; + char *user; + Vers_TS *vers; + + user = argv[i]; + vers = Version_TS (repository, options, tag, date, user, + force_tag_match, 0, entries, (RCSNode *) NULL); + if (vers->ts_user == NULL) + { + line = xmalloc (strlen (user) + 15); + (void) sprintf (line, "Initial %s", user); + Register (entries, user, vers->vn_rcs ? vers->vn_rcs : "0", + line, vers->options, vers->tag, + vers->date, (char *) 0); + free (line); + } + freevers_ts (&vers); + } + + Entries_Close (entries); + } + + /* Don't log "export", just regular "checkouts" */ + if (strcmp (command_name, "export") != 0 && !pipeout) + history_write ('O', preload_update_dir, (tag ? tag : date), where, + repository); + + /* go ahead and call update now that everything is set */ + err += do_update (*pargc - 1, argv + 1, options, tag, date, + force_tag_match, local_specified, 1 /* update -d */, + aflag, checkout_prune_dirs, pipeout, which, join_rev1, + join_rev2, preload_update_dir); + free (preload_update_dir); + preload_update_dir = oldupdate; + return (err); +} + +static char * +findslash (start, p) + char *start; + char *p; +{ + while (p >= start && *p != '/') + p--; + if (p < start) + return (NULL); + else + return (p); +} + +/* + * build all the dirs along the path to dir with CVS subdirs with appropriate + * repositories and Entries.Static files + */ +static int +build_dirs_and_chdir (dir, prepath, realdir, sticky) + char *dir; + char *prepath; + char *realdir; + int sticky; +{ + FILE *fp; + char repository[PATH_MAX]; + char path[PATH_MAX]; + char path2[PATH_MAX]; + char *slash; + char *slash2; + char *cp; + char *cp2; + + (void) strcpy (path, dir); + (void) strcpy (path2, realdir); + for (cp = path, cp2 = path2; + (slash = strchr (cp, '/')) != NULL && (slash2 = strchr (cp2, '/')) != NULL; + cp = slash + 1, cp2 = slash2 + 1) + { + *slash = '\0'; + *slash2 = '\0'; + (void) CVS_MKDIR (cp, 0777); + if (chdir (cp) < 0) + { + error (0, errno, "cannot chdir to %s", cp); + return (1); + } + if (!isfile (CVSADM) && strcmp (command_name, "export") != 0) + { + (void) sprintf (repository, "%s/%s", prepath, path2); + /* I'm not sure whether this check is redundant. */ + if (!isdir (repository)) + error (1, 0, "there is no repository %s", repository); + Create_Admin (".", path, repository, sticky ? (char *) NULL : tag, + sticky ? (char *) NULL : date); + if (!noexec) + { + fp = open_file (CVSADM_ENTSTAT, "w+"); + if (fclose(fp) == EOF) + error(1, errno, "cannot close %s", CVSADM_ENTSTAT); +#ifdef SERVER_SUPPORT + if (server_active) + server_set_entstat (path, repository); +#endif + } + } + *slash = '/'; + *slash2 = '/'; + } + (void) CVS_MKDIR (cp, 0777); + if (chdir (cp) < 0) + { + error (0, errno, "cannot chdir to %s", cp); + return (1); + } + return (0); +} diff --git a/contrib/cvs/src/classify.c b/contrib/cvs/src/classify.c new file mode 100644 index 0000000..924314b --- /dev/null +++ b/contrib/cvs/src/classify.c @@ -0,0 +1,493 @@ +/* + * Copyright (c) 1992, Brian Berliner and Jeff Polk + * Copyright (c) 1989-1992, Brian Berliner + * + * You may distribute under the terms of the GNU General Public License as + * specified in the README file that comes with the CVS 1.4 kit. + * + */ + +#include "cvs.h" + +#ifdef SERVER_SUPPORT +static void sticky_ck PROTO((char *file, int aflag, Vers_TS * vers, + List * entries, + char *repository, char *update_dir)); +#else +static void sticky_ck PROTO((char *file, int aflag, Vers_TS * vers, List * entries)); +#endif + +/* + * Classify the state of a file + */ +Ctype +Classify_File (file, tag, date, options, force_tag_match, aflag, repository, + entries, rcsnode, versp, update_dir, pipeout) + char *file; + char *tag; + char *date; + char *options; + int force_tag_match; + int aflag; + char *repository; + List *entries; + RCSNode *rcsnode; + Vers_TS **versp; + char *update_dir; + int pipeout; +{ + Vers_TS *vers; + Ctype ret; + char *fullname; + + fullname = xmalloc (strlen (update_dir) + strlen (file) + 10); + if (update_dir[0] == '\0') + strcpy (fullname, file); + else + sprintf (fullname, "%s/%s", update_dir, file); + + /* get all kinds of good data about the file */ + vers = Version_TS (repository, options, tag, date, file, + force_tag_match, 0, entries, rcsnode); + + if (vers->vn_user == NULL) + { + /* No entry available, ts_rcs is invalid */ + if (vers->vn_rcs == NULL) + { + /* there is no RCS file either */ + if (vers->ts_user == NULL) + { + /* there is no user file */ + if (!force_tag_match || !(vers->tag || vers->date)) + if (!really_quiet) + error (0, 0, "nothing known about %s", fullname); + ret = T_UNKNOWN; + } + else + { + /* there is a user file */ + if (!force_tag_match || !(vers->tag || vers->date)) + if (!really_quiet) + error (0, 0, "use `cvs add' to create an entry for %s", + fullname); + ret = T_UNKNOWN; + } + } + else if (RCS_isdead (vers->srcfile, vers->vn_rcs)) + { + if (vers->ts_user == NULL) + /* + * Logically seems to me this should be T_UPTODATE. + * But the joining code in update.c seems to expect + * T_CHECKOUT, and that is what has traditionally been + * returned for this case. + */ + ret = T_CHECKOUT; + else + { + error (0, 0, "use `cvs add' to create an entry for %s", + fullname); + ret = T_UNKNOWN; + } + } + else + { + /* there is an rcs file */ + + if (vers->ts_user == NULL) + { + /* There is no user file; needs checkout */ + ret = T_CHECKOUT; + } + else + { + if (pipeout) + { + /* + * The user file doesn't necessarily have anything + * to do with this. + */ + ret = T_CHECKOUT; + } + /* + * There is a user file; print a warning and add it to the + * conflict list, only if it is indeed different from what we + * plan to extract + */ + else if (No_Difference (file, vers, entries, + repository, update_dir)) + { + /* the files were different so it is a conflict */ + if (!really_quiet) + error (0, 0, "move away %s; it is in the way", + fullname); + ret = T_CONFLICT; + } + else + /* since there was no difference, still needs checkout */ + ret = T_CHECKOUT; + } + } + } + else if (strcmp (vers->vn_user, "0") == 0) + { + /* An entry for a new-born file; ts_rcs is dummy */ + + if (vers->ts_user == NULL) + { + /* + * There is no user file, but there should be one; remove the + * entry + */ + if (!really_quiet) + error (0, 0, "warning: new-born %s has disappeared", fullname); + ret = T_REMOVE_ENTRY; + } + else + { + /* There is a user file */ + + if (vers->vn_rcs == NULL) + /* There is no RCS file, added file */ + ret = T_ADDED; + else if (RCS_isdead (vers->srcfile, vers->vn_rcs)) + /* we are resurrecting. */ + ret = T_ADDED; + else + { + if (vers->srcfile->flags & INATTIC + && vers->srcfile->flags & VALID) + { + /* This file has been added on some branch other than + the one we are looking at. In the branch we are + looking at, the file was already valid. */ + if (!really_quiet) + error (0, 0, + "\ +conflict: %s has been added, but already exists", + fullname); + } + else + { + /* + * There is an RCS file, so someone else must have checked + * one in behind our back; conflict + */ + if (!really_quiet) + error (0, 0, + "\ +conflict: %s created independently by second party", + fullname); + } + ret = T_CONFLICT; + } + } + } + else if (vers->vn_user[0] == '-') + { + /* An entry for a removed file, ts_rcs is invalid */ + + if (vers->ts_user == NULL) + { + char tmp[PATH_MAX]; + + /* There is no user file (as it should be) */ + + (void) sprintf (tmp, "-%s", vers->vn_rcs ? vers->vn_rcs : ""); + + if (vers->vn_rcs == NULL) + { + + /* + * There is no RCS file; this is all-right, but it has been + * removed independently by a second party; remove the entry + */ + ret = T_REMOVE_ENTRY; + } + else if (strcmp (tmp, vers->vn_user) == 0) + + /* + * The RCS file is the same version as the user file was, and + * that's OK; remove it + */ + ret = T_REMOVED; + else + { + + /* + * The RCS file is a newer version than the removed user file + * and this is definitely not OK; make it a conflict. + */ + if (!really_quiet) + error (0, 0, + "conflict: removed %s was modified by second party", + fullname); + ret = T_CONFLICT; + } + } + else + { + /* The user file shouldn't be there */ + if (!really_quiet) + error (0, 0, "%s should be removed and is still there", + fullname); + ret = T_REMOVED; + } + } + else + { + /* A normal entry, TS_Rcs is valid */ + if (vers->vn_rcs == NULL) + { + /* There is no RCS file */ + + if (vers->ts_user == NULL) + { + /* There is no user file, so just remove the entry */ + if (!really_quiet) + error (0, 0, "warning: %s is not (any longer) pertinent", + fullname); + ret = T_REMOVE_ENTRY; + } + else if (strcmp (vers->ts_user, vers->ts_rcs) == 0) + { + + /* + * The user file is still unmodified, so just remove it from + * the entry list + */ + if (!really_quiet) + error (0, 0, "%s is no longer in the repository", + fullname); + ret = T_REMOVE_ENTRY; + } + else + { + /* + * The user file has been modified and since it is no longer + * in the repository, a conflict is raised + */ + if (No_Difference (file, vers, entries, + repository, update_dir)) + { + /* they are different -> conflict */ + if (!really_quiet) + error (0, 0, + "conflict: %s is modified but no longer in the repository", + fullname); + ret = T_CONFLICT; + } + else + { + /* they weren't really different */ + if (!really_quiet) + error (0, 0, + "warning: %s is not (any longer) pertinent", + fullname); + ret = T_REMOVE_ENTRY; + } + } + } + else if (strcmp (vers->vn_rcs, vers->vn_user) == 0) + { + /* The RCS file is the same version as the user file */ + + if (vers->ts_user == NULL) + { + + /* + * There is no user file, so note that it was lost and + * extract a new version + */ + if (strcmp (command_name, "update") == 0) + if (!really_quiet) + error (0, 0, "warning: %s was lost", fullname); + ret = T_CHECKOUT; + } + else if (strcmp (vers->ts_user, vers->ts_rcs) == 0) + { + + /* + * The user file is still unmodified, so nothing special at + * all to do -- no lists updated, unless the sticky -k option + * has changed. If the sticky tag has changed, we just need + * to re-register the entry + */ + if (vers->entdata->options && + strcmp (vers->entdata->options, vers->options) != 0) + ret = T_CHECKOUT; + else + { +#ifdef SERVER_SUPPORT + sticky_ck (file, aflag, vers, entries, + repository, update_dir); +#else + sticky_ck (file, aflag, vers, entries); +#endif + ret = T_UPTODATE; + } + } + else + { + + /* + * The user file appears to have been modified, but we call + * No_Difference to verify that it really has been modified + */ + if (No_Difference (file, vers, entries, + repository, update_dir)) + { + + /* + * they really are different; modified if we aren't + * changing any sticky -k options, else needs merge + */ +#ifdef XXX_FIXME_WHEN_RCSMERGE_IS_FIXED + if (strcmp (vers->entdata->options ? + vers->entdata->options : "", vers->options) == 0) + ret = T_MODIFIED; + else + ret = T_NEEDS_MERGE; +#else + ret = T_MODIFIED; +#ifdef SERVER_SUPPORT + sticky_ck (file, aflag, vers, entries, + repository, update_dir); +#else + sticky_ck (file, aflag, vers, entries); +#endif /* SERVER_SUPPORT */ +#endif + } + else + { + /* file has not changed; check out if -k changed */ + if (strcmp (vers->entdata->options ? + vers->entdata->options : "", vers->options) != 0) + { + ret = T_CHECKOUT; + } + else + { + + /* + * else -> note that No_Difference will Register the + * file already for us, using the new tag/date. This + * is the desired behaviour + */ + ret = T_UPTODATE; + } + } + } + } + else + { + /* The RCS file is a newer version than the user file */ + + if (vers->ts_user == NULL) + { + /* There is no user file, so just get it */ + + if (strcmp (command_name, "update") == 0) + if (!really_quiet) + error (0, 0, "warning: %s was lost", fullname); + ret = T_CHECKOUT; + } + else if (strcmp (vers->ts_user, vers->ts_rcs) == 0) + { + + /* + * The user file is still unmodified, so just get it as well + */ +#ifdef SERVER_SUPPORT + if (strcmp (vers->entdata->options ? + vers->entdata->options : "", vers->options) != 0 + || (vers->srcfile != NULL + && (vers->srcfile->flags & INATTIC) != 0)) + ret = T_CHECKOUT; + else + ret = T_PATCH; +#else + ret = T_CHECKOUT; +#endif + } + else + { + if (No_Difference (file, vers, entries, + repository, update_dir)) + /* really modified, needs to merge */ + ret = T_NEEDS_MERGE; +#ifdef SERVER_SUPPORT + else if ((strcmp (vers->entdata->options ? + vers->entdata->options : "", vers->options) + != 0) + || (vers->srcfile != NULL + && (vers->srcfile->flags & INATTIC) != 0)) + /* not really modified, check it out */ + ret = T_CHECKOUT; + else + ret = T_PATCH; +#else + else + /* not really modified, check it out */ + ret = T_CHECKOUT; +#endif + } + } + } + + /* free up the vers struct, or just return it */ + if (versp != (Vers_TS **) NULL) + *versp = vers; + else + freevers_ts (&vers); + + free (fullname); + + /* return the status of the file */ + return (ret); +} + +static void +#ifdef SERVER_SUPPORT +sticky_ck (file, aflag, vers, entries, repository, update_dir) +#else +sticky_ck (file, aflag, vers, entries) +#endif + char *file; + int aflag; + Vers_TS *vers; + List *entries; +#ifdef SERVER_SUPPORT + char *repository; + char *update_dir; +#endif +{ + if (aflag || vers->tag || vers->date) + { + char *enttag = vers->entdata->tag; + char *entdate = vers->entdata->date; + + if ((enttag && vers->tag && strcmp (enttag, vers->tag)) || + ((enttag && !vers->tag) || (!enttag && vers->tag)) || + (entdate && vers->date && strcmp (entdate, vers->date)) || + ((entdate && !vers->date) || (!entdate && vers->date))) + { + Register (entries, file, vers->vn_user, vers->ts_rcs, + vers->options, vers->tag, vers->date, vers->ts_conflict); + +#ifdef SERVER_SUPPORT + if (server_active) + { + /* We need to update the entries line on the client side. + It is possible we will later update it again via + server_updated or some such, but that is OK. */ + server_update_entries + (file, update_dir, repository, + strcmp (vers->ts_rcs, vers->ts_user) == 0 ? + SERVER_UPDATED : SERVER_MERGED); + } +#endif + } + } +} diff --git a/contrib/cvs/src/client.c b/contrib/cvs/src/client.c new file mode 100644 index 0000000..c0557ce --- /dev/null +++ b/contrib/cvs/src/client.c @@ -0,0 +1,4490 @@ +/* CVS client-related stuff. */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif /* HAVE_CONFIG_H */ + +#include "cvs.h" +#include "getline.h" +#include "edit.h" + +#ifdef CLIENT_SUPPORT + +#include "md5.h" + +#if defined(AUTH_CLIENT_SUPPORT) || HAVE_KERBEROS || USE_DIRECT_TCP +# ifdef HAVE_WINSOCK_H +# include <winsock.h> +# else /* No winsock.h */ +# include <sys/socket.h> +# include <netinet/in.h> +# include <netdb.h> +# endif /* No winsock.h */ +#endif /* defined(AUTH_CLIENT_SUPPORT) || HAVE_KERBEROS || USE_DIRECT_TCP */ + +#ifdef AUTH_CLIENT_SUPPORT +char *get_cvs_password PROTO((char *user, char *host, char *cvsrooot)); +#endif /* AUTH_CLIENT_SUPPORT */ + +#if HAVE_KERBEROS || USE_DIRECT_TCP +#define CVS_PORT 1999 + +#if HAVE_KERBEROS +#include <krb.h> + +extern char *krb_realmofhost (); +#ifndef HAVE_KRB_GET_ERR_TEXT +#define krb_get_err_text(status) krb_err_txt[status] +#endif /* HAVE_KRB_GET_ERR_TEXT */ +#endif /* HAVE_KERBEROS */ + +#endif /* HAVE_KERBEROS || USE_DIRECT_TCP */ + +static void add_prune_candidate PROTO((char *)); + +/* All the commands. */ +int add PROTO((int argc, char **argv)); +int admin PROTO((int argc, char **argv)); +int checkout PROTO((int argc, char **argv)); +int commit PROTO((int argc, char **argv)); +int diff PROTO((int argc, char **argv)); +int history PROTO((int argc, char **argv)); +int import PROTO((int argc, char **argv)); +int cvslog PROTO((int argc, char **argv)); +int patch PROTO((int argc, char **argv)); +int release PROTO((int argc, char **argv)); +int cvsremove PROTO((int argc, char **argv)); +int rtag PROTO((int argc, char **argv)); +int status PROTO((int argc, char **argv)); +int tag PROTO((int argc, char **argv)); +int update PROTO((int argc, char **argv)); + +/* All the response handling functions. */ +static void handle_ok PROTO((char *, int)); +static void handle_error PROTO((char *, int)); +static void handle_valid_requests PROTO((char *, int)); +static void handle_checked_in PROTO((char *, int)); +static void handle_new_entry PROTO((char *, int)); +static void handle_checksum PROTO((char *, int)); +static void handle_copy_file PROTO((char *, int)); +static void handle_updated PROTO((char *, int)); +static void handle_merged PROTO((char *, int)); +static void handle_patched PROTO((char *, int)); +static void handle_removed PROTO((char *, int)); +static void handle_remove_entry PROTO((char *, int)); +static void handle_set_static_directory PROTO((char *, int)); +static void handle_clear_static_directory PROTO((char *, int)); +static void handle_set_sticky PROTO((char *, int)); +static void handle_clear_sticky PROTO((char *, int)); +static void handle_set_checkin_prog PROTO((char *, int)); +static void handle_set_update_prog PROTO((char *, int)); +static void handle_module_expansion PROTO((char *, int)); +static void handle_m PROTO((char *, int)); +static void handle_e PROTO((char *, int)); +static void handle_notified PROTO((char *, int)); + +static size_t try_read_from_server PROTO ((char *, size_t)); +#endif /* CLIENT_SUPPORT */ + +#if defined(CLIENT_SUPPORT) || defined(SERVER_SUPPORT) + +/* Shared with server. */ + +/* + * Return a malloc'd, '\0'-terminated string + * corresponding to the mode in SB. + */ +char * +#ifdef __STDC__ +mode_to_string (mode_t mode) +#else /* ! __STDC__ */ +mode_to_string (mode) + mode_t mode; +#endif /* __STDC__ */ +{ + char buf[18], u[4], g[4], o[4]; + int i; + + i = 0; + if (mode & S_IRUSR) u[i++] = 'r'; + if (mode & S_IWUSR) u[i++] = 'w'; + if (mode & S_IXUSR) u[i++] = 'x'; + u[i] = '\0'; + + i = 0; + if (mode & S_IRGRP) g[i++] = 'r'; + if (mode & S_IWGRP) g[i++] = 'w'; + if (mode & S_IXGRP) g[i++] = 'x'; + g[i] = '\0'; + + i = 0; + if (mode & S_IROTH) o[i++] = 'r'; + if (mode & S_IWOTH) o[i++] = 'w'; + if (mode & S_IXOTH) o[i++] = 'x'; + o[i] = '\0'; + + sprintf(buf, "u=%s,g=%s,o=%s", u, g, o); + return xstrdup(buf); +} + +/* + * Change mode of FILENAME to MODE_STRING. + * Returns 0 for success or errno code. + */ +int +change_mode (filename, mode_string) + char *filename; + char *mode_string; +{ +#ifdef CHMOD_BROKEN + char *p; + int writeable = 0; + + /* We can only distinguish between + 1) readable + 2) writeable + 3) Picasso's "Blue Period" + We handle the first two. */ + p = mode_string; + while (*p != '\0') + { + if ((p[0] == 'u' || p[0] == 'g' || p[0] == 'o') && p[1] == '=') + { + char *q = p + 2; + while (*q != ',' && *q != '\0') + { + if (*q == 'w') + writeable = 1; + ++q; + } + } + /* Skip to the next field. */ + while (*p != ',' && *p != '\0') + ++p; + if (*p == ',') + ++p; + } + + xchmod (filename, writeable); + return 0; + +#else /* ! CHMOD_BROKEN */ + + char *p; + mode_t mode = 0; + + p = mode_string; + while (*p != '\0') + { + if ((p[0] == 'u' || p[0] == 'g' || p[0] == 'o') && p[1] == '=') + { + int can_read = 0, can_write = 0, can_execute = 0; + char *q = p + 2; + while (*q != ',' && *q != '\0') + { + if (*q == 'r') + can_read = 1; + else if (*q == 'w') + can_write = 1; + else if (*q == 'x') + can_execute = 1; + ++q; + } + if (p[0] == 'u') + { + if (can_read) + mode |= S_IRUSR; + if (can_write) + mode |= S_IWUSR; + if (can_execute) + mode |= S_IXUSR; + } + else if (p[0] == 'g') + { + if (can_read) + mode |= S_IRGRP; + if (can_write) + mode |= S_IWGRP; + if (can_execute) + mode |= S_IXGRP; + } + else if (p[0] == 'o') + { + if (can_read) + mode |= S_IROTH; + if (can_write) + mode |= S_IWOTH; + if (can_execute) + mode |= S_IXOTH; + } + } + /* Skip to the next field. */ + while (*p != ',' && *p != '\0') + ++p; + if (*p == ',') + ++p; + } + + if (chmod (filename, mode) < 0) + return errno; + return 0; +#endif /* ! CHMOD_BROKEN */ +} + +#endif /* CLIENT_SUPPORT or SERVER_SUPPORT */ + +#ifdef CLIENT_SUPPORT + +/* The host part of CVSROOT. */ +static char *server_host; +/* The user part of CVSROOT */ +static char *server_user; +/* The repository part of CVSROOT. */ +static char *server_cvsroot; + +int client_active; + +int client_prune_dirs; + +static int cvsroot_parsed = 0; + +static List *ignlist = (List *) NULL; + +/* Set server_host and server_cvsroot. */ +static void +parse_cvsroot () +{ + char *p; +#ifdef AUTH_CLIENT_SUPPORT + static char *access_method; +#endif /* AUTH_CLIENT_SUPPORT */ + + /* Don't go through the trouble twice. */ + if (cvsroot_parsed) + return; + + server_host = xstrdup (CVSroot); + +#ifdef AUTH_CLIENT_SUPPORT + if ((server_host[0] == ':')) + { + /* Access method specified, as in + * "cvs -d :pserver:user@host:/path". + * We need to get past that part of CVSroot before parsing the + * rest of it. + */ + access_method = p = &(server_host[1]); + + if (! *access_method) + error (1, 0, "bad CVSroot: %s", CVSroot); + + if (! *(p = strchr (access_method, ':'))) + error (1, 0, "bad CVSroot: %s", CVSroot); + + *p = '\0'; + p++; + + server_host = p; + + if (! *server_host) + error (1, 0, "bad CVSroot: %s", CVSroot); + + if (strcmp (access_method, "pserver") == 0) + use_authenticating_server = 1; + else + error (1, 0, "unknown access method: %s", access_method); + } +#endif /* AUTH_CLIENT_SUPPORT */ + + /* First get just the pathname. */ + server_cvsroot = strchr (server_host, ':'); + *server_cvsroot = '\0'; + ++server_cvsroot; + + /* Then deal with host and possible user. */ + if ( (p = strchr (server_host, '@')) == NULL) + { + server_user = NULL; + } + else + { + server_user = server_host; + server_host = p; + ++server_host; + *p = '\0'; + } + + client_active = 1; + cvsroot_parsed = 1; +} + +#ifdef NO_SOCKET_TO_FD +/* Under certain circumstances, we must communicate with the server + via a socket using send() and recv(). This is because under some + operating systems (OS/2 and Windows 95 come to mind), a socket + cannot be converted to a file descriptor -- it must be treated as a + socket and nothing else. */ +static int use_socket_style = 0; +static int server_sock; +#endif /* NO_SOCKET_TO_FD */ + +/* Stream to write to the server. */ +static FILE *to_server; +/* Stream to read from the server. */ +static FILE *from_server; + +/* We might want to log client/server traffic. */ +static FILE *from_server_logfile; +static FILE *to_server_logfile; + +#if ! RSH_NOT_TRANSPARENT +/* Process ID of rsh subprocess. */ +static int rsh_pid = -1; +#endif /* ! RSH_NOT_TRANSPARENT */ + + +/* + * Read a line from the server. Result does not include the terminating \n. + * + * Space for the result is malloc'd and should be freed by the caller. + * + * Returns number of bytes read. If EOF_OK, then return 0 on end of file, + * else end of file is an error. + */ +static int +read_line (resultp, eof_ok) + char **resultp; + int eof_ok; +{ + int c; + char *result; + size_t input_index = 0; + size_t result_size = 80; + +#ifdef NO_SOCKET_TO_FD + if (! use_socket_style) +#endif /* NO_SOCKET_TO_FD */ + fflush (to_server); + + result = (char *) xmalloc (result_size); + + while (1) + { + +#ifdef NO_SOCKET_TO_FD + if (use_socket_style) + { + char ch; + /* Yes, this sucks performance-wise. Short of implementing + our own buffering, I'm not sure how to effect a big + improvement. We could at least avoid calling + read_from_server() for each character if we were willing + to duplicate a lot of its code, but I'm not sure that's + worth it. */ + read_from_server (&ch, 1); + c = ch; + } + else +#endif /* NO_SOCKET_TO_FD */ + c = getc (from_server); + + if (c == EOF) + { + free (result); + +#ifdef NO_SOCKET_TO_FD + if (! use_socket_style) +#endif /* NO_SOCKET_TO_FD */ + if (ferror (from_server)) + error (1, errno, "reading from server"); + + /* It's end of file. */ + if (eof_ok) + return 0; + else + error (1, 0, "end of file from server (consult above messages if any)"); + } + + if (c == '\n') + break; + + result[input_index++] = c; + while (input_index + 1 >= result_size) + { + result_size *= 2; + result = (char *) xrealloc (result, result_size); + } + } + + if (resultp) + *resultp = result; + + /* Terminate it just for kicks, but we *can* deal with embedded NULs. */ + result[input_index] = '\0'; + +#ifdef NO_SOCKET_TO_FD + if (! use_socket_style) +#endif /* NO_SOCKET_TO_FD */ + { + /* + * If we're using socket style, then everything has already + * been logged because read_from_server() was used to get the + * individual chars, and read_from_server() logs already. + */ + if (from_server_logfile) + { + if (fwrite (result, 1, input_index, from_server_logfile) + < input_index) + error (0, errno, "writing to from-server logfile"); + putc ('\n', from_server_logfile); + } + } + + if (resultp == NULL) + free (result); + return input_index; +} + +#endif /* CLIENT_SUPPORT */ + + +#if defined(CLIENT_SUPPORT) || defined(SERVER_SUPPORT) + +/* + * Zero if compression isn't supported or requested; non-zero to indicate + * a compression level to request from gzip. + */ +int gzip_level; + +int filter_through_gzip (fd, dir, level, pidp) + int fd, dir, level; + pid_t *pidp; +{ + static char buf[5] = "-"; + static char *gzip_argv[3] = { "gzip", buf }; + + sprintf (buf+1, "%d", level); + return filter_stream_through_program (fd, dir, &gzip_argv[0], pidp); +} + +int filter_through_gunzip (fd, dir, pidp) + int fd, dir; + pid_t *pidp; +{ + static char *gunzip_argv[3] = { "gunzip", "-d" }; + return filter_stream_through_program (fd, dir, &gunzip_argv[0], pidp); +} + +#endif /* CLIENT_SUPPORT or SERVER_SUPPORT */ + +#ifdef CLIENT_SUPPORT + +/* + * The Repository for the top level of this command (not necessarily + * the CVSROOT, just the current directory at the time we do it). + */ +static char *toplevel_repos; + +/* Working directory when we first started. */ +char toplevel_wd[PATH_MAX]; + +static void +handle_ok (args, len) + char *args; + int len; +{ + return; +} + +static void +handle_error (args, len) + char *args; + int len; +{ + int something_printed; + + /* + * First there is a symbolic error code followed by a space, which + * we ignore. + */ + char *p = strchr (args, ' '); + if (p == NULL) + { + error (0, 0, "invalid data from cvs server"); + return; + } + ++p; + len -= p - args; + something_printed = 0; + for (; len > 0; --len) + { + something_printed = 1; + putc (*p++, stderr); + } + if (something_printed) + putc ('\n', stderr); +} + +static void +handle_valid_requests (args, len) + char *args; + int len; +{ + char *p = args; + char *q; + struct request *rq; + do + { + q = strchr (p, ' '); + if (q != NULL) + *q++ = '\0'; + for (rq = requests; rq->name != NULL; ++rq) + { + if (strcmp (rq->name, p) == 0) + break; + } + if (rq->name == NULL) + /* + * It is a request we have never heard of (and thus never + * will want to use). So don't worry about it. + */ + ; + else + { + if (rq->status == rq_enableme) + { + /* + * Server wants to know if we have this, to enable the + * feature. + */ + send_to_server (rq->name, 0); + send_to_server ("\012", 0); + + if (!strcmp("UseUnchanged",rq->name)) + use_unchanged = 1; + } + else + rq->status = rq_supported; + } + p = q; + } while (q != NULL); + for (rq = requests; rq->name != NULL; ++rq) + { + if (rq->status == rq_essential) + error (1, 0, "request `%s' not supported by server", rq->name); + else if (rq->status == rq_optional) + rq->status = rq_not_supported; + } +} + +static int use_directory = -1; + +static char *get_short_pathname PROTO((const char *)); + +static char * +get_short_pathname (name) + const char *name; +{ + const char *retval; + if (use_directory) + return (char *) name; + if (strncmp (name, toplevel_repos, strlen (toplevel_repos)) != 0) + error (1, 0, "server bug: name `%s' doesn't specify file in `%s'", + name, toplevel_repos); + retval = name + strlen (toplevel_repos) + 1; + if (retval[-1] != '/') + error (1, 0, "server bug: name `%s' doesn't specify file in `%s'", + name, toplevel_repos); + return (char *) retval; +} + +/* + * Do all the processing for PATHNAME, where pathname consists of the + * repository and the filename. The parameters we pass to FUNC are: + * DATA is just the DATA parameter which was passed to + * call_in_directory; ENT_LIST is a pointer to an entries list (which + * we manage the storage for); SHORT_PATHNAME is the pathname of the + * file relative to the (overall) directory in which the command is + * taking place; and FILENAME is the filename portion only of + * SHORT_PATHNAME. When we call FUNC, the curent directory points to + * the directory portion of SHORT_PATHNAME. */ + +static char *last_dir_name; + +static void +call_in_directory (pathname, func, data) + char *pathname; + void (*func) PROTO((char *data, List *ent_list, char *short_pathname, + char *filename)); + char *data; +{ + static List *last_entries; + + char *dir_name; + char *filename; + /* Just the part of pathname relative to toplevel_repos. */ + char *short_pathname = get_short_pathname (pathname); + char *p; + + /* + * Do the whole descent in parallel for the repositories, so we + * know what to put in CVS/Repository files. I'm not sure the + * full hair is necessary since the server does a similar + * computation; I suspect that we only end up creating one + * directory at a time anyway. + * + * Also note that we must *only* worry about this stuff when we + * are creating directories; `cvs co foo/bar; cd foo/bar; cvs co + * CVSROOT; cvs update' is legitimate, but in this case + * foo/bar/CVSROOT/CVS/Repository is not a subdirectory of + * foo/bar/CVS/Repository. + */ + char *reposname; + char *short_repos; + char *reposdirname; + char *rdirp; + int reposdirname_absolute; + + reposname = NULL; + if (use_directory) + read_line (&reposname, 0); + + reposdirname_absolute = 0; + if (reposname != NULL) + { + if (strncmp (reposname, toplevel_repos, strlen (toplevel_repos)) != 0) + { + reposdirname_absolute = 1; + short_repos = reposname; + } + else + { + short_repos = reposname + strlen (toplevel_repos) + 1; + if (short_repos[-1] != '/') + { + reposdirname_absolute = 1; + short_repos = reposname; + } + } + } + else + { + short_repos = short_pathname; + } + reposdirname = xstrdup (short_repos); + p = strrchr (reposdirname, '/'); + if (p == NULL) + { + reposdirname = xrealloc (reposdirname, 2); + reposdirname[0] = '.'; reposdirname[1] = '\0'; + } + else + *p = '\0'; + + dir_name = xstrdup (short_pathname); + p = strrchr (dir_name, '/'); + if (p == NULL) + { + dir_name = xrealloc (dir_name, 2); + dir_name[0] = '.'; dir_name[1] = '\0'; + } + else + *p = '\0'; + if (client_prune_dirs) + add_prune_candidate (dir_name); + + filename = strrchr (short_repos, '/'); + if (filename == NULL) + filename = short_repos; + else + ++filename; + + if (reposname != NULL) + { + /* This is the use_directory case. */ + + short_pathname = xmalloc (strlen (pathname) + strlen (filename) + 5); + strcpy (short_pathname, pathname); + strcat (short_pathname, filename); + } + + if (last_dir_name == NULL + || strcmp (last_dir_name, dir_name) != 0) + { + if (last_dir_name) + free (last_dir_name); + last_dir_name = dir_name; + + if (toplevel_wd[0] == '\0') + if (getwd (toplevel_wd) == NULL) + error (1, 0, + "could not get working directory: %s", toplevel_wd); + + if (chdir (toplevel_wd) < 0) + error (1, errno, "could not chdir to %s", toplevel_wd); + if (chdir (dir_name) < 0) + { + char *dir; + char *dirp; + + if (! existence_error (errno)) + error (1, errno, "could not chdir to %s", dir_name); + + /* Directory does not exist, we need to create it. */ + dir = xmalloc (strlen (dir_name) + 1); + dirp = dir_name; + rdirp = reposdirname; + + /* This algorithm makes nested directories one at a time + and create CVS administration files in them. For + example, we're checking out foo/bar/baz from the + repository: + + 1) create foo, point CVS/Repository to <root>/foo + 2) .. foo/bar .. <root>/foo/bar + 3) .. foo/bar/baz .. <root>/foo/bar/baz + + As you can see, we're just stepping along DIR_NAME (with + DIRP) and REPOSDIRNAME (with RDIRP) respectively. + + We need to be careful when we are checking out a + module, however, since DIR_NAME and REPOSDIRNAME are not + going to be the same. Since modules will not have any + slashes in their names, we should watch the output of + STRCHR to decide whether or not we should use STRCHR on + the RDIRP. That is, if we're down to a module name, + don't keep picking apart the repository directory name. */ + + do + { + dirp = strchr (dirp, '/'); + if (dirp) + { + strncpy (dir, dir_name, dirp - dir_name); + dir[dirp - dir_name] = '\0'; + /* Skip the slash. */ + ++dirp; + if (rdirp == NULL) + error (0, 0, + "internal error: repository string too short."); + else + rdirp = strchr (rdirp, '/'); + } + else + { + /* If there are no more slashes in the dir name, + we're down to the most nested directory -OR- to + the name of a module. In the first case, we + should be down to a DIRP that has no slashes, + so it won't help/hurt to do another STRCHR call + on DIRP. It will definitely hurt, however, if + we're down to a module name, since a module + name can point to a nested directory (that is, + DIRP will still have slashes in it. Therefore, + we should set it to NULL so the routine below + copies the contents of REMOTEDIRNAME onto the + root repository directory (does this if rdirp + is set to NULL, because we used to do an extra + STRCHR call here). */ + + rdirp = NULL; + strcpy (dir, dir_name); + } + + if (CVS_MKDIR (dir, 0777) < 0) + { + /* Now, let me get this straight. In IBM C/C++ + * under OS/2, the error string for EEXIST is: + * + * "The file already exists", + * + * and the error string for EACCESS is: + * + * "The file or directory specified is read-only". + * + * Nonetheless, mkdir() will set EACCESS if the + * directory *exists*, according both to the + * documentation and its actual behavior. + * + * I'm sure that this made sense, to someone, + * somewhere, sometime. Just not me, here, now. + */ +#ifdef EACCESS + if ((errno != EACCESS) && (errno != EEXIST)) + error (1, errno, "cannot make directory %s", dir); +#else /* ! defined(EACCESS) */ + if ((errno != EEXIST)) + error (1, errno, "cannot make directory %s", dir); +#endif /* defined(EACCESS) */ + + /* It already existed, fine. Just keep going. */ + } + else if (strcmp (command_name, "export") == 0) + /* Don't create CVSADM directories if this is export. */ + ; + else + { + /* + * Put repository in CVS/Repository. For historical + * (pre-CVS/Root) reasons, this is an absolute pathname, + * but what really matters is the part of it which is + * relative to cvsroot. + */ + char *repo; + char *r; + + repo = xmalloc (strlen (reposdirname) + + strlen (toplevel_repos) + + 80); + if (reposdirname_absolute) + r = repo; + else + { + strcpy (repo, toplevel_repos); + strcat (repo, "/"); + r = repo + strlen (repo); + } + + if (rdirp) + { + strncpy (r, reposdirname, rdirp - reposdirname); + r[rdirp - reposdirname] = '\0'; + } + else + strcpy (r, reposdirname); + + Create_Admin (dir, dir, repo, + (char *)NULL, (char *)NULL); + free (repo); + } + + if (rdirp != NULL) + { + /* Skip the slash. */ + ++rdirp; + } + + } while (dirp != NULL); + free (dir); + /* Now it better work. */ + if (chdir (dir_name) < 0) + error (1, errno, "could not chdir to %s", dir_name); + } + + if (strcmp (command_name, "export") != 0) + { + if (last_entries) + Entries_Close (last_entries); + last_entries = Entries_Open (0); + } + } + else + free (dir_name); + free (reposdirname); + (*func) (data, last_entries, short_pathname, filename); + if (reposname != NULL) + { + free (short_pathname); + free (reposname); + } +} + +static void +copy_a_file (data, ent_list, short_pathname, filename) + char *data; + List *ent_list; + char *short_pathname; + char *filename; +{ + char *newname; +#ifdef USE_VMS_FILENAMES + char *p; +#endif + + read_line (&newname, 0); + +#ifdef USE_VMS_FILENAMES + /* Mogrify the filename so VMS is happy with it. */ + for(p = newname; *p; p++) + if(*p == '.' || *p == '#') *p = '_'; +#endif + + copy_file (filename, newname); + free (newname); +} + +static void +handle_copy_file (args, len) + char *args; + int len; +{ + call_in_directory (args, copy_a_file, (char *)NULL); +} + + +static void read_counted_file PROTO ((char *, char *)); + +/* Read from the server the count for the length of a file, then read + the contents of that file and write them to FILENAME. FULLNAME is + the name of the file for use in error messages. FIXME-someday: + extend this to deal with compressed files and make update_entries + use it. On error, gives a fatal error. */ +static void +read_counted_file (filename, fullname) + char *filename; + char *fullname; +{ + char *size_string; + size_t size; + char *buf; + + /* Pointers in buf to the place to put data which will be read, + and the data which needs to be written, respectively. */ + char *pread; + char *pwrite; + /* Number of bytes left to read and number of bytes in buf waiting to + be written, respectively. */ + size_t nread; + size_t nwrite; + + FILE *fp; + + read_line (&size_string, 0); + if (size_string[0] == 'z') + error (1, 0, "\ +protocol error: compressed files not supported for that operation"); + /* FIXME: should be doing more error checking, probably. Like using + strtoul and making sure we used up the whole line. */ + size = atoi (size_string); + free (size_string); + + /* A more sophisticated implementation would use only a limited amount + of buffer space (8K perhaps), and read that much at a time. We allocate + a buffer for the whole file only to make it easy to keep track what + needs to be read and written. */ + buf = xmalloc (size); + + /* FIXME-someday: caller should pass in a flag saying whether it + is binary or not. I haven't carefully looked into whether + CVS/Template files should use local text file conventions or + not. */ + fp = fopen (filename, "wb"); + if (fp == NULL) + error (1, errno, "cannot write %s", fullname); + nread = size; + nwrite = 0; + pread = buf; + pwrite = buf; + while (nread > 0 || nwrite > 0) + { + size_t n; + + if (nread > 0) + { + n = try_read_from_server (pread, nread); + nread -= n; + pread += n; + nwrite += n; + } + + if (nwrite > 0) + { + n = fwrite (pwrite, 1, nwrite, fp); + if (ferror (fp)) + error (1, errno, "cannot write %s", fullname); + nwrite -= n; + pwrite += n; + } + } + free (buf); + if (fclose (fp) < 0) + error (1, errno, "cannot close %s", fullname); +} + +/* + * The Checksum response gives the checksum for the file transferred + * over by the next Updated, Merged or Patch response. We just store + * it here, and then check it in update_entries. + */ + +static int stored_checksum_valid; +static unsigned char stored_checksum[16]; + +static void +handle_checksum (args, len) + char *args; + int len; +{ + char *s; + char buf[3]; + int i; + + if (stored_checksum_valid) + error (1, 0, "Checksum received before last one was used"); + + s = args; + buf[2] = '\0'; + for (i = 0; i < 16; i++) + { + char *bufend; + + buf[0] = *s++; + buf[1] = *s++; + stored_checksum[i] = (char) strtol (buf, &bufend, 16); + if (bufend != buf + 2) + break; + } + + if (i < 16 || *s != '\0') + error (1, 0, "Invalid Checksum response: `%s'", args); + + stored_checksum_valid = 1; +} + +static int stored_mode_valid; +static char *stored_mode; + +static void handle_mode PROTO ((char *, int)); + +static void +handle_mode (args, len) + char *args; + int len; +{ + if (stored_mode_valid) + error (1, 0, "protocol error: duplicate Mode"); + if (stored_mode != NULL) + free (stored_mode); + stored_mode = xstrdup (args); + stored_mode_valid = 1; +} + +/* + * If we receive a patch, but the patch program fails to apply it, we + * want to request the original file. We keep a list of files whose + * patches have failed. + */ + +char **failed_patches; +int failed_patches_count; + +struct update_entries_data +{ + enum { + /* + * We are just getting an Entries line; the local file is + * correct. + */ + UPDATE_ENTRIES_CHECKIN, + /* We are getting the file contents as well. */ + UPDATE_ENTRIES_UPDATE, + /* + * We are getting a patch against the existing local file, not + * an entire new file. + */ + UPDATE_ENTRIES_PATCH + } contents; + + /* + * String to put in the timestamp field or NULL to use the timestamp + * of the file. + */ + char *timestamp; +}; + +/* Update the Entries line for this file. */ +static void +update_entries (data_arg, ent_list, short_pathname, filename) + char *data_arg; + List *ent_list; + char *short_pathname; + char *filename; +{ + char *entries_line; + struct update_entries_data *data = (struct update_entries_data *)data_arg; + + char *cp; + char *user; + char *vn; + /* Timestamp field. Always empty according to the protocol. */ + char *ts; + char *options; + char *tag; + char *date; + char *tag_or_date; + char *scratch_entries; + int bin; + + read_line (&entries_line, 0); + + /* + * Parse the entries line. + */ + if (strcmp (command_name, "export") != 0) + { + scratch_entries = xstrdup (entries_line); + + if (scratch_entries[0] != '/') + error (1, 0, "bad entries line `%s' from server", entries_line); + user = scratch_entries + 1; + if ((cp = strchr (user, '/')) == NULL) + error (1, 0, "bad entries line `%s' from server", entries_line); + *cp++ = '\0'; + vn = cp; + if ((cp = strchr (vn, '/')) == NULL) + error (1, 0, "bad entries line `%s' from server", entries_line); + *cp++ = '\0'; + + ts = cp; + if ((cp = strchr (ts, '/')) == NULL) + error (1, 0, "bad entries line `%s' from server", entries_line); + *cp++ = '\0'; + options = cp; + if ((cp = strchr (options, '/')) == NULL) + error (1, 0, "bad entries line `%s' from server", entries_line); + *cp++ = '\0'; + tag_or_date = cp; + + /* If a slash ends the tag_or_date, ignore everything after it. */ + cp = strchr (tag_or_date, '/'); + if (cp != NULL) + *cp = '\0'; + tag = (char *) NULL; + date = (char *) NULL; + if (*tag_or_date == 'T') + tag = tag_or_date + 1; + else if (*tag_or_date == 'D') + date = tag_or_date + 1; + } + else + /* For cvs export, assume it is a text file. FIXME: This is + broken behavior--we should be having the server tell us + whether it is text or binary and dealing accordingly. I + think maybe we can parse the entries line, get the options, + and then ignore the entries line otherwise, but I haven't + checked to see whether the server sends the entries line + correctly in this case. */ + options = NULL; + + if (data->contents == UPDATE_ENTRIES_UPDATE + || data->contents == UPDATE_ENTRIES_PATCH) + { + char *size_string; + char *mode_string; + int size; + int fd; + char *buf; + char *temp_filename; + int use_gzip, gzip_status; + pid_t gzip_pid = 0; + + read_line (&mode_string, 0); + + read_line (&size_string, 0); + if (size_string[0] == 'z') + { + use_gzip = 1; + size = atoi (size_string+1); + } + else + { + use_gzip = 0; + size = atoi (size_string); + } + free (size_string); + + temp_filename = xmalloc (strlen (filename) + 80); +#ifdef USE_VMS_FILENAMES + /* A VMS rename of "blah.dat" to "foo" to implies a + destination of "foo.dat" which is unfortinate for CVS */ + sprintf (temp_filename, "%s_new_", filename); +#else +#ifdef _POSIX_NO_TRUNC + sprintf (temp_filename, ".new.%.9s", filename); +#else /* _POSIX_NO_TRUNC */ + sprintf (temp_filename, ".new.%s", filename); +#endif /* _POSIX_NO_TRUNC */ +#endif /* USE_VMS_FILENAMES */ + buf = xmalloc (size); + + /* Some systems, like OS/2 and Windows NT, end lines with CRLF + instead of just LF. Format translation is done in the C + library I/O funtions. Here we tell them whether or not to + convert -- if this file is marked "binary" with the RCS -kb + flag, then we don't want to convert, else we do (because + CVS assumes text files by default). */ + + if (options) + bin = !(strcmp (options, "-kb")); + else + bin = 0; + + fd = open (temp_filename, + O_WRONLY | O_CREAT | O_TRUNC | (bin ? OPEN_BINARY : 0), + 0777); + + if (fd < 0) + error (1, errno, "writing %s", short_pathname); + + if (use_gzip) + fd = filter_through_gunzip (fd, 0, &gzip_pid); + + if (size > 0) + { + read_from_server (buf, size); + + if (write (fd, buf, size) != size) + error (1, errno, "writing %s", short_pathname); + } + + if (close (fd) < 0) + error (1, errno, "writing %s", short_pathname); + if (gzip_pid > 0) + { + if (waitpid (gzip_pid, &gzip_status, 0) == -1) + error (1, errno, "waiting for gzip process %ld", + (long) gzip_pid); + else if (gzip_status != 0) + error (1, 0, "gzip process exited %d", gzip_status); + } + + gzip_pid = -1; + + /* Since gunzip writes files without converting LF to CRLF + (a reasonable behavior), we now have a patch file in LF + format. Leave the file as is if we're just going to feed + it to patch; patch can handle it. However, if it's the + final source file, convert it. */ + + if (data->contents == UPDATE_ENTRIES_UPDATE) + { +#ifdef LINES_CRLF_TERMINATED + + /* `bin' is non-zero iff `options' contains "-kb", meaning + treat this file as binary. */ + + if (use_gzip && (! bin)) + { + convert_file (temp_filename, O_RDONLY | OPEN_BINARY, + filename, O_WRONLY | O_CREAT | O_TRUNC); + if (unlink (temp_filename) < 0) + error (0, errno, "warning: couldn't delete %s", + temp_filename); + } + else + rename_file (temp_filename, filename); + +#else /* ! LINES_CRLF_TERMINATED */ + rename_file (temp_filename, filename); +#endif /* LINES_CRLF_TERMINATED */ + } + else + { + int retcode; + char backup[PATH_MAX]; + struct stat s; + + (void) sprintf (backup, "%s~", filename); + (void) unlink_file (backup); + if (!isfile (filename)) + error (1, 0, "patch original file %s does not exist", + short_pathname); + if (stat (temp_filename, &s) < 0) + error (1, 1, "can't stat patch file %s", temp_filename); + if (s.st_size == 0) + retcode = 0; + else + { + run_setup ("%s -f -s -b ~ %s %s", PATCH_PROGRAM, + filename, temp_filename); + retcode = run_exec (DEVNULL, RUN_TTY, RUN_TTY, RUN_NORMAL); + } + /* FIXME: should we really be silently ignoring errors? */ + (void) unlink_file (temp_filename); + if (retcode == 0) + { + /* FIXME: should we really be silently ignoring errors? */ + (void) unlink_file (backup); + } + else + { + int old_errno = errno; + char *path_tmp; + + if (isfile (backup)) + rename_file (backup, filename); + + /* Get rid of the patch reject file. */ + path_tmp = xmalloc (strlen (filename) + 10); + strcpy (path_tmp, filename); + strcat (path_tmp, ".rej"); + /* FIXME: should we really be silently ignoring errors? */ + (void) unlink_file (path_tmp); + free (path_tmp); + + /* Save this file to retrieve later. */ + failed_patches = + (char **) xrealloc ((char *) failed_patches, + ((failed_patches_count + 1) + * sizeof (char *))); + failed_patches[failed_patches_count] = + xstrdup (short_pathname); + ++failed_patches_count; + + error (retcode == -1 ? 1 : 0, retcode == -1 ? old_errno : 0, + "could not patch %s%s", filename, + retcode == -1 ? "" : "; will refetch"); + + stored_checksum_valid = 0; + + return; + } + } + free (temp_filename); + + if (stored_checksum_valid) + { + FILE *e; + struct MD5Context context; + unsigned char buf[8192]; + unsigned len; + unsigned char checksum[16]; + + /* + * Compute the MD5 checksum. This will normally only be + * used when receiving a patch, so we always compute it + * here on the final file, rather than on the received + * data. + * + * Note that if the file is a text file, we should read it + * here using text mode, so its lines will be terminated the same + * way they were transmitted. + */ + e = fopen (filename, "r"); + if (e == NULL) + error (1, errno, "could not open %s", short_pathname); + + MD5Init (&context); + while ((len = fread (buf, 1, sizeof buf, e)) != 0) + MD5Update (&context, buf, len); + if (ferror (e)) + error (1, errno, "could not read %s", short_pathname); + MD5Final (checksum, &context); + + fclose (e); + + stored_checksum_valid = 0; + + if (memcmp (checksum, stored_checksum, 16) != 0) + { + if (data->contents != UPDATE_ENTRIES_PATCH) + error (1, 0, "checksum failure on %s", + short_pathname); + + error (0, 0, + "checksum failure after patch to %s; will refetch", + short_pathname); + + /* Save this file to retrieve later. */ + failed_patches = + (char **) xrealloc ((char *) failed_patches, + ((failed_patches_count + 1) + * sizeof (char *))); + failed_patches[failed_patches_count] = + xstrdup (short_pathname); + ++failed_patches_count; + + return; + } + } + + { + /* FIXME: we should be respecting the umask. */ + int status = change_mode (filename, mode_string); + if (status != 0) + error (0, status, "cannot change mode of %s", short_pathname); + } + + free (mode_string); + free (buf); + } + + if (stored_mode_valid) + change_mode (filename, stored_mode); + stored_mode_valid = 0; + + /* + * Process the entries line. Do this after we've written the file, + * since we need the timestamp. + */ + if (strcmp (command_name, "export") != 0) + { + char *local_timestamp; + char *file_timestamp; + + local_timestamp = data->timestamp; + if (local_timestamp == NULL || ts[0] == '+') + file_timestamp = time_stamp (filename); + else + file_timestamp = NULL; + + /* + * These special version numbers signify that it is not up to + * date. Create a dummy timestamp which will never compare + * equal to the timestamp of the file. + */ + if (vn[0] == '\0' || vn[0] == '0' || vn[0] == '-') + local_timestamp = "dummy timestamp"; + else if (local_timestamp == NULL) + { + local_timestamp = file_timestamp; + mark_up_to_date (filename); + } + + Register (ent_list, filename, vn, local_timestamp, + options, tag, date, ts[0] == '+' ? file_timestamp : NULL); + + if (file_timestamp) + free (file_timestamp); + + free (scratch_entries); + } + free (entries_line); +} + +static void +handle_checked_in (args, len) + char *args; + int len; +{ + struct update_entries_data dat; + dat.contents = UPDATE_ENTRIES_CHECKIN; + dat.timestamp = NULL; + call_in_directory (args, update_entries, (char *)&dat); +} + +static void +handle_new_entry (args, len) + char *args; + int len; +{ + struct update_entries_data dat; + dat.contents = UPDATE_ENTRIES_CHECKIN; + dat.timestamp = "dummy timestamp from new-entry"; + call_in_directory (args, update_entries, (char *)&dat); +} + +static void +handle_updated (args, len) + char *args; + int len; +{ + struct update_entries_data dat; + dat.contents = UPDATE_ENTRIES_UPDATE; + dat.timestamp = NULL; + call_in_directory (args, update_entries, (char *)&dat); +} + +static void +handle_merged (args, len) + char *args; + int len; +{ + struct update_entries_data dat; + dat.contents = UPDATE_ENTRIES_UPDATE; + dat.timestamp = "Result of merge"; + call_in_directory (args, update_entries, (char *)&dat); +} + +static void +handle_patched (args, len) + char *args; + int len; +{ + struct update_entries_data dat; + dat.contents = UPDATE_ENTRIES_PATCH; + dat.timestamp = NULL; + call_in_directory (args, update_entries, (char *)&dat); +} + +static void +remove_entry (data, ent_list, short_pathname, filename) + char *data; + List *ent_list; + char *short_pathname; + char *filename; +{ + Scratch_Entry (ent_list, filename); +} + +static void +handle_remove_entry (args, len) + char *args; + int len; +{ + call_in_directory (args, remove_entry, (char *)NULL); +} + +static void +remove_entry_and_file (data, ent_list, short_pathname, filename) + char *data; + List *ent_list; + char *short_pathname; + char *filename; +{ + Scratch_Entry (ent_list, filename); + if (unlink_file (filename) < 0) + error (0, errno, "unable to remove %s", short_pathname); +} + +static void +handle_removed (args, len) + char *args; + int len; +{ + call_in_directory (args, remove_entry_and_file, (char *)NULL); +} + +/* Is this the top level (directory containing CVSROOT)? */ +static int +is_cvsroot_level (pathname) + char *pathname; +{ + char *short_pathname; + + if (strcmp (toplevel_repos, server_cvsroot) != 0) + return 0; + + if (!use_directory) + { + if (strncmp (pathname, server_cvsroot, strlen (server_cvsroot)) != 0) + error (1, 0, + "server bug: pathname `%s' doesn't specify file in `%s'", + pathname, server_cvsroot); + short_pathname = pathname + strlen (server_cvsroot) + 1; + if (short_pathname[-1] != '/') + error (1, 0, + "server bug: pathname `%s' doesn't specify file in `%s'", + pathname, server_cvsroot); + return strchr (short_pathname, '/') == NULL; + } + else + { + return strchr (pathname, '/') == NULL; + } +} + +static void +set_static (data, ent_list, short_pathname, filename) + char *data; + List *ent_list; + char *short_pathname; + char *filename; +{ + FILE *fp; + fp = open_file (CVSADM_ENTSTAT, "w+"); + if (fclose (fp) == EOF) + error (1, errno, "cannot close %s", CVSADM_ENTSTAT); +} + +static void +handle_set_static_directory (args, len) + char *args; + int len; +{ + if (strcmp (command_name, "export") == 0) + { + /* Swallow the repository. */ + read_line (NULL, 0); + return; + } + call_in_directory (args, set_static, (char *)NULL); +} + +static void +clear_static (data, ent_list, short_pathname, filename) + char *data; + List *ent_list; + char *short_pathname; + char *filename; +{ + if (unlink_file (CVSADM_ENTSTAT) < 0 && ! existence_error (errno)) + error (1, errno, "cannot remove file %s", CVSADM_ENTSTAT); +} + +static void +handle_clear_static_directory (pathname, len) + char *pathname; + int len; +{ + if (strcmp (command_name, "export") == 0) + { + /* Swallow the repository. */ + read_line (NULL, 0); + return; + } + + if (is_cvsroot_level (pathname)) + { + /* + * Top level (directory containing CVSROOT). This seems to normally + * lack a CVS directory, so don't try to create files in it. + */ + return; + } + call_in_directory (pathname, clear_static, (char *)NULL); +} + +static void +set_sticky (data, ent_list, short_pathname, filename) + char *data; + List *ent_list; + char *short_pathname; + char *filename; +{ + char *tagspec; + FILE *f; + + read_line (&tagspec, 0); + f = open_file (CVSADM_TAG, "w+"); + if (fprintf (f, "%s\n", tagspec) < 0) + error (1, errno, "writing %s", CVSADM_TAG); + if (fclose (f) == EOF) + error (1, errno, "closing %s", CVSADM_TAG); + free (tagspec); +} + +static void +handle_set_sticky (pathname, len) + char *pathname; + int len; +{ + if (strcmp (command_name, "export") == 0) + { + /* Swallow the repository. */ + read_line (NULL, 0); + /* Swallow the tag line. */ + (void) read_line (NULL, 0); + return; + } + if (is_cvsroot_level (pathname)) + { + /* + * Top level (directory containing CVSROOT). This seems to normally + * lack a CVS directory, so don't try to create files in it. + */ + + /* Swallow the repository. */ + read_line (NULL, 0); + /* Swallow the tag line. */ + (void) read_line (NULL, 0); + return; + } + + call_in_directory (pathname, set_sticky, (char *)NULL); +} + +static void +clear_sticky (data, ent_list, short_pathname, filename) + char *data; + List *ent_list; + char *short_pathname; + char *filename; +{ + if (unlink_file (CVSADM_TAG) < 0 && ! existence_error (errno)) + error (1, errno, "cannot remove %s", CVSADM_TAG); +} + +static void +handle_clear_sticky (pathname, len) + char *pathname; + int len; +{ + if (strcmp (command_name, "export") == 0) + { + /* Swallow the repository. */ + read_line (NULL, 0); + return; + } + + if (is_cvsroot_level (pathname)) + { + /* + * Top level (directory containing CVSROOT). This seems to normally + * lack a CVS directory, so don't try to create files in it. + */ + return; + } + + call_in_directory (pathname, clear_sticky, (char *)NULL); +} + + +static void template PROTO ((char *, List *, char *, char *)); + +static void +template (data, ent_list, short_pathname, filename) + char *data; + List *ent_list; + char *short_pathname; + char *filename; +{ + /* FIXME: should be computing second argument from CVSADM_TEMPLATE + and short_pathname. */ + read_counted_file (CVSADM_TEMPLATE, "<CVS/Template file>"); +} + +static void handle_template PROTO ((char *, int)); + +static void +handle_template (pathname, len) + char *pathname; + int len; +{ + call_in_directory (pathname, template, NULL); +} + + +struct save_prog { + char *name; + char *dir; + struct save_prog *next; +}; + +static struct save_prog *checkin_progs; +static struct save_prog *update_progs; + +/* + * Unlike some responses this doesn't include the repository. So we can't + * just call call_in_directory and have the right thing happen; we save up + * the requests and do them at the end. + */ +static void +handle_set_checkin_prog (args, len) + char *args; + int len; +{ + char *prog; + struct save_prog *p; + read_line (&prog, 0); + p = (struct save_prog *) xmalloc (sizeof (struct save_prog)); + p->next = checkin_progs; + p->dir = xstrdup (args); + p->name = prog; + checkin_progs = p; +} + +static void +handle_set_update_prog (args, len) + char *args; + int len; +{ + char *prog; + struct save_prog *p; + read_line (&prog, 0); + p = (struct save_prog *) xmalloc (sizeof (struct save_prog)); + p->next = update_progs; + p->dir = xstrdup (args); + p->name = prog; + update_progs = p; +} + +static void do_deferred_progs PROTO((void)); + +static void +do_deferred_progs () +{ + struct save_prog *p; + struct save_prog *q; + + char fname[PATH_MAX]; + FILE *f; + if (toplevel_wd[0] != '\0') + { + if (chdir (toplevel_wd) < 0) + error (1, errno, "could not chdir to %s", toplevel_wd); + } + for (p = checkin_progs; p != NULL; ) + { + sprintf (fname, "%s/%s", p->dir, CVSADM_CIPROG); + f = open_file (fname, "w"); + if (fprintf (f, "%s\n", p->name) < 0) + error (1, errno, "writing %s", fname); + if (fclose (f) == EOF) + error (1, errno, "closing %s", fname); + free (p->name); + free (p->dir); + q = p->next; + free (p); + p = q; + } + checkin_progs = NULL; + for (p = update_progs; p != NULL; p = p->next) + { + sprintf (fname, "%s/%s", p->dir, CVSADM_UPROG); + f = open_file (fname, "w"); + if (fprintf (f, "%s\n", p->name) < 0) + error (1, errno, "writing %s", fname); + if (fclose (f) == EOF) + error (1, errno, "closing %s", fname); + free (p->name); + free (p->dir); + free (p); + } + update_progs = NULL; +} + +static int client_isemptydir PROTO((char *)); + +/* + * Returns 1 if the argument directory exists and is completely empty, + * other than the existence of the CVS directory entry. Zero otherwise. + */ +static int +client_isemptydir (dir) + char *dir; +{ + DIR *dirp; + struct dirent *dp; + + if ((dirp = opendir (dir)) == NULL) + { + if (! existence_error (errno)) + error (0, errno, "cannot open directory %s for empty check", dir); + return (0); + } + errno = 0; + while ((dp = readdir (dirp)) != NULL) + { + if (strcmp (dp->d_name, ".") != 0 && strcmp (dp->d_name, "..") != 0 && + strcmp (dp->d_name, CVSADM) != 0) + { + (void) closedir (dirp); + return (0); + } + } + if (errno != 0) + { + error (0, errno, "cannot read directory %s", dir); + (void) closedir (dirp); + return (0); + } + (void) closedir (dirp); + return (1); +} + +struct save_dir { + char *dir; + struct save_dir *next; +}; + +struct save_dir *prune_candidates; + +static void +add_prune_candidate (dir) + char *dir; +{ + struct save_dir *p; + + if (dir[0] == '.' && dir[1] == '\0') + return; + p = (struct save_dir *) xmalloc (sizeof (struct save_dir)); + p->dir = xstrdup (dir); + p->next = prune_candidates; + prune_candidates = p; +} + +static void process_prune_candidates PROTO((void)); + +static void +process_prune_candidates () +{ + struct save_dir *p; + struct save_dir *q; + + if (toplevel_wd[0] != '\0') + { + if (chdir (toplevel_wd) < 0) + error (1, errno, "could not chdir to %s", toplevel_wd); + } + for (p = prune_candidates; p != NULL; ) + { + if (client_isemptydir (p->dir)) + { + unlink_file_dir (p->dir); + } + free (p->dir); + q = p->next; + free (p); + p = q; + } +} + +/* Send a Repository line. */ + +static char *last_repos; +static char *last_update_dir; + +static void send_repository PROTO((char *, char *, char *)); + +static void +send_repository (dir, repos, update_dir) + char *dir; + char *repos; + char *update_dir; +{ + char *adm_name; + + /* FIXME: this is probably not the best place to check; I wish I + * knew where in here's callers to really trap this bug. To + * reproduce the bug, just do this: + * + * mkdir junk + * cd junk + * cvs -d some_repos update foo + * + * Poof, CVS seg faults and dies! It's because it's trying to + * send a NULL string to the server but dies in send_to_server. + * That string was supposed to be the repository, but it doesn't + * get set because there's no CVSADM dir, and somehow it's not + * getting set from the -d argument either... ? + */ + if (repos == NULL) + { + /* Lame error. I want a real fix but can't stay up to track + this down right now. */ + error (1, 0, "no repository"); + } + + if (update_dir == NULL || update_dir[0] == '\0') + update_dir = "."; + + if (last_repos != NULL + && strcmp (repos, last_repos) == 0 + && last_update_dir != NULL + && strcmp (update_dir, last_update_dir) == 0) + /* We've already sent it. */ + return; + + if (client_prune_dirs) + add_prune_candidate (update_dir); + + /* 80 is large enough for any of CVSADM_*. */ + adm_name = xmalloc (strlen (dir) + 80); + + if (use_directory == -1) + use_directory = supported_request ("Directory"); + + if (use_directory) + { + send_to_server ("Directory ", 0); + send_to_server (update_dir, 0); + send_to_server ("\012", 1); + send_to_server (repos, 0); + send_to_server ("\012", 1); + } + else + { + send_to_server ("Repository ", 0); + send_to_server (repos, 0); + send_to_server ("\012", 1); + } + if (supported_request ("Static-directory")) + { + adm_name[0] = '\0'; + if (dir[0] != '\0') + { + strcat (adm_name, dir); + strcat (adm_name, "/"); + } + strcat (adm_name, CVSADM_ENTSTAT); + if (isreadable (adm_name)) + { + send_to_server ("Static-directory\012", 0); + } + } + if (supported_request ("Sticky")) + { + FILE *f; + if (dir[0] == '\0') + strcpy (adm_name, CVSADM_TAG); + else + sprintf (adm_name, "%s/%s", dir, CVSADM_TAG); + + f = fopen (adm_name, "r"); + if (f == NULL) + { + if (! existence_error (errno)) + error (1, errno, "reading %s", adm_name); + } + else + { + char line[80]; + char *nl; + send_to_server ("Sticky ", 0); + while (fgets (line, sizeof (line), f) != NULL) + { + send_to_server (line, 0); + nl = strchr (line, '\n'); + if (nl != NULL) + break; + } + if (nl == NULL) + send_to_server ("\012", 1); + if (fclose (f) == EOF) + error (0, errno, "closing %s", adm_name); + } + } + if (supported_request ("Checkin-prog")) + { + FILE *f; + if (dir[0] == '\0') + strcpy (adm_name, CVSADM_CIPROG); + else + sprintf (adm_name, "%s/%s", dir, CVSADM_CIPROG); + + f = fopen (adm_name, "r"); + if (f == NULL) + { + if (! existence_error (errno)) + error (1, errno, "reading %s", adm_name); + } + else + { + char line[80]; + char *nl; + + send_to_server ("Checkin-prog ", 0); + + while (fgets (line, sizeof (line), f) != NULL) + { + send_to_server (line, 0); + + nl = strchr (line, '\n'); + if (nl != NULL) + break; + } + if (nl == NULL) + send_to_server ("\012", 1); + if (fclose (f) == EOF) + error (0, errno, "closing %s", adm_name); + } + } + if (supported_request ("Update-prog")) + { + FILE *f; + if (dir[0] == '\0') + strcpy (adm_name, CVSADM_UPROG); + else + sprintf (adm_name, "%s/%s", dir, CVSADM_UPROG); + + f = fopen (adm_name, "r"); + if (f == NULL) + { + if (! existence_error (errno)) + error (1, errno, "reading %s", adm_name); + } + else + { + char line[80]; + char *nl; + + send_to_server ("Update-prog ", 0); + + while (fgets (line, sizeof (line), f) != NULL) + { + send_to_server (line, 0); + + nl = strchr (line, '\n'); + if (nl != NULL) + break; + } + if (nl == NULL) + send_to_server ("\012", 1); + if (fclose (f) == EOF) + error (0, errno, "closing %s", adm_name); + } + } + free (adm_name); + if (last_repos != NULL) + free (last_repos); + if (last_update_dir != NULL) + free (last_update_dir); + last_repos = xstrdup (repos); + last_update_dir = xstrdup (update_dir); +} + +/* Send a Repository line and set toplevel_repos. */ +static void send_a_repository PROTO((char *, char *, char *)); + +static void +send_a_repository (dir, repository, update_dir) + char *dir; + char *repository; + char *update_dir; +{ + if (toplevel_repos == NULL && repository != NULL) + { + if (update_dir[0] == '\0' + || (update_dir[0] == '.' && update_dir[1] == '\0')) + toplevel_repos = xstrdup (repository); + else + { + /* + * Get the repository from a CVS/Repository file if update_dir + * is absolute. This is not correct in general, because + * the CVS/Repository file might not be the top-level one. + * This is for cases like "cvs update /foo/bar" (I'm not + * sure it matters what toplevel_repos we get, but it does + * matter that we don't hit the "internal error" code below). + */ + if (update_dir[0] == '/') + toplevel_repos = Name_Repository (update_dir, update_dir); + else + { + /* + * Guess the repository of that directory by looking at a + * subdirectory and removing as many pathname components + * as are in update_dir. I think that will always (or at + * least almost always) be 1. + * + * So this deals with directories which have been + * renamed, though it doesn't necessarily deal with + * directories which have been put inside other + * directories (and cvs invoked on the containing + * directory). I'm not sure the latter case needs to + * work. + */ + /* + * This gets toplevel_repos wrong for "cvs update ../foo" + * but I'm not sure toplevel_repos matters in that case. + */ + int slashes_in_update_dir; + int slashes_skipped; + char *p; + + slashes_in_update_dir = 0; + for (p = update_dir; *p != '\0'; ++p) + if (*p == '/') + ++slashes_in_update_dir; + + slashes_skipped = 0; + p = repository + strlen (repository); + while (1) + { + if (p == repository) + error (1, 0, + "internal error: not enough slashes in %s", + repository); + if (*p == '/') + ++slashes_skipped; + if (slashes_skipped < slashes_in_update_dir + 1) + --p; + else + break; + } + toplevel_repos = xmalloc (p - repository + 1); + /* Note that we don't copy the trailing '/'. */ + strncpy (toplevel_repos, repository, p - repository); + toplevel_repos[p - repository] = '\0'; + } + } + } + + send_repository (dir, repository, update_dir); +} + +/* The "expanded" modules. */ +static int modules_count; +static int modules_allocated; +static char **modules_vector; + +static void +handle_module_expansion (args, len) + char *args; + int len; +{ + if (modules_vector == NULL) + { + modules_allocated = 1; /* Small for testing */ + modules_vector = (char **) xmalloc + (modules_allocated * sizeof (modules_vector[0])); + } + else if (modules_count >= modules_allocated) + { + modules_allocated *= 2; + modules_vector = (char **) xrealloc + ((char *) modules_vector, + modules_allocated * sizeof (modules_vector[0])); + } + modules_vector[modules_count] = xmalloc (strlen (args) + 1); + strcpy (modules_vector[modules_count], args); + ++modules_count; +} + +/* Original, not "expanded" modules. */ +static int module_argc; +static char **module_argv; + +void +client_expand_modules (argc, argv, local) + int argc; + char **argv; + int local; +{ + int errs; + int i; + + module_argc = argc; + module_argv = (char **) xmalloc ((argc + 1) * sizeof (module_argv[0])); + for (i = 0; i < argc; ++i) + module_argv[i] = xstrdup (argv[i]); + module_argv[argc] = NULL; + + for (i = 0; i < argc; ++i) + send_arg (argv[i]); + send_a_repository ("", server_cvsroot, ""); + + send_to_server ("expand-modules\012", 0); + + errs = get_server_responses (); + if (last_repos != NULL) + free (last_repos); + last_repos = NULL; + if (last_update_dir != NULL) + free (last_update_dir); + last_update_dir = NULL; + if (errs) + error (errs, 0, "cannot expand modules"); +} + +void +client_send_expansions (local) + int local; +{ + int i; + char *argv[1]; + + /* Send the original module names. The "expanded" module name might + not be suitable as an argument to a co request (e.g. it might be + the result of a -d argument in the modules file). It might be + cleaner if we genuinely expanded module names, all the way to a + local directory and repository, but that isn't the way it works + now. */ + send_file_names (module_argc, module_argv, 0); + + for (i = 0; i < modules_count; ++i) + { + argv[0] = modules_vector[i]; + if (isfile (argv[0])) + send_files (1, argv, local, 0); + } + send_a_repository ("", server_cvsroot, ""); +} + +void +client_nonexpanded_setup () +{ + send_a_repository ("", server_cvsroot, ""); +} + +static void +handle_m (args, len) + char *args; + int len; +{ + fwrite (args, len, sizeof (*args), stdout); + putc ('\n', stdout); +} + +static void +handle_e (args, len) + char *args; + int len; +{ + fwrite (args, len, sizeof (*args), stderr); + putc ('\n', stderr); +} + +#endif /* CLIENT_SUPPORT */ +#if defined(CLIENT_SUPPORT) || defined(SERVER_SUPPORT) + +/* This table must be writeable if the server code is included. */ +struct response responses[] = +{ +#ifdef CLIENT_SUPPORT +#define RSP_LINE(n, f, t, s) {n, f, t, s} +#else /* ! CLIENT_SUPPORT */ +#define RSP_LINE(n, f, t, s) {n, s} +#endif /* CLIENT_SUPPORT */ + + RSP_LINE("ok", handle_ok, response_type_ok, rs_essential), + RSP_LINE("error", handle_error, response_type_error, rs_essential), + RSP_LINE("Valid-requests", handle_valid_requests, response_type_normal, + rs_essential), + RSP_LINE("Checked-in", handle_checked_in, response_type_normal, + rs_essential), + RSP_LINE("New-entry", handle_new_entry, response_type_normal, rs_optional), + RSP_LINE("Checksum", handle_checksum, response_type_normal, rs_optional), + RSP_LINE("Copy-file", handle_copy_file, response_type_normal, rs_optional), + RSP_LINE("Updated", handle_updated, response_type_normal, rs_essential), + RSP_LINE("Merged", handle_merged, response_type_normal, rs_essential), + RSP_LINE("Patched", handle_patched, response_type_normal, rs_optional), + RSP_LINE("Mode", handle_mode, response_type_normal, rs_optional), + RSP_LINE("Removed", handle_removed, response_type_normal, rs_essential), + RSP_LINE("Remove-entry", handle_remove_entry, response_type_normal, + rs_optional), + RSP_LINE("Set-static-directory", handle_set_static_directory, + response_type_normal, + rs_optional), + RSP_LINE("Clear-static-directory", handle_clear_static_directory, + response_type_normal, + rs_optional), + RSP_LINE("Set-sticky", handle_set_sticky, response_type_normal, + rs_optional), + RSP_LINE("Clear-sticky", handle_clear_sticky, response_type_normal, + rs_optional), + RSP_LINE("Template", handle_template, response_type_normal, + rs_optional), + RSP_LINE("Set-checkin-prog", handle_set_checkin_prog, response_type_normal, + rs_optional), + RSP_LINE("Set-update-prog", handle_set_update_prog, response_type_normal, + rs_optional), + RSP_LINE("Notified", handle_notified, response_type_normal, rs_optional), + RSP_LINE("Module-expansion", handle_module_expansion, response_type_normal, + rs_optional), + RSP_LINE("M", handle_m, response_type_normal, rs_essential), + RSP_LINE("E", handle_e, response_type_normal, rs_essential), + /* Possibly should be response_type_error. */ + RSP_LINE(NULL, NULL, response_type_normal, rs_essential) + +#undef RSP_LINE +}; + +#endif /* CLIENT_SUPPORT or SERVER_SUPPORT */ +#ifdef CLIENT_SUPPORT + +/* + * If LEN is 0, then send_to_server() computes string's length itself. + * + * Therefore, pass the real length when transmitting data that might + * contain 0's. + */ +void +send_to_server (str, len) + char *str; + size_t len; +{ + if (len == 0) + len = strlen (str); + +#ifdef NO_SOCKET_TO_FD + if (use_socket_style) + { + int just_wrtn = 0; + size_t wrtn = 0; + +#ifdef VMS + /* send() blocks under VMS */ + if (send (server_sock, str + wrtn, len - wrtn, 0) < 0) + error (1, errno, "writing to server socket"); +#else /* VMS */ + while (wrtn < len) + { + just_wrtn = send (server_sock, str + wrtn, len - wrtn, 0); + + if (just_wrtn == -1) + error (1, errno, "writing to server socket"); + + wrtn += just_wrtn; + if (wrtn == len) + break; + } +#endif /* VMS */ + } + else +#endif /* NO_SOCKET_TO_FD */ + { + size_t wrtn = 0; + + while (wrtn < len) + { + wrtn += fwrite (str + wrtn, 1, len - wrtn, to_server); + + if (wrtn == len) + break; + + if (ferror (to_server)) + error (1, errno, "writing to server"); + if (feof (to_server)) + error (1, 0, "premature end-of-file on server"); + } + } + + if (to_server_logfile) + if (fwrite (str, 1, len, to_server_logfile) < len) + error (0, errno, "writing to to-server logfile"); +} + +/* Read up to LEN bytes from the server. Returns actual number of bytes + read. Gives a fatal error on EOF or error. */ +static size_t +try_read_from_server (buf, len) + char *buf; + size_t len; +{ + int nread; + +#ifdef NO_SOCKET_TO_FD + if (use_socket_style) + { + nread = recv (server_sock, buf, len, 0); + if (nread == -1) + error (1, errno, "reading from server"); + } + else +#endif + { + nread = fread (buf, 1, len, from_server); + if (ferror (from_server)) + error (1, errno, "reading from server"); + if (feof (from_server)) + error (1, 0, + "end of file from server (consult above messages if any)"); + } + + /* Log, if that's what we're doing. */ + if (from_server_logfile != NULL && nread > 0) + { + size_t towrite = nread; + if (fwrite (buf, 1, towrite, from_server_logfile) < towrite) + error (0, errno, "writing to from-server logfile"); + } + + return nread; +} + +/* + * Read LEN bytes from the server or die trying. + */ +void +read_from_server (buf, len) + char *buf; + size_t len; +{ + size_t red = 0; + while (red < len) + { + red += try_read_from_server (buf + red, len - red); + if (red == len) + break; + } +} + +/* + * Get some server responses and process them. Returns nonzero for + * error, 0 for success. */ +int +get_server_responses () +{ + struct response *rs; + do + { + char *cmd; + int len; + + len = read_line (&cmd, 0); + for (rs = responses; rs->name != NULL; ++rs) + if (strncmp (cmd, rs->name, strlen (rs->name)) == 0) + { + int cmdlen = strlen (rs->name); + if (cmd[cmdlen] == '\0') + ; + else if (cmd[cmdlen] == ' ') + ++cmdlen; + else + /* + * The first len characters match, but it's a different + * response. e.g. the response is "oklahoma" but we + * matched "ok". + */ + continue; + (*rs->func) (cmd + cmdlen, len - cmdlen); + break; + } + if (rs->name == NULL) + /* It's OK to print just to the first '\0'. */ + error (0, 0, + "warning: unrecognized response `%s' from cvs server", + cmd); + free (cmd); + } while (rs->type == response_type_normal); + return rs->type == response_type_error ? 1 : 0; +} + +/* Get the responses and then close the connection. */ +int server_fd = -1; + +/* + * Flag var; we'll set it in start_server() and not one of its + * callees, such as start_rsh_server(). This means that there might + * be a small window between the starting of the server and the + * setting of this var, but all the code in that window shouldn't care + * because it's busy checking return values to see if the server got + * started successfully anyway. + */ +int server_started = 0; + +int +get_responses_and_close () +{ + int errs = get_server_responses (); + + do_deferred_progs (); + + if (client_prune_dirs) + process_prune_candidates (); + +#ifdef NO_SOCKET_TO_FD + if (use_socket_style) + { + if (shutdown (server_sock, 2) < 0) + error (1, errno, "shutting down server socket"); + } + else +#endif /* NO_SOCKET_TO_FD */ + { +#if defined(HAVE_KERBEROS) || defined(USE_DIRECT_TCP) || defined(AUTH_CLIENT_SUPPORT) + if (server_fd != -1) + { + if (shutdown (server_fd, 1) < 0) + error (1, errno, "shutting down connection to %s", server_host); + /* + * This test will always be true because we dup the descriptor + */ + if (fileno (from_server) != fileno (to_server)) + { + if (fclose (to_server) != 0) + error (1, errno, + "closing down connection to %s", + server_host); + } + } + else +#endif /* HAVE_KERBEROS || USE_DIRECT_TCP || AUTH_CLIENT_SUPPORT */ + +#ifdef SHUTDOWN_SERVER + SHUTDOWN_SERVER (fileno (to_server)); +#else /* ! SHUTDOWN_SERVER */ + { + +#ifdef START_RSH_WITH_POPEN_RW + if (pclose (to_server) == EOF) +#else /* ! START_RSH_WITH_POPEN_RW */ + if (fclose (to_server) == EOF) +#endif /* START_RSH_WITH_POPEN_RW */ + { + error (1, errno, "closing connection to %s", server_host); + } + } + + if (getc (from_server) != EOF) + error (0, 0, "dying gasps from %s unexpected", server_host); + else if (ferror (from_server)) + error (0, errno, "reading from %s", server_host); + + fclose (from_server); +#endif /* SHUTDOWN_SERVER */ + } + +#if ! RSH_NOT_TRANSPARENT + if (rsh_pid != -1 + && waitpid (rsh_pid, (int *) 0, 0) == -1) + error (1, errno, "waiting for process %d", rsh_pid); +#endif /* ! RSH_NOT_TRANSPARENT */ + + server_started = 0; + + return errs; +} + +#ifndef RSH_NOT_TRANSPARENT +static void start_rsh_server PROTO((int *, int *)); +#endif /* RSH_NOT_TRANSPARENT */ + +int +supported_request (name) + char *name; +{ + struct request *rq; + + for (rq = requests; rq->name; rq++) + if (!strcmp (rq->name, name)) + return rq->status == rq_supported; + error (1, 0, "internal error: testing support for unknown option?"); + /* NOTREACHED */ + return 0; +} + + +#ifdef AUTH_CLIENT_SUPPORT +void +init_sockaddr (name, hostname, port) + struct sockaddr_in *name; + const char *hostname; + unsigned short int port; +{ + struct hostent *hostinfo; + + memset (name, 0, sizeof (*name)); + name->sin_family = AF_INET; + name->sin_port = htons (port); + hostinfo = gethostbyname (hostname); + if (hostinfo == NULL) + { + fprintf (stderr, "Unknown host %s.\n", hostname); + exit (EXIT_FAILURE); + } + name->sin_addr = *(struct in_addr *) hostinfo->h_addr; +} + + +int +auth_server_port_number () +{ + return CVS_AUTH_PORT; +} + + +/* + * Connect to the authenticating server. + * + * If VERIFY_ONLY is non-zero, then just verify that the password is + * correct and then shutdown the connection. In this case, the return + * values is 1 if the password was correct, 0 if not. + * + * If VERIFY_ONLY is 0, then really connect to the server. In this + * case the return value is 1 on succees, but is probably ignored. If + * fail to connect, then die with error. + */ +int +connect_to_pserver (tofdp, fromfdp, verify_only) + int *tofdp, *fromfdp; + int verify_only; +{ + int sock; +#ifndef NO_SOCKET_TO_FD + int tofd, fromfd; +#endif + int port_number; + struct sockaddr_in client_sai; + + /* Does nothing if already called before now. */ + parse_cvsroot (); + + sock = socket (AF_INET, SOCK_STREAM, 0); + if (sock == -1) + { + fprintf (stderr, "socket() failed\n"); + exit (EXIT_FAILURE); + } + port_number = auth_server_port_number (); + init_sockaddr (&client_sai, server_host, port_number); + if (connect (sock, (struct sockaddr *) &client_sai, sizeof (client_sai)) + < 0) + error (1, errno, "connect to %s:%d failed", server_host, + CVS_AUTH_PORT); + + /* Run the authorization mini-protocol before anything else. */ + { + int i; + char ch, read_buf[PATH_MAX]; + char *begin = NULL; + char *repository = server_cvsroot; + char *username = server_user; + char *password = NULL; + char *end = NULL; + + if (verify_only) + { + begin = "BEGIN VERIFICATION REQUEST\n"; + end = "END VERIFICATION REQUEST\n"; + } + else + { + begin = "BEGIN AUTH REQUEST\n"; + end = "END AUTH REQUEST\n"; + } + + /* Get the password, probably from ~/.cvspass. */ + password = get_cvs_password (server_user, server_host, server_cvsroot); + + /* Announce that we're starting the authorization protocol. */ + send (sock, begin, strlen (begin), 0); + + /* Send the data the server needs. */ + send (sock, repository, strlen (repository), 0); + send (sock, "\n", 1, 0); + send (sock, username, strlen (username), 0); + send (sock, "\n", 1, 0); + send (sock, password, strlen (password), 0); + send (sock, "\n", 1, 0); + + /* Announce that we're ending the authorization protocol. */ + send (sock, end, strlen (end), 0); + + /* Paranoia. */ + memset (password, 0, strlen (password)); + + /* Get ACK or NACK from the server. + * + * We could avoid this careful read-char loop by having the ACK + * and NACK cookies be of the same length, so we'd simply read + * that length and see what we got. But then there'd be Yet + * Another Protocol Requirement floating around, and someday + * someone would make a change that breaks it and spend a hellish + * day tracking it down. Therefore, we use "\n" to mark off the + * end of both ACK and NACK, and we loop, reading until "\n". + */ + ch = 0; + memset (read_buf, 0, PATH_MAX); + for (i = 0; (i < (PATH_MAX - 1)) && (ch != '\n'); i++) + { + if (recv (sock, &ch, 1, 0) < 0) + error (1, errno, "recv() from server %s", server_host); + + read_buf[i] = ch; + } + + if (strcmp (read_buf, "I HATE YOU\n") == 0) + { + /* Authorization not granted. */ + if (shutdown (sock, 2) < 0) + { + error (0, 0, + "authorization failed: server %s rejected access", + server_host); + error (1, errno, + "shutdown() failed (server %s)", server_host); + } + + if (verify_only) + return 0; + else + error (1, 0, + "authorization failed: server %s rejected access", + server_host); + } + else if (strcmp (read_buf, "I LOVE YOU\n") != 0) + { + /* Unrecognized response from server. */ + if (shutdown (sock, 2) < 0) + { + error (0, 0, + "unrecognized auth response from %s: %s", + server_host, read_buf); + error (1, errno, "shutdown() failed, server %s", server_host); + } + error (1, 0, + "unrecognized auth response from %s: %s", + server_host, read_buf); + } + } + + if (verify_only) + { + if (shutdown (sock, 2) < 0) + error (0, errno, "shutdown() failed, server %s", server_host); + return 1; + } + else + { +#ifdef NO_SOCKET_TO_FD + use_socket_style = 1; + server_sock = sock; + /* Try to break mistaken callers: */ + *tofdp = 0; + *fromfdp = 0; +#else /* ! NO_SOCKET_TO_FD */ + server_fd = sock; + close_on_exec (server_fd); + tofd = fromfd = sock; + /* Hand them back to the caller. */ + *tofdp = tofd; + *fromfdp = fromfd; +#endif /* NO_SOCKET_TO_FD */ + } + + return 1; +} +#endif /* AUTH_CLIENT_SUPPORT */ + + +#if HAVE_KERBEROS || USE_DIRECT_TCP + +/* + * FIXME: this function has not been changed to deal with + * NO_SOCKET_TO_FD (i.e., systems on which sockets cannot be converted + * to file descriptors. The first person to try building a kerberos + * client on such a system (OS/2, Windows 95, and maybe others) will + * have to make take care of this. + */ +void +start_tcp_server (tofdp, fromfdp) + int *tofdp, *fromfdp; +{ + int tofd, fromfd; + + struct hostent *hp; + char *hname; + const char *portenv; + int port; + struct sockaddr_in sin; + int s; + + +#if HAVE_KERBEROS + KTEXT_ST ticket; + const char *realm; +#endif /* HAVE_KERBEROS */ + + int status; + + /* + * We look up the host to give a better error message if it + * does not exist. However, we then pass server_host to + * krb_sendauth, rather than the canonical name, because + * krb_sendauth is going to do its own canonicalization anyhow + * and that lets us not worry about the static storage used by + * gethostbyname. + */ + hp = gethostbyname (server_host); + if (hp == NULL) + error (1, 0, "%s: unknown host", server_host); + hname = xmalloc (strlen (hp->h_name) + 1); + strcpy (hname, hp->h_name); + +#if HAVE_KERBEROS + realm = krb_realmofhost (hname); +#endif /* HAVE_KERBEROS */ + + /* Get CVS_CLIENT_PORT or look up cvs/tcp with CVS_PORT as default */ + portenv = getenv ("CVS_CLIENT_PORT"); + if (portenv != NULL) + { + port = atoi (portenv); + if (port <= 0) + goto try_rsh_no_message; + if (trace) + fprintf(stderr, "Using TCP port %d to contact server.\n", port); + port = htons (port); + } + else + { + struct servent *sp; + + sp = getservbyname ("cvs", "tcp"); + if (sp == NULL) + port = htons (CVS_PORT); + else + port = sp->s_port; + } + + s = socket (AF_INET, SOCK_STREAM, 0); + if (s < 0) + error (1, errno, "socket"); + + memset (&sin, 0, sizeof sin); + sin.sin_family = AF_INET; + sin.sin_addr.s_addr = INADDR_ANY; + sin.sin_port = 0; + + if (bind (s, (struct sockaddr *) &sin, sizeof sin) < 0) + error (1, errno, "bind"); + + memcpy (&sin.sin_addr, hp->h_addr, hp->h_length); + sin.sin_port = port; + + tofd = -1; + if (connect (s, (struct sockaddr *) &sin, sizeof sin) < 0) + { + error (0, errno, "connect"); + close (s); + } + else + { +#ifdef HAVE_KERBEROS + struct sockaddr_in laddr; + int laddrlen; + MSG_DAT msg_data; + CREDENTIALS cred; + Key_schedule sched; + + laddrlen = sizeof (laddr); + if (getsockname (s, (struct sockaddr *) &laddr, &laddrlen) < 0) + error (1, errno, "getsockname"); + + /* We don't care about the checksum, and pass it as zero. */ + status = krb_sendauth (KOPT_DO_MUTUAL, s, &ticket, "rcmd", + hname, realm, (unsigned long) 0, &msg_data, + &cred, sched, &laddr, &sin, "KCVSV1.0"); + if (status != KSUCCESS) + { + error (0, 0, "kerberos: %s", krb_get_err_text(status)); + close (s); + } + else + { +#endif /* HAVE_KERBEROS */ + + server_fd = s; + close_on_exec (server_fd); + tofd = fromfd = s; + +#ifdef HAVE_KERBEROS + } +#endif /* HAVE_KERBEROS */ + } + + if (tofd == -1) + { + /* FIXME: Falling back like this is slow and we should probably + just make it a fatal error (so that people use the right + environment variables or, when we get around to implementing + the right ones, access methods). */ + error (0, 0, "trying to start server using rsh"); + try_rsh_no_message: + server_fd = -1; +#if ! RSH_NOT_TRANSPARENT + start_rsh_server (&tofd, &fromfd); +#else /* RSH_NOT_TRANSPARENT */ +#if defined (START_SERVER) + START_SERVER (&tofd, &fromfd, getcaller (), + server_user, server_host, server_cvsroot); +#endif /* defined (START_SERVER) */ +#endif /* ! RSH_NOT_TRANSPARENT */ + } + free (hname); + + /* Give caller the values it wants. */ + *tofdp = tofd; + *fromfdp = fromfd; +} + +#endif /* HAVE_KERBEROS || USE_DIRECT_TCP */ + +static int send_variable_proc PROTO ((Node *, void *)); + +static int +send_variable_proc (node, closure) + Node *node; + void *closure; +{ + send_to_server ("Set ", 0); + send_to_server (node->key, 0); + send_to_server ("=", 1); + send_to_server (node->data, 0); + send_to_server ("\012", 1); + return 0; +} + +/* Contact the server. */ +void +start_server () +{ + int tofd, fromfd; + char *log = getenv ("CVS_CLIENT_LOG"); + + /* Note that generally speaking we do *not* fall back to a different + way of connecting if the first one does not work. This is slow + (*really* slow on a 14.4kbps link); the clean way to have a CVS + which supports several ways of connecting is with access methods. */ + + /* Init these to NULL. They will be set later if logging is on. */ + from_server_logfile = (FILE *) NULL; + to_server_logfile = (FILE *) NULL; + +#ifdef AUTH_CLIENT_SUPPORT + if (use_authenticating_server) + { + /* Toss the return value. It will die with error if anything + goes wrong anyway. */ + connect_to_pserver (&tofd, &fromfd, 0); + } + else +#endif /* AUTH_CLIENT_SUPPORT */ + { +#if HAVE_KERBEROS || USE_DIRECT_TCP + start_tcp_server (&tofd, &fromfd); +#else + +# if ! RSH_NOT_TRANSPARENT + start_rsh_server (&tofd, &fromfd); +# else + +# if defined(START_SERVER) + START_SERVER (&tofd, &fromfd, getcaller (), + server_user, server_host, server_cvsroot); +# endif +# endif +#endif + } + +#if defined(VMS) && defined(NO_SOCKET_TO_FD) + /* Avoid mixing sockets with stdio */ + use_socket_style = 1; + server_sock = tofd; +#endif /* VMS && NO_SOCKET_TO_FD */ + + /* "Hi, I'm Darlene and I'll be your server tonight..." */ + server_started = 1; + + /* Set up logfiles, if any. */ + if (log) + { + int len = strlen (log); + char *buf = xmalloc (len + 5); + char *p; + + strcpy (buf, log); + p = buf + len; + + strcpy (p, ".in"); + to_server_logfile = open_file (buf, "w"); + if (to_server_logfile == NULL) + error (0, errno, "opening to-server logfile %s", buf); + + strcpy (p, ".out"); + from_server_logfile = open_file (buf, "w"); + if (from_server_logfile == NULL) + error (0, errno, "opening from-server logfile %s", buf); + + free (buf); + } + +#ifdef NO_SOCKET_TO_FD + if (! use_socket_style) +#endif /* NO_SOCKET_TO_FD */ + { + /* todo: some OS's don't need these calls... */ + close_on_exec (tofd); + close_on_exec (fromfd); + + /* SCO 3 and AIX have a nasty bug in the I/O libraries which precludes + fdopening the same file descriptor twice, so dup it if it is the + same. */ + if (tofd == fromfd) + { + fromfd = dup (tofd); + if (fromfd < 0) + error (1, errno, "cannot dup net connection"); + } + + /* These will use binary mode on systems which have it. */ + to_server = fdopen (tofd, FOPEN_BINARY_WRITE); + if (to_server == NULL) + error (1, errno, "cannot fdopen %d for write", tofd); + from_server = fdopen (fromfd, FOPEN_BINARY_READ); + if (from_server == NULL) + error (1, errno, "cannot fdopen %d for read", fromfd); + } + + /* Clear static variables. */ + if (toplevel_repos != NULL) + free (toplevel_repos); + toplevel_repos = NULL; + if (last_dir_name != NULL) + free (last_dir_name); + last_dir_name = NULL; + if (last_repos != NULL) + free (last_repos); + last_repos = NULL; + if (last_update_dir != NULL) + free (last_update_dir); + last_update_dir = NULL; + stored_checksum_valid = 0; + stored_mode_valid = 0; + + if (strcmp (command_name, "init") != 0) + { + send_to_server ("Root ", 0); + send_to_server (server_cvsroot, 0); + send_to_server ("\012", 1); + } + + { + struct response *rs; + + send_to_server ("Valid-responses", 0); + + for (rs = responses; rs->name != NULL; ++rs) + { + send_to_server (" ", 0); + send_to_server (rs->name, 0); + } + send_to_server ("\012", 1); + } + send_to_server ("valid-requests\012", 0); + + if (get_server_responses ()) + exit (EXIT_FAILURE); + + /* + * Now handle global options. + * + * -H, -f, -d, -e should be handled OK locally. + * + * -b we ignore (treating it as a server installation issue). + * FIXME: should be an error message. + * + * -v we print local version info; FIXME: Add a protocol request to get + * the version from the server so we can print that too. + * + * -l -t -r -w -q -n and -Q need to go to the server. + */ + + { + int have_global = supported_request ("Global_option"); + + if (noexec) + { + if (have_global) + { + send_to_server ("Global_option -n\012", 0); + } + else + error (1, 0, + "This server does not support the global -n option."); + } + if (quiet) + { + if (have_global) + { + send_to_server ("Global_option -q\012", 0); + } + else + error (1, 0, + "This server does not support the global -q option."); + } + if (really_quiet) + { + if (have_global) + { + send_to_server ("Global_option -Q\012", 0); + } + else + error (1, 0, + "This server does not support the global -Q option."); + } + if (!cvswrite) + { + if (have_global) + { + send_to_server ("Global_option -r\012", 0); + } + else + error (1, 0, + "This server does not support the global -r option."); + } + if (trace) + { + if (have_global) + { + send_to_server ("Global_option -t\012", 0); + } + else + error (1, 0, + "This server does not support the global -t option."); + } + if (logoff) + { + if (have_global) + { + send_to_server ("Global_option -l\012", 0); + } + else + error (1, 0, + "This server does not support the global -l option."); + } + } + if (gzip_level) + { + if (supported_request ("gzip-file-contents")) + { + char gzip_level_buf[5]; + send_to_server ("gzip-file-contents ", 0); + sprintf (gzip_level_buf, "%d", gzip_level); + send_to_server (gzip_level_buf, 0); + + send_to_server ("\012", 1); + } + else + { + fprintf (stderr, "server doesn't support gzip-file-contents\n"); + gzip_level = 0; + } + } + +#ifdef FILENAMES_CASE_INSENSITIVE + if (supported_request ("Case")) + send_to_server ("Case\012", 0); +#endif + + /* If "Set" is not supported, just silently fail to send the variables. + Users with an old server should get a useful error message when it + fails to recognize the ${=foo} syntax. This way if someone uses + several servers, some of which are new and some old, they can still + set user variables in their .cvsrc without trouble. */ + if (supported_request ("Set")) + walklist (variable_list, send_variable_proc, NULL); +} + +#ifndef RSH_NOT_TRANSPARENT +/* Contact the server by starting it with rsh. */ + +/* Right now, we have two different definitions for this function, + depending on whether we start the rsh server using popenRW or not. + This isn't ideal, and the best thing would probably be to change + the OS/2 port to be more like the regular Unix client (i.e., by + implementing piped_child)... but I'm doing something else at the + moment, and wish to make only one change at a time. -Karl */ + +#ifdef START_RSH_WITH_POPEN_RW + +/* This is actually a crock -- it's OS/2-specific, for no one else + uses it. If I get time, I want to make piped_child and all the + other stuff in os2/run.c work right. In the meantime, this gets us + up and running, and that's most important. */ + +static void +start_rsh_server (tofdp, fromfdp) + int *tofdp, *fromfdp; +{ + int pipes[2]; + + /* If you're working through firewalls, you can set the + CVS_RSH environment variable to a script which uses rsh to + invoke another rsh on a proxy machine. */ + char *cvs_rsh = getenv ("CVS_RSH"); + char *cvs_server = getenv ("CVS_SERVER"); + char command[PATH_MAX]; + int i = 0; + /* This needs to fit "rsh", "-b", "-l", "USER", "host", + "cmd (w/ args)", and NULL. We leave some room to grow. */ + char *rsh_argv[10]; + + if (!cvs_rsh) + cvs_rsh = "rsh"; + if (!cvs_server) + cvs_server = "cvs"; + + /* If you are running a very old (Nov 3, 1994, before 1.5) + * version of the server, you need to make sure that your .bashrc + * on the server machine does not set CVSROOT to something + * containing a colon (or better yet, upgrade the server). */ + + /* The command line starts out with rsh. */ + rsh_argv[i++] = cvs_rsh; + +#ifdef RSH_NEEDS_BINARY_FLAG + /* "-b" for binary, under OS/2. */ + rsh_argv[i++] = "-b"; +#endif /* RSH_NEEDS_BINARY_FLAG */ + + /* Then we strcat more things on the end one by one. */ + if (server_user != NULL) + { + rsh_argv[i++] = "-l"; + rsh_argv[i++] = server_user; + } + + rsh_argv[i++] = server_host; + rsh_argv[i++] = cvs_server; + rsh_argv[i++] = "server"; + + /* Mark the end of the arg list. */ + rsh_argv[i] = (char *) NULL; + + if (trace) + { + fprintf (stderr, " -> Starting server: "); + fprintf (stderr, "%s", command); + putc ('\n', stderr); + } + + /* Do the deed. */ + rsh_pid = popenRW (rsh_argv, pipes); + if (rsh_pid < 0) + error (1, errno, "cannot start server via rsh"); + + /* Give caller the file descriptors. */ + *tofdp = pipes[0]; + *fromfdp = pipes[1]; +} + +#else /* ! START_RSH_WITH_POPEN_RW */ + +static void +start_rsh_server (tofdp, fromfdp) + int *tofdp; + int *fromfdp; +{ + /* If you're working through firewalls, you can set the + CVS_RSH environment variable to a script which uses rsh to + invoke another rsh on a proxy machine. */ + char *cvs_rsh = getenv ("CVS_RSH"); + char *cvs_server = getenv ("CVS_SERVER"); + char *command; + + if (!cvs_rsh) + cvs_rsh = "rsh"; + if (!cvs_server) + cvs_server = "cvs"; + + /* Pass the command to rsh as a single string. This shouldn't + affect most rsh servers at all, and will pacify some buggy + versions of rsh that grab switches out of the middle of the + command (they're calling the GNU getopt routines incorrectly). */ + command = xmalloc (strlen (cvs_server) + + strlen (server_cvsroot) + + 50); + + /* If you are running a very old (Nov 3, 1994, before 1.5) + * version of the server, you need to make sure that your .bashrc + * on the server machine does not set CVSROOT to something + * containing a colon (or better yet, upgrade the server). */ + sprintf (command, "%s server", cvs_server); + + { + char *argv[10]; + char **p = argv; + + *p++ = cvs_rsh; + *p++ = server_host; + + /* If the login names differ between client and server + * pass it on to rsh. + */ + if (server_user != NULL) + { + *p++ = "-l"; + *p++ = server_user; + } + + *p++ = command; + *p++ = NULL; + + if (trace) + { + int i; + + fprintf (stderr, " -> Starting server: "); + for (i = 0; argv[i]; i++) + fprintf (stderr, "%s ", argv[i]); + putc ('\n', stderr); + } + rsh_pid = piped_child (argv, tofdp, fromfdp); + + if (rsh_pid < 0) + error (1, errno, "cannot start server via rsh"); + } +} + +#endif /* START_RSH_WITH_POPEN_RW */ +#endif /* ! RSH_NOT_TRANSPARENT */ + + + +/* Send an argument STRING. */ +void +send_arg (string) + char *string; +{ + char buf[1]; + char *p = string; + + send_to_server ("Argument ", 0); + + while (*p) + { + if (*p == '\n') + { + send_to_server ("\012Argumentx ", 0); + } + else + { + buf[0] = *p; + send_to_server (buf, 1); + } + ++p; + } + send_to_server ("\012", 1); +} + +static void send_modified PROTO ((char *, char *, Vers_TS *)); + +static void +send_modified (file, short_pathname, vers) + char *file; + char *short_pathname; + Vers_TS *vers; +{ + /* File was modified, send it. */ + struct stat sb; + int fd; + char *buf; + char *mode_string; + int bufsize; + int bin; + + /* Don't think we can assume fstat exists. */ + if (stat (file, &sb) < 0) + error (1, errno, "reading %s", short_pathname); + + mode_string = mode_to_string (sb.st_mode); + + /* Beware: on systems using CRLF line termination conventions, + the read and write functions will convert CRLF to LF, so the + number of characters read is not the same as sb.st_size. Text + files should always be transmitted using the LF convention, so + we don't want to disable this conversion. */ + bufsize = sb.st_size; + buf = xmalloc (bufsize); + + /* Is the file marked as containing binary data by the "-kb" flag? + If so, make sure to open it in binary mode: */ + + if (vers && vers->options) + bin = !(strcmp (vers->options, "-kb")); + else + bin = 0; + + fd = open (file, O_RDONLY | (bin ? OPEN_BINARY : 0)); + + if (fd < 0) + error (1, errno, "reading %s", short_pathname); + + if (gzip_level && sb.st_size > 100) + { + int nread, newsize = 0, gzip_status; + pid_t gzip_pid; + char *bufp = buf; + int readsize = 8192; +#ifdef LINES_CRLF_TERMINATED + char tempfile[L_tmpnam]; + int converting; +#endif /* LINES_CRLF_TERMINATED */ + +#ifdef LINES_CRLF_TERMINATED + /* Assume everything in a "cvs import" is text. */ + if (vers == NULL) + converting = 1; + else + /* Otherwise, we convert things unless they're binary. */ + converting = (! bin); + + if (converting) + { + /* gzip reads and writes files without munging CRLF + sequences, as it should, but files should be + transmitted in LF form. Convert CRLF to LF before + gzipping, on systems where this is necessary. + + If Windows NT supported fork, we could do this by + pushing another filter on in front of gzip. But it + doesn't. I'd have to write a trivial little program to + do the conversion and have CVS spawn it off. But + little executables like that always get lost. + + Alternatively, this cruft could go away if we switched + to a gzip library instead of a subprocess; then we + could tell gzip to open the file with CRLF translation + enabled. */ + if (close (fd) < 0) + error (0, errno, "warning: can't close %s", short_pathname); + + tmpnam (tempfile); + convert_file (file, O_RDONLY, + tempfile, + O_WRONLY | O_CREAT | O_TRUNC | OPEN_BINARY); + + /* This OPEN_BINARY doesn't make any difference, I think, because + gzip will deal with the inherited handle as it pleases. But I + do remember something obscure in the manuals about propagating + the translation mode to created processes via environment + variables, ick. */ + fd = open (tempfile, O_RDONLY | OPEN_BINARY); + if (fd < 0) + error (1, errno, "reading %s", short_pathname); + } +#endif /* LINES_CRLF_TERMINATED */ + + fd = filter_through_gzip (fd, 1, gzip_level, &gzip_pid); + + /* FIXME: is there any reason to go through all this realloc'ing + when we could just be writing the data to the network as we read + it from gzip? */ + while (1) + { + if ((bufp - buf) + readsize >= bufsize) + { + /* + * We need to expand the buffer if gzip ends up expanding + * the file. + */ + newsize = bufp - buf; + while (newsize + readsize >= bufsize) + bufsize *= 2; + buf = xrealloc (buf, bufsize); + bufp = buf + newsize; + } + nread = read (fd, bufp, readsize); + if (nread < 0) + error (1, errno, "reading from gzip pipe"); + else if (nread == 0) + /* eof */ + break; + bufp += nread; + } + newsize = bufp - buf; + if (close (fd) < 0) + error (0, errno, "warning: can't close %s", short_pathname); + + if (waitpid (gzip_pid, &gzip_status, 0) != gzip_pid) + error (1, errno, "waiting for gzip proc %ld", (long) gzip_pid); + else if (gzip_status != 0) + error (1, errno, "gzip exited %d", gzip_status); + +#if LINES_CRLF_TERMINATED + if (converting) + { + if (unlink (tempfile) < 0) + error (0, errno, + "warning: can't remove temp file %s", tempfile); + } +#endif /* LINES_CRLF_TERMINATED */ + + { + char tmp[80]; + + send_to_server ("Modified ", 0); + send_to_server (file, 0); + send_to_server ("\012", 1); + send_to_server (mode_string, 0); + send_to_server ("\012z", 2); + sprintf (tmp, "%lu\n", (unsigned long) newsize); + send_to_server (tmp, 0); + + send_to_server (buf, newsize); + } + } + else + { + int newsize; + + { + char *bufp = buf; + int len; + + /* FIXME: This is gross. It assumes that we might read + less than st_size bytes (true on NT), but not more. + Instead of this we should just be reading a block of + data (e.g. 8192 bytes), writing it to the network, and + so on until EOF. */ + while ((len = read (fd, bufp, (buf + sb.st_size) - bufp)) > 0) + bufp += len; + + if (len < 0) + error (1, errno, "reading %s", short_pathname); + + newsize = bufp - buf; + } + if (close (fd) < 0) + error (0, errno, "warning: can't close %s", short_pathname); + + { + char tmp[80]; + + send_to_server ("Modified ", 0); + send_to_server (file, 0); + send_to_server ("\012", 1); + send_to_server (mode_string, 0); + send_to_server ("\012", 1); + sprintf (tmp, "%lu\012", (unsigned long) newsize); + send_to_server (tmp, 0); + } + + /* + * Note that this only ends with a newline if the file ended with + * one. + */ + if (newsize > 0) + send_to_server (buf, newsize); + } + free (buf); + free (mode_string); +} + +static int send_fileproc PROTO ((struct file_info *finfo)); + +/* Deal with one file. */ +static int +send_fileproc (finfo) + struct file_info *finfo; +{ + Vers_TS *vers; + + send_a_repository ("", finfo->repository, finfo->update_dir); + + vers = Version_TS ((char *)NULL, (char *)NULL, (char *)NULL, + (char *)NULL, + finfo->file, 0, 0, finfo->entries, (RCSNode *)NULL); + + if (vers->vn_user != NULL) + { + char *tmp; + + tmp = xmalloc (strlen (finfo->file) + strlen (vers->vn_user) + + strlen (vers->options) + 200); + sprintf (tmp, "Entry /%s/%s/%s%s/%s/", + finfo->file, vers->vn_user, + vers->ts_conflict == NULL ? "" : "+", + (vers->ts_conflict == NULL ? "" + : (vers->ts_user != NULL && + strcmp (vers->ts_conflict, vers->ts_user) == 0 + ? "=" + : "modified")), + vers->options); + + /* The Entries request. */ + /* Not sure about whether this deals with -k and stuff right. */ + send_to_server (tmp, 0); + free (tmp); + if (vers->entdata != NULL && vers->entdata->tag) + { + send_to_server ("T", 0); + send_to_server (vers->entdata->tag, 0); + } + else if (vers->entdata != NULL && vers->entdata->date) + { + send_to_server ("D", 0); + send_to_server (vers->entdata->date, 0); + } + send_to_server ("\012", 1); + } + + if (vers->ts_user == NULL) + { + /* + * Do we want to print "file was lost" like normal CVS? + * Would it always be appropriate? + */ + /* File no longer exists. */ + if (!use_unchanged) + { + /* if the server is old, use the old request... */ + send_to_server ("Lost ", 0); + send_to_server (finfo->file, 0); + send_to_server ("\012", 1); + /* + * Otherwise, don't do anything for missing files, + * they just happen. + */ + } + } + else if (vers->ts_rcs == NULL + || strcmp (vers->ts_user, vers->ts_rcs) != 0) + { + send_modified (finfo->file, finfo->fullname, vers); + } + else + { + /* Only use this request if the server supports it... */ + if (use_unchanged) + { + send_to_server ("Unchanged ", 0); + send_to_server (finfo->file, 0); + send_to_server ("\012", 1); + } + } + + /* if this directory has an ignore list, add this file to it */ + if (ignlist) + { + Node *p; + + p = getnode (); + p->type = FILES; + p->key = xstrdup (finfo->file); + (void) addnode (ignlist, p); + } + + freevers_ts (&vers); + return 0; +} + +static void send_ignproc PROTO ((char *, char *)); + +static void +send_ignproc (file, dir) + char *file; + char *dir; +{ + if (ign_inhibit_server || !supported_request ("Questionable")) + { + if (dir[0] != '\0') + (void) printf ("? %s/%s\n", dir, file); + else + (void) printf ("? %s\n", file); + } + else + { + send_to_server ("Questionable ", 0); + send_to_server (file, 0); + send_to_server ("\012", 1); + } +} + +static int send_filesdoneproc PROTO ((int, char *, char *)); + +static int +send_filesdoneproc (err, repository, update_dir) + int err; + char *repository; + char *update_dir; +{ + /* if this directory has an ignore list, process it then free it */ + if (ignlist) + { + ignore_files (ignlist, update_dir, send_ignproc); + dellist (&ignlist); + } + + return (err); +} + +static Dtype send_dirent_proc PROTO ((char *, char *, char *)); + +/* + * send_dirent_proc () is called back by the recursion processor before a + * sub-directory is processed for update. + * A return code of 0 indicates the directory should be + * processed by the recursion code. A return of non-zero indicates the + * recursion code should skip this directory. + * + */ +static Dtype +send_dirent_proc (dir, repository, update_dir) + char *dir; + char *repository; + char *update_dir; +{ + int dir_exists; + char *cvsadm_repos_name; + + /* + * If the directory does not exist yet (e.g. "cvs update -d + * foo"), no need to send any files from it. + */ + dir_exists = isdir (dir); + + if (ignore_directory (update_dir)) + { + /* print the warm fuzzy message */ + if (!quiet) + error (0, 0, "Ignoring %s", update_dir); + return (R_SKIP_ALL); + } + + /* initialize the ignore list for this directory */ + ignlist = getlist (); + + /* + * If there is an empty directory (e.g. we are doing `cvs add' on a + * newly-created directory), the server still needs to know about it. + */ + + cvsadm_repos_name = xmalloc (strlen (dir) + sizeof (CVSADM_REP) + 80); + sprintf (cvsadm_repos_name, "%s/%s", dir, CVSADM_REP); + if (dir_exists && isreadable (cvsadm_repos_name)) + { + /* + * Get the repository from a CVS/Repository file whenever possible. + * The repository variable is wrong if the names in the local + * directory don't match the names in the repository. + */ + char *repos = Name_Repository (dir, update_dir); + send_a_repository (dir, repos, update_dir); + free (repos); + } + else + send_a_repository (dir, repository, update_dir); + free (cvsadm_repos_name); + + return (dir_exists ? R_PROCESS : R_SKIP_ALL); +} + +/* + * Send each option in a string to the server, one by one. + * This assumes that the options are single characters. For + * more complex parsing, do it yourself. + */ + +void +send_option_string (string) + char *string; +{ + char *p; + char it[3]; + + for (p = string; p[0]; p++) { + if (p[0] == ' ') + continue; + if (p[0] == '-') + continue; + it[0] = '-'; + it[1] = p[0]; + it[2] = '\0'; + send_arg (it); + } +} + + +/* Send the names of all the argument files to the server. */ + +void +send_file_names (argc, argv, flags) + int argc; + char **argv; + unsigned int flags; +{ + int i; + char *p; + char *q; + int level; + int max_level; + + /* The fact that we do this here as well as start_recursion is a bit + of a performance hit. Perhaps worth cleaning up someday. */ + if (flags & SEND_EXPAND_WILD) + expand_wild (argc, argv, &argc, &argv); + + /* Send Max-dotdot if needed. */ + max_level = 0; + for (i = 0; i < argc; ++i) + { + p = argv[i]; + level = 0; + do + { + q = strchr (p, '/'); + if (q != NULL) + ++q; + if (p[0] == '.' && p[1] == '.' && (p[2] == '\0' || p[2] == '/')) + { + --level; + if (-level > max_level) + max_level = -level; + } + else if (p[0] == '.' && (p[1] == '\0' || p[1] == '/')) + ; + else + ++level; + p = q; + } while (p != NULL); + } + if (max_level > 0) + { + if (supported_request ("Max-dotdot")) + { + char buf[10]; + sprintf (buf, "%d", max_level); + + send_to_server ("Max-dotdot ", 0); + send_to_server (buf, 0); + send_to_server ("\012", 1); + } + else + /* + * "leading .." is not strictly correct, as this also includes + * cases like "foo/../..". But trying to explain that in the + * error message would probably just confuse users. + */ + error (1, 0, + "leading .. not supported by old (pre-Max-dotdot) servers"); + } + + for (i = 0; i < argc; ++i) + { + char buf[1]; + char *p = argv[i]; + + send_to_server ("Argument ", 0); + + while (*p) + { + if (*p == '\n') + { + send_to_server ("\012Argumentx ", 0); + } + else if (ISDIRSEP (*p)) + { + buf[0] = '/'; + send_to_server (buf, 1); + } + else + { + buf[0] = *p; + send_to_server (buf, 1); + } + ++p; + } + send_to_server ("\012", 1); + } + + if (flags & SEND_EXPAND_WILD) + { + int i; + for (i = 0; i < argc; ++i) + free (argv[i]); + free (argv); + } +} + + +/* + * Send Repository, Modified and Entry. argc and argv contain only + * the files to operate on (or empty for everything), not options. + * local is nonzero if we should not recurse (-l option). Also sends + * Argument lines for argc and argv, so should be called after options + * are sent. + */ +void +send_files (argc, argv, local, aflag) + int argc; + char **argv; + int local; + int aflag; +{ + int err; + + /* + * aflag controls whether the tag/date is copied into the vers_ts. + * But we don't actually use it, so I don't think it matters what we pass + * for aflag here. + */ + err = start_recursion + (send_fileproc, send_filesdoneproc, + send_dirent_proc, (DIRLEAVEPROC)NULL, + argc, argv, local, W_LOCAL, aflag, 0, (char *)NULL, 0, 0); + if (err) + exit (EXIT_FAILURE); + if (toplevel_repos == NULL) + /* + * This happens if we are not processing any files, + * or for checkouts in directories without any existing stuff + * checked out. The following assignment is correct for the + * latter case; I don't think toplevel_repos matters for the + * former. + */ + toplevel_repos = xstrdup (server_cvsroot); + send_repository ("", toplevel_repos, "."); +} + +void +client_import_setup (repository) + char *repository; +{ + if (toplevel_repos == NULL) /* should always be true */ + send_a_repository ("", repository, ""); +} + +/* + * Process the argument import file. + */ +int +client_process_import_file (message, vfile, vtag, targc, targv, repository) + char *message; + char *vfile; + char *vtag; + int targc; + char *targv[]; + char *repository; +{ + char *short_pathname; + int first_time; + + /* FIXME: I think this is always false now that we call + client_import_setup at the start. */ + + first_time = toplevel_repos == NULL; + + if (first_time) + send_a_repository ("", repository, ""); + + if (strncmp (repository, toplevel_repos, strlen (toplevel_repos)) != 0) + error (1, 0, + "internal error: pathname `%s' doesn't specify file in `%s'", + repository, toplevel_repos); + short_pathname = repository + strlen (toplevel_repos) + 1; + + if (!first_time) + { + send_a_repository ("", repository, short_pathname); + } + send_modified (vfile, short_pathname, NULL); + return 0; +} + +void +client_import_done () +{ + if (toplevel_repos == NULL) + /* + * This happens if we are not processing any files, + * or for checkouts in directories without any existing stuff + * checked out. The following assignment is correct for the + * latter case; I don't think toplevel_repos matters for the + * former. + */ + /* FIXME: "can't happen" now that we call client_import_setup + at the beginning. */ + toplevel_repos = xstrdup (server_cvsroot); + send_repository ("", toplevel_repos, "."); +} + +static void +notified_a_file (data, ent_list, short_pathname, filename) + char *data; + List *ent_list; + char *short_pathname; + char *filename; +{ + FILE *fp; + FILE *newf; + size_t line_len = 8192; + char *line = xmalloc (line_len); + char *cp; + int nread; + int nwritten; + char *p; + + fp = open_file (CVSADM_NOTIFY, "r"); + if (getline (&line, &line_len, fp) < 0) + { + error (0, errno, "cannot read %s", CVSADM_NOTIFY); + goto error_exit; + } + cp = strchr (line, '\t'); + if (cp == NULL) + { + error (0, 0, "malformed %s file", CVSADM_NOTIFY); + goto error_exit; + } + *cp = '\0'; + if (strcmp (filename, line + 1) != 0) + { + error (0, 0, "protocol error: notified %s, expected %s", filename, + line + 1); + } + + if (getline (&line, &line_len, fp) < 0) + { + if (feof (fp)) + { + if (fclose (fp) < 0) + error (0, errno, "cannot close %s", CVSADM_NOTIFY); + if (unlink (CVSADM_NOTIFY) < 0) + error (0, errno, "cannot remove %s", CVSADM_NOTIFY); + return; + } + else + { + error (0, errno, "cannot read %s", CVSADM_NOTIFY); + goto error_exit; + } + } + newf = open_file (CVSADM_NOTIFYTMP, "w"); + if (fputs (line, newf) < 0) + { + error (0, errno, "cannot write %s", CVSADM_NOTIFYTMP); + goto error2; + } + while ((nread = fread (line, 1, line_len, fp)) > 0) + { + p = line; + while ((nwritten = fwrite (p, 1, nread, newf)) > 0) + { + nread -= nwritten; + p += nwritten; + } + if (ferror (newf)) + { + error (0, errno, "cannot write %s", CVSADM_NOTIFYTMP); + goto error2; + } + } + if (ferror (fp)) + { + error (0, errno, "cannot read %s", CVSADM_NOTIFY); + goto error2; + } + if (fclose (newf) < 0) + { + error (0, errno, "cannot close %s", CVSADM_NOTIFYTMP); + goto error_exit; + } + if (fclose (fp) < 0) + { + error (0, errno, "cannot close %s", CVSADM_NOTIFY); + return; + } + + { + /* In this case, we want rename_file() to ignore noexec. */ + int saved_noexec = noexec; + noexec = 0; + rename_file (CVSADM_NOTIFYTMP, CVSADM_NOTIFY); + noexec = saved_noexec; + } + + return; + error2: + (void) fclose (newf); + error_exit: + (void) fclose (fp); +} + +static void +handle_notified (args, len) + char *args; + int len; +{ + call_in_directory (args, notified_a_file, NULL); +} + +void +client_notify (repository, update_dir, filename, notif_type, val) + char *repository; + char *update_dir; + char *filename; + int notif_type; + char *val; +{ + char buf[2]; + + send_a_repository ("", repository, update_dir); + send_to_server ("Notify ", 0); + send_to_server (filename, 0); + send_to_server ("\012", 1); + buf[0] = notif_type; + buf[1] = '\0'; + send_to_server (buf, 1); + send_to_server ("\t", 1); + send_to_server (val, 0); +} + +/* + * Send an option with an argument, dealing correctly with newlines in + * the argument. If ARG is NULL, forget the whole thing. + */ +void +option_with_arg (option, arg) + char *option; + char *arg; +{ + if (arg == NULL) + return; + + send_to_server ("Argument ", 0); + send_to_server (option, 0); + send_to_server ("\012", 1); + + send_arg (arg); +} + +/* + * Send a date to the server. This will passed a string which is the + * result of Make_Date, and looks like YY.MM.DD.HH.MM.SS, where all + * the letters are single digits. The time will be GMT. getdate on + * the server can't parse that, so we turn it back into something + * which it can parse. + */ + +void +client_senddate (date) + const char *date; +{ + int year, month, day, hour, minute, second; + char buf[100]; + + if (sscanf (date, DATEFORM, &year, &month, &day, &hour, &minute, &second) + != 6) + { + error (1, 0, "diff_client_senddate: sscanf failed on date"); + } + +#ifndef HAVE_RCS5 + /* We need to fix the timezone in this case; see Make_Date. */ + abort (); +#endif /* HAVE_RCS5 */ + + sprintf (buf, "%d/%d/%d %d:%d:%d GMT", month, day, year, + hour, minute, second); + option_with_arg ("-D", buf); +} + +int +client_commit (argc, argv) + int argc; + char **argv; +{ + parse_cvsroot (); + + return commit (argc, argv); +} + +int +client_update (argc, argv) + int argc; + char **argv; +{ + parse_cvsroot (); + + return update (argc, argv); +} + +int +client_checkout (argc, argv) + int argc; + char **argv; +{ + parse_cvsroot (); + + return checkout (argc, argv); +} + +int +client_diff (argc, argv) + int argc; + char **argv; +{ + parse_cvsroot (); + + return diff (argc, argv); /* Call real code */ +} + +int +client_status (argc, argv) + int argc; + char **argv; +{ + parse_cvsroot (); + return status (argc, argv); +} + +int +client_log (argc, argv) + int argc; + char **argv; +{ + + parse_cvsroot (); + + return cvslog (argc, argv); /* Call real code */ +} + +int +client_add (argc, argv) + int argc; + char **argv; +{ + + parse_cvsroot (); + + return add (argc, argv); /* Call real code */ +} + +int +client_remove (argc, argv) + int argc; + char **argv; +{ + + parse_cvsroot (); + + return cvsremove (argc, argv); /* Call real code */ +} + +int +client_rdiff (argc, argv) + int argc; + char **argv; +{ + + parse_cvsroot (); + + return patch (argc, argv); /* Call real code */ +} + +int +client_tag (argc, argv) + int argc; + char **argv; +{ + + parse_cvsroot (); + + return tag (argc, argv); /* Call real code */ +} + +int +client_rtag (argc, argv) + int argc; + char **argv; +{ + + parse_cvsroot (); + + return rtag (argc, argv); /* Call real code */ +} + +int +client_import (argc, argv) + int argc; + char **argv; +{ + + parse_cvsroot (); + + return import (argc, argv); /* Call real code */ +} + +int +client_admin (argc, argv) + int argc; + char **argv; +{ + + parse_cvsroot (); + + return admin (argc, argv); /* Call real code */ +} + +int +client_export (argc, argv) + int argc; + char **argv; +{ + + parse_cvsroot (); + + return checkout (argc, argv); /* Call real code */ +} + +int +client_history (argc, argv) + int argc; + char **argv; +{ + + parse_cvsroot (); + + return history (argc, argv); /* Call real code */ +} + +int +client_release (argc, argv) + int argc; + char **argv; +{ + + parse_cvsroot (); + + return release (argc, argv); /* Call real code */ +} + +int +client_watch (argc, argv) + int argc; + char **argv; +{ + + parse_cvsroot (); + + return watch (argc, argv); /* Call real code */ +} + +int +client_watchers (argc, argv) + int argc; + char **argv; +{ + + parse_cvsroot (); + + return watchers (argc, argv); /* Call real code */ +} + +int +client_editors (argc, argv) + int argc; + char **argv; +{ + + parse_cvsroot (); + + return editors (argc, argv); /* Call real code */ +} + +int +client_edit (argc, argv) + int argc; + char **argv; +{ + + parse_cvsroot (); + + return edit (argc, argv); /* Call real code */ +} + +int +client_unedit (argc, argv) + int argc; + char **argv; +{ + + parse_cvsroot (); + + return unedit (argc, argv); /* Call real code */ +} + +void +send_init_command () +{ + /* This is here because we need the server_cvsroot variable. */ + send_to_server ("init ", 0); + send_to_server (server_cvsroot, 0); + send_to_server ("\012", 0); +} + +int +client_init (argc, argv) + int argc; + char **argv; +{ + parse_cvsroot (); + + return init (argc, argv); /* Call real code */ +} + +int +client_annotate (argc, argv) + int argc; + char **argv; +{ + parse_cvsroot (); + + return annotate (argc, argv); /* Call real code */ +} +#endif /* CLIENT_SUPPORT */ diff --git a/contrib/cvs/src/client.h b/contrib/cvs/src/client.h new file mode 100644 index 0000000..b238c32 --- /dev/null +++ b/contrib/cvs/src/client.h @@ -0,0 +1,191 @@ +/* Interface between the client and the rest of CVS. */ + +/* Stuff shared with the server. */ +extern char *mode_to_string PROTO((mode_t)); +extern int change_mode PROTO((char *, char *)); + +extern int gzip_level; +extern int filter_through_gzip PROTO((int, int, int, pid_t *)); +extern int filter_through_gunzip PROTO((int, int, pid_t *)); + +#ifdef CLIENT_SUPPORT +/* + * Functions to perform CVS commands via the protocol. argc and argv + * are the arguments and the return value is the exit status (zero success + * nonzero failure). + */ +extern int client_commit PROTO((int argc, char **argv)); +extern int client_update PROTO((int argc, char **argv)); +extern int client_checkout PROTO((int argc, char **argv)); +extern int client_diff PROTO((int argc, char **argv)); +extern int client_log PROTO((int argc, char **argv)); +extern int client_add PROTO((int argc, char **argv)); +extern int client_remove PROTO((int argc, char **argv)); +extern int client_status PROTO((int argc, char **argv)); +extern int client_rdiff PROTO((int argc, char **argv)); +extern int client_tag PROTO((int argc, char **argv)); +extern int client_rtag PROTO((int argc, char **argv)); +extern int client_import PROTO((int argc, char **argv)); +extern int client_admin PROTO((int argc, char **argv)); +extern int client_export PROTO((int argc, char **argv)); +extern int client_history PROTO((int argc, char **argv)); +extern int client_release PROTO((int argc, char **argv)); +extern int client_watch PROTO((int argc, char **argv)); +extern int client_watchers PROTO((int argc, char **argv)); +extern int client_editors PROTO((int argc, char **argv)); +extern int client_edit PROTO((int argc, char **argv)); +extern int client_unedit PROTO((int argc, char **argv)); +extern int client_init PROTO ((int argc, char **argv)); +extern int client_annotate PROTO ((int argc, char **argv)); + +/* + * Flag variable for seeing whether common code is running as a client + * or to do a local operation. + */ +extern int client_active; + +/* + * Flag variable for seeing whether the server has been started yet. + * As of this writing, only edit.c:notify_check() uses it. + */ +extern int server_started; + +/* Is the -P option to checkout or update specified? */ +extern int client_prune_dirs; + +#ifdef AUTH_CLIENT_SUPPORT +extern int use_authenticating_server; +int connect_to_pserver PROTO((int *tofdp, int* fromfdp, int verify_only)); +# ifndef CVS_AUTH_PORT +# define CVS_AUTH_PORT 2401 +# endif /* CVS_AUTH_PORT */ +#endif /* AUTH_CLIENT_SUPPORT */ + +#ifdef AUTH_SERVER_SUPPORT +extern void authenticate_connection PROTO ((void)); +#endif + +/* Talking to the server. */ +void send_to_server PROTO((char *str, size_t len)); +void read_from_server PROTO((char *buf, size_t len)); + +/* Internal functions that handle client communication to server, etc. */ +int supported_request PROTO ((char *)); +void option_with_arg PROTO((char *option, char *arg)); + +/* Get the responses and then close the connection. */ +extern int get_responses_and_close PROTO((void)); + +extern int get_server_responses PROTO((void)); + +/* Start up the connection to the server on the other end. */ +void +start_server PROTO((void)); + +/* Send the names of all the argument files to the server. */ +void +send_file_names PROTO((int argc, char **argv, unsigned int flags)); + +/* Flags for send_file_names. */ +/* Expand wild cards? */ +#define SEND_EXPAND_WILD 1 + +/* + * Send Repository, Modified and Entry. argc and argv contain only + * the files to operate on (or empty for everything), not options. + * local is nonzero if we should not recurse (-l option). + */ +void +send_files PROTO((int argc, char **argv, int local, int aflag)); + +/* + * Like send_files but never send "Unchanged"--just send the contents of the + * file in that case. This is used to fix it if you import a directory which + * happens to have CVS directories (yes it is obscure but the testsuite tests + * it). + */ +void +send_files_contents PROTO((int argc, char **argv, int local, int aflag)); + +/* Send an argument to the remote server. */ +void +send_arg PROTO((char *string)); + +/* Send a string of single-char options to the remote server, one by one. */ +void +send_option_string PROTO((char *string)); + +#endif /* CLIENT_SUPPORT */ + +/* + * This structure is used to catalog the responses the client is + * prepared to see from the server. + */ + +struct response +{ + /* Name of the response. */ + char *name; + +#ifdef CLIENT_SUPPORT + /* + * Function to carry out the response. ARGS is the text of the + * command after name and, if present, a single space, have been + * stripped off. The function can scribble into ARGS if it wants. + */ + void (*func) PROTO((char *args, int len)); + + /* + * ok and error are special; they indicate we are at the end of the + * responses, and error indicates we should exit with nonzero + * exitstatus. + */ + enum {response_type_normal, response_type_ok, response_type_error} type; +#endif + + /* Used by the server to indicate whether response is supported by + the client, as set by the Valid-responses request. */ + enum { + /* + * Failure to implement this response can imply a fatal + * error. This should be set only for responses which were in the + * original version of the protocol; it should not be set for new + * responses. + */ + rs_essential, + + /* Some clients might not understand this response. */ + rs_optional, + + /* + * Set by the server to one of the following based on what this + * client actually supports. + */ + rs_supported, + rs_not_supported + } status; +}; + +/* Table of responses ending in an entry with a NULL name. */ + +extern struct response responses[]; + +#ifdef CLIENT_SUPPORT + +extern void client_senddate PROTO((const char *date)); +extern void client_expand_modules PROTO((int argc, char **argv, int local)); +extern void client_send_expansions PROTO((int local)); +extern void client_nonexpanded_setup PROTO((void)); + +extern void send_init_command PROTO ((void)); + +extern char **failed_patches; +extern int failed_patches_count; +extern char toplevel_wd[]; +extern void client_import_setup PROTO((char *repository)); +extern int client_process_import_file + PROTO((char *message, char *vfile, char *vtag, + int targc, char *targv[], char *repository)); +extern void client_import_done PROTO((void)); +extern void client_notify PROTO((char *, char *, char *, int, char *)); +#endif /* CLIENT_SUPPORT */ diff --git a/contrib/cvs/src/commit.c b/contrib/cvs/src/commit.c new file mode 100644 index 0000000..ac81790 --- /dev/null +++ b/contrib/cvs/src/commit.c @@ -0,0 +1,1824 @@ +/* + * Copyright (c) 1992, Brian Berliner and Jeff Polk + * Copyright (c) 1989-1992, Brian Berliner + * + * You may distribute under the terms of the GNU General Public License as + * specified in the README file that comes with the CVS 1.4 kit. + * + * Commit Files + * + * "commit" commits the present version to the RCS repository, AFTER + * having done a test on conflicts. + * + * The call is: cvs commit [options] files... + * + */ + +#include "cvs.h" +#include "getline.h" +#include "edit.h" +#include "fileattr.h" + +static Dtype check_direntproc PROTO((char *dir, char *repos, char *update_dir)); +static int check_fileproc PROTO((struct file_info *finfo)); +static int check_filesdoneproc PROTO((int err, char *repos, char *update_dir)); +static int checkaddfile PROTO((char *file, char *repository, char *tag, + char *options, RCSNode **rcsnode)); +static Dtype commit_direntproc PROTO((char *dir, char *repos, char *update_dir)); +static int commit_dirleaveproc PROTO((char *dir, int err, char *update_dir)); +static int commit_fileproc PROTO((struct file_info *finfo)); +static int commit_filesdoneproc PROTO((int err, char *repository, char *update_dir)); +static int finaladd PROTO((char *file, char *revision, char *tag, + char *options, char *update_dir, + char *repository, List *entries)); +static int findmaxrev PROTO((Node * p, void *closure)); +static int lock_RCS PROTO((char *user, char *rcs, char *rev, char *repository)); +static int lockrcsfile PROTO((char *file, char *repository, char *rev)); +static int precommit_list_proc PROTO((Node * p, void *closure)); +static int precommit_proc PROTO((char *repository, char *filter)); +static int remove_file PROTO((char *file, char *repository, char *tag, + char *message, List *entries, RCSNode *rcsnode)); +static void fix_rcs_modes PROTO((char *rcs, char *user)); +static void fixaddfile PROTO((char *file, char *repository)); +static void fixbranch PROTO((char *file, char *repository, char *branch)); +static void unlockrcs PROTO((char *file, char *repository)); +static void ci_delproc PROTO((Node *p)); +static void masterlist_delproc PROTO((Node *p)); +static void locate_rcs PROTO((char *file, char *repository, char *rcs)); + +struct commit_info +{ + Ctype status; /* as returned from Classify_File() */ + char *rev; /* a numeric rev, if we know it */ + char *tag; /* any sticky tag, or -r option */ + char *options; /* Any sticky -k option */ +}; +struct master_lists +{ + List *ulist; /* list for Update_Logfile */ + List *cilist; /* list with commit_info structs */ +}; + +static int force_ci = 0; +static int got_message; +static int run_module_prog = 1; +static int aflag; +static char *tag; +static char *write_dirtag; +static char *logfile; +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", + "\t-n\tDo not run the module program (if any).\n", + "\t-R\tProcess directories recursively.\n", + "\t-l\tLocal directory only (not recursive).\n", + "\t-f\tForce the file to be committed; disables recursion.\n", + "\t-F file\tRead the log message from file.\n", + "\t-m msg\tLog message.\n", + "\t-r rev\tCommit to this branch or trunk revision.\n", + NULL +}; + +#ifdef CLIENT_SUPPORT +struct find_data { + List *ulist; + int argc; + char **argv; +}; + +/* Pass as a static until we get around to fixing start_recursion to + pass along a void * where we can stash it. */ +struct find_data *find_data_static; + +static int find_fileproc PROTO ((struct file_info *finfo)); + +/* Machinery to find out what is modified, added, and removed. It is + possible this should be broken out into a new client_classify function; + merging it with classify_file is almost sure to be a mess, though, + because classify_file has all kinds of repository processing. */ +static int +find_fileproc (finfo) + struct file_info *finfo; +{ + Vers_TS *vers; + enum classify_type status; + Node *node; + struct find_data *args = find_data_static; + + vers = Version_TS ((char *)NULL, (char *)NULL, (char *)NULL, + (char *)NULL, + finfo->file, 0, 0, finfo->entries, (RCSNode *)NULL); + if (vers->ts_user == NULL + && vers->vn_user != NULL + && vers->vn_user[0] == '-') + status = T_REMOVED; + else if (vers->vn_user == NULL) + { + if (vers->ts_user == NULL) + error (0, 0, "nothing known about `%s'", finfo->fullname); + else + error (0, 0, "use `cvs add' to create an entry for %s", + finfo->fullname); + return 1; + } + else if (vers->ts_user != NULL + && vers->vn_user != NULL + && vers->vn_user[0] == '0') + status = T_ADDED; + else if (vers->ts_user != NULL + && vers->ts_rcs != NULL + && strcmp (vers->ts_user, vers->ts_rcs) != 0) + status = T_MODIFIED; + else + { + /* This covers unmodified files, as well as a variety of other + cases. FIXME: we probably should be printing a message and + returning 1 for many of those cases (but I'm not sure + exactly which ones). */ + return 0; + } + + node = getnode (); + node->key = xstrdup (finfo->fullname); + + node->type = UPDATE; + node->delproc = update_delproc; + node->data = (char *) status; + (void)addnode (args->ulist, node); + + ++args->argc; + + return 0; +} + +static int copy_ulist PROTO ((Node *, void *)); + +static int +copy_ulist (node, data) + Node *node; + void *data; +{ + struct find_data *args = (struct find_data *)data; + args->argv[args->argc++] = node->key; + return 0; +} +#endif /* CLIENT_SUPPORT */ + +int +commit (argc, argv) + int argc; + char **argv; +{ + int c; + int err = 0; + int local = 0; + + if (argc == -1) + usage (commit_usage); + +#ifdef CVS_BADROOT + /* + * For log purposes, do not allow "root" to commit files. If you look + * like root, but are really logged in as a non-root user, it's OK. + */ + if (geteuid () == (uid_t) 0) + { + struct passwd *pw; + + if ((pw = (struct passwd *) getpwnam (getcaller ())) == NULL) + error (1, 0, "you are unknown to this system"); + if (pw->pw_uid == (uid_t) 0) + error (1, 0, "cannot commit files as 'root'"); + } +#endif /* CVS_BADROOT */ + + optind = 1; + while ((c = getopt (argc, argv, "nlRm:fF:r:")) != -1) + { + switch (c) + { + case 'n': + run_module_prog = 0; + break; + case 'm': +#ifdef FORCE_USE_EDITOR + use_editor = TRUE; +#else + use_editor = FALSE; +#endif + if (message) + { + free (message); + message = NULL; + } + + message = xstrdup(optarg); + break; + case 'r': + if (tag) + free (tag); + tag = xstrdup (optarg); + break; + case 'l': + local = 1; + break; + case 'R': + local = 0; + break; + case 'f': + force_ci = 1; + local = 1; /* also disable recursion */ + break; + case 'F': +#ifdef FORCE_USE_EDITOR + use_editor = TRUE; +#else + use_editor = FALSE; +#endif + logfile = optarg; + break; + case '?': + default: + usage (commit_usage); + break; + } + } + argc -= optind; + argv += optind; + + /* numeric specified revision means we ignore sticky tags... */ + if (tag && isdigit (*tag)) + { + aflag = 1; + /* strip trailing dots */ + while (tag[strlen (tag) - 1] == '.') + tag[strlen (tag) - 1] = '\0'; + } + + /* some checks related to the "-F logfile" option */ + if (logfile) + { + int n, logfd; + struct stat statbuf; + + if (message) + error (1, 0, "cannot specify both a message and a log file"); + + /* FIXME: Why is this binary? Needs more investigation. */ + if ((logfd = open (logfile, O_RDONLY | OPEN_BINARY)) < 0) + error (1, errno, "cannot open log file %s", logfile); + + if (fstat(logfd, &statbuf) < 0) + error (1, errno, "cannot find size of log file %s", logfile); + + message = xmalloc (statbuf.st_size + 1); + + /* FIXME: Should keep reading until EOF, rather than assuming the + first read gets the whole thing. */ + if ((n = read (logfd, message, statbuf.st_size + 1)) < 0) + error (1, errno, "cannot read log message from %s", logfile); + + (void) close (logfd); + message[n] = '\0'; + } + +#ifdef CLIENT_SUPPORT + if (client_active) + { + struct find_data find_args; + + ign_setup (); + + /* Note that we don't do ignore file processing here, and we + don't call ignore_files. This means that we won't print "? + foo" for stray files. Sounds OK, the doc only promises + that update does that. */ + find_args.ulist = getlist (); + find_args.argc = 0; + find_data_static = &find_args; + err = start_recursion (find_fileproc, (FILESDONEPROC) NULL, + (DIRENTPROC) NULL, (DIRLEAVEPROC) NULL, + argc, argv, local, W_LOCAL, 0, 0, + (char *)NULL, 0, 0); + if (err) + error (1, 0, "correct above errors first!"); + + if (find_args.argc == 0) + return 0; + + /* Now we keep track of which files we actually are going to + operate on, and only work with those files in the future. + This saves time--we don't want to search the file system + of the working directory twice. */ + find_args.argv = (char **) xmalloc (find_args.argc * sizeof (char **)); + find_args.argc = 0; + walklist (find_args.ulist, copy_ulist, &find_args); + + /* + * Do this before calling do_editor; don't ask for a log + * message if we can't talk to the server. But do it after we + * have made the checks that we can locally (to more quickly + * catch syntax errors, the case where no files are modified, + * added or removed, etc.). */ + start_server (); + + /* + * We do this once, not once for each directory as in normal CVS. + * The protocol is designed this way. This is a feature. + */ + if (use_editor) + do_editor (".", &message, (char *)NULL, find_args.ulist); + + /* We always send some sort of message, even if empty. */ + option_with_arg ("-m", message); + + if (local) + send_arg("-l"); + if (force_ci) + send_arg("-f"); + if (!run_module_prog) + send_arg("-n"); + option_with_arg ("-r", tag); + + /* Sending only the names of the files which were modified, added, + or removed means that the server will only do an up-to-date + check on those files. This is different from local CVS and + previous versions of client/server CVS, but it probably is a Good + Thing, or at least Not Such A Bad Thing. */ + send_file_names (find_args.argc, find_args.argv, 0); + send_files (find_args.argc, find_args.argv, local, 0); + + send_to_server ("ci\012", 0); + return get_responses_and_close (); + } +#endif + + if (tag != NULL) + tag_check_valid (tag, argc, argv, local, aflag, ""); + + /* XXX - this is not the perfect check for this */ + if (argc <= 0) + write_dirtag = tag; + + wrap_setup (); + + lock_tree_for_write (argc, argv, local, aflag); + + /* + * Set up the master update list + */ + mulist = getlist (); + + /* + * Run the recursion processor to verify the files are all up-to-date + */ + err = start_recursion (check_fileproc, check_filesdoneproc, + check_direntproc, (DIRLEAVEPROC) NULL, argc, + argv, local, W_LOCAL, aflag, 0, (char *) NULL, 1, + 0); + if (err) + { + lock_tree_cleanup (); + error (1, 0, "correct above errors first!"); + } + + /* + * Run the recursion processor to commit the files + */ + if (noexec == 0) + err = start_recursion (commit_fileproc, commit_filesdoneproc, + commit_direntproc, commit_dirleaveproc, + argc, argv, local, W_LOCAL, aflag, 0, + (char *) NULL, 1, 0); + + /* + * Unlock all the dirs and clean up + */ + lock_tree_cleanup (); + dellist (&mulist); + + if (last_register_time) + { + time_t now; + + (void) time (&now); + if (now == last_register_time) + { + sleep (1); /* to avoid time-stamp races */ + } + } + + return (err); +} + +/* + * Check to see if a file is ok to commit and make sure all files are + * up-to-date + */ +/* ARGSUSED */ +static int +check_fileproc (finfo) + struct file_info *finfo; +{ + Ctype status; + char *xdir; + Node *p; + List *ulist, *cilist; + Vers_TS *vers; + struct commit_info *ci; + int save_noexec, save_quiet, save_really_quiet; + + save_noexec = noexec; + save_quiet = quiet; + save_really_quiet = really_quiet; + noexec = quiet = really_quiet = 1; + + /* handle specified numeric revision specially */ + if (tag && isdigit (*tag)) + { + /* If the tag is for the trunk, make sure we're at the head */ + if (numdots (tag) < 2) + { + status = Classify_File (finfo->file, (char *) NULL, (char *) NULL, + (char *) NULL, 1, aflag, finfo->repository, + finfo->entries, finfo->rcs, &vers, finfo->update_dir, 0); + if (status == T_UPTODATE || status == T_MODIFIED || + status == T_ADDED) + { + Ctype xstatus; + + freevers_ts (&vers); + xstatus = Classify_File (finfo->file, tag, (char *) NULL, + (char *) NULL, 1, aflag, finfo->repository, + finfo->entries, finfo->rcs, &vers, finfo->update_dir, + 0); + if (xstatus == T_REMOVE_ENTRY) + status = T_MODIFIED; + else if (status == T_MODIFIED && xstatus == T_CONFLICT) + status = T_MODIFIED; + else + status = xstatus; + } + } + else + { + char *xtag, *cp; + + /* + * The revision is off the main trunk; make sure we're + * up-to-date with the head of the specified branch. + */ + xtag = xstrdup (tag); + if ((numdots (xtag) & 1) != 0) + { + cp = strrchr (xtag, '.'); + *cp = '\0'; + } + status = Classify_File (finfo->file, xtag, (char *) NULL, + (char *) NULL, 1, aflag, finfo->repository, + finfo->entries, finfo->rcs, &vers, finfo->update_dir, 0); + if ((status == T_REMOVE_ENTRY || status == T_CONFLICT) + && (cp = strrchr (xtag, '.')) != NULL) + { + /* pluck one more dot off the revision */ + *cp = '\0'; + freevers_ts (&vers); + status = Classify_File (finfo->file, xtag, (char *) NULL, + (char *) NULL, 1, aflag, finfo->repository, + finfo->entries, finfo->rcs, &vers, finfo->update_dir, + 0); + if (status == T_UPTODATE || status == T_REMOVE_ENTRY) + status = T_MODIFIED; + } + /* now, muck with vers to make the tag correct */ + free (vers->tag); + vers->tag = xstrdup (tag); + free (xtag); + } + } + else + status = Classify_File (finfo->file, tag, (char *) NULL, (char *) NULL, + 1, 0, finfo->repository, finfo->entries, finfo->rcs, &vers, + finfo->update_dir, 0); + noexec = save_noexec; + quiet = save_quiet; + really_quiet = save_really_quiet; + + /* + * If the force-commit option is enabled, and the file in question + * appears to be up-to-date, just make it look modified so that + * it will be committed. + */ + if (force_ci && status == T_UPTODATE) + status = T_MODIFIED; + + switch (status) + { + case T_CHECKOUT: +#ifdef SERVER_SUPPORT + case T_PATCH: +#endif + case T_NEEDS_MERGE: + case T_CONFLICT: + case T_REMOVE_ENTRY: + error (0, 0, "Up-to-date check failed for `%s'", finfo->fullname); + freevers_ts (&vers); + return (1); + case T_MODIFIED: + case T_ADDED: + case T_REMOVED: + /* + * some quick sanity checks; if no numeric -r option specified: + * - can't have a sticky date + * - can't have a sticky tag that is not a branch + * Also, + * - if status is T_REMOVED, can't have a numeric tag + * - if status is T_ADDED, rcs file must not exist + * - if status is T_ADDED, can't have a non-trunk numeric rev + * - if status is T_MODIFIED and a Conflict marker exists, don't + * allow the commit if timestamp is identical or if we find + * an RCS_MERGE_PAT in the file. + */ + if (!tag || !isdigit (*tag)) + { + if (vers->date) + { + error (0, 0, + "cannot commit with sticky date for file `%s'", + finfo->fullname); + freevers_ts (&vers); + return (1); + } + if (status == T_MODIFIED && vers->tag && + !RCS_isbranch (finfo->rcs, vers->tag)) + { + error (0, 0, + "sticky tag `%s' for file `%s' is not a branch", + vers->tag, finfo->fullname); + freevers_ts (&vers); + return (1); + } + } + if (status == T_MODIFIED && !force_ci && vers->ts_conflict) + { + char *filestamp; + int retcode; + + /* + * We found a "conflict" marker. + * + * If the timestamp on the file is the same as the + * timestamp stored in the Entries file, we block the commit. + */ +#ifdef SERVER_SUPPORT + if (server_active) + retcode = vers->ts_conflict[0] != '='; + else { + filestamp = time_stamp (finfo->file); + retcode = strcmp (vers->ts_conflict, filestamp); + free (filestamp); + } +#else + filestamp = time_stamp (finfo->file); + retcode = strcmp (vers->ts_conflict, filestamp); + free (filestamp); +#endif + if (retcode == 0) + { + error (0, 0, + "file `%s' had a conflict and has not been modified", + finfo->fullname); + freevers_ts (&vers); + return (1); + } + + /* + * If the timestamps differ, look for Conflict indicators + * in the file to see if we should block the commit anyway + */ + run_setup ("%s", GREP); + run_arg (RCS_MERGE_PAT); + run_arg (finfo->file); + retcode = run_exec (RUN_TTY, DEVNULL, RUN_TTY, RUN_REALLY); + + if (retcode == -1) + { + error (1, errno, + "fork failed while examining conflict in `%s'", + finfo->fullname); + } + else if (retcode == 0) + { + error (0, 0, + "file `%s' still contains conflict indicators", + finfo->fullname); + freevers_ts (&vers); + return (1); + } + } + + if (status == T_REMOVED && vers->tag && isdigit (*vers->tag)) + { + error (0, 0, + "cannot remove file `%s' which has a numeric sticky tag of `%s'", + finfo->fullname, vers->tag); + freevers_ts (&vers); + return (1); + } + if (status == T_ADDED) + { + char rcs[PATH_MAX]; + + /* Don't look in the attic; if it exists there we will + move it back out in checkaddfile. */ + sprintf(rcs, "%s/%s%s", finfo->repository, finfo->file, RCSEXT); + if (isreadable (rcs)) + { + error (0, 0, + "cannot add file `%s' when RCS file `%s' already exists", + finfo->fullname, rcs); + freevers_ts (&vers); + return (1); + } + if (vers->tag && isdigit (*vers->tag) && + numdots (vers->tag) > 1) + { + error (0, 0, + "cannot add file `%s' with revision `%s'; must be on trunk", + finfo->fullname, vers->tag); + freevers_ts (&vers); + return (1); + } + } + + /* done with consistency checks; now, to get on with the commit */ + if (finfo->update_dir[0] == '\0') + xdir = "."; + else + xdir = finfo->update_dir; + if ((p = findnode (mulist, xdir)) != NULL) + { + ulist = ((struct master_lists *) p->data)->ulist; + cilist = ((struct master_lists *) p->data)->cilist; + } + else + { + struct master_lists *ml; + + ulist = getlist (); + cilist = getlist (); + p = getnode (); + p->key = xstrdup (xdir); + p->type = UPDATE; + ml = (struct master_lists *) + xmalloc (sizeof (struct master_lists)); + ml->ulist = ulist; + ml->cilist = cilist; + p->data = (char *) ml; + p->delproc = masterlist_delproc; + (void) addnode (mulist, p); + } + + /* first do ulist, then cilist */ + p = getnode (); + p->key = xstrdup (finfo->file); + p->type = UPDATE; + p->delproc = update_delproc; + p->data = (char *) status; + (void) addnode (ulist, p); + + p = getnode (); + p->key = xstrdup (finfo->file); + p->type = UPDATE; + p->delproc = ci_delproc; + ci = (struct commit_info *) xmalloc (sizeof (struct commit_info)); + ci->status = status; + if (vers->tag) + if (isdigit (*vers->tag)) + ci->rev = xstrdup (vers->tag); + else + ci->rev = RCS_whatbranch (finfo->rcs, vers->tag); + else + ci->rev = (char *) NULL; + ci->tag = xstrdup (vers->tag); + ci->options = xstrdup(vers->options); + p->data = (char *) ci; + (void) addnode (cilist, p); + break; + case T_UNKNOWN: + error (0, 0, "nothing known about `%s'", finfo->fullname); + freevers_ts (&vers); + return (1); + case T_UPTODATE: + break; + default: + error (0, 0, "CVS internal error: unknown status %d", status); + break; + } + + freevers_ts (&vers); + return (0); +} + +/* + * Print warm fuzzies while examining the dirs + */ +/* ARGSUSED */ +static Dtype +check_direntproc (dir, repos, update_dir) + char *dir; + char *repos; + char *update_dir; +{ + if (!quiet) + error (0, 0, "Examining %s", update_dir); + + return (R_PROCESS); +} + +/* + * Walklist proc to run pre-commit checks + */ +static int +precommit_list_proc (p, closure) + Node *p; + void *closure; +{ + if (p->data == (char *) T_ADDED || p->data == (char *) T_MODIFIED || + p->data == (char *) T_REMOVED) + { + run_arg (p->key); + } + return (0); +} + +/* + * Callback proc for pre-commit checking + */ +static List *ulist; +static int +precommit_proc (repository, filter) + char *repository; + char *filter; +{ + /* see if the filter is there, only if it's a full path */ + if (isabsolute (filter)) + { + char *s, *cp; + + s = xstrdup (filter); + for (cp = s; *cp; cp++) + if (isspace (*cp)) + { + *cp = '\0'; + break; + } + if (!isfile (s)) + { + error (0, errno, "cannot find pre-commit filter `%s'", s); + free (s); + return (1); /* so it fails! */ + } + free (s); + } + + run_setup ("%s %s", filter, repository); + (void) walklist (ulist, precommit_list_proc, NULL); + return (run_exec (RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL|RUN_REALLY)); +} + +/* + * Run the pre-commit checks for the dir + */ +/* ARGSUSED */ +static int +check_filesdoneproc (err, repos, update_dir) + int err; + char *repos; + char *update_dir; +{ + int n; + Node *p; + + /* find the update list for this dir */ + p = findnode (mulist, update_dir); + if (p != NULL) + ulist = ((struct master_lists *) p->data)->ulist; + else + ulist = (List *) NULL; + + /* skip the checks if there's nothing to do */ + if (ulist == NULL || ulist->list->next == ulist->list) + return (err); + + /* run any pre-commit checks */ + if ((n = Parse_Info (CVSROOTADM_COMMITINFO, repos, precommit_proc, 1)) > 0) + { + error (0, 0, "Pre-commit check failed"); + err += n; + } + + return (err); +} + +/* + * Do the work of committing a file + */ +static int maxrev; +static char sbranch[PATH_MAX]; + +/* ARGSUSED */ +static int +commit_fileproc (finfo) + struct file_info *finfo; +{ + Node *p; + int err = 0; + List *ulist, *cilist; + struct commit_info *ci; + char rcs[PATH_MAX]; + + if (finfo->update_dir[0] == '\0') + p = findnode (mulist, "."); + else + p = findnode (mulist, finfo->update_dir); + + /* + * if p is null, there were file type command line args which were + * all up-to-date so nothing really needs to be done + */ + if (p == NULL) + return (0); + ulist = ((struct master_lists *) p->data)->ulist; + cilist = ((struct master_lists *) p->data)->cilist; + + /* + * At this point, we should have the commit message unless we were called + * with files as args from the command line. In that latter case, we + * need to get the commit message ourselves + */ + if (use_editor && !got_message) + { + got_message = 1; + do_editor (finfo->update_dir, &message, finfo->repository, ulist); + } + + p = findnode (cilist, finfo->file); + if (p == NULL) + return (0); + + ci = (struct commit_info *) p->data; + if (ci->status == T_MODIFIED) + { + if (lockrcsfile (finfo->file, finfo->repository, ci->rev) != 0) + { + unlockrcs (finfo->file, finfo->repository); + err = 1; + goto out; + } + } + else if (ci->status == T_ADDED) + { + if (checkaddfile (finfo->file, finfo->repository, ci->tag, ci->options, + &finfo->rcs) != 0) + { + fixaddfile (finfo->file, finfo->repository); + err = 1; + goto out; + } + + /* adding files with a tag, now means adding them on a branch. + Since the branch test was done in check_fileproc for + modified files, we need to stub it in again here. */ + + if (ci->tag) { + locate_rcs (finfo->file, finfo->repository, rcs); + ci->rev = RCS_whatbranch (finfo->rcs, ci->tag); + err = Checkin ('A', finfo->file, finfo->update_dir, finfo->repository, rcs, ci->rev, + ci->tag, ci->options, message, finfo->entries); + if (err != 0) + { + unlockrcs (finfo->file, finfo->repository); + fixbranch (finfo->file, finfo->repository, sbranch); + } + + (void) time (&last_register_time); + + ci->status = T_UPTODATE; + } + } + + /* + * Add the file for real + */ + if (ci->status == T_ADDED) + { + char *xrev = (char *) NULL; + + if (ci->rev == NULL) + { + /* find the max major rev number in this directory */ + maxrev = 0; + (void) walklist (finfo->entries, findmaxrev, NULL); + if (maxrev == 0) + maxrev = 1; + xrev = xmalloc (20); + (void) sprintf (xrev, "%d", maxrev); + } + + /* XXX - an added file with symbolic -r should add tag as well */ + err = finaladd (finfo->file, ci->rev ? ci->rev : xrev, ci->tag, ci->options, + finfo->update_dir, finfo->repository, finfo->entries); + if (xrev) + free (xrev); + } + else if (ci->status == T_MODIFIED) + { + locate_rcs (finfo->file, finfo->repository, rcs); + err = Checkin ('M', finfo->file, finfo->update_dir, finfo->repository, + rcs, ci->rev, ci->tag, + ci->options, message, finfo->entries); + + (void) time (&last_register_time); + + if (err != 0) + { + unlockrcs (finfo->file, finfo->repository); + fixbranch (finfo->file, finfo->repository, sbranch); + } + } + else if (ci->status == T_REMOVED) + { + err = remove_file (finfo->file, finfo->repository, ci->tag, message, + finfo->entries, finfo->rcs); +#ifdef SERVER_SUPPORT + if (server_active) { + server_scratch_entry_only (); + server_updated (finfo->file, finfo->update_dir, finfo->repository, + /* Doesn't matter, it won't get checked. */ + SERVER_UPDATED, (struct stat *) NULL, + (unsigned char *) NULL); + } +#endif + } + + /* Clearly this is right for T_MODIFIED. I haven't thought so much + about T_ADDED or T_REMOVED. */ + notify_do ('C', finfo->file, getcaller (), NULL, NULL, finfo->repository); + +out: + if (err != 0) + { + /* on failure, remove the file from ulist */ + p = findnode (ulist, finfo->file); + if (p) + delnode (p); + } + + return (err); +} + +/* + * Log the commit and clean up the update list + */ +/* ARGSUSED */ +static int +commit_filesdoneproc (err, repository, update_dir) + int err; + char *repository; + char *update_dir; +{ + char *xtag = (char *) NULL; + Node *p; + List *ulist; + + p = findnode (mulist, update_dir); + if (p == NULL) + return (err); + + ulist = ((struct master_lists *) p->data)->ulist; + + got_message = 0; + + /* see if we need to specify a per-directory or -r option tag */ + if (tag == NULL) + ParseTag (&xtag, (char **) NULL); + + Update_Logfile (repository, message, tag ? tag : xtag, (FILE *) 0, ulist); + if (xtag) + free (xtag); + + /* Build the administrative files if necessary. */ + { + char *p; + + if (strncmp (CVSroot, repository, strlen (CVSroot)) != 0) + error (0, 0, "internal error: repository doesn't begin with root"); + p = repository + strlen (CVSroot); + if (*p == '/') + ++p; + if (strcmp ("CVSROOT", p) == 0) + { + /* "Database" might a little bit grandiose and/or vague, + but "checked-out copies of administrative files, unless + in the case of modules and you are using ndbm in which + case modules.{pag,dir,db}" is verbose and excessively + focused on how the database is implemented. */ + + cvs_output (program_name, 0); + cvs_output (" ", 1); + cvs_output (command_name, 0); + cvs_output (": Rebuilding administrative file database\n", 0); + mkmodules (repository); + } + } + + if (err == 0 && run_module_prog) + { + FILE *fp; + + if ((fp = fopen (CVSADM_CIPROG, "r")) != NULL) + { + char *line; + int line_length; + size_t line_chars_allocated; + char *repository; + + line = NULL; + line_chars_allocated = 0; + line_length = getline (&line, &line_chars_allocated, fp); + if (line_length > 0) + { + /* Remove any trailing newline. */ + if (line[line_length - 1] == '\n') + line[--line_length] = '\0'; + repository = Name_Repository ((char *) NULL, update_dir); + run_setup ("%s %s", line, repository); + (void) printf ("%s %s: Executing '", program_name, + command_name); + run_print (stdout); + (void) printf ("'\n"); + (void) run_exec (RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL); + free (repository); + } + else + { + if (ferror (fp)) + error (0, errno, "warning: error reading %s", + CVSADM_CIPROG); + } + if (line != NULL) + free (line); + if (fclose (fp) < 0) + error (0, errno, "warning: cannot close %s", CVSADM_CIPROG); + } + else + { + if (! existence_error (errno)) + error (0, errno, "warning: cannot open %s", CVSADM_CIPROG); + } + } + + return (err); +} + +/* + * Get the log message for a dir and print a warm fuzzy + */ +/* ARGSUSED */ +static Dtype +commit_direntproc (dir, repos, update_dir) + char *dir; + char *repos; + char *update_dir; +{ + Node *p; + List *ulist; + char *real_repos; + + /* find the update list for this dir */ + p = findnode (mulist, update_dir); + if (p != NULL) + ulist = ((struct master_lists *) p->data)->ulist; + else + ulist = (List *) NULL; + + /* skip the files as an optimization */ + if (ulist == NULL || ulist->list->next == ulist->list) + return (R_SKIP_FILES); + + /* print the warm fuzzy */ + if (!quiet) + error (0, 0, "Committing %s", update_dir); + + /* get commit message */ + if (use_editor) + { + got_message = 1; + real_repos = Name_Repository (dir, update_dir); + do_editor (update_dir, &message, real_repos, ulist); + free (real_repos); + } + return (R_PROCESS); +} + +/* + * Process the post-commit proc if necessary + */ +/* ARGSUSED */ +static int +commit_dirleaveproc (dir, err, update_dir) + char *dir; + int err; + char *update_dir; +{ + /* update the per-directory tag info */ + if (err == 0 && write_dirtag != NULL) + { + WriteTag ((char *) NULL, write_dirtag, (char *) NULL); +#ifdef SERVER_SUPPORT + if (server_active) + server_set_sticky (update_dir, Name_Repository (dir, update_dir), + write_dirtag, (char *) NULL); +#endif + } + + return (err); +} + +/* + * find the maximum major rev number in an entries file + */ +static int +findmaxrev (p, closure) + Node *p; + void *closure; +{ + char *cp; + int thisrev; + Entnode *entdata; + + entdata = (Entnode *) p->data; + cp = strchr (entdata->version, '.'); + if (cp != NULL) + *cp = '\0'; + thisrev = atoi (entdata->version); + if (cp != NULL) + *cp = '.'; + if (thisrev > maxrev) + maxrev = thisrev; + return (0); +} + +/* + * Actually remove a file by moving it to the attic + * XXX - if removing a ,v file that is a relative symbolic link to + * another ,v file, we probably should add a ".." component to the + * link to keep it relative after we move it into the attic. + */ +static int +remove_file (file, repository, tag, message, entries, rcsnode) + char *file; + char *repository; + char *tag; + char *message; + List *entries; + RCSNode *rcsnode; +{ + mode_t omask; + int retcode; + char rcs[PATH_MAX]; + char *tmp; + + int branch; + int lockflag; + char *corev; + char *rev; + char *prev_rev; + + corev = NULL; + rev = NULL; + prev_rev = NULL; + + retcode = 0; + + locate_rcs (file, repository, rcs); + + branch = 0; + if (tag && !(branch = RCS_isbranch (rcsnode, tag))) + { + /* a symbolic tag is specified; just remove the tag from the file */ + if ((retcode = RCS_deltag (rcs, tag, 1)) != 0) + { + if (!quiet) + error (0, retcode == -1 ? errno : 0, + "failed to remove tag `%s' from `%s'", tag, rcs); + return (1); + } + Scratch_Entry (entries, file); + return (0); + } + + /* we are removing the file from either the head or a branch */ + /* commit a new, dead revision. */ + + /* Print message indicating that file is going to be removed. */ + (void) printf ("Removing %s;\n", file); + + rev = NULL; + lockflag = RCS_FLAGS_LOCK; + if (branch) + { + char *branchname; + + rev = RCS_whatbranch (rcsnode, tag); + if (rev == NULL) + { + error (0, 0, "cannot find branch \"%s\".", tag); + return (1); + } + + if (rcsnode == NULL) + { + error (0, 0, "boy, I'm confused."); + return (1); + } + branchname = RCS_getbranch (rcsnode, rev, 1); + if (branchname == NULL) + { + /* no revision exists on this branch. use the previous + revision but do not lock. */ + corev = RCS_gettag (rcsnode, tag, 1, 0); + prev_rev = xstrdup(rev); + lockflag = 0; + } else + { + corev = xstrdup (rev); + prev_rev = xstrdup(branchname); + free (branchname); + } + + } else /* Not a branch */ + { + + /* Get current head revision of file. */ + if (rcsnode == NULL) + { + error (0, 0, "could not find parsed rcsfile %s", file); + return (1); + } + prev_rev = RCS_head (rcsnode); + } + + /* if removing without a tag or a branch, then make sure the default + branch is the trunk. */ + if (!tag && !branch) + { + if (RCS_setbranch (rcs, NULL) != 0) + { + error (0, 0, "cannot change branch to default for %s", + rcs); + return (1); + } + } + +#ifdef SERVER_SUPPORT + if (server_active) { + /* If this is the server, there will be a file sitting in the + temp directory which is the kludgy way in which server.c + tells time_stamp that the file is no longer around. Remove + it so we can create temp files with that name (ignore errors). */ + unlink_file (file); + } +#endif + + /* check something out. Generally this is the head. If we have a + particular rev, then name it. except when creating a branch, + lock the rev we're checking out. */ + retcode = RCS_checkout (rcs, "", rev ? corev : NULL, NULL, RUN_TTY, + lockflag, 1); + if (retcode != 0) + { + if (!quiet) + error (0, retcode == -1 ? errno : 0, + "failed to check out `%s'", rcs); + return (1); + } + + if (corev != NULL) + free (corev); + + retcode = RCS_checkin (rcs, NULL, message, rev, RCS_FLAGS_DEAD, 1); + if (retcode != 0) + { + if (!quiet) + error (0, retcode == -1 ? errno : 0, + "failed to commit dead revision for `%s'", rcs); + return (1); + } + + if (rev != NULL) + free (rev); + + if (!branch) + { + /* this was the head; really move it into the Attic */ + tmp = xmalloc(strlen(repository) + + sizeof('/') + + sizeof(CVSATTIC) + + sizeof('/') + + strlen(file) + + sizeof(RCSEXT) + 1); + (void) sprintf (tmp, "%s/%s", repository, CVSATTIC); + omask = umask (cvsumask); + (void) CVS_MKDIR (tmp, 0777); + (void) umask (omask); + (void) sprintf (tmp, "%s/%s/%s%s", repository, CVSATTIC, file, RCSEXT); + + if (strcmp (rcs, tmp) != 0 + && rename (rcs, tmp) == -1 + && (isreadable (rcs) || !isreadable (tmp))) + { + free(tmp); + return (1); + } + free(tmp); + } + + /* Print message that file was removed. */ + (void) printf ("%s <-- %s\n", rcs, file); + (void) printf ("new revision: delete; "); + (void) printf ("previous revision: %s\n", prev_rev); + (void) printf ("done\n"); + free(prev_rev); + + Scratch_Entry (entries, file); + return (0); +} + +/* + * Do the actual checkin for added files + */ +static int +finaladd (file, rev, tag, options, update_dir, repository, entries) + char *file; + char *rev; + char *tag; + char *options; + char *update_dir; + char *repository; + List *entries; +{ + int ret; + char tmp[PATH_MAX]; + char rcs[PATH_MAX]; + + locate_rcs (file, repository, rcs); + ret = Checkin ('A', file, update_dir, repository, rcs, rev, tag, options, + message, entries); + if (ret == 0) + { + (void) sprintf (tmp, "%s/%s%s", CVSADM, file, CVSEXT_LOG); + (void) unlink_file (tmp); + } + else + fixaddfile (file, repository); + + (void) time (&last_register_time); + + return (ret); +} + +/* + * Unlock an rcs file + */ +static void +unlockrcs (file, repository) + char *file; + char *repository; +{ + char rcs[PATH_MAX]; + int retcode = 0; + + locate_rcs (file, repository, rcs); + + if ((retcode = RCS_unlock (rcs, NULL, 0)) != 0) + error (retcode == -1 ? 1 : 0, retcode == -1 ? errno : 0, + "could not unlock %s", rcs); +} + +/* + * remove a partially added file. if we can parse it, leave it alone. + */ +static void +fixaddfile (file, repository) + char *file; + char *repository; +{ + RCSNode *rcsfile; + char rcs[PATH_MAX]; + int save_really_quiet; + + locate_rcs (file, repository, rcs); + save_really_quiet = really_quiet; + really_quiet = 1; + if ((rcsfile = RCS_parsercsfile (rcs)) == NULL) + (void) unlink_file (rcs); + else + freercsnode (&rcsfile); + really_quiet = save_really_quiet; +} + +/* + * put the branch back on an rcs file + */ +static void +fixbranch (file, repository, branch) + char *file; + char *repository; + char *branch; +{ + char rcs[PATH_MAX]; + int retcode = 0; + + if (branch != NULL && branch[0] != '\0') + { + locate_rcs (file, repository, rcs); + if ((retcode = RCS_setbranch (rcs, branch)) != 0) + error (retcode == -1 ? 1 : 0, retcode == -1 ? errno : 0, + "cannot restore branch to %s for %s", branch, rcs); + } +} + +/* + * do the initial part of a file add for the named file. if adding + * with a tag, put the file in the Attic and point the symbolic tag + * at the committed revision. + */ + +static int +checkaddfile (file, repository, tag, options, rcsnode) + char *file; + char *repository; + char *tag; + char *options; + RCSNode **rcsnode; +{ + char rcs[PATH_MAX]; + char fname[PATH_MAX]; + mode_t omask; + int retcode = 0; + int newfile = 0; + + if (tag) + { + (void) sprintf(rcs, "%s/%s", repository, CVSATTIC); + omask = umask (cvsumask); + if (CVS_MKDIR (rcs, 0777) != 0 && errno != EEXIST) + error (1, errno, "cannot make directory `%s'", rcs);; + (void) umask (omask); + (void) sprintf (rcs, "%s/%s/%s%s", repository, CVSATTIC, file, RCSEXT); + } + else + locate_rcs (file, repository, rcs); + + if (isreadable(rcs)) + { + /* file has existed in the past. Prepare to resurrect. */ + char oldfile[PATH_MAX]; + char *rev; + RCSNode *rcsfile; + + if (tag == NULL) + { + /* we are adding on the trunk, so move the file out of the + Attic. */ + strcpy (oldfile, rcs); + sprintf (rcs, "%s/%s%s", repository, file, RCSEXT); + + if (strcmp (oldfile, rcs) == 0 + || rename (oldfile, rcs) != 0 + || isreadable (oldfile) + || !isreadable (rcs)) + { + error (0, 0, "failed to move `%s' out of the attic.", + file); + return (1); + } + } + + if ((rcsfile = *rcsnode) == NULL) + { + error (0, 0, "could not find parsed rcsfile %s", file); + return (1); + } + + rev = RCS_getversion (rcsfile, tag, NULL, 1, 0); + /* and lock it */ + if (lock_RCS (file, rcs, rev, repository)) { + error (0, 0, "cannot lock `%s'.", rcs); + free (rev); + return (1); + } + + free (rev); + } else { + /* this is the first time we have ever seen this file; create + an rcs file. */ + run_setup ("%s%s -x,v/ -i", Rcsbin, RCS); + + (void) sprintf (fname, "%s/%s%s", CVSADM, file, CVSEXT_LOG); + /* If the file does not exist, no big deal. In particular, the + server does not (yet at least) create CVSEXT_LOG files. */ + if (isfile (fname)) + run_args ("-t%s/%s%s", CVSADM, file, CVSEXT_LOG); + + /* Set RCS keyword expansion options. */ + if (options && options[0] == '-' && options[1] == 'k') + run_arg (options); + run_arg (rcs); + if ((retcode = run_exec (RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL)) != 0) + { + error (retcode == -1 ? 1 : 0, retcode == -1 ? errno : 0, + "could not create %s", rcs); + return (1); + } + newfile = 1; + } + + /* when adding a file for the first time, and using a tag, we need + to create a dead revision on the trunk. */ + if (tag && newfile) + { + char *tmp; + + /* move the new file out of the way. */ + (void) sprintf (fname, "%s/%s%s", CVSADM, CVSPREFIX, file); + rename_file (file, fname); + copy_file (DEVNULL, file); + + tmp = xmalloc (strlen (file) + strlen (tag) + 80); + /* commit a dead revision. */ + (void) sprintf (tmp, "file %s was initially added on branch %s.", + file, tag); + retcode = RCS_checkin (rcs, NULL, tmp, NULL, + RCS_FLAGS_DEAD | RCS_FLAGS_QUIET, 0); + free (tmp); + if (retcode != 0) + { + error (retcode == -1 ? 1 : 0, retcode == -1 ? errno : 0, + "could not create initial dead revision %s", rcs); + return (1); + } + + /* put the new file back where it was */ + rename_file (fname, file); + + /* and lock it once again. */ + if (lock_RCS (file, rcs, NULL, repository)) { + error (0, 0, "cannot lock `%s'.", rcs); + return (1); + } + } + + if (tag != NULL) + { + /* when adding with a tag, we need to stub a branch, if it + doesn't already exist. */ + RCSNode *rcsfile; + + rcsfile = RCS_parse (file, repository); + if (rcsfile == NULL) + { + error (0, 0, "could not read %s", rcs); + return (1); + } + + if (!RCS_nodeisbranch (rcsfile, tag)) { + /* branch does not exist. Stub it. */ + char *head; + char *magicrev; + + head = RCS_getversion (rcsfile, NULL, NULL, 0, 0); + magicrev = RCS_magicrev (rcsfile, head); + if ((retcode = RCS_settag(rcs, tag, magicrev)) != 0) + { + error (retcode == -1 ? 1 : 0, retcode == -1 ? errno : 0, + "could not stub branch %s for %s", tag, rcs); + return (1); + } + + freercsnode (&rcsfile); + + /* reparse the file, then add it to our list. */ + rcsfile = RCS_parse (file, repository); + if (rcsfile == NULL) + { + error (0, 0, "could not reparse %s", rcs); + return (1); + } + + free (head); + free (magicrev); + } + else + { + /* lock the branch. (stubbed branches need not be locked.) */ + if (lock_RCS (file, rcs, NULL, repository)) { + error (0, 0, "cannot lock `%s'.", rcs); + return (1); + } + } + + if (rcsnode) + freercsnode(rcsnode); + *rcsnode = rcsfile; + } + + fileattr_newfile (file); + + fix_rcs_modes (rcs, file); + return (0); +} + +/* + * Lock the rcs file ``file'' + */ +static int +lockrcsfile (file, repository, rev) + char *file; + char *repository; + char *rev; +{ + char rcs[PATH_MAX]; + + locate_rcs (file, repository, rcs); + if (lock_RCS (file, rcs, rev, repository) != 0) + return (1); + else + return (0); +} + +/* + * Attempt to place a lock on the RCS file; returns 0 if it could and 1 if it + * couldn't. If the RCS file currently has a branch as the head, we must + * move the head back to the trunk before locking the file, and be sure to + * put the branch back as the head if there are any errors. + */ +static int +lock_RCS (user, rcs, rev, repository) + char *user; + char *rcs; + char *rev; + char *repository; +{ + RCSNode *rcsfile; + char *branch = NULL; + int err = 0; + + /* + * For a specified, numeric revision of the form "1" or "1.1", (or when + * no revision is specified ""), definitely move the branch to the trunk + * before locking the RCS file. + * + * The assumption is that if there is more than one revision on the trunk, + * the head points to the trunk, not a branch... and as such, it's not + * necessary to move the head in this case. + */ + if (rev == NULL || (rev && isdigit (*rev) && numdots (rev) < 2)) + { + if ((rcsfile = RCS_parsercsfile (rcs)) == NULL) + { + /* invalid rcs file? */ + err = 1; + } + else + { + /* rcsfile is valid */ + branch = xstrdup (rcsfile->branch); + freercsnode (&rcsfile); + if (branch != NULL) + { + if (RCS_setbranch (rcs, NULL) != 0) + { + error (0, 0, "cannot change branch to default for %s", + rcs); + if (branch) + free (branch); + return (1); + } + } + err = RCS_lock(rcs, NULL, 0); + } + } + else + { + (void) RCS_lock(rcs, rev, 1); + } + + if (err == 0) + { + if (branch) + { + (void) strcpy (sbranch, branch); + free (branch); + } + else + sbranch[0] = '\0'; + return (0); + } + + /* try to restore the branch if we can on error */ + if (branch != NULL) + fixbranch (user, repository, branch); + + if (branch) + free (branch); + return (1); +} + +/* + * Called when "add"ing files to the RCS respository, as it is necessary to + * preserve the file modes in the same fashion that RCS does. This would be + * automatic except that we are placing the RCS ,v file very far away from + * the user file, and I can't seem to convince RCS of the location of the + * user file. So we munge it here, after the ,v file has been successfully + * initialized with "rcs -i". + */ +static void +fix_rcs_modes (rcs, user) + char *rcs; + char *user; +{ + struct stat sb; + + if (stat (user, &sb) != -1) + (void) chmod (rcs, (int) sb.st_mode & ~0222); +} + +/* + * free an UPDATE node's data (really nothing to do) + */ +void +update_delproc (p) + Node *p; +{ + p->data = (char *) NULL; +} + +/* + * Free the commit_info structure in p. + */ +static void +ci_delproc (p) + Node *p; +{ + struct commit_info *ci; + + ci = (struct commit_info *) p->data; + if (ci->rev) + free (ci->rev); + if (ci->tag) + free (ci->tag); + if (ci->options) + free (ci->options); + free (ci); +} + +/* + * Free the commit_info structure in p. + */ +static void +masterlist_delproc (p) + Node *p; +{ + struct master_lists *ml; + + ml = (struct master_lists *) p->data; + dellist (&ml->ulist); + dellist (&ml->cilist); + free (ml); +} + +/* + * Find an RCS file in the repository. + */ +static void +locate_rcs (file, repository, rcs) + char *file; + char *repository; + char *rcs; +{ + (void) sprintf (rcs, "%s/%s%s", repository, file, RCSEXT); + if (!isreadable (rcs)) + { + (void) sprintf (rcs, "%s/%s/%s%s", repository, CVSATTIC, file, RCSEXT); + if (!isreadable (rcs)) + (void) sprintf (rcs, "%s/%s%s", repository, file, RCSEXT); + } +} diff --git a/contrib/cvs/src/create_adm.c b/contrib/cvs/src/create_adm.c new file mode 100644 index 0000000..fd7fd4d --- /dev/null +++ b/contrib/cvs/src/create_adm.c @@ -0,0 +1,140 @@ +/* + * Copyright (c) 1992, Brian Berliner and Jeff Polk + * Copyright (c) 1989-1992, Brian Berliner + * + * You may distribute under the terms of the GNU General Public License as + * specified in the README file that comes with the CVS 1.4 kit. + * + * Create Administration. + * + * Creates a CVS administration directory based on the argument repository; the + * "Entries" file is prefilled from the "initrecord" argument. + */ + +#include "cvs.h" + +/* update_dir includes dir as its last component. */ + +void +Create_Admin (dir, update_dir, repository, tag, date) + char *dir; + char *update_dir; + char *repository; + char *tag; + char *date; +{ + FILE *fout; + char *cp; + char tmp[PATH_MAX]; + +#ifdef SERVER_SUPPORT + if (trace) + { + char wd[PATH_MAX]; + getwd (wd); + fprintf (stderr, "%c-> Create_Admin (%s, %s, %s, %s, %s) in %s\n", + (server_active) ? 'S' : ' ', + dir, update_dir, repository, tag ? tag : "", + date ? date : "", wd); + } +#endif + + if (noexec) + return; + + if (dir != NULL) + (void) sprintf (tmp, "%s/%s", dir, CVSADM); + else + (void) strcpy (tmp, CVSADM); + if (isfile (tmp)) + error (1, 0, "there is a version in %s already", update_dir); + + make_directory (tmp); + + /* record the current cvs root for later use */ + + Create_Root (dir, CVSroot); + if (dir != NULL) + (void) sprintf (tmp, "%s/%s", dir, CVSADM_REP); + else + (void) strcpy (tmp, CVSADM_REP); + fout = fopen (tmp, "w+"); + if (fout == NULL) + { + if (update_dir[0] == '\0') + error (1, errno, "cannot open %s", tmp); + else + error (1, errno, "cannot open %s/%s", update_dir, CVSADM_REP); + } + cp = repository; + strip_path (cp); + +#ifdef RELATIVE_REPOS + /* + * If the Repository file is to hold a relative path, try to strip off + * the leading CVSroot argument. + */ + if (CVSroot != NULL) + { + char path[PATH_MAX]; + + (void) sprintf (path, "%s/", CVSroot); + if (strncmp (repository, path, strlen (path)) == 0) + cp = repository + strlen (path); + } +#endif + + if (fprintf (fout, "%s\n", cp) < 0) + { + if (update_dir[0] == '\0') + error (1, errno, "write to %s failed", tmp); + else + error (1, errno, "write to %s/%s failed", update_dir, CVSADM_REP); + } + if (fclose (fout) == EOF) + { + if (update_dir[0] == '\0') + error (1, errno, "cannot close %s", tmp); + else + error (1, errno, "cannot close %s/%s", update_dir, CVSADM_REP); + } + + /* now, do the Entries file */ + if (dir != NULL) + (void) sprintf (tmp, "%s/%s", dir, CVSADM_ENT); + else + (void) strcpy (tmp, CVSADM_ENT); + fout = fopen (tmp, "w+"); + if (fout == NULL) + { + if (update_dir[0] == '\0') + error (1, errno, "cannot open %s", tmp); + else + error (1, errno, "cannot open %s/%s", update_dir, CVSADM_ENT); + } + if (fclose (fout) == EOF) + { + if (update_dir[0] == '\0') + error (1, errno, "cannot close %s", tmp); + else + error (1, errno, "cannot close %s/%s", update_dir, CVSADM_ENT); + } + + /* Create a new CVS/Tag file */ + WriteTag (dir, tag, date); + +#ifdef SERVER_SUPPORT + if (server_active) + { + server_set_sticky (update_dir, repository, tag, date); + server_template (update_dir, repository); + } + + if (trace) + { + fprintf (stderr, "%c<- Create_Admin\n", + (server_active) ? 'S' : ' '); + } +#endif + +} diff --git a/contrib/cvs/src/cvs.h b/contrib/cvs/src/cvs.h new file mode 100644 index 0000000..8e50f5d --- /dev/null +++ b/contrib/cvs/src/cvs.h @@ -0,0 +1,690 @@ +/* $CVSid: @(#)cvs.h 1.86 94/10/22 $ */ + +/* + * basic information used in all source files + * + */ + + +#include "config.h" /* this is stuff found via autoconf */ +#include "options.h" /* these are some larger questions which + can't easily be automatically checked + for */ + +/* Changed from if __STDC__ to ifdef __STDC__ because of Sun's acc compiler */ + +#ifdef __STDC__ +#define PTR void * +#else +#define PTR char * +#endif + +/* Add prototype support. */ +#ifndef PROTO +#if defined (USE_PROTOTYPES) ? USE_PROTOTYPES : defined (__STDC__) +#define PROTO(ARGS) ARGS +#else +#define PROTO(ARGS) () +#endif +#endif + +#include <stdio.h> + +/* Under OS/2, <stdio.h> doesn't define popen()/pclose(). */ +#ifdef USE_OWN_POPEN +#include "popen.h" +#endif + +#ifdef STDC_HEADERS +#include <stdlib.h> +#else +extern void exit (); +extern char *getenv(); +#endif + +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif + +#ifdef HAVE_STRING_H +#include <string.h> +#else +#include <strings.h> +#endif + +#ifdef SERVER_SUPPORT +/* If the system doesn't provide strerror, it won't be declared in + string.h. */ +char *strerror (); +#endif + +#include <fnmatch.h> /* This is supposed to be available on Posix systems */ + +#include <ctype.h> +#include <pwd.h> +#include <signal.h> + +#ifdef HAVE_ERRNO_H +#include <errno.h> +#else +#ifndef errno +extern int errno; +#endif /* !errno */ +#endif /* HAVE_ERRNO_H */ + +#include "system.h" + +#include "hash.h" +#if defined(SERVER_SUPPORT) || defined(CLIENT_SUPPORT) +#include "server.h" +#include "client.h" +#endif + +#ifdef MY_NDBM +#include "myndbm.h" +#else +#include <ndbm.h> +#endif /* MY_NDBM */ + +#include "regex.h" +#include "getopt.h" +#include "wait.h" + +#include "rcs.h" + + +/* XXX - for now this is static */ +#ifndef PATH_MAX +#ifdef MAXPATHLEN +#define PATH_MAX MAXPATHLEN+2 +#else +#define PATH_MAX 1024+2 +#endif +#endif /* PATH_MAX */ + +/* just in case this implementation does not define this */ +#ifndef L_tmpnam +#define L_tmpnam 50 +#endif + + +/* + * Copyright (c) 1992, Brian Berliner and Jeff Polk + * Copyright (c) 1989-1992, Brian Berliner + * + * You may distribute under the terms of the GNU General Public License as + * specified in the README file that comes with the CVS 1.4 kit. + * + * Definitions for the CVS Administrative directory and the files it contains. + * Here as #define's to make changing the names a simple task. + */ + +#ifdef USE_VMS_FILENAMES +#define CVSADM "CVS" +#define CVSADM_ENT "CVS/Entries." +#define CVSADM_ENTBAK "CVS/Entries.Backup" +#define CVSADM_ENTLOG "CVS/Entries.Log" +#define CVSADM_ENTSTAT "CVS/Entries.Static" +#define CVSADM_REP "CVS/Repository." +#define CVSADM_ROOT "CVS/Root." +#define CVSADM_CIPROG "CVS/Checkin.prog" +#define CVSADM_UPROG "CVS/Update.prog" +#define CVSADM_TAG "CVS/Tag." +#define CVSADM_NOTIFY "CVS/Notify." +#define CVSADM_NOTIFYTMP "CVS/Notify.tmp" +#define CVSADM_BASE "CVS/Base" +#define CVSADM_TEMPLATE "CVS/Template." +#else /* USE_VMS_FILENAMES */ +#define CVSADM "CVS" +#define CVSADM_ENT "CVS/Entries" +#define CVSADM_ENTBAK "CVS/Entries.Backup" +#define CVSADM_ENTLOG "CVS/Entries.Log" +#define CVSADM_ENTSTAT "CVS/Entries.Static" +#define CVSADM_REP "CVS/Repository" +#define CVSADM_ROOT "CVS/Root" +#define CVSADM_CIPROG "CVS/Checkin.prog" +#define CVSADM_UPROG "CVS/Update.prog" +#define CVSADM_TAG "CVS/Tag" +#define CVSADM_NOTIFY "CVS/Notify" +#define CVSADM_NOTIFYTMP "CVS/Notify.tmp" +/* A directory in which we store base versions of files we currently are + editing with "cvs edit". */ +#define CVSADM_BASE "CVS/Base" +/* File which contains the template for use in log messages. */ +#define CVSADM_TEMPLATE "CVS/Template" +#endif /* USE_VMS_FILENAMES */ + +/* This is the special directory which we use to store various extra + per-directory information in the repository. It must be the same as + CVSADM to avoid creating a new reserved directory name which users cannot + use, but is a separate #define because if anyone changes it (which I don't + recommend), one needs to deal with old, unconverted, repositories. + + See fileattr.h for details about file attributes, the only thing stored + in CVSREP currently. */ +#define CVSREP "CVS" + +/* + * Definitions for the CVSROOT Administrative directory and the files it + * contains. This directory is created as a sub-directory of the $CVSROOT + * environment variable, and holds global administration information for the + * entire source repository beginning at $CVSROOT. + */ +#define CVSROOTADM "CVSROOT" +#define CVSROOTADM_MODULES "modules" +#define CVSROOTADM_LOGINFO "loginfo" +#define CVSROOTADM_RCSINFO "rcsinfo" +#define CVSROOTADM_COMMITINFO "commitinfo" +#define CVSROOTADM_TAGINFO "taginfo" +#define CVSROOTADM_EDITINFO "editinfo" +#define CVSROOTADM_HISTORY "history" +#define CVSROOTADM_VALTAGS "val-tags" +#define CVSROOTADM_IGNORE "cvsignore" +#define CVSROOTADM_CHECKOUTLIST "checkoutlist" +#define CVSROOTADM_WRAPPER "cvswrappers" +#define CVSROOTADM_NOTIFY "notify" +#define CVSROOTADM_USERS "users" + +#define CVSNULLREPOS "Emptydir" /* an empty directory */ + +/* Other CVS file names */ + +/* Files go in the attic if the head main branch revision is dead, + otherwise they go in the regular repository directories. The whole + concept of having an attic is sort of a relic from before death + support but on the other hand, it probably does help the speed of + some operations (such as main branch checkouts and updates). */ +#define CVSATTIC "Attic" + +#define CVSLCK "#cvs.lock" +#define CVSRFL "#cvs.rfl" +#define CVSWFL "#cvs.wfl" +#define CVSRFLPAT "#cvs.rfl.*" /* wildcard expr to match read locks */ +#define CVSEXT_LOG ",t" +#define CVSPREFIX ",," +#define CVSDOTIGNORE ".cvsignore" +#define CVSDOTWRAPPER ".cvswrappers" + +/* miscellaneous CVS defines */ +#define CVSEDITPREFIX "CVS: " +#define CVSLCKAGE (60*60) /* 1-hour old lock files cleaned up */ +#define CVSLCKSLEEP 30 /* wait 30 seconds before retrying */ +#define CVSBRANCH "1.1.1" /* RCS branch used for vendor srcs */ + +#ifdef USE_VMS_FILENAMES +#define BAKPREFIX "_$" +#define DEVNULL "NLA0:" +#else /* USE_VMS_FILENAMES */ +#define BAKPREFIX ".#" /* when rcsmerge'ing */ +#ifndef DEVNULL +#define DEVNULL "/dev/null" +#endif +#endif /* USE_VMS_FILENAMES */ + +#define FALSE 0 +#define TRUE 1 + +/* + * Special tags. -rHEAD refers to the head of an RCS file, regardless of any + * sticky tags. -rBASE refers to the current revision the user has checked + * out This mimics the behaviour of RCS. + */ +#define TAG_HEAD "HEAD" +#define TAG_BASE "BASE" + +/* Environment variable used by CVS */ +#define CVSREAD_ENV "CVSREAD" /* make files read-only */ +#define CVSREAD_DFLT FALSE /* writable files by default */ + +#define RCSBIN_ENV "RCSBIN" /* RCS binary directory */ +/* #define RCSBIN_DFLT Set by config.h */ + +#define EDITOR1_ENV "CVSEDITOR" /* which editor to use */ +#define EDITOR2_ENV "VISUAL" /* which editor to use */ +#define EDITOR3_ENV "EDITOR" /* which editor to use */ +/* #define EDITOR_DFLT Set by config.h */ + +#define CVSROOT_ENV "CVSROOT" /* source directory root */ +#define CVSROOT_DFLT NULL /* No dflt; must set for checkout */ + +#define IGNORE_ENV "CVSIGNORE" /* More files to ignore */ +#define WRAPPER_ENV "CVSWRAPPERS" /* name of the wrapper file */ + +#define CVSUMASK_ENV "CVSUMASK" /* Effective umask for repository */ +/* #define CVSUMASK_DFLT Set by config.h */ + +/* + * If the beginning of the Repository matches the following string, strip it + * so that the output to the logfile does not contain a full pathname. + * + * If the CVSROOT environment variable is set, it overrides this define. + */ +#define REPOS_STRIP "/master/" + +/* + * The maximum number of files per each CVS directory. This is mainly for + * sizing arrays statically rather than dynamically. 3000 seems plenty for + * now. + */ +#define MAXFILEPERDIR 3000 +#define MAXLINELEN 5000 /* max input line from a file */ +#define MAXPROGLEN 30000 /* max program length to system() */ +#define MAXLISTLEN 40000 /* For [A-Z]list holders */ +#define MAXDATELEN 50 /* max length for a date */ + +/* structure of a entry record */ +struct entnode +{ + char *user; + char *version; + char *timestamp; + char *options; + char *tag; + char *date; + char *conflict; +}; +typedef struct entnode Entnode; + +/* The type of request that is being done in do_module() */ +enum mtype +{ + CHECKOUT, TAG, PATCH, EXPORT +}; + +/* + * defines for Classify_File() to determine the current state of a file. + * These are also used as types in the data field for the list we make for + * Update_Logfile in commit, import, and add. + */ +enum classify_type +{ + T_UNKNOWN = 1, /* no old-style analog existed */ + T_CONFLICT, /* C (conflict) list */ + T_NEEDS_MERGE, /* G (needs merging) list */ + T_MODIFIED, /* M (needs checked in) list */ + T_CHECKOUT, /* O (needs checkout) list */ + T_ADDED, /* A (added file) list */ + T_REMOVED, /* R (removed file) list */ + T_REMOVE_ENTRY, /* W (removed entry) list */ + T_UPTODATE, /* File is up-to-date */ +#ifdef SERVER_SUPPORT + T_PATCH, /* P Like C, but can patch */ +#endif + T_TITLE /* title for node type */ +}; +typedef enum classify_type Ctype; + +/* + * a struct vers_ts contains all the information about a file including the + * user and rcs file names, and the version checked out and the head. + * + * this is usually obtained from a call to Version_TS which takes a tag argument + * for the RCS file if desired + */ +struct vers_ts +{ + /* rcs version user file derives from, from CVS/Entries. + * it can have the following special values: + * empty = no user file + * 0 = user file is new + * -vers = user file to be removed. */ + char *vn_user; + + /* Numeric revision number corresponding to ->vn_tag (->vn_tag + will often be symbolic). */ + char *vn_rcs; + /* If ->tag corresponds to a tag which really exists in this file, + this is just a copy of ->tag. If not, this is either NULL or + the head revision. (Or something like that, see RCS_getversion + and friends). */ + char *vn_tag; + + /* This is the timestamp from stating the file in the working directory. + It is NULL if there is no file in the working directory. */ + char *ts_user; + /* Timestamp from CVS/Entries. For the server, ts_user and ts_rcs + are computed in a slightly different way, but the fact remains that + if they are equal the file in the working directory is unmodified + and if they differ it is modified. */ + char *ts_rcs; + + /* Options from CVS/Entries (keyword expansion). */ + char *options; + + /* If non-NULL, there was a conflict (or merely a merge? See merge_file) + and the time stamp in this field is the time stamp of the working + directory file which was created with the conflict markers in it. + This is from CVS/Entries. */ + char *ts_conflict; + + /* Tag specified on the command line, or if none, tag stored in + CVS/Entries. */ + char *tag; + /* Date specified on the command line, or if none, date stored in + CVS/Entries. */ + char *date; + + /* Pointer to entries file node */ + Entnode *entdata; + + /* Pointer to parsed src file info */ + RCSNode *srcfile; +}; +typedef struct vers_ts Vers_TS; + +/* + * structure used for list-private storage by Entries_Open() and + * Version_TS(). + */ +struct stickydirtag +{ + int aflag; + char *tag; + char *date; + char *options; +}; + +/* Flags for find_{names,dirs} routines */ +#define W_LOCAL 0x01 /* look for files locally */ +#define W_REPOS 0x02 /* look for files in the repository */ +#define W_ATTIC 0x04 /* look for files in the attic */ + +/* Flags for return values of direnter procs for the recursion processor */ +enum direnter_type +{ + R_PROCESS = 1, /* process files and maybe dirs */ + R_SKIP_FILES, /* don't process files in this dir */ + R_SKIP_DIRS, /* don't process sub-dirs */ + R_SKIP_ALL /* don't process files or dirs */ +}; +typedef enum direnter_type Dtype; + +extern char *program_name, *program_path, *command_name; +extern char *Rcsbin, *Editor, *CVSroot; +extern char *CVSADM_Root; +extern int cvsadmin_root; +extern char *CurDir; +extern int really_quiet, quiet; +extern int use_editor; +extern int cvswrite; +extern mode_t cvsumask; + +extern int trace; /* Show all commands */ +extern int noexec; /* Don't modify disk anywhere */ +extern int logoff; /* Don't write history entry */ + +extern char hostname[]; + +/* Externs that are included directly in the CVS sources */ + +int RCS_settag PROTO((const char *, const char *, const char *)); +int RCS_deltag PROTO((const char *, const char *, int)); +int RCS_setbranch PROTO((const char *, const char *)); +int RCS_lock PROTO((const char *, const char *, int)); +int RCS_unlock PROTO((const char *, const char *, int)); +int RCS_merge PROTO((const char *, const char *, const char *, const char *)); +int RCS_checkout PROTO ((char *rcsfile, char *workfile, char *tag, + char *options, + char *sout, int flags, int noerr)); +/* Flags used by RCS_* functions. See the description of the individual + functions for which flags mean what for each function. */ +#define RCS_FLAGS_LOCK 1 +#define RCS_FLAGS_FORCE 2 +#define RCS_FLAGS_DEAD 4 +#define RCS_FLAGS_QUIET 8 +#define RCS_FLAGS_MODTIME 16 +int RCS_checkin PROTO ((char *rcsfile, char *workfile, char *message, + char *rev, int flags, int noerr)); + + + +#include "error.h" + +DBM *open_module PROTO((void)); +FILE *open_file PROTO((const char *, const char *)); +List *Find_Directories PROTO((char *repository, int which)); +void Entries_Close PROTO((List *entries)); +List *Entries_Open PROTO((int aflag)); +char *Make_Date PROTO((char *rawdate)); +char *Name_Repository PROTO((char *dir, char *update_dir)); +char *Name_Root PROTO((char *dir, char *update_dir)); +void Create_Root PROTO((char *dir, char *rootdir)); +int same_directories PROTO((char *dir1, char *dir2)); +char *Short_Repository PROTO((char *repository)); +char *gca PROTO((char *rev1, char *rev2)); +char *getcaller PROTO((void)); +char *time_stamp PROTO((char *file)); +char *xmalloc PROTO((size_t bytes)); +void *xrealloc PROTO((void *ptr, size_t bytes)); +char *xstrdup PROTO((const char *str)); +void strip_trailing_newlines PROTO((char *str)); +int No_Difference PROTO((char *file, Vers_TS * vers, List * entries, + char *repository, char *update_dir)); +typedef int (*CALLPROC) PROTO((char *repository, char *value)); +int Parse_Info PROTO((char *infofile, char *repository, CALLPROC callproc, int all)); +int Reader_Lock PROTO((char *xrepository)); +typedef RETSIGTYPE (*SIGCLEANUPPROC) PROTO(()); +int SIG_register PROTO((int sig, SIGCLEANUPPROC sigcleanup)); +int Writer_Lock PROTO((List * list)); +int isdir PROTO((const char *file)); +int isfile PROTO((const char *file)); +int islink PROTO((const char *file)); +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 *last_component PROTO((char *path)); +char *get_homedir PROTO ((void)); + +int numdots PROTO((const char *s)); +int unlink_file PROTO((const char *f)); +int link_file PROTO ((const char *from, const char *to)); +int unlink_file_dir PROTO((const char *f)); +int update PROTO((int argc, char *argv[])); +int xcmp PROTO((const char *file1, const char *file2)); +int yesno PROTO((void)); +void *valloc PROTO((size_t bytes)); +time_t get_date PROTO((char *date, struct timeb *now)); +void Create_Admin PROTO((char *dir, char *update_dir, + char *repository, char *tag, char *date)); + +void Lock_Cleanup PROTO((void)); + +/* Writelock an entire subtree, well the part specified by ARGC, ARGV, LOCAL, + and AFLAG, anyway. */ +void lock_tree_for_write PROTO ((int argc, char **argv, int local, int aflag)); + +/* Remove locks set by lock_tree_for_write. Currently removes readlocks + too. */ +void lock_tree_cleanup PROTO ((void)); + +void ParseTag PROTO((char **tagp, char **datep)); +void Scratch_Entry PROTO((List * list, char *fname)); +void WriteTag PROTO((char *dir, char *tag, char *date)); +void cat_module PROTO((int status)); +void check_entries PROTO((char *dir)); +void close_module PROTO((DBM * db)); +void copy_file PROTO((const char *from, const char *to)); +void (*error_set_cleanup PROTO((void (*) (void)))) PROTO ((void)); +void fperror PROTO((FILE * fp, int status, int errnum, char *message,...)); +void free_names PROTO((int *pargc, char *argv[])); +void freevers_ts PROTO((Vers_TS ** versp)); + +extern int ign_name PROTO ((char *name)); +void ign_add PROTO((char *ign, int hold)); +void ign_add_file PROTO((char *file, int hold)); +void ign_setup PROTO((void)); +void ign_dir_add PROTO((char *name)); +int ignore_directory PROTO((char *name)); +typedef void (*Ignore_proc) PROTO ((char *, char *)); +extern void ignore_files PROTO ((List *, char *, Ignore_proc)); +extern int ign_inhibit_server; +extern int ign_case; + +#include "update.h" + +void line2argv PROTO((int *pargc, char *argv[], char *line)); +void make_directories PROTO((const char *name)); +void make_directory PROTO((const char *name)); +void rename_file PROTO((const char *from, const char *to)); +/* Expand wildcards in each element of (ARGC,ARGV). This is according to the + files which exist in the current directory, and accordingly to OS-specific + conventions regarding wildcard syntax. It might be desirable to change the + former in the future (e.g. "cvs status *.h" including files which don't exist + in the working directory). The result is placed in *PARGC and *PARGV; + the *PARGV array itself and all the strings it contains are newly + malloc'd. It is OK to call it with PARGC == &ARGC or PARGV == &ARGV. */ +extern void expand_wild PROTO ((int argc, char **argv, + int *pargc, char ***pargv)); + +void strip_path PROTO((char *path)); +void strip_trailing_slashes PROTO((char *path)); +void update_delproc PROTO((Node * p)); +void usage PROTO((const char *const *cpp)); +void xchmod PROTO((char *fname, int writable)); +char *xgetwd PROTO((void)); +int Checkin PROTO((int type, char *file, char *update_dir, + char *repository, char *rcs, char *rev, + char *tag, char *options, char *message, List *entries)); +Ctype Classify_File PROTO((char *file, char *tag, char *date, char *options, + int force_tag_match, int aflag, char *repository, + List *entries, RCSNode *rcsnode, Vers_TS **versp, + char *update_dir, int pipeout)); +List *Find_Names PROTO((char *repository, int which, int aflag, + List ** optentries)); +void Register PROTO((List * list, char *fname, char *vn, char *ts, + char *options, char *tag, char *date, char *ts_conflict)); +void Update_Logfile PROTO((char *repository, char *xmessage, char *xrevision, + FILE * xlogfp, List * xchanges)); +Vers_TS *Version_TS PROTO((char *repository, char *options, char *tag, + char *date, char *user, int force_tag_match, + int set_time, List * entries, RCSNode * rcs)); +void do_editor PROTO((char *dir, char **messagep, + char *repository, List * changes)); + +typedef int (*CALLBACKPROC) PROTO((int *pargc, char *argv[], char *where, + char *mwhere, char *mfile, int horten, int local_specified, + char *omodule, char *msg)); + +/* This is the structure that the recursion processor passes to the + fileproc to tell it about a particular file. */ +struct file_info +{ + /* Name of the file, without any directory component. */ + char *file; + + /* Name of the directory we are in, relative to the directory in + which this command was issued. We have cd'd to this directory + (either in the working directory or in the repository, depending + on which sort of recursion we are doing). If we are in the directory + in which the command was issued, this is "". */ + char *update_dir; + + /* update_dir and file put together, with a slash between them as + necessary. This is the proper way to refer to the file in user + messages. */ + char *fullname; + + /* Name of the directory corresponding to the repository which contains + this file. */ + char *repository; + + /* The pre-parsed entries for this directory. */ + List *entries; + + RCSNode *rcs; +}; + +typedef int (*FILEPROC) PROTO((struct file_info *finfo)); +typedef int (*FILESDONEPROC) PROTO((int err, char *repository, char *update_dir)); +typedef Dtype (*DIRENTPROC) PROTO((char *dir, char *repos, char *update_dir)); +typedef int (*DIRLEAVEPROC) PROTO((char *dir, int err, char *update_dir)); + +extern int mkmodules PROTO ((char *dir)); +extern int init PROTO ((int argc, char **argv)); + +int do_module PROTO((DBM * db, char *mname, enum mtype m_type, char *msg, + CALLBACKPROC callback_proc, char *where, int shorten, + int local_specified, int run_module_prog, char *extra_arg)); +int do_recursion PROTO((FILEPROC xfileproc, FILESDONEPROC xfilesdoneproc, + DIRENTPROC xdirentproc, DIRLEAVEPROC xdirleaveproc, + Dtype xflags, int xwhich, int xaflag, int xreadlock, + int xdosrcs)); +void history_write PROTO((int type, char *update_dir, char *revs, char *name, + char *repository)); +int start_recursion PROTO((FILEPROC fileproc, FILESDONEPROC filesdoneproc, + DIRENTPROC direntproc, DIRLEAVEPROC dirleaveproc, + int argc, char *argv[], int local, int which, + int aflag, int readlock, char *update_preload, + int dosrcs, int wd_is_repos)); +void SIG_beginCrSect PROTO((void)); +void SIG_endCrSect PROTO((void)); +void read_cvsrc PROTO((int *argc, char ***argv, char *cmdname)); + +char *make_message_rcslegal PROTO((char *message)); + +/* flags for run_exec(), the fast system() for CVS */ +#define RUN_NORMAL 0x0000 /* no special behaviour */ +#define RUN_COMBINED 0x0001 /* stdout is duped to stderr */ +#define RUN_REALLY 0x0002 /* do the exec, even if noexec is on */ +#define RUN_STDOUT_APPEND 0x0004 /* append to stdout, don't truncate */ +#define RUN_STDERR_APPEND 0x0008 /* append to stderr, don't truncate */ +#define RUN_SIGIGNORE 0x0010 /* ignore interrupts for command */ +#define RUN_TTY (char *)0 /* for the benefit of lint */ + +void run_arg PROTO((const char *s)); +void run_print PROTO((FILE * fp)); +#ifdef HAVE_VPRINTF +void run_setup PROTO((const char *fmt,...)); +void run_args PROTO((const char *fmt,...)); +#else +void run_setup (); +void run_args (); +#endif +int run_exec PROTO((char *stin, char *stout, char *sterr, int flags)); + +/* other similar-minded stuff from run.c. */ +FILE *run_popen PROTO((const char *, const char *)); +int piped_child PROTO((char **, int *, int *)); +void close_on_exec PROTO((int)); +int filter_stream_through_program PROTO((int, int, char **, pid_t *)); + +pid_t waitpid PROTO((pid_t, int *, int)); + +/* Wrappers. */ + +typedef enum { WRAP_MERGE, WRAP_COPY } WrapMergeMethod; +typedef enum { WRAP_TOCVS, WRAP_FROMCVS, WRAP_CONFLICT } WrapMergeHas; + +void wrap_setup PROTO((void)); +int wrap_name_has PROTO((const char *name,WrapMergeHas has)); +char *wrap_tocvs_process_file PROTO((const char *fileName)); +int wrap_merge_is_copy PROTO((const char *fileName)); +char *wrap_fromcvs_process_file PROTO((const char *fileName)); +void wrap_add_file PROTO((const char *file,int temp)); +void wrap_add PROTO((char *line,int temp)); + +/* Pathname expansion */ +char *expand_path PROTO((char *name, char *file, int line)); + +/* User variables. */ +extern List *variable_list; + +extern void variable_set PROTO ((char *nameval)); + +int watch PROTO ((int argc, char **argv)); +int edit PROTO ((int argc, char **argv)); +int unedit PROTO ((int argc, char **argv)); +int editors PROTO ((int argc, char **argv)); +int watchers PROTO ((int argc, char **argv)); +extern int annotate PROTO ((int argc, char **argv)); + +#if defined(AUTH_CLIENT_SUPPORT) || defined(AUTH_SERVER_SUPPORT) +char *scramble PROTO ((char *str)); +char *descramble PROTO ((char *str)); +#endif /* AUTH_CLIENT_SUPPORT || AUTH_SERVER_SUPPORT */ + +extern void tag_check_valid PROTO ((char *, int, char **, int, int, char *)); + +extern void cvs_output PROTO ((char *, size_t)); +extern void cvs_outerr PROTO ((char *, size_t)); diff --git a/contrib/cvs/src/cvsbug.sh b/contrib/cvs/src/cvsbug.sh new file mode 100755 index 0000000..ab26cfc --- /dev/null +++ b/contrib/cvs/src/cvsbug.sh @@ -0,0 +1,528 @@ +#! /bin/sh +# Submit a problem report to a GNATS site. +# Copyright (C) 1993 Free Software Foundation, Inc. +# Contributed by Brendan Kehoe (brendan@cygnus.com), based on a +# version written by Heinz G. Seidl (hgs@ide.com). +# +# This file is part of GNU GNATS. +# Modified by Berliner for CVS. +# +#ident "@(#)cvs/src:$Name: $:$Id: cvsbug.sh,v 1.10 1995/11/15 00:18:00 woods Exp $" +# +# GNU GNATS 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. +# +# GNU GNATS 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. +# +# You should have received a copy of the GNU General Public License +# along with GNU GNATS; see the file COPYING. If not, write to +# the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + +# The version of this send-pr. +VERSION=3.2 + +# The submitter-id for your site. +SUBMITTER=net + +## # Where the GNATS directory lives, if at all. +## [ -z "$GNATS_ROOT" ] && +## GNATS_ROOT=/usr/local/lib/gnats/gnats-db + +# The default mail address for PR submissions. +GNATS_ADDR=bug-cvs@prep.ai.mit.edu + +## # Where the gnats category tree lives. +## DATADIR=/usr/local/lib + +## # If we've been moved around, try using GCC_EXEC_PREFIX. +## [ ! -d $DATADIR/gnats -a -d "$GCC_EXEC_PREFIX" ] && DATADIR=${GCC_EXEC_PREFIX}.. + +# The default release for this host. +DEFAULT_RELEASE="xVERSIONx" + +# The default organization. +DEFAULT_ORGANIZATION="net" + +## # The default site to look for. +## GNATS_SITE=unknown + +## # Newer config information? +## [ -f ${GNATS_ROOT}/gnats-adm/config ] && . ${GNATS_ROOT}/gnats-adm/config + +# What mailer to use. This must come after the config file, since it is +# host-dependent. +if [ -f /usr/sbin/sendmail ]; then + MAIL_AGENT="/usr/sbin/sendmail -oi -t" +else + MAIL_AGENT="/usr/lib/sendmail -oi -t" +fi +MAILER=`echo $MAIL_AGENT | sed -e 's, .*,,'` +if [ ! -f "$MAILER" ] ; then + echo "$COMMAND: Cannot file mail program \"$MAILER\"." + echo "$COMMAND: Please fix the MAIL_AGENT entry in the $COMMAND file." + exit 1 +fi + +if test "`echo -n foo`" = foo ; then + ECHON=bsd +elif test "`echo 'foo\c'`" = foo ; then + ECHON=sysv +else + ECHON=none +fi + +if [ $ECHON = bsd ] ; then + ECHON1="echo -n" + ECHON2= +elif [ $ECHON = sysv ] ; then + ECHON1=echo + ECHON2='\c' +else + ECHON1=echo + ECHON2= +fi + +# + +[ -z "$TMPDIR" ] && TMPDIR=/tmp + +TEMP=$TMPDIR/p$$ +BAD=$TMPDIR/pbad$$ +REF=$TMPDIR/pf$$ + +if [ -z "$LOGNAME" -a -n "$USER" ]; then + LOGNAME=$USER +fi + +FROM="$LOGNAME" +REPLY_TO="$LOGNAME" + +# Find out the name of the originator of this PR. +if [ -n "$NAME" ]; then + ORIGINATOR="$NAME" +elif [ -f $HOME/.fullname ]; then + ORIGINATOR="`sed -e '1q' $HOME/.fullname`" +elif [ -f /bin/domainname ]; then + if [ "`/bin/domainname`" != "" -a -f /usr/bin/ypcat ]; then + # Must use temp file due to incompatibilities in quoting behavior + # and to protect shell metacharacters in the expansion of $LOGNAME + /usr/bin/ypcat passwd 2>/dev/null | cat - /etc/passwd | grep "^$LOGNAME:" | + cut -f5 -d':' | sed -e 's/,.*//' > $TEMP + ORIGINATOR="`cat $TEMP`" + rm -f $TEMP + fi +fi + +if [ "$ORIGINATOR" = "" ]; then + grep "^$LOGNAME:" /etc/passwd | cut -f5 -d':' | sed -e 's/,.*//' > $TEMP + ORIGINATOR="`cat $TEMP`" + rm -f $TEMP +fi + +if [ -n "$ORGANIZATION" ]; then + if [ -f "$ORGANIZATION" ]; then + ORGANIZATION="`cat $ORGANIZATION`" + fi +else + if [ -n "$DEFAULT_ORGANIZATION" ]; then + ORGANIZATION="$DEFAULT_ORGANIZATION" + elif [ -f $HOME/.organization ]; then + ORGANIZATION="`cat $HOME/.organization`" + elif [ -f $HOME/.signature ]; then + ORGANIZATION="`cat $HOME/.signature`" + fi +fi + +# If they don't have a preferred editor set, then use +if [ -z "$VISUAL" ]; then + if [ -z "$EDITOR" ]; then + EDIT=vi + else + EDIT="$EDITOR" + fi +else + EDIT="$VISUAL" +fi + +# Find out some information. +SYSTEM=`( [ -f /bin/uname ] && /bin/uname -a ) || \ + ( [ -f /usr/bin/uname ] && /usr/bin/uname -a ) || echo ""` +ARCH=`[ -f /bin/arch ] && /bin/arch` +MACHINE=`[ -f /bin/machine ] && /bin/machine` + +COMMAND=`echo $0 | sed -e 's,.*/,,'` +## USAGE="Usage: $COMMAND [-PVL] [-t address] [-f filename] [--request-id] +USAGE="Usage: $COMMAND [-PVL] +[--version]" +REMOVE= +BATCH= + +while [ $# -gt 0 ]; do + case "$1" in + -r) ;; # Ignore for backward compat. +## -t | --to) if [ $# -eq 1 ]; then echo "$USAGE"; exit 1; fi +## shift ; GNATS_ADDR="$1" +## EXPLICIT_GNATS_ADDR=true +## ;; +## -f | --file) if [ $# -eq 1 ]; then echo "$USAGE"; exit 1; fi +## shift ; IN_FILE="$1" +## if [ "$IN_FILE" != "-" -a ! -r "$IN_FILE" ]; then +## echo "$COMMAND: cannot read $IN_FILE" +## exit 1 +## fi +## ;; + -b | --batch) BATCH=true ;; + -p | -P | --print) PRINT=true ;; + -L | --list) FORMAT=norm ;; + -l | -CL | --lisp) FORMAT=lisp ;; +## --request-id) REQUEST_ID=true ;; + -h | --help) echo "$USAGE"; exit 0 ;; + -V | --version) echo "$VERSION"; exit 0 ;; + -*) echo "$USAGE" ; exit 1 ;; + *) echo "$USAGE" ; exit 1 +## if [ -z "$USER_GNATS_SITE" ]; then +## if [ ! -r "$DATADIR/gnats/$1" ]; then +## echo "$COMMAND: the GNATS site $1 does not have a categories list." +## exit 1 +## else +## # The site name is the alias they'll have to have created. +## USER_GNATS_SITE=$1 +## fi +## else +## echo "$USAGE" ; exit 1 +## fi + ;; + esac + shift +done + +if [ -n "$USER_GNATS_SITE" ]; then + GNATS_SITE=$USER_GNATS_SITE + GNATS_ADDR=$USER_GNATS_SITE-gnats +fi + +if [ "$SUBMITTER" = "unknown" -a -z "$REQUEST_ID" -a -z "$IN_FILE" ]; then + cat << '__EOF__' +It seems that send-pr is not installed with your unique submitter-id. +You need to run + + install-sid YOUR-SID + +where YOUR-SID is the identification code you received with `send-pr'. +`send-pr' will automatically insert this value into the template field +`>Submitter-Id'. If you've downloaded `send-pr' from the Net, use `net' +for this value. If you do not know your id, run `send-pr --request-id' to +get one from your support site. +__EOF__ + exit 1 +fi + +## if [ -r "$DATADIR/gnats/$GNATS_SITE" ]; then +## CATEGORIES=`grep -v '^#' $DATADIR/gnats/$GNATS_SITE | sort` +## else +## echo "$COMMAND: could not read $DATADIR/gnats/$GNATS_SITE for categories list." +## exit 1 +## fi +CATEGORIES="contrib cvs doc pcl-cvs portability" + +if [ -z "$CATEGORIES" ]; then + echo "$COMMAND: the categories list for $GNATS_SITE was empty!" + exit 1 +fi + +case "$FORMAT" in + lisp) echo "$CATEGORIES" | \ + awk 'BEGIN {printf "( "} {printf "(\"%s\") ",$0} END {printf ")\n"}' + exit 0 + ;; + norm) l=`echo "$CATEGORIES" | \ + awk 'BEGIN {max = 0; } { if (length($0) > max) { max = length($0); } } + END {print max + 1;}'` + c=`expr 70 / $l` + if [ $c -eq 0 ]; then c=1; fi + echo "$CATEGORIES" | \ + awk 'BEGIN {print "Known categories:"; i = 0 } + { printf ("%-'$l'.'$l's", $0); if ((++i % '$c') == 0) { print "" } } + END { print ""; }' + exit 0 + ;; +esac + +ORIGINATOR_C='<name of the PR author (one line)>' +ORGANIZATION_C='<organization of PR author (multiple lines)>' +CONFIDENTIAL_C='<[ yes | no ] (one line)>' +SYNOPSIS_C='<synopsis of the problem (one line)>' +SEVERITY_C='<[ non-critical | serious | critical ] (one line)>' +PRIORITY_C='<[ low | medium | high ] (one line)>' +CATEGORY_C='<name of the product (one line)>' +CLASS_C='<[ sw-bug | doc-bug | change-request | support ] (one line)>' +RELEASE_C='<release number or tag (one line)>' +ENVIRONMENT_C='<machine, os, target, libraries (multiple lines)>' +DESCRIPTION_C='<precise description of the problem (multiple lines)>' +HOW_TO_REPEAT_C='<code/input/activities to reproduce the problem (multiple lines)>' +FIX_C='<how to correct or work around the problem, if known (multiple lines)>' + +# Catch some signals. ($xs kludge needed by Sun /bin/sh) +xs=0 +trap 'rm -f $REF $TEMP; exit $xs' 0 +trap 'echo "$COMMAND: Aborting ..."; rm -f $REF $TEMP; xs=1; exit' 1 2 3 13 15 + +# If they told us to use a specific file, then do so. +if [ -n "$IN_FILE" ]; then + if [ "$IN_FILE" = "-" ]; then + # The PR is coming from the standard input. + if [ -n "$EXPLICIT_GNATS_ADDR" ]; then + sed -e "s;^[Tt][Oo]:.*;To: $GNATS_ADDR;" > $TEMP + else + cat > $TEMP + fi + else + # Use the file they named. + if [ -n "$EXPLICIT_GNATS_ADDR" ]; then + sed -e "s;^[Tt][Oo]:.*;To: $GNATS_ADDR;" $IN_FILE > $TEMP + else + cat $IN_FILE > $TEMP + fi + fi +else + + if [ -n "$PR_FORM" -a -z "$PRINT_INTERN" ]; then + # If their PR_FORM points to a bogus entry, then bail. + if [ ! -f "$PR_FORM" -o ! -r "$PR_FORM" -o ! -s "$PR_FORM" ]; then + echo "$COMMAND: can't seem to read your template file (\`$PR_FORM'), ignoring PR_FORM" + sleep 1 + PRINT_INTERN=bad_prform + fi + fi + + if [ -n "$PR_FORM" -a -z "$PRINT_INTERN" ]; then + cp $PR_FORM $TEMP || + ( echo "$COMMAND: could not copy $PR_FORM" ; xs=1; exit ) + else + for file in $TEMP $REF ; do + cat > $file << '__EOF__' +SEND-PR: -*- send-pr -*- +SEND-PR: Lines starting with `SEND-PR' will be removed automatically, as +SEND-PR: will all comments (text enclosed in `<' and `>'). +SEND-PR: +SEND-PR: Choose from the following categories: +SEND-PR: +__EOF__ + + # Format the categories so they fit onto lines. + l=`echo "$CATEGORIES" | \ + awk 'BEGIN {max = 0; } { if (length($0) > max) { max = length($0); } } + END {print max + 1;}'` + c=`expr 61 / $l` + if [ $c -eq 0 ]; then c=1; fi + echo "$CATEGORIES" | \ + awk 'BEGIN {printf "SEND-PR: "; i = 0 } + { printf ("%-'$l'.'$l's", $0); + if ((++i % '$c') == 0) { printf "\nSEND-PR: " } } + END { printf "\nSEND-PR:\n"; }' >> $file + + cat >> $file << __EOF__ +To: $GNATS_ADDR +Subject: +From: $FROM +Reply-To: $REPLY_TO +X-send-pr-version: $VERSION + + +>Submitter-Id: $SUBMITTER +>Originator: $ORIGINATOR +>Organization: +${ORGANIZATION-$ORGANIZATION_C} +>Confidential: $CONFIDENTIAL_C +>Synopsis: $SYNOPSIS_C +>Severity: $SEVERITY_C +>Priority: $PRIORITY_C +>Category: $CATEGORY_C +>Class: $CLASS_C +>Release: ${DEFAULT_RELEASE-$RELEASE_C} +>Environment: + $ENVIRONMENT_C +`[ -n "$SYSTEM" ] && echo System: $SYSTEM` +`[ -n "$ARCH" ] && echo Architecture: $ARCH` +`[ -n "$MACHINE" ] && echo Machine: $MACHINE` +>Description: + $DESCRIPTION_C +>How-To-Repeat: + $HOW_TO_REPEAT_C +>Fix: + $FIX_C +__EOF__ + done + fi + + if [ "$PRINT" = true -o "$PRINT_INTERN" = true ]; then + cat $TEMP + xs=0; exit + fi + + chmod u+w $TEMP + if [ -z "$REQUEST_ID" ]; then + eval $EDIT $TEMP + else + ed -s $TEMP << '__EOF__' +/^Subject/s/^Subject:.*/Subject: request for a customer id/ +/^>Category/s/^>Category:.*/>Category: send-pr/ +w +q +__EOF__ + fi + + if cmp -s $REF $TEMP ; then + echo "$COMMAND: problem report not filled out, therefore not sent" + xs=1; exit + fi +fi + +# +# Check the enumeration fields + +# This is a "sed-subroutine" with one keyword parameter +# (with workaround for Sun sed bug) +# +SED_CMD=' +/$PATTERN/{ +s||| +s|<.*>|| +s|^[ ]*|| +s|[ ]*$|| +p +q +}' + + +while [ -z "$REQUEST_ID" ]; do + CNT=0 + + # 1) Confidential + # + PATTERN=">Confidential:" + CONFIDENTIAL=`eval sed -n -e "\"$SED_CMD\"" $TEMP` + case "$CONFIDENTIAL" in + ""|yes|no) CNT=`expr $CNT + 1` ;; + *) echo "$COMMAND: \`$CONFIDENTIAL' is not a valid value for \`Confidential'." ;; + esac + # + # 2) Severity + # + PATTERN=">Severity:" + SEVERITY=`eval sed -n -e "\"$SED_CMD\"" $TEMP` + case "$SEVERITY" in + ""|non-critical|serious|critical) CNT=`expr $CNT + 1` ;; + *) echo "$COMMAND: \`$SEVERITY' is not a valid value for \`Severity'." + esac + # + # 3) Priority + # + PATTERN=">Priority:" + PRIORITY=`eval sed -n -e "\"$SED_CMD\"" $TEMP` + case "$PRIORITY" in + ""|low|medium|high) CNT=`expr $CNT + 1` ;; + *) echo "$COMMAND: \`$PRIORITY' is not a valid value for \`Priority'." + esac + # + # 4) Category + # + PATTERN=">Category:" + CATEGORY=`eval sed -n -e "\"$SED_CMD\"" $TEMP` + FOUND= + for C in $CATEGORIES + do + if [ "$C" = "$CATEGORY" ]; then FOUND=true ; break ; fi + done + if [ -n "$FOUND" ]; then + CNT=`expr $CNT + 1` + else + if [ -z "$CATEGORY" ]; then + echo "$COMMAND: you must include a Category: field in your report." + else + echo "$COMMAND: \`$CATEGORY' is not a known category." + fi + fi + # + # 5) Class + # + PATTERN=">Class:" + CLASS=`eval sed -n -e "\"$SED_CMD\"" $TEMP` + case "$CLASS" in + ""|sw-bug|doc-bug|change-request|support) CNT=`expr $CNT + 1` ;; + *) echo "$COMMAND: \`$CLASS' is not a valid value for \`Class'." + esac + + [ $CNT -lt 5 -a -z "$BATCH" ] && + echo "Errors were found with the problem report." + + while true; do + if [ -z "$BATCH" ]; then + $ECHON1 "a)bort, e)dit or s)end? $ECHON2" + read input + else + if [ $CNT -eq 5 ]; then + input=s + else + input=a + fi + fi + case "$input" in + a*) + if [ -z "$BATCH" ]; then + echo "$COMMAND: the problem report remains in $BAD and is not sent." + mv $TEMP $BAD + else + echo "$COMMAND: the problem report is not sent." + fi + xs=1; exit + ;; + e*) + eval $EDIT $TEMP + continue 2 + ;; + s*) + break 2 + ;; + esac + done +done +# +# Remove comments and send the problem report +# (we have to use patterns, where the comment contains regex chars) +# +# /^>Originator:/s;$ORIGINATOR;; +sed -e " +/^SEND-PR:/d +/^>Organization:/,/^>[A-Za-z-]*:/s;$ORGANIZATION_C;; +/^>Confidential:/s;<.*>;; +/^>Synopsis:/s;$SYNOPSIS_C;; +/^>Severity:/s;<.*>;; +/^>Priority:/s;<.*>;; +/^>Category:/s;$CATEGORY_C;; +/^>Class:/s;<.*>;; +/^>Release:/,/^>[A-Za-z-]*:/s;$RELEASE_C;; +/^>Environment:/,/^>[A-Za-z-]*:/s;$ENVIRONMENT_C;; +/^>Description:/,/^>[A-Za-z-]*:/s;$DESCRIPTION_C;; +/^>How-To-Repeat:/,/^>[A-Za-z-]*:/s;$HOW_TO_REPEAT_C;; +/^>Fix:/,/^>[A-Za-z-]*:/s;$FIX_C;; +" $TEMP > $REF + +if $MAIL_AGENT < $REF; then + echo "$COMMAND: problem report sent" + xs=0; exit +else + echo "$COMMAND: mysterious mail failure." + if [ -z "$BATCH" ]; then + echo "$COMMAND: the problem report remains in $BAD and is not sent." + mv $REF $BAD + else + echo "$COMMAND: the problem report is not sent." + fi + xs=1; exit +fi diff --git a/contrib/cvs/src/cvsrc.c b/contrib/cvs/src/cvsrc.c new file mode 100644 index 0000000..140ce1c --- /dev/null +++ b/contrib/cvs/src/cvsrc.c @@ -0,0 +1,150 @@ +/* + * Copyright (c) 1993 david d zuhn + * + * written by david d `zoo' zuhn while at Cygnus Support + * + * You may distribute under the terms of the GNU General Public License + * as specified in the README file that comes with the CVS 1.4 kit. + * + */ + + +#include "cvs.h" +#include "getline.h" + +/* this file is to be found in the user's home directory */ + +#ifndef CVSRC_FILENAME +#define CVSRC_FILENAME ".cvsrc" +#endif +char cvsrc[] = CVSRC_FILENAME; + +#define GROW 10 + +extern char *strtok (); + +/* Read cvsrc, processing options matching CMDNAME ("cvs" for global + options, and update *ARGC and *ARGV accordingly. */ + +void +read_cvsrc (argc, argv, cmdname) + int *argc; + char ***argv; + char *cmdname; +{ + char *homedir; + char *homeinit; + FILE *cvsrcfile; + + char *line; + int line_length; + size_t line_chars_allocated; + + char *optstart; + + int command_len; + int found = 0; + + int i; + + int new_argc; + int max_new_argv; + char **new_argv; + + /* don't do anything if argc is -1, since that implies "help" mode */ + if (*argc == -1) + return; + + /* setup the new options list */ + + new_argc = 1; + max_new_argv = (*argc) + GROW; + new_argv = (char **) xmalloc (max_new_argv * sizeof (char*)); + new_argv[0] = xstrdup ((*argv)[0]); + + /* determine filename for ~/.cvsrc */ + + homedir = get_homedir (); + if (!homedir) + return; + + homeinit = (char *) xmalloc (strlen (homedir) + strlen (cvsrc) + 10); + strcpy (homeinit, homedir); + strcat (homeinit, "/"); + strcat (homeinit, cvsrc); + + /* if it can't be read, there's no point to continuing */ + + if (!isreadable (homeinit)) + { + free (homeinit); + return; + } + + /* now scan the file until we find the line for the command in question */ + + line = NULL; + line_chars_allocated = 0; + command_len = strlen (cmdname); + cvsrcfile = open_file (homeinit, "r"); + while ((line_length = getline (&line, &line_chars_allocated, cvsrcfile)) + >= 0) + { + /* skip over comment lines */ + if (line[0] == '#') + continue; + + /* stop if we match the current command */ + if (!strncmp (line, cmdname, command_len) + && isspace (*(line + command_len))) + { + found = 1; + break; + } + } + + fclose (cvsrcfile); + + if (found) + { + /* skip over command in the options line */ + optstart = strtok (line + command_len, "\t \n"); + + do + { + new_argv [new_argc] = xstrdup (optstart); + new_argv [new_argc+1] = NULL; + new_argc += 1; + + if (new_argc >= max_new_argv) + { + char **tmp_argv; + max_new_argv += GROW; + tmp_argv = (char **) xmalloc (max_new_argv * sizeof (char*)); + for (i = 0; i <= new_argc; i++) + tmp_argv[i] = new_argv[i]; + free(new_argv); + new_argv = tmp_argv; + } + + } + while ((optstart = strtok (NULL, "\t \n")) != NULL); + } + + if (line != NULL) + free (line); + + /* now copy the remaining arguments */ + + for (i=1; i < *argc; i++) + { + new_argv [new_argc] = (*argv)[i]; + new_argc += 1; + } + + *argc = new_argc; + *argv = new_argv; + + free (homeinit); + return; +} diff --git a/contrib/cvs/src/diff.c b/contrib/cvs/src/diff.c new file mode 100644 index 0000000..7520cec --- /dev/null +++ b/contrib/cvs/src/diff.c @@ -0,0 +1,623 @@ +/* + * Copyright (c) 1992, Brian Berliner and Jeff Polk + * Copyright (c) 1989-1992, Brian Berliner + * + * You may distribute under the terms of the GNU General Public License as + * specified in the README file that comes with the CVS 1.4 kit. + * + * Difference + * + * Run diff against versions in the repository. Options that are specified are + * passed on directly to "rcsdiff". + * + * Without any file arguments, runs diff against all the currently modified + * files. + */ + +#include "cvs.h" + +static Dtype diff_dirproc PROTO((char *dir, char *pos_repos, char *update_dir)); +static int diff_filesdoneproc PROTO((int err, char *repos, char *update_dir)); +static int diff_dirleaveproc PROTO((char *dir, int err, char *update_dir)); +static int diff_file_nodiff PROTO((char *file, char *repository, List *entries, + RCSNode *rcs, Vers_TS *vers)); +static int diff_fileproc PROTO((struct file_info *finfo)); +static void diff_mark_errors PROTO((int err)); + +static char *diff_rev1, *diff_rev2; +static char *diff_date1, *diff_date2; +static char *use_rev1, *use_rev2; + +#ifdef SERVER_SUPPORT +/* Revision of the user file, if it is unchanged from something in the + repository and we want to use that fact. */ +static char *user_file_rev; +#endif + +static char *options; +static char opts[PATH_MAX]; +static int diff_errors; +static int empty_files = 0; + +static const char *const diff_usage[] = +{ + "Usage: %s %s [-lN] [rcsdiff-options]\n", +#ifdef CVS_DIFFDATE + " [[-r rev1 | -D date1] [-r rev2 | -D date2]] [files...] \n", +#else + " [-r rev1 [-r rev2]] [files...] \n", +#endif + "\t-l\tLocal directory only, not recursive\n", + "\t-D d1\tDiff revision for date against working file.\n", + "\t-D d2\tDiff rev1/date1 against date2.\n", + "\t-N\tinclude diffs for added and removed files.\n", + "\t-r rev1\tDiff revision for rev1 against working file.\n", + "\t-r rev2\tDiff rev1/date1 against rev2.\n", + NULL +}; + +int +diff (argc, argv) + int argc; + char **argv; +{ + char tmp[50]; + int c, err = 0; + int local = 0; + int which; + + if (argc == -1) + usage (diff_usage); + + /* + * Note that we catch all the valid arguments here, so that we can + * intercept the -r arguments for doing revision diffs; and -l/-R for a + * non-recursive/recursive diff. + */ +#ifdef SERVER_SUPPORT + /* Need to be able to do this command more than once (according to + the protocol spec, even if the current client doesn't use it). */ + opts[0] = '\0'; +#endif + optind = 1; + while ((c = getopt (argc, argv, + "abcdefhilnpqtuw0123456789BHNQRTC:D:F:I:L:V:k:r:")) != -1) + { + switch (c) + { + case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': + case 'h': case 'i': case 'n': case 'p': case 't': case 'u': + case 'w': case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': case 'B': + case 'H': case 'T': case 'Q': + (void) sprintf (tmp, " -%c", (char) c); + (void) strcat (opts, tmp); + if (c == 'Q') + { + quiet = 1; + really_quiet = 1; + c = 'q'; + } + break; + case 'C': case 'F': case 'I': case 'L': case 'V': +#ifndef CVS_DIFFDATE + case 'D': +#endif + (void) sprintf (tmp, " -%c%s", (char) c, optarg); + (void) strcat (opts, tmp); + break; + case 'R': + local = 0; + break; + case 'l': + local = 1; + break; + case 'q': + quiet = 1; + break; + case 'k': + if (options) + free (options); + options = RCS_check_kflag (optarg); + break; + case 'r': + if (diff_rev2 != NULL || diff_date2 != NULL) + error (1, 0, + "no more than two revisions/dates can be specified"); + if (diff_rev1 != NULL || diff_date1 != NULL) + diff_rev2 = optarg; + else + diff_rev1 = optarg; + break; +#ifdef CVS_DIFFDATE + case 'D': + if (diff_rev2 != NULL || diff_date2 != NULL) + error (1, 0, + "no more than two revisions/dates can be specified"); + if (diff_rev1 != NULL || diff_date1 != NULL) + diff_date2 = Make_Date (optarg); + else + diff_date1 = Make_Date (optarg); + break; +#endif + case 'N': + empty_files = 1; + break; + case '?': + default: + usage (diff_usage); + break; + } + } + argc -= optind; + argv += optind; + + /* make sure options is non-null */ + if (!options) + options = xstrdup (""); + +#ifdef CLIENT_SUPPORT + if (client_active) { + /* We're the client side. Fire up the remote server. */ + start_server (); + + ign_setup (); + + if (local) + send_arg("-l"); + if (empty_files) + send_arg("-N"); + send_option_string (opts); + if (diff_rev1) + option_with_arg ("-r", diff_rev1); + if (diff_date1) + client_senddate (diff_date1); + if (diff_rev2) + option_with_arg ("-r", diff_rev2); + if (diff_date2) + client_senddate (diff_date2); + + send_file_names (argc, argv, SEND_EXPAND_WILD); +#if 0 + /* FIXME: We shouldn't have to send current files to diff two + revs, but it doesn't work yet and I haven't debugged it. + So send the files -- it's slower but it works. + gnu@cygnus.com Apr94 */ + /* Send the current files unless diffing two revs from the archive */ + if (diff_rev2 == NULL && diff_date2 == NULL) +#endif + send_files (argc, argv, local, 0); + + send_to_server ("diff\012", 0); + err = get_responses_and_close (); + free (options); + return (err); + } +#endif + + if (diff_rev1 != NULL) + tag_check_valid (diff_rev1, argc, argv, local, 0, ""); + if (diff_rev2 != NULL) + tag_check_valid (diff_rev2, argc, argv, local, 0, ""); + + which = W_LOCAL; + if (diff_rev2 != NULL || diff_date2 != NULL) + which |= W_REPOS | W_ATTIC; + + wrap_setup (); + + /* start the recursion processor */ + err = start_recursion (diff_fileproc, diff_filesdoneproc, diff_dirproc, + diff_dirleaveproc, argc, argv, local, + which, 0, 1, (char *) NULL, 1, 0); + + /* clean up */ + free (options); + return (err); +} + +/* + * Do a file diff + */ +/* ARGSUSED */ +static int +diff_fileproc (finfo) + struct file_info *finfo; +{ + int status, err = 2; /* 2 == trouble, like rcsdiff */ + Vers_TS *vers; + enum { + DIFF_ERROR, + DIFF_ADDED, + DIFF_REMOVED, + DIFF_NEITHER + } empty_file = DIFF_NEITHER; + char tmp[L_tmpnam+1]; + char *tocvsPath; + char fname[PATH_MAX]; + +#ifdef SERVER_SUPPORT + user_file_rev = 0; +#endif + vers = Version_TS (finfo->repository, (char *) NULL, (char *) NULL, (char *) NULL, + finfo->file, 1, 0, finfo->entries, finfo->rcs); + + if (diff_rev2 != NULL || diff_date2 != NULL) + { + /* Skip all the following checks regarding the user file; we're + not using it. */ + } + else if (vers->vn_user == NULL) + { + error (0, 0, "I know nothing about %s", finfo->fullname); + freevers_ts (&vers); + diff_mark_errors (err); + return (err); + } + else if (vers->vn_user[0] == '0' && vers->vn_user[1] == '\0') + { + if (empty_files) + empty_file = DIFF_ADDED; + else + { + error (0, 0, "%s is a new entry, no comparison available", + finfo->fullname); + freevers_ts (&vers); + diff_mark_errors (err); + return (err); + } + } + else if (vers->vn_user[0] == '-') + { + if (empty_files) + empty_file = DIFF_REMOVED; + else + { + error (0, 0, "%s was removed, no comparison available", + finfo->fullname); + freevers_ts (&vers); + diff_mark_errors (err); + return (err); + } + } + else + { + if (vers->vn_rcs == NULL && vers->srcfile == NULL) + { + error (0, 0, "cannot find revision control file for %s", + finfo->fullname); + freevers_ts (&vers); + diff_mark_errors (err); + return (err); + } + else + { + if (vers->ts_user == NULL) + { + error (0, 0, "cannot find %s", finfo->fullname); + freevers_ts (&vers); + diff_mark_errors (err); + return (err); + } +#ifdef SERVER_SUPPORT + else if (!strcmp (vers->ts_user, vers->ts_rcs)) + { + /* The user file matches some revision in the repository + Diff against the repository (for remote CVS, we might not + have a copy of the user file around). */ + user_file_rev = vers->vn_user; + } +#endif + } + } + + if (empty_file == DIFF_NEITHER && diff_file_nodiff (finfo->file, finfo->repository, finfo->entries, finfo->rcs, vers)) + { + freevers_ts (&vers); + return (0); + } + + /* FIXME: Check whether use_rev1 and use_rev2 are dead and deal + accordingly. */ + + /* Output an "Index:" line for patch to use */ + (void) fflush (stdout); + (void) printf ("Index: %s\n", finfo->fullname); + (void) fflush (stdout); + + tocvsPath = wrap_tocvs_process_file(finfo->file); + if (tocvsPath) + { + /* Backup the current version of the file to CVS/,,filename */ + sprintf(fname,"%s/%s%s",CVSADM, CVSPREFIX, finfo->file); + if (unlink_file_dir (fname) < 0) + if (! existence_error (errno)) + error (1, errno, "cannot remove %s", finfo->file); + rename_file (finfo->file, fname); + /* Copy the wrapped file to the current directory then go to work */ + copy_file (tocvsPath, finfo->file); + } + + if (empty_file == DIFF_ADDED || empty_file == DIFF_REMOVED) + { + /* This is file, not fullname, because it is the "Index:" line which + is supposed to contain the directory. */ + (void) printf ("===================================================================\nRCS file: %s\n", + finfo->file); + (void) printf ("diff -N %s\n", finfo->file); + + if (empty_file == DIFF_ADDED) + { + run_setup ("%s %s %s %s", DIFF, opts, DEVNULL, finfo->file); + } + else + { + int retcode; + + /* + * FIXME: Should be setting use_rev1 using the logic in + * diff_file_nodiff, and using that revision. This code + * is broken for "cvs diff -N -r foo". + */ + retcode = RCS_checkout (vers->srcfile->path, NULL, vers->vn_rcs, + *options ? options : vers->options, tmpnam (tmp), + 0, 0); + if (retcode == -1) + { + (void) unlink (tmp); + error (1, errno, "fork failed during checkout of %s", + vers->srcfile->path); + } + /* FIXME: what if retcode > 0? */ + + run_setup ("%s %s %s %s", DIFF, opts, tmp, DEVNULL); + } + } + else + { + if (use_rev2) + { + run_setup ("%s%s -x,v/ %s %s -r%s -r%s", Rcsbin, RCS_DIFF, + opts, *options ? options : vers->options, + use_rev1, use_rev2); + } + else + { + run_setup ("%s%s -x,v/ %s %s -r%s", Rcsbin, RCS_DIFF, opts, + *options ? options : vers->options, use_rev1); + } + run_arg (vers->srcfile->path); + } + + switch ((status = run_exec (RUN_TTY, RUN_TTY, RUN_TTY, + RUN_REALLY|RUN_COMBINED))) + { + case -1: /* fork failed */ + error (1, errno, "fork failed during rcsdiff of %s", + vers->srcfile->path); + case 0: /* everything ok */ + err = 0; + break; + default: /* other error */ + err = status; + break; + } + + if (tocvsPath) + { + if (unlink_file_dir (finfo->file) < 0) + if (! existence_error (errno)) + error (1, errno, "cannot remove %s", finfo->file); + + rename_file (fname,finfo->file); + if (unlink_file (tocvsPath) < 0) + error (1, errno, "cannot remove %s", finfo->file); + } + + if (empty_file == DIFF_REMOVED) + (void) unlink (tmp); + + (void) fflush (stdout); + freevers_ts (&vers); + diff_mark_errors (err); + return (err); +} + +/* + * Remember the exit status for each file. + */ +static void +diff_mark_errors (err) + int err; +{ + if (err > diff_errors) + diff_errors = err; +} + +/* + * Print a warm fuzzy message when we enter a dir + * + * Don't try to diff directories that don't exist! -- DW + */ +/* ARGSUSED */ +static Dtype +diff_dirproc (dir, pos_repos, update_dir) + char *dir; + char *pos_repos; + char *update_dir; +{ + /* XXX - check for dirs we don't want to process??? */ + + /* YES ... for instance dirs that don't exist!!! -- DW */ + if (!isdir (dir) ) + return (R_SKIP_ALL); + + if (!quiet) + error (0, 0, "Diffing %s", update_dir); + return (R_PROCESS); +} + +/* + * Concoct the proper exit status - done with files + */ +/* ARGSUSED */ +static int +diff_filesdoneproc (err, repos, update_dir) + int err; + char *repos; + char *update_dir; +{ + return (diff_errors); +} + +/* + * Concoct the proper exit status - leaving directories + */ +/* ARGSUSED */ +static int +diff_dirleaveproc (dir, err, update_dir) + char *dir; + int err; + char *update_dir; +{ + return (diff_errors); +} + +/* + * verify that a file is different 0=same 1=different + */ +static int +diff_file_nodiff (file, repository, entries, rcs, vers) + char *file; + char *repository; + List *entries; + RCSNode *rcs; + Vers_TS *vers; +{ + Vers_TS *xvers; + char tmp[L_tmpnam+1]; + int retcode; + + /* free up any old use_rev* variables and reset 'em */ + if (use_rev1) + free (use_rev1); + if (use_rev2) + free (use_rev2); + use_rev1 = use_rev2 = (char *) NULL; + + if (diff_rev1 || diff_date1) + { + /* special handling for TAG_HEAD */ + if (diff_rev1 && strcmp (diff_rev1, TAG_HEAD) == 0) + use_rev1 = xstrdup (vers->vn_rcs); + else + { + xvers = Version_TS (repository, (char *) NULL, diff_rev1, + diff_date1, file, 1, 0, entries, rcs); + if (xvers->vn_rcs == NULL) + { + /* Don't gripe if it doesn't exist, just ignore! */ + if (! isfile (file)) + /* null statement */ ; + else if (diff_rev1) + error (0, 0, "tag %s is not in file %s", diff_rev1, file); + else + error (0, 0, "no revision for date %s in file %s", + diff_date1, file); + + freevers_ts (&xvers); + return (1); + } + use_rev1 = xstrdup (xvers->vn_rcs); + freevers_ts (&xvers); + } + } + if (diff_rev2 || diff_date2) + { + /* special handling for TAG_HEAD */ + if (diff_rev2 && strcmp (diff_rev2, TAG_HEAD) == 0) + use_rev2 = xstrdup (vers->vn_rcs); + else + { + xvers = Version_TS (repository, (char *) NULL, diff_rev2, + diff_date2, file, 1, 0, entries, rcs); + if (xvers->vn_rcs == NULL) + { + /* Don't gripe if it doesn't exist, just ignore! */ + if (! isfile (file)) + /* null statement */ ; + else if (diff_rev1) + error (0, 0, "tag %s is not in file %s", diff_rev2, file); + else + error (0, 0, "no revision for date %s in file %s", + diff_date2, file); + + freevers_ts (&xvers); + return (1); + } + use_rev2 = xstrdup (xvers->vn_rcs); + freevers_ts (&xvers); + } + + /* now, see if we really need to do the diff */ + if (use_rev1 && use_rev2) { + return (strcmp (use_rev1, use_rev2) == 0); + } else { + error(0, 0, "No HEAD revision for file %s", file); + return (1); + } + } +#ifdef SERVER_SUPPORT + if (user_file_rev) + { + /* drop user_file_rev into first unused use_rev */ + if (!use_rev1) + use_rev1 = xstrdup (user_file_rev); + else if (!use_rev2) + use_rev2 = xstrdup (user_file_rev); + /* and if not, it wasn't needed anyhow */ + user_file_rev = 0; + } + + /* now, see if we really need to do the diff */ + if (use_rev1 && use_rev2) + { + return (strcmp (use_rev1, use_rev2) == 0); + } +#endif /* SERVER_SUPPORT */ + if (use_rev1 == NULL || strcmp (use_rev1, vers->vn_user) == 0) + { + if (strcmp (vers->ts_rcs, vers->ts_user) == 0 && + (!(*options) || strcmp (options, vers->options) == 0)) + { + return (1); + } + if (use_rev1 == NULL) + use_rev1 = xstrdup (vers->vn_user); + } + + /* + * with 0 or 1 -r option specified, run a quick diff to see if we + * should bother with it at all. + */ + retcode = RCS_checkout (vers->srcfile->path, NULL, use_rev1, + *options ? options : vers->options, tmpnam (tmp), 0, 0); + switch (retcode) + { + case 0: /* everything ok */ + if (xcmp (file, tmp) == 0) + { + (void) unlink (tmp); + return (1); + } + break; + case -1: /* fork failed */ + (void) unlink (tmp); + error (1, errno, "fork failed during checkout of %s", + vers->srcfile->path); + default: + break; + } + (void) unlink (tmp); + return (0); +} diff --git a/contrib/cvs/src/edit.c b/contrib/cvs/src/edit.c new file mode 100644 index 0000000..0473a03 --- /dev/null +++ b/contrib/cvs/src/edit.c @@ -0,0 +1,1020 @@ +/* Implementation for "cvs edit", "cvs watch on", and related commands + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2, or (at your option) + any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ + +#include "cvs.h" +#include "getline.h" +#include "watch.h" +#include "edit.h" +#include "fileattr.h" + +static int watch_onoff PROTO ((int, char **)); + +static int setting_default; +static int turning_on; + +static int setting_tedit; +static int setting_tunedit; +static int setting_tcommit; + +static int onoff_fileproc PROTO ((struct file_info *finfo)); + +static int +onoff_fileproc (finfo) + struct file_info *finfo; +{ + fileattr_set (finfo->file, "_watched", turning_on ? "" : NULL); + return 0; +} + +static int onoff_filesdoneproc PROTO ((int, char *, char *)); + +static int +onoff_filesdoneproc (err, repository, update_dir) + int err; + char *repository; + char *update_dir; +{ + if (setting_default) + fileattr_set (NULL, "_watched", turning_on ? "" : NULL); + return err; +} + +static int +watch_onoff (argc, argv) + int argc; + char **argv; +{ + int c; + int local = 0; + int err; + + optind = 1; + while ((c = getopt (argc, argv, "l")) != -1) + { + switch (c) + { + case 'l': + local = 1; + break; + case '?': + default: + usage (watch_usage); + break; + } + } + argc -= optind; + argv += optind; + +#ifdef CLIENT_SUPPORT + if (client_active) + { + start_server (); + + ign_setup (); + + if (local) + send_arg ("-l"); + send_file_names (argc, argv, SEND_EXPAND_WILD); + /* FIXME: We shouldn't have to send current files, but I'm not sure + whether it works. So send the files -- + it's slower but it works. */ + send_files (argc, argv, local, 0); + send_to_server (turning_on ? "watch-on\012" : "watch-off\012", 0); + return get_responses_and_close (); + } +#endif /* CLIENT_SUPPORT */ + + setting_default = (argc <= 0); + + lock_tree_for_write (argc, argv, local, 0); + + err = start_recursion (onoff_fileproc, onoff_filesdoneproc, + (DIRENTPROC) NULL, (DIRLEAVEPROC) NULL, + argc, argv, local, W_LOCAL, 0, 0, (char *)NULL, + 0, 0); + + lock_tree_cleanup (); + return err; +} + +int +watch_on (argc, argv) + int argc; + char **argv; +{ + turning_on = 1; + return watch_onoff (argc, argv); +} + +int +watch_off (argc, argv) + int argc; + char **argv; +{ + turning_on = 0; + return watch_onoff (argc, argv); +} + +static int dummy_fileproc PROTO ((struct file_info *finfo)); + +static int +dummy_fileproc (finfo) + struct file_info *finfo; +{ + /* This is a pretty hideous hack, but the gist of it is that recurse.c + won't call notify_check unless there is a fileproc, so we can't just + pass NULL for fileproc. */ + return 0; +} + +static int ncheck_fileproc PROTO ((struct file_info *finfo)); + +/* Check for and process notifications. Local only. I think that doing + this as a fileproc is the only way to catch all the + cases (e.g. foo/bar.c), even though that means checking over and over + for the same CVSADM_NOTIFY file which we removed the first time we + processed the directory. */ + +static int +ncheck_fileproc (finfo) + struct file_info *finfo; +{ + int notif_type; + char *filename; + char *val; + char *cp; + char *watches; + + FILE *fp; + char *line = NULL; + size_t line_len = 0; + + /* We send notifications even if noexec. I'm not sure which behavior + is most sensible. */ + + fp = fopen (CVSADM_NOTIFY, "r"); + if (fp == NULL) + { + if (!existence_error (errno)) + error (0, errno, "cannot open %s", CVSADM_NOTIFY); + return 0; + } + + while (getline (&line, &line_len, fp) > 0) + { + notif_type = line[0]; + if (notif_type == '\0') + continue; + filename = line + 1; + cp = strchr (filename, '\t'); + if (cp == NULL) + continue; + *cp++ = '\0'; + val = cp; + cp = strchr (val, '\t'); + if (cp == NULL) + continue; + *cp++ = '+'; + cp = strchr (cp, '\t'); + if (cp == NULL) + continue; + *cp++ = '+'; + cp = strchr (cp, '\t'); + if (cp == NULL) + continue; + *cp++ = '\0'; + watches = cp; + cp = strchr (cp, '\n'); + if (cp == NULL) + continue; + *cp = '\0'; + + notify_do (notif_type, filename, getcaller (), val, watches, + finfo->repository); + } + free (line); + + if (ferror (fp)) + error (0, errno, "cannot read %s", CVSADM_NOTIFY); + if (fclose (fp) < 0) + error (0, errno, "cannot close %s", CVSADM_NOTIFY); + + if (unlink (CVSADM_NOTIFY) < 0) + error (0, errno, "cannot remove %s", CVSADM_NOTIFY); + + return 0; +} + +static int send_notifications PROTO ((int, char **, int)); + +/* Look through the CVSADM_NOTIFY file and process each item there + accordingly. */ +static int +send_notifications (argc, argv, local) + int argc; + char **argv; + int local; +{ + int err = 0; + +#ifdef CLIENT_SUPPORT + /* OK, we've done everything which needs to happen on the client side. + Now we can try to contact the server; if we fail, then the + notifications stay in CVSADM_NOTIFY to be sent next time. */ + if (client_active) + { + if (strcmp (command_name, "release") != 0) + { + start_server (); + ign_setup (); + } + + err += start_recursion (dummy_fileproc, (FILESDONEPROC) NULL, + (DIRENTPROC) NULL, (DIRLEAVEPROC) NULL, + argc, argv, local, W_LOCAL, 0, 0, (char *)NULL, + 0, 0); + + send_to_server ("noop\012", 0); + if (strcmp (command_name, "release") == 0) + err += get_server_responses (); + else + err += get_responses_and_close (); + } + else +#endif + { + /* Local. */ + + lock_tree_for_write (argc, argv, local, 0); + err += start_recursion (ncheck_fileproc, (FILESDONEPROC) NULL, + (DIRENTPROC) NULL, (DIRLEAVEPROC) NULL, + argc, argv, local, W_LOCAL, 0, 0, (char *)NULL, + 0, 0); + lock_tree_cleanup (); + } + return err; +} + +static int edit_fileproc PROTO ((struct file_info *finfo)); + +static int +edit_fileproc (finfo) + struct file_info *finfo; +{ + FILE *fp; + time_t now; + char *ascnow; + char *basefilename; + + if (noexec) + return 0; + + fp = open_file (CVSADM_NOTIFY, "a"); + + (void) time (&now); + ascnow = asctime (gmtime (&now)); + ascnow[24] = '\0'; + fprintf (fp, "E%s\t%s GMT\t%s\t%s\t", finfo->file, + ascnow, hostname, CurDir); + if (setting_tedit) + fprintf (fp, "E"); + if (setting_tunedit) + fprintf (fp, "U"); + if (setting_tcommit) + fprintf (fp, "C"); + fprintf (fp, "\n"); + + if (fclose (fp) < 0) + { + if (finfo->update_dir[0] == '\0') + error (0, errno, "cannot close %s", CVSADM_NOTIFY); + else + error (0, errno, "cannot close %s/%s", finfo->update_dir, + CVSADM_NOTIFY); + } + + xchmod (finfo->file, 1); + + /* Now stash the file away in CVSADM so that unedit can revert even if + it can't communicate with the server. We stash away a writable + copy so that if the user removes the working file, then restores it + with "cvs update" (which clears _editors but does not update + CVSADM_BASE), then a future "cvs edit" can still win. */ + /* Could save a system call by only calling mkdir if trying to create + the output file fails. But copy_file isn't set up to facilitate + that. */ + if (CVS_MKDIR (CVSADM_BASE, 0777) < 0) + { + if (errno != EEXIST +#ifdef EACCESS + /* OS/2; see longer comment in client.c. */ + && errno != EACCESS +#endif + ) + error (1, errno, "cannot mkdir %s", CVSADM_BASE); + } + basefilename = xmalloc (10 + sizeof CVSADM_BASE + strlen (finfo->file)); + strcpy (basefilename, CVSADM_BASE); + strcat (basefilename, "/"); + strcat (basefilename, finfo->file); + copy_file (finfo->file, basefilename); + free (basefilename); + + return 0; +} + +static const char *const edit_usage[] = +{ + "Usage: %s %s [-l] [files...]\n", + "-l: Local directory only, not recursive\n", + "-a: Specify what actions for temporary watch, one of\n", + " edit,unedit,commit.all,none\n", + NULL +}; + +int +edit (argc, argv) + int argc; + char **argv; +{ + int local = 0; + int c; + int err; + int a_omitted; + + if (argc == -1) + usage (edit_usage); + + a_omitted = 1; + setting_tedit = 0; + setting_tunedit = 0; + setting_tcommit = 0; + optind = 1; + while ((c = getopt (argc, argv, "la:")) != -1) + { + switch (c) + { + case 'l': + local = 1; + break; + case 'a': + a_omitted = 0; + if (strcmp (optarg, "edit") == 0) + setting_tedit = 1; + else if (strcmp (optarg, "unedit") == 0) + setting_tunedit = 1; + else if (strcmp (optarg, "commit") == 0) + setting_tcommit = 1; + else if (strcmp (optarg, "all") == 0) + { + setting_tedit = 1; + setting_tunedit = 1; + setting_tcommit = 1; + } + else if (strcmp (optarg, "none") == 0) + { + setting_tedit = 0; + setting_tunedit = 0; + setting_tcommit = 0; + } + else + usage (edit_usage); + break; + case '?': + default: + usage (edit_usage); + break; + } + } + argc -= optind; + argv += optind; + + if (a_omitted) + { + setting_tedit = 1; + setting_tunedit = 1; + setting_tcommit = 1; + } + + /* No need to readlock since we aren't doing anything to the + repository. */ + err = start_recursion (edit_fileproc, (FILESDONEPROC) NULL, + (DIRENTPROC) NULL, (DIRLEAVEPROC) NULL, + argc, argv, local, W_LOCAL, 0, 0, (char *)NULL, + 0, 0); + + err += send_notifications (argc, argv, local); + + return err; +} + +static int unedit_fileproc PROTO ((struct file_info *finfo)); + +static int +unedit_fileproc (finfo) + struct file_info *finfo; +{ + FILE *fp; + time_t now; + char *ascnow; + char *basefilename; + + if (noexec) + return 0; + + basefilename = xmalloc (10 + sizeof CVSADM_BASE + strlen (finfo->file)); + strcpy (basefilename, CVSADM_BASE); + strcat (basefilename, "/"); + strcat (basefilename, finfo->file); + if (!isfile (basefilename)) + { + /* This file apparently was never cvs edit'd (e.g. we are uneditting + a directory where only some of the files were cvs edit'd. */ + free (basefilename); + return 0; + } + + if (xcmp (finfo->file, basefilename) != 0) + { + printf ("%s has been modified; revert changes? ", finfo->fullname); + if (!yesno ()) + { + /* "no". */ + free (basefilename); + return 0; + } + } + rename_file (basefilename, finfo->file); + free (basefilename); + + fp = open_file (CVSADM_NOTIFY, "a"); + + (void) time (&now); + ascnow = asctime (gmtime (&now)); + ascnow[24] = '\0'; + fprintf (fp, "U%s\t%s GMT\t%s\t%s\t\n", finfo->file, + ascnow, hostname, CurDir); + + if (fclose (fp) < 0) + { + if (finfo->update_dir[0] == '\0') + error (0, errno, "cannot close %s", CVSADM_NOTIFY); + else + error (0, errno, "cannot close %s/%s", finfo->update_dir, + CVSADM_NOTIFY); + } + + xchmod (finfo->file, 0); + return 0; +} + +int +unedit (argc, argv) + int argc; + char **argv; +{ + int local = 0; + int c; + int err; + + if (argc == -1) + usage (edit_usage); + + optind = 1; + while ((c = getopt (argc, argv, "l")) != -1) + { + switch (c) + { + case 'l': + local = 1; + break; + case '?': + default: + usage (edit_usage); + break; + } + } + argc -= optind; + argv += optind; + + /* No need to readlock since we aren't doing anything to the + repository. */ + err = start_recursion (unedit_fileproc, (FILESDONEPROC) NULL, + (DIRENTPROC) NULL, (DIRLEAVEPROC) NULL, + argc, argv, local, W_LOCAL, 0, 0, (char *)NULL, + 0, 0); + + err += send_notifications (argc, argv, local); + + return err; +} + +void +mark_up_to_date (file) + char *file; +{ + char *base; + + /* The file is up to date, so we better get rid of an out of + date file in CVSADM_BASE. */ + base = xmalloc (strlen (file) + 80); + strcpy (base, CVSADM_BASE); + strcat (base, "/"); + strcat (base, file); + if (unlink_file (base) < 0 && ! existence_error (errno)) + error (0, errno, "cannot remove %s", file); + free (base); +} + + +void +editor_set (filename, editor, val) + char *filename; + char *editor; + char *val; +{ + char *edlist; + char *newlist; + + edlist = fileattr_get0 (filename, "_editors"); + newlist = fileattr_modify (edlist, editor, val, '>', ','); + if (edlist != NULL) + free (edlist); + /* If the attributes is unchanged, don't rewrite the attribute file. */ + if (!((edlist == NULL && newlist == NULL) + || (edlist != NULL + && newlist != NULL + && strcmp (edlist, newlist) == 0))) + fileattr_set (filename, "_editors", newlist); + if (newlist != NULL) + free (newlist); +} + +struct notify_proc_args { + /* What kind of notification, "edit", "tedit", etc. */ + char *type; + /* User who is running the command which causes notification. */ + char *who; + /* User to be notified. */ + char *notifyee; + /* File. */ + char *file; +}; + +/* Pass as a static until we get around to fixing Parse_Info to pass along + a void * where we can stash it. */ +static struct notify_proc_args *notify_args; + +static int notify_proc PROTO ((char *repository, char *filter)); + +static int +notify_proc (repository, filter) + char *repository; + char *filter; +{ + FILE *pipefp; + char *prog; + char *expanded_prog; + char *p; + char *q; + char *srepos; + struct notify_proc_args *args = notify_args; + + srepos = Short_Repository (repository); + prog = xmalloc (strlen (filter) + strlen (args->notifyee) + 1); + /* Copy FILTER to PROG, replacing the first occurrence of %s with + the notifyee. We only allocated enough memory for one %s, and I doubt + there is a need for more. */ + for (p = filter, q = prog; *p != '\0'; ++p) + { + if (p[0] == '%') + { + if (p[1] == 's') + { + strcpy (q, args->notifyee); + q += strlen (q); + strcpy (q, p + 2); + q += strlen (q); + break; + } + else + continue; + } + *q++ = *p; + } + *q = '\0'; + + /* FIXME: why are we calling expand_proc? Didn't we already + expand it in Parse_Info, before passing it to notify_proc? */ + expanded_prog = expand_path (prog, "notify", 0); + if (!expanded_prog) + { + free (prog); + return 1; + } + + pipefp = run_popen (expanded_prog, "w"); + if (pipefp == NULL) + { + error (0, errno, "cannot write entry to notify filter: %s", prog); + free (prog); + free (expanded_prog); + return 1; + } + + fprintf (pipefp, "%s %s\n---\n", srepos, args->file); + fprintf (pipefp, "Triggered %s watch on %s\n", args->type, repository); + fprintf (pipefp, "By %s\n", args->who); + + /* Lots more potentially useful information we could add here; see + logfile_write for inspiration. */ + + free (prog); + free (expanded_prog); + return (pclose (pipefp)); +} + +void +notify_do (type, filename, who, val, watches, repository) + int type; + char *filename; + char *who; + char *val; + char *watches; + char *repository; +{ + static struct addremove_args blank; + struct addremove_args args; + char *watchers; + char *p; + char *endp; + char *nextp; + + /* Initialize fields to 0, NULL, or 0.0. */ + args = blank; + switch (type) + { + case 'E': + editor_set (filename, who, val); + break; + case 'U': + case 'C': + editor_set (filename, who, NULL); + break; + default: + return; + } + + watchers = fileattr_get0 (filename, "_watchers"); + p = watchers; + while (p != NULL) + { + char *q; + char *endq; + char *nextq; + char *notif; + + endp = strchr (p, '>'); + if (endp == NULL) + break; + nextp = strchr (p, ','); + + if ((size_t)(endp - p) == strlen (who) && strncmp (who, p, endp - p) == 0) + { + /* Don't notify user of their own changes. Would perhaps + be better to check whether it is the same working + directory, not the same user, but that is hairy. */ + p = nextp == NULL ? nextp : nextp + 1; + continue; + } + + /* Now we point q at a string which looks like + "edit+unedit+commit,"... and walk down it. */ + q = endp + 1; + notif = NULL; + while (q != NULL) + { + endq = strchr (q, '+'); + if (endq == NULL || (nextp != NULL && endq > nextp)) + { + if (nextp == NULL) + endq = q + strlen (q); + else + endq = nextp; + nextq = NULL; + } + else + nextq = endq + 1; + + /* If there is a temporary and a regular watch, send a single + notification, for the regular watch. */ + if (type == 'E' && endq - q == 4 && strncmp ("edit", q, 4) == 0) + { + notif = "edit"; + } + else if (type == 'U' + && endq - q == 6 && strncmp ("unedit", q, 6) == 0) + { + notif = "unedit"; + } + else if (type == 'C' + && endq - q == 6 && strncmp ("commit", q, 6) == 0) + { + notif = "commit"; + } + else if (type == 'E' + && endq - q == 5 && strncmp ("tedit", q, 5) == 0) + { + if (notif == NULL) + notif = "temporary edit"; + } + else if (type == 'U' + && endq - q == 7 && strncmp ("tunedit", q, 7) == 0) + { + if (notif == NULL) + notif = "temporary unedit"; + } + else if (type == 'C' + && endq - q == 7 && strncmp ("tcommit", q, 7) == 0) + { + if (notif == NULL) + notif = "temporary commit"; + } + q = nextq; + } + if (nextp != NULL) + ++nextp; + + if (notif != NULL) + { + struct notify_proc_args args; + size_t len = endp - p; + FILE *fp; + char *usersname; + char *line = NULL; + size_t line_len = 0; + + args.notifyee = NULL; + usersname = xmalloc (strlen (CVSroot) + + sizeof CVSROOTADM + + sizeof CVSROOTADM_USERS + + 20); + strcpy (usersname, CVSroot); + strcat (usersname, "/"); + strcat (usersname, CVSROOTADM); + strcat (usersname, "/"); + strcat (usersname, CVSROOTADM_USERS); + fp = fopen (usersname, "r"); + if (fp == NULL && !existence_error (errno)) + error (0, errno, "cannot read %s", usersname); + if (fp != NULL) + { + while (getline (&line, &line_len, fp) >= 0) + { + if (strncmp (line, p, len) == 0 + && line[len] == ':') + { + char *cp; + args.notifyee = xstrdup (line + len + 1); + cp = strchr (args.notifyee, ':'); + if (cp != NULL) + *cp = '\0'; + break; + } + } + if (ferror (fp)) + error (0, errno, "cannot read %s", usersname); + if (fclose (fp) < 0) + error (0, errno, "cannot close %s", usersname); + } + free (usersname); + free (line); + + if (args.notifyee == NULL) + { + args.notifyee = xmalloc (endp - p + 1); + strncpy (args.notifyee, p, endp - p); + args.notifyee[endp - p] = '\0'; + } + + notify_args = &args; + args.type = notif; + args.who = who; + args.file = filename; + + (void) Parse_Info (CVSROOTADM_NOTIFY, repository, notify_proc, 1); + free (args.notifyee); + } + + p = nextp; + } + if (watchers != NULL) + free (watchers); + + switch (type) + { + case 'E': + if (*watches == 'E') + { + args.add_tedit = 1; + ++watches; + } + if (*watches == 'U') + { + args.add_tunedit = 1; + ++watches; + } + if (*watches == 'C') + { + args.add_tcommit = 1; + } + watch_modify_watchers (filename, &args); + break; + case 'U': + case 'C': + args.remove_temp = 1; + watch_modify_watchers (filename, &args); + break; + } +} + +#ifdef CLIENT_SUPPORT +/* Check and send notifications. This is only for the client. */ +void +notify_check (repository, update_dir) + char *repository; + char *update_dir; +{ + FILE *fp; + char *line = NULL; + size_t line_len = 0; + + if (! server_started) + /* We are in the midst of a command which is not to talk to + the server (e.g. the first phase of a cvs edit). Just chill + out, we'll catch the notifications on the flip side. */ + return; + + /* We send notifications even if noexec. I'm not sure which behavior + is most sensible. */ + + fp = fopen (CVSADM_NOTIFY, "r"); + if (fp == NULL) + { + if (!existence_error (errno)) + error (0, errno, "cannot open %s", CVSADM_NOTIFY); + return; + } + while (getline (&line, &line_len, fp) > 0) + { + int notif_type; + char *filename; + char *val; + char *cp; + + notif_type = line[0]; + if (notif_type == '\0') + continue; + filename = line + 1; + cp = strchr (filename, '\t'); + if (cp == NULL) + continue; + *cp++ = '\0'; + val = cp; + + client_notify (repository, update_dir, filename, notif_type, val); + } + + if (ferror (fp)) + error (0, errno, "cannot read %s", CVSADM_NOTIFY); + if (fclose (fp) < 0) + error (0, errno, "cannot close %s", CVSADM_NOTIFY); + + /* Leave the CVSADM_NOTIFY file there, until the server tells us it + has dealt with it. */ +} +#endif /* CLIENT_SUPPORT */ + + +static const char *const editors_usage[] = +{ + "Usage: %s %s [files...]\n", + NULL +}; + +static int editors_fileproc PROTO ((struct file_info *finfo)); + +static int +editors_fileproc (finfo) + struct file_info *finfo; +{ + char *them; + char *p; + + them = fileattr_get0 (finfo->file, "_editors"); + if (them == NULL) + return 0; + + fputs (finfo->fullname, stdout); + + p = them; + while (1) + { + putc ('\t', stdout); + while (*p != '>' && *p != '\0') + putc (*p++, stdout); + if (*p == '\0') + { + /* Only happens if attribute is misformed. */ + putc ('\n', stdout); + break; + } + ++p; + putc ('\t', stdout); + while (1) + { + while (*p != '+' && *p != ',' && *p != '\0') + putc (*p++, stdout); + if (*p == '\0') + { + putc ('\n', stdout); + goto out; + } + if (*p == ',') + { + ++p; + break; + } + ++p; + putc ('\t', stdout); + } + putc ('\n', stdout); + } + out:; + return 0; +} + +int +editors (argc, argv) + int argc; + char **argv; +{ + int local = 0; + int c; + + if (argc == -1) + usage (editors_usage); + + optind = 1; + while ((c = getopt (argc, argv, "l")) != -1) + { + switch (c) + { + case 'l': + local = 1; + break; + case '?': + default: + usage (editors_usage); + break; + } + } + argc -= optind; + argv += optind; + +#ifdef CLIENT_SUPPORT + if (client_active) + { + start_server (); + ign_setup (); + + if (local) + send_arg ("-l"); + send_file_names (argc, argv, SEND_EXPAND_WILD); + /* FIXME: We shouldn't have to send current files, but I'm not sure + whether it works. So send the files -- + it's slower but it works. */ + send_files (argc, argv, local, 0); + send_to_server ("editors\012", 0); + return get_responses_and_close (); + } +#endif /* CLIENT_SUPPORT */ + + return start_recursion (editors_fileproc, (FILESDONEPROC) NULL, + (DIRENTPROC) NULL, (DIRLEAVEPROC) NULL, + argc, argv, local, W_LOCAL, 0, 1, (char *)NULL, + 0, 0); +} diff --git a/contrib/cvs/src/edit.h b/contrib/cvs/src/edit.h new file mode 100644 index 0000000..0a823ad --- /dev/null +++ b/contrib/cvs/src/edit.h @@ -0,0 +1,42 @@ +/* Interface to "cvs edit", "cvs watch on", and related features + + 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. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ + +extern int watch_on PROTO ((int argc, char **argv)); +extern int watch_off PROTO ((int argc, char **argv)); + +#ifdef CLIENT_SUPPORT +/* Check to see if any notifications are sitting around in need of being + sent. These are the notifications stored in CVSADM_NOTIFY (edit,unedit); + commit calls notify_do directly. */ +extern void notify_check PROTO ((char *repository, char *update_dir)); +#endif /* CLIENT_SUPPORT */ + +/* Issue a notification for file FILENAME. TYPE is 'E' for edit, 'U' + for unedit, and 'C' for commit. WHO is the user currently running. + For TYPE 'E', VAL is the time+host+directory data which goes in + _editors, and WATCHES is zero or more of E,U,C, in that order, to specify + what kinds of temporary watches to set. */ +extern void notify_do PROTO ((int type, char *filename, char *who, + char *val, char *watches, char *repository)); + +/* Set attributes to reflect the fact that EDITOR is editing FILENAME. + VAL is time+host+directory, or NULL if we are to say that EDITOR is + *not* editing FILENAME. */ +extern void editor_set PROTO ((char *filename, char *editor, char *val)); + +/* Take note of the fact that FILE is up to date (this munges CVS/Base; + processing of CVS/Entries is done separately). */ +extern void mark_up_to_date PROTO ((char *file)); diff --git a/contrib/cvs/src/entries.c b/contrib/cvs/src/entries.c new file mode 100644 index 0000000..350f7f8 --- /dev/null +++ b/contrib/cvs/src/entries.c @@ -0,0 +1,548 @@ +/* + * Copyright (c) 1992, Brian Berliner and Jeff Polk + * Copyright (c) 1989-1992, Brian Berliner + * + * You may distribute under the terms of the GNU General Public License as + * specified in the README file that comes with the CVS 1.4 kit. + * + * Entries file to Files file + * + * Creates the file Files containing the names that comprise the project, from + * the Entries file. + */ + +#include "cvs.h" +#include "getline.h" + +static Node *AddEntryNode PROTO((List * list, Entnode *entnode)); + +static Entnode *fgetentent PROTO((FILE *)); +static int fputentent PROTO((FILE *, Entnode *)); + +static FILE *entfile; +static char *entfilename; /* for error messages */ + +/* + * Construct an Entnode + */ +Entnode * +Entnode_Create(user, vn, ts, options, tag, date, ts_conflict) + const char *user; + const char *vn; + const char *ts; + const char *options; + const char *tag; + const char *date; + const char *ts_conflict; +{ + Entnode *ent; + + /* Note that timestamp and options must be non-NULL */ + ent = (Entnode *) xmalloc (sizeof (Entnode)); + ent->user = xstrdup (user); + ent->version = xstrdup (vn); + ent->timestamp = xstrdup (ts ? ts : ""); + ent->options = xstrdup (options ? options : ""); + ent->tag = xstrdup (tag); + ent->date = xstrdup (date); + ent->conflict = xstrdup (ts_conflict); + + return ent; +} + +/* + * Destruct an Entnode + */ +void +Entnode_Destroy (ent) + Entnode *ent; +{ + free (ent->user); + free (ent->version); + free (ent->timestamp); + free (ent->options); + if (ent->tag) + free (ent->tag); + if (ent->date) + free (ent->date); + if (ent->conflict) + free (ent->conflict); + free (ent); +} + +/* + * Write out the line associated with a node of an entries file + */ +static int write_ent_proc PROTO ((Node *, void *)); +static int +write_ent_proc (node, closure) + Node *node; + void *closure; +{ + if (fputentent(entfile, (Entnode *) node->data)) + error (1, errno, "cannot write %s", entfilename); + + return (0); +} + +/* + * write out the current entries file given a list, making a backup copy + * first of course + */ +static void +write_entries (list) + List *list; +{ + /* open the new one and walk the list writing entries */ + entfilename = CVSADM_ENTBAK; + entfile = open_file (entfilename, "w+"); + (void) walklist (list, write_ent_proc, NULL); + if (fclose (entfile) == EOF) + error (1, errno, "error closing %s", entfilename); + + /* now, atomically (on systems that support it) rename it */ + rename_file (entfilename, CVSADM_ENT); + + /* now, remove the log file */ + unlink_file (CVSADM_ENTLOG); +} + +/* + * Removes the argument file from the Entries file if necessary. + */ +void +Scratch_Entry (list, fname) + List *list; + char *fname; +{ + Node *node; + + if (trace) +#ifdef SERVER_SUPPORT + (void) fprintf (stderr, "%c-> Scratch_Entry(%s)\n", + (server_active) ? 'S' : ' ', fname); +#else + (void) fprintf (stderr, "-> Scratch_Entry(%s)\n", fname); +#endif + + /* hashlookup to see if it is there */ + if ((node = findnode_fn (list, fname)) != NULL) + { + delnode (node); /* delete the node */ +#ifdef SERVER_SUPPORT + if (server_active) + server_scratch (fname); +#endif + if (!noexec) + write_entries (list); /* re-write the file */ + } +} + +/* + * Enters the given file name/version/time-stamp into the Entries file, + * removing the old entry first, if necessary. + */ +void +Register (list, fname, vn, ts, options, tag, date, ts_conflict) + List *list; + char *fname; + char *vn; + char *ts; + char *options; + char *tag; + char *date; + char *ts_conflict; +{ + Entnode *entnode; + Node *node; + +#ifdef SERVER_SUPPORT + if (server_active) + { + server_register (fname, vn, ts, options, tag, date, ts_conflict); + } +#endif + + if (trace) + { +#ifdef SERVER_SUPPORT + (void) fprintf (stderr, "%c-> Register(%s, %s, %s%s%s, %s, %s %s)\n", + (server_active) ? 'S' : ' ', + fname, vn, ts ? ts : "", + ts_conflict ? "+" : "", ts_conflict ? ts_conflict : "", + options, tag ? tag : "", date ? date : ""); +#else + (void) fprintf (stderr, "-> Register(%s, %s, %s%s%s, %s, %s %s)\n", + fname, vn, ts ? ts : "", + ts_conflict ? "+" : "", ts_conflict ? ts_conflict : "", + options, tag ? tag : "", date ? date : ""); +#endif + } + + entnode = Entnode_Create(fname, vn, ts, options, tag, date, ts_conflict); + node = AddEntryNode (list, entnode); + + if (!noexec) + { + entfile = open_file (CVSADM_ENTLOG, "a"); + + write_ent_proc (node, NULL); + + if (fclose (entfile) == EOF) + error (1, errno, "error closing %s", CVSADM_ENTLOG); + } +} + +/* + * Node delete procedure for list-private sticky dir tag/date info + */ +static void +freesdt (p) + Node *p; +{ + struct stickydirtag *sdtp; + + sdtp = (struct stickydirtag *) p->data; + if (sdtp->tag) + free (sdtp->tag); + if (sdtp->date) + free (sdtp->date); + if (sdtp->options) + free (sdtp->options); + free ((char *) sdtp); +} + +static Entnode * +fgetentent(fpin) + FILE *fpin; +{ + Entnode *ent; + char *line; + size_t line_chars_allocated; + register char *cp; + char *user, *vn, *ts, *options; + char *tag_or_date, *tag, *date, *ts_conflict; + + line = NULL; + line_chars_allocated = 0; + + ent = NULL; + while (getline (&line, &line_chars_allocated, fpin) > 0) + { + if (line[0] != '/') + continue; + + user = line + 1; + if ((cp = strchr (user, '/')) == NULL) + continue; + *cp++ = '\0'; + vn = cp; + if ((cp = strchr (vn, '/')) == NULL) + continue; + *cp++ = '\0'; + ts = cp; + if ((cp = strchr (ts, '/')) == NULL) + continue; + *cp++ = '\0'; + options = cp; + if ((cp = strchr (options, '/')) == NULL) + continue; + *cp++ = '\0'; + tag_or_date = cp; + if ((cp = strchr (tag_or_date, '\n')) == NULL) + continue; + *cp = '\0'; + tag = (char *) NULL; + date = (char *) NULL; + if (*tag_or_date == 'T') + tag = tag_or_date + 1; + else if (*tag_or_date == 'D') + date = tag_or_date + 1; + + if ((ts_conflict = strchr (ts, '+'))) + *ts_conflict++ = '\0'; + + /* + * XXX - Convert timestamp from old format to new format. + * + * If the timestamp doesn't match the file's current + * mtime, we'd have to generate a string that doesn't + * match anyways, so cheat and base it on the existing + * string; it doesn't have to match the same mod time. + * + * For an unmodified file, write the correct timestamp. + */ + { + struct stat sb; + if (strlen (ts) > 30 && stat (user, &sb) == 0) + { + char *c = ctime (&sb.st_mtime); + + if (!strncmp (ts + 25, c, 24)) + ts = time_stamp (user); + else + { + ts += 24; + ts[0] = '*'; + } + } + } + + ent = Entnode_Create(user, vn, ts, options, tag, date, ts_conflict); + break; + } + + free (line); + return ent; +} + +static int +fputentent(fp, p) + FILE *fp; + Entnode *p; +{ + if (fprintf (fp, "/%s/%s/%s", p->user, p->version, p->timestamp) < 0) + return 1; + if (p->conflict) + { + if (fprintf (fp, "+%s", p->conflict) < 0) + return 1; + } + if (fprintf (fp, "/%s/", p->options) < 0) + return 1; + + if (p->tag) + { + if (fprintf (fp, "T%s\n", p->tag) < 0) + return 1; + } + else if (p->date) + { + if (fprintf (fp, "D%s\n", p->date) < 0) + return 1; + } + else + { + if (fprintf (fp, "\n") < 0) + return 1; + } + + return 0; +} + + +/* + * Read the entries file into a list, hashing on the file name. + */ +List * +Entries_Open (aflag) + int aflag; +{ + List *entries; + Entnode *ent; + char *dirtag, *dirdate; + int do_rewrite = 0; + FILE *fpin; + + /* get a fresh list... */ + entries = getlist (); + + /* + * Parse the CVS/Tag file, to get any default tag/date settings. Use + * list-private storage to tuck them away for Version_TS(). + */ + ParseTag (&dirtag, &dirdate); + if (aflag || dirtag || dirdate) + { + struct stickydirtag *sdtp; + + sdtp = (struct stickydirtag *) xmalloc (sizeof (*sdtp)); + memset ((char *) sdtp, 0, sizeof (*sdtp)); + sdtp->aflag = aflag; + sdtp->tag = xstrdup (dirtag); + sdtp->date = xstrdup (dirdate); + + /* feed it into the list-private area */ + entries->list->data = (char *) sdtp; + entries->list->delproc = freesdt; + } + + fpin = fopen (CVSADM_ENT, "r"); + if (fpin == NULL) + error (0, errno, "cannot open %s for reading", CVSADM_ENT); + else + { + while ((ent = fgetentent (fpin)) != NULL) + { + (void) AddEntryNode (entries, ent); + } + + fclose (fpin); + } + + fpin = fopen (CVSADM_ENTLOG, "r"); + if (fpin != NULL) + { + while ((ent = fgetentent (fpin)) != NULL) + { + (void) AddEntryNode (entries, ent); + } + do_rewrite = 1; + fclose (fpin); + } + + if (do_rewrite && !noexec) + write_entries (entries); + + /* clean up and return */ + if (dirtag) + free (dirtag); + if (dirdate) + free (dirdate); + return (entries); +} + +void +Entries_Close(list) + List *list; +{ + if (list) + { + if (!noexec) + { + if (isfile (CVSADM_ENTLOG)) + write_entries (list); + } + dellist(&list); + } +} + + +/* + * Free up the memory associated with the data section of an ENTRIES type + * node + */ +static void +Entries_delproc (node) + Node *node; +{ + Entnode *p; + + p = (Entnode *) node->data; + Entnode_Destroy(p); +} + +/* + * Get an Entries file list node, initialize it, and add it to the specified + * list + */ +static Node * +AddEntryNode (list, entdata) + List *list; + Entnode *entdata; +{ + Node *p; + + /* was it already there? */ + if ((p = findnode_fn (list, entdata->user)) != NULL) + { + /* take it out */ + delnode (p); + } + + /* get a node and fill in the regular stuff */ + p = getnode (); + p->type = ENTRIES; + p->delproc = Entries_delproc; + + /* this one gets a key of the name for hashing */ + /* FIXME This results in duplicated data --- the hash package shouldn't + assume that the key is dynamically allocated. The user's free proc + should be responsible for freeing the key. */ + p->key = xstrdup (entdata->user); + p->data = (char *) entdata; + + /* put the node into the list */ + addnode (list, p); + return (p); +} + +/* + * Write out/Clear the CVS/Tag file. + */ +void +WriteTag (dir, tag, date) + char *dir; + char *tag; + char *date; +{ + FILE *fout; + char tmp[PATH_MAX]; + + if (noexec) + return; + + if (dir == NULL) + (void) strcpy (tmp, CVSADM_TAG); + else + (void) sprintf (tmp, "%s/%s", dir, CVSADM_TAG); + + if (tag || date) + { + fout = open_file (tmp, "w+"); + if (tag) + { + if (fprintf (fout, "T%s\n", tag) < 0) + error (1, errno, "write to %s failed", tmp); + } + else + { + if (fprintf (fout, "D%s\n", date) < 0) + error (1, errno, "write to %s failed", tmp); + } + if (fclose (fout) == EOF) + error (1, errno, "cannot close %s", tmp); + } + else + if (unlink_file (tmp) < 0 && ! existence_error (errno)) + error (1, errno, "cannot remove %s", tmp); +} + +/* + * Parse the CVS/Tag file for the current directory. + */ +void +ParseTag (tagp, datep) + char **tagp; + char **datep; +{ + FILE *fp; + + if (tagp) + *tagp = (char *) NULL; + if (datep) + *datep = (char *) NULL; + fp = fopen (CVSADM_TAG, "r"); + if (fp) + { + char *line; + int line_length; + size_t line_chars_allocated; + + line = NULL; + line_chars_allocated = 0; + + if ((line_length = getline (&line, &line_chars_allocated, fp)) > 0) + { + /* Remove any trailing newline. */ + if (line[line_length - 1] == '\n') + line[--line_length] = '\0'; + if (*line == 'T' && tagp) + *tagp = xstrdup (line + 1); + else if (*line == 'D' && datep) + *datep = xstrdup (line + 1); + } + (void) fclose (fp); + free (line); + } +} diff --git a/contrib/cvs/src/error.c b/contrib/cvs/src/error.c new file mode 100644 index 0000000..8a10cc7 --- /dev/null +++ b/contrib/cvs/src/error.c @@ -0,0 +1,256 @@ +/* error.c -- error handler for noninteractive utilities + Copyright (C) 1990-1992 Free Software Foundation, Inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2, or (at your option) + any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ + +/* David MacKenzie */ +/* Brian Berliner added support for CVS */ + +#include "cvs.h" + +#include <stdio.h> + +/* If non-zero, error will use the CVS protocol to stdout to report error + messages. This will only be set in the CVS server parent process; + most other code is run via do_cvs_command, which forks off a child + process and packages up its stderr in the protocol. */ +int error_use_protocol; + +#ifdef HAVE_VPRINTF + +#if __STDC__ +#include <stdarg.h> +#define VA_START(args, lastarg) va_start(args, lastarg) +#else /* ! __STDC__ */ +#include <varargs.h> +#define VA_START(args, lastarg) va_start(args) +#endif /* __STDC__ */ + +#else /* ! HAVE_VPRINTF */ + +#ifdef HAVE_DOPRNT +#define va_alist args +#define va_dcl int args; +#else /* ! HAVE_DOPRNT */ +#define va_alist a1, a2, a3, a4, a5, a6, a7, a8 +#define va_dcl char *a1, *a2, *a3, *a4, *a5, *a6, *a7, *a8; +#endif /* HAVE_DOPRNT */ + +#endif /* HAVE_VPRINTF */ + +#if STDC_HEADERS +#include <stdlib.h> +#include <string.h> +#else /* ! STDC_HEADERS */ +#if __STDC__ +void exit(int status); +#else /* ! __STDC__ */ +void exit (); +#endif /* __STDC__ */ +#endif /* STDC_HEADERS */ + +extern char *strerror (); + +extern int vasprintf (); + +typedef void (*fn_returning_void) PROTO((void)); + +/* Function to call before exiting. */ +static fn_returning_void cleanup_fn; + +fn_returning_void +error_set_cleanup (arg) + fn_returning_void arg; +{ + fn_returning_void retval = cleanup_fn; + cleanup_fn = arg; + return retval; +} + +/* Print the program name and error message MESSAGE, which is a printf-style + format string with optional args. + If ERRNUM is nonzero, print its corresponding system error message. + Exit with status EXIT_FAILURE if STATUS is nonzero. */ +/* VARARGS */ +void +#if defined (HAVE_VPRINTF) && __STDC__ +error (int status, int errnum, const char *message, ...) +#else +error (status, errnum, message, va_alist) + int status; + int errnum; + const char *message; + va_dcl +#endif +{ + FILE *out = stderr; +#ifdef HAVE_VPRINTF + va_list args; +#endif + + if (error_use_protocol) + { + out = stdout; + printf ("E "); + } + +#ifdef HAVE_VPRINTF + { + char *mess = NULL; + char *entire; + size_t len; + + VA_START (args, message); + vasprintf (&mess, message, args); + va_end (args); + + if (mess == NULL) + { + entire = NULL; + status = 1; + } + else + { + len = strlen (mess) + strlen (program_name) + 80; + if (command_name != NULL) + len += strlen (command_name); + if (errnum != 0) + len += strlen (strerror (errnum)); + entire = malloc (len); + if (entire == NULL) + { + free (mess); + status = 1; + } + else + { + strcpy (entire, program_name); + if (command_name != NULL && command_name[0] != '\0') + { + strcat (entire, " "); + if (status != 0) + strcat (entire, "["); + strcat (entire, command_name); + if (status != 0) + strcat (entire, " aborted]"); + } + strcat (entire, ": "); + strcat (entire, mess); + if (errnum != 0) + { + strcat (entire, ": "); + strcat (entire, strerror (errnum)); + } + strcat (entire, "\n"); + free (mess); + } + } + if (error_use_protocol) + fputs (entire ? entire : "out of memory", out); + else + cvs_outerr (entire ? entire : "out of memory", 0); + if (entire != NULL) + free (entire); + } + +#else /* No HAVE_VPRINTF */ + /* I think that all relevant systems have vprintf these days. But + just in case, I'm leaving this code here. */ + + if (command_name && *command_name) + { + if (status) + fprintf (out, "%s [%s aborted]: ", program_name, command_name); + else + fprintf (out, "%s %s: ", program_name, command_name); + } + else + fprintf (out, "%s: ", program_name); + +#ifdef HAVE_VPRINTF + VA_START (args, message); + vfprintf (out, message, args); + va_end (args); +#else +#ifdef HAVE_DOPRNT + _doprnt (message, &args, out); +#else + fprintf (out, message, a1, a2, a3, a4, a5, a6, a7, a8); +#endif +#endif + if (errnum) + fprintf (out, ": %s", strerror (errnum)); + putc ('\n', out); + +#endif /* No HAVE_VPRINTF */ + + /* In the error_use_protocol case, this probably does something useful. + In most other cases, I suspect it is a noop (either stderr is line + buffered or we haven't written anything to stderr) or unnecessary + (if stderr is not line buffered, maybe there is a reason....). */ + fflush (out); + + if (status) + { + if (cleanup_fn) + (*cleanup_fn) (); + exit (EXIT_FAILURE); + } +} + +/* Print the program name and error message MESSAGE, which is a printf-style + format string with optional args to the file specified by FP. + If ERRNUM is nonzero, print its corresponding system error message. + Exit with status EXIT_FAILURE if STATUS is nonzero. */ +/* VARARGS */ +void +#if defined (HAVE_VPRINTF) && __STDC__ +fperror (FILE *fp, int status, int errnum, char *message, ...) +#else +fperror (fp, status, errnum, message, va_alist) + FILE *fp; + int status; + int errnum; + char *message; + va_dcl +#endif +{ +#ifdef HAVE_VPRINTF + va_list args; +#endif + + fprintf (fp, "%s: ", program_name); +#ifdef HAVE_VPRINTF + VA_START (args, message); + vfprintf (fp, message, args); + va_end (args); +#else +#ifdef HAVE_DOPRNT + _doprnt (message, &args, fp); +#else + fprintf (fp, message, a1, a2, a3, a4, a5, a6, a7, a8); +#endif +#endif + if (errnum) + fprintf (fp, ": %s", strerror (errnum)); + putc ('\n', fp); + fflush (fp); + if (status) + { + if (cleanup_fn) + (*cleanup_fn) (); + exit (EXIT_FAILURE); + } +} diff --git a/contrib/cvs/src/error.h b/contrib/cvs/src/error.h new file mode 100644 index 0000000..7d4f535 --- /dev/null +++ b/contrib/cvs/src/error.h @@ -0,0 +1,47 @@ +/* error.h -- declaration for error-reporting function + Copyright (C) 1995 Software Foundation, Inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2, or (at your option) + any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ + +#ifndef _error_h_ +#define _error_h_ + +#ifndef __attribute__ +/* This feature is available in gcc versions 2.5 and later. */ +# if __GNUC__ < 2 || (__GNUC__ == 2 && __GNUC_MINOR__ < 5) || __STRICT_ANSI__ +# define __attribute__(Spec) /* empty */ +# endif +/* The __-protected variants of `format' and `printf' attributes + are accepted by gcc versions 2.6.4 (effectively 2.7) and later. */ +# if __GNUC__ < 2 || (__GNUC__ == 2 && __GNUC_MINOR__ < 7) +# define __format__ format +# define __printf__ printf +# endif +#endif + +#if __STDC__ +void error (int, int, const char *, ...) \ + __attribute__ ((__format__ (__printf__, 3, 4))); +#else +void error (); +#endif + +/* If non-zero, error will use the CVS protocol to report error + messages. This will only be set in the CVS server parent process; + most other code is run via do_cvs_command, which forks off a child + process and packages up its stderr in the protocol. */ +extern int error_use_protocol; + +#endif /* _error_h_ */ diff --git a/contrib/cvs/src/expand_path.c b/contrib/cvs/src/expand_path.c new file mode 100644 index 0000000..9898051 --- /dev/null +++ b/contrib/cvs/src/expand_path.c @@ -0,0 +1,241 @@ +/* expand_path.c -- expand environmental variables in passed in string + * + * The main routine is expand_path(), it is the routine that handles + * the '~' character in four forms: + * ~name + * ~name/ + * ~/ + * ~ + * and handles environment variables contained within the pathname + * which are defined by: + * ${var_name} (var_name is the name of the environ variable) + * $var_name (var_name ends w/ non-alphanumeric char other than '_') + */ + +#include "cvs.h" +#include <sys/types.h> + +static char *expand_variable PROTO((char *env, char *file, int line)); + + +/* User variables. */ + +List *variable_list = NULL; + +static void variable_delproc PROTO ((Node *)); + +static void +variable_delproc (node) + Node *node; +{ + free (node->data); +} + +/* Currently used by -s option; we might want a way to set user + variables in a file in the $CVSROOT/CVSROOT directory too. */ + +void +variable_set (nameval) + char *nameval; +{ + char *p; + char *name; + Node *node; + + p = nameval; + while (isalnum (*p) || *p == '_') + ++p; + if (*p != '=') + error (1, 0, "illegal character in user variable name in %s", nameval); + if (p == nameval) + error (1, 0, "empty user variable name in %s", nameval); + name = xmalloc (p - nameval + 1); + strncpy (name, nameval, p - nameval); + name[p - nameval] = '\0'; + /* Make p point to the value. */ + ++p; + if (strchr (p, '\012') != NULL) + error (1, 0, "linefeed in user variable value in %s", nameval); + + if (variable_list == NULL) + variable_list = getlist (); + + node = findnode (variable_list, name); + if (node == NULL) + { + node = getnode (); + node->type = VARIABLE; + node->delproc = variable_delproc; + node->key = name; + node->data = xstrdup (p); + (void) addnode (variable_list, node); + } + else + { + /* Replace the old value. For example, this means that -s + options on the command line override ones from .cvsrc. */ + free (node->data); + node->data = xstrdup (p); + free (name); + } +} + +/* This routine will expand the pathname to account for ~ and $ + characters as described above. If an error occurs, an error + message is printed via error() and NULL is returned. FILE and + LINE are the filename and linenumber to include in the error + message. */ +char * +expand_path (name, file, line) + char *name; + char *file; + int line; +{ + char *s; + char *d; + /* FIXME: arbitrary limit. */ + char mybuf[PATH_MAX]; + char buf[PATH_MAX]; + char *result; + s = name; + d = mybuf; + while ((*d++ = *s)) + if (*s++ == '$') + { + char *p = d; + char *e; + int flag = (*s == '{'); + + for (; (*d++ = *s); s++) + if (flag + ? *s =='}' + : isalnum (*s) == 0 && *s != '_') + break; + *--d = 0; + e = expand_variable (&p[flag], file, line); + + if (e) + { + for (d = &p[-1]; (*d++ = *e++);) + ; + --d; + if (flag && *s) + s++; + } + else + /* expand_variable has already printed an error message. */ + return NULL; + } + *d = 0; + s = mybuf; + d = buf; + /* If you don't want ~username ~/ to be expanded simply remove + * This entire if statement including the else portion + */ + if (*s++ == '~') + { + char *t; + char *p=s; + if (*s=='/' || *s==0) + t = get_homedir (); + else + { + struct passwd *ps; + for (; *p!='/' && *p; p++) + ; + *p = 0; + ps = getpwnam (s); + if (ps == 0) + { + if (line != 0) + error (0, 0, "%s:%d: no such user %s", + file, line, s); + else + error (0, 0, "%s: no such user %s", file, s); + return NULL; + } + t = ps->pw_dir; + } + while ((*d++ = *t++)) + ; + --d; + if (*p == 0) + *p = '/'; /* always add / */ + s=p; + } + else + --s; + /* Kill up to here */ + while ((*d++ = *s++)) + ; + *d=0; + result = xmalloc (sizeof(char) * strlen(buf)+1); + strcpy (result, buf); + return result; +} + +static char * +expand_variable (name, file, line) + char *name; + char *file; + int line; +{ + if (strcmp (name, CVSROOT_ENV) == 0) + return CVSroot; + else if (strcmp (name, RCSBIN_ENV) == 0) + return Rcsbin; + else if (strcmp (name, EDITOR1_ENV) == 0) + return Editor; + else if (strcmp (name, EDITOR2_ENV) == 0) + return Editor; + else if (strcmp (name, EDITOR3_ENV) == 0) + return Editor; + else if (strcmp (name, "USER") == 0) + return getcaller (); + else if (isalpha (name[0])) + { + /* These names are reserved for future versions of CVS, + so that is why it is an error. */ + if (line != 0) + error (0, 0, "%s:%d: no such internal variable $%s", + file, line, name); + else + error (0, 0, "%s: no such internal variable $%s", + file, name); + return NULL; + } + else if (name[0] == '=') + { + Node *node; + /* Crazy syntax for a user variable. But we want + *something* that lets the user name a user variable + anything he wants, without interference from + (existing or future) internal variables. */ + node = findnode (variable_list, name + 1); + if (node == NULL) + { + if (line != 0) + error (0, 0, "%s:%d: no such user variable ${%s}", + file, line, name); + else + error (0, 0, "%s: no such user variable ${%s}", + file, name); + return NULL; + } + return node->data; + } + else + { + /* It is an unrecognized character. We return an error to + reserve these for future versions of CVS; it is plausible + that various crazy syntaxes might be invented for inserting + information about revisions, branches, etc. */ + if (line != 0) + error (0, 0, "%s:%d: unrecognized varaible syntax %s", + file, line, name); + else + error (0, 0, "%s: unrecognized varaible syntax %s", + file, name); + return NULL; + } +} diff --git a/contrib/cvs/src/fileattr.c b/contrib/cvs/src/fileattr.c new file mode 100644 index 0000000..827c69c --- /dev/null +++ b/contrib/cvs/src/fileattr.c @@ -0,0 +1,517 @@ +/* Implementation for file attribute munging features. + + 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. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ + +#include "cvs.h" +#include "getline.h" +#include "fileattr.h" +#include <assert.h> + +static void fileattr_read PROTO ((void)); +static int writeattr_proc PROTO ((Node *, void *)); + +/* Where to look for CVSREP_FILEATTR. */ +static char *fileattr_stored_repos; + +/* The in-memory attributes. */ +static List *attrlist; +static char *fileattr_default_attrs; +/* We have already tried to read attributes and failed in this directory + (for example, there is no CVSREP_FILEATTR file). */ +static int attr_read_attempted; + +/* Have the in-memory attributes been modified since we read them? */ +static int attrs_modified; + +/* Note that if noone calls fileattr_get, this is very cheap. No stat(), + no open(), no nothing. */ +void +fileattr_startdir (repos) + char *repos; +{ + assert (fileattr_stored_repos == NULL); + fileattr_stored_repos = xstrdup (repos); + assert (attrlist == NULL); + attr_read_attempted = 0; +} + +static void +fileattr_delproc (node) + Node *node; +{ + assert (node->data != NULL); + free (node->data); + node->data = NULL; +} + +/* Read all the attributes for the current directory into memory. */ +static void +fileattr_read () +{ + char *fname; + FILE *fp; + char *line = NULL; + size_t line_len = 0; + + /* If there are no attributes, don't waste time repeatedly looking + for the CVSREP_FILEATTR file. */ + if (attr_read_attempted) + return; + + /* If NULL was passed to fileattr_startdir, then it isn't kosher to look + at attributes. */ + assert (fileattr_stored_repos != NULL); + + fname = xmalloc (strlen (fileattr_stored_repos) + + 1 + + sizeof (CVSREP_FILEATTR) + + 1); + + strcpy (fname, fileattr_stored_repos); + strcat (fname, "/"); + strcat (fname, CVSREP_FILEATTR); + + attr_read_attempted = 1; + fp = fopen (fname, FOPEN_BINARY_READ); + if (fp == NULL) + { + if (!existence_error (errno)) + error (0, errno, "cannot read %s", fname); + free (fname); + return; + } + attrlist = getlist (); + while (1) { + int nread; + nread = getline (&line, &line_len, fp); + if (nread < 0) + break; + /* Remove trailing newline. */ + line[nread - 1] = '\0'; + if (line[0] == 'F') + { + char *p; + Node *newnode; + + p = strchr (line, '\t'); + *p++ = '\0'; + newnode = getnode (); + newnode->type = FILEATTR; + newnode->delproc = fileattr_delproc; + newnode->key = xstrdup (line + 1); + newnode->data = xstrdup (p); + addnode (attrlist, newnode); + } + else if (line[0] == 'D') + { + char *p; + /* Currently nothing to skip here, but for future expansion, + ignore anything located here. */ + p = strchr (line, '\t'); + ++p; + fileattr_default_attrs = xstrdup (p); + } + /* else just ignore the line, for future expansion. */ + } + if (ferror (fp)) + error (0, errno, "cannot read %s", fname); + if (line != NULL) + free (line); + if (fclose (fp) < 0) + error (0, errno, "cannot close %s", fname); + attrs_modified = 0; + free (fname); +} + +char * +fileattr_get (filename, attrname) + char *filename; + char *attrname; +{ + Node *node; + size_t attrname_len = strlen (attrname); + char *p; + + if (attrlist == NULL) + fileattr_read (); + if (attrlist == NULL) + /* Either nothing has any attributes, or fileattr_read already printed + an error message. */ + return NULL; + + if (filename == NULL) + p = fileattr_default_attrs; + else + { + node = findnode (attrlist, filename); + if (node == NULL) + /* A file not mentioned has no attributes. */ + return NULL; + p = node->data; + } + while (p) + { + if (strncmp (attrname, p, attrname_len) == 0 + && p[attrname_len] == '=') + { + /* Found it. */ + return p + attrname_len + 1; + } + p = strchr (p, ';'); + if (p == NULL) + break; + ++p; + } + /* The file doesn't have this attribute. */ + return NULL; +} + +char * +fileattr_get0 (filename, attrname) + char *filename; + char *attrname; +{ + char *cp; + char *cpend; + char *retval; + + cp = fileattr_get (filename, attrname); + if (cp == NULL) + return NULL; + cpend = strchr (cp, ';'); + if (cpend == NULL) + cpend = cp + strlen (cp); + retval = xmalloc (cpend - cp + 1); + strncpy (retval, cp, cpend - cp); + retval[cpend - cp] = '\0'; + return retval; +} + +char * +fileattr_modify (list, attrname, attrval, namevalsep, entsep) + char *list; + char *attrname; + char *attrval; + int namevalsep; + int entsep; +{ + char *retval; + char *rp; + size_t attrname_len = strlen (attrname); + + /* Portion of list before the attribute to be replaced. */ + char *pre; + char *preend; + /* Portion of list after the attribute to be replaced. */ + char *post; + + char *p; + char *p2; + + p = list; + pre = list; + preend = NULL; + /* post is NULL unless set otherwise. */ + post = NULL; + p2 = NULL; + if (list != NULL) + { + while (1) { + p2 = strchr (p, entsep); + if (p2 == NULL) + { + p2 = p + strlen (p); + if (preend == NULL) + preend = p2; + } + else + ++p2; + if (strncmp (attrname, p, attrname_len) == 0 + && p[attrname_len] == namevalsep) + { + /* Found it. */ + preend = p; + if (preend > list) + /* Don't include the preceding entsep. */ + --preend; + + post = p2; + } + if (p2[0] == '\0') + break; + p = p2; + } + } + if (post == NULL) + post = p2; + + if (preend == pre && attrval == NULL && post == p2) + return NULL; + + retval = xmalloc ((preend - pre) + + 1 + + (attrval == NULL ? 0 : (attrname_len + 1 + + strlen (attrval))) + + 1 + + (p2 - post) + + 1); + if (preend != pre) + { + strncpy (retval, pre, preend - pre); + rp = retval + (preend - pre); + if (attrval != NULL) + *rp++ = entsep; + *rp = '\0'; + } + else + retval[0] = '\0'; + if (attrval != NULL) + { + strcat (retval, attrname); + rp = retval + strlen (retval); + *rp++ = namevalsep; + strcpy (rp, attrval); + } + if (post != p2) + { + rp = retval + strlen (retval); + if (preend != pre || attrval != NULL) + *rp++ = entsep; + strncpy (rp, post, p2 - post); + rp += p2 - post; + *rp = '\0'; + } + return retval; +} + +void +fileattr_set (filename, attrname, attrval) + char *filename; + char *attrname; + char *attrval; +{ + Node *node; + char *p; + + attrs_modified = 1; + + if (filename == NULL) + { + p = fileattr_modify (fileattr_default_attrs, attrname, attrval, + '=', ';'); + if (fileattr_default_attrs != NULL) + free (fileattr_default_attrs); + fileattr_default_attrs = p; + return; + } + if (attrlist == NULL) + fileattr_read (); + if (attrlist == NULL) + { + /* Not sure this is a graceful way to handle things + in the case where fileattr_read was unable to read the file. */ + /* No attributes existed previously. */ + attrlist = getlist (); + } + + node = findnode (attrlist, filename); + if (node == NULL) + { + if (attrval == NULL) + /* Attempt to remove an attribute which wasn't there. */ + return; + + /* First attribute for this file. */ + node = getnode (); + node->type = FILEATTR; + node->delproc = fileattr_delproc; + node->key = xstrdup (filename); + node->data = xmalloc (strlen (attrname) + 1 + strlen (attrval) + 1); + strcpy (node->data, attrname); + strcat (node->data, "="); + strcat (node->data, attrval); + addnode (attrlist, node); + } + + p = fileattr_modify (node->data, attrname, attrval, '=', ';'); + free (node->data); + node->data = NULL; + if (p == NULL) + delnode (node); + else + node->data = p; +} + +void +fileattr_newfile (filename) + char *filename; +{ + Node *node; + + if (attrlist == NULL) + fileattr_read (); + + if (fileattr_default_attrs == NULL) + return; + + if (attrlist == NULL) + { + /* Not sure this is a graceful way to handle things + in the case where fileattr_read was unable to read the file. */ + /* No attributes existed previously. */ + attrlist = getlist (); + } + + node = getnode (); + node->type = FILEATTR; + node->delproc = fileattr_delproc; + node->key = xstrdup (filename); + node->data = xstrdup (fileattr_default_attrs); + addnode (attrlist, node); + attrs_modified = 1; +} + +static int +writeattr_proc (node, data) + Node *node; + void *data; +{ + FILE *fp = (FILE *)data; + fputs ("F", fp); + fputs (node->key, fp); + fputs ("\t", fp); + fputs (node->data, fp); + fputs ("\012", fp); + return 0; +} + +void +fileattr_write () +{ + FILE *fp; + char *fname; + mode_t omask; + + if (!attrs_modified) + return; + + if (noexec) + return; + + /* If NULL was passed to fileattr_startdir, then it isn't kosher to set + attributes. */ + assert (fileattr_stored_repos != NULL); + + fname = xmalloc (strlen (fileattr_stored_repos) + + 1 + + sizeof (CVSREP_FILEATTR) + + 1); + + strcpy (fname, fileattr_stored_repos); + strcat (fname, "/"); + strcat (fname, CVSREP_FILEATTR); + + if (list_isempty (attrlist) && fileattr_default_attrs == NULL) + { + /* There are no attributes. */ + if (unlink_file (fname) < 0) + { + if (!existence_error (errno)) + { + error (0, errno, "cannot remove %s", fname); + } + } + + /* Now remove CVSREP directory, if empty. The main reason we bother + is that CVS 1.6 and earlier will choke if a CVSREP directory + exists, so provide the user a graceful way to remove it. */ + strcpy (fname, fileattr_stored_repos); + strcat (fname, "/"); + strcat (fname, CVSREP); + if (rmdir (fname) < 0) + { + if (errno != ENOTEMPTY + + /* Don't know why we would be here if there is no CVSREP + directory, but it seemed to be happening anyway, so + check for it. */ + && !existence_error (errno)) + error (0, errno, "cannot remove %s", fname); + } + + free (fname); + return; + } + + omask = umask (cvsumask); + fp = fopen (fname, FOPEN_BINARY_WRITE); + if (fp == NULL) + { + if (existence_error (errno)) + { + /* Maybe the CVSREP directory doesn't exist. Try creating it. */ + char *repname; + + repname = xmalloc (strlen (fileattr_stored_repos) + + 1 + + sizeof (CVSREP) + + 1); + strcpy (repname, fileattr_stored_repos); + strcat (repname, "/"); + strcat (repname, CVSREP); + + if (CVS_MKDIR (repname, 0777) < 0 && errno != EEXIST) + { + error (0, errno, "cannot make directory %s", repname); + (void) umask (omask); + free (repname); + return; + } + free (repname); + + fp = fopen (fname, FOPEN_BINARY_WRITE); + } + if (fp == NULL) + { + error (0, errno, "cannot write %s", fname); + (void) umask (omask); + return; + } + } + (void) umask (omask); + walklist (attrlist, writeattr_proc, fp); + if (fileattr_default_attrs != NULL) + { + fputs ("D\t", fp); + fputs (fileattr_default_attrs, fp); + fputs ("\012", fp); + } + if (fclose (fp) < 0) + error (0, errno, "cannot close %s", fname); + attrs_modified = 0; + free (fname); +} + +void +fileattr_free () +{ + dellist (&attrlist); + if (fileattr_stored_repos != NULL) + free (fileattr_stored_repos); + fileattr_stored_repos = NULL; + if (fileattr_default_attrs != NULL) + free (fileattr_default_attrs); + fileattr_default_attrs = NULL; +} diff --git a/contrib/cvs/src/fileattr.h b/contrib/cvs/src/fileattr.h new file mode 100644 index 0000000..c24c035 --- /dev/null +++ b/contrib/cvs/src/fileattr.h @@ -0,0 +1,125 @@ +/* Declarations for file attribute munging features. + + 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. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ + +#ifndef FILEATTR_H + +/* File containing per-file attributes. Format is a series of entries: + + ENT-TYPE FILENAME <tab> ATTRNAME = ATTRVAL + {; ATTRNAME = ATTRVAL} <linefeed> + + ENT-TYPE is 'F' for a file, in which case the entry specifies the + attributes for that file. + + ENT-TYPE is 'D', and FILENAME empty, to specify default attributes + to be used for newly added files. + + There is currently no way of quoting tabs or linefeeds in the + filename, '=' in ATTRNAME, ';' in ATTRVAL, etc. I'm not sure + whether I think we need one. Note: the current implementation also + doesn't handle '\0' in any of the fields. + + By convention, ATTRNAME starting with '_' is for an attribute given + special meaning by CVS; other ATTRNAMEs are for user-defined attributes + (or will be, once we add commands to manipulate user-defined attributes). + + Builtin attributes: + + _watched: Present means the file is watched and should be checked out + read-only. + + _watchers: Users with watches for this file. Value is + WATCHER > TYPE { , WATCHER > TYPE } + where WATCHER is a username, and TYPE is edit,unedit,commit separated by + + (or nothing if none; there is no "none" or "all" keyword). + + _editors: Users editing this file. Value is + EDITOR > VAL { , EDITOR > VAL } + where EDITOR is a username, and VAL is TIME+HOSTNAME+PATHNAME, where + TIME is when the "cvs edit" command happened, + and HOSTNAME and PATHNAME are for the working directory. */ + +#define CVSREP_FILEATTR "CVS/fileattr" + +/* Prepare for a new directory with repository REPOS. If REPOS is NULL, + then prepare for a "non-directory"; the caller can call fileattr_write + and fileattr_free, but must not call fileattr_get or fileattr_set. */ +extern void fileattr_startdir PROTO ((char *repos)); + +/* Get the attribute ATTRNAME for file FILENAME. The return value + points into memory managed by the fileattr_* routines, should not + be altered by the caller, and is only good until the next call to + fileattr_clear or fileattr_set. It points to the value, terminated + by '\0' or ';'. Return NULL if said file lacks said attribute. + If FILENAME is NULL, return default attributes (attributes for + files created in the future). */ +extern char *fileattr_get PROTO ((char *filename, char *attrname)); + +/* Like fileattr_get, but return a pointer to a newly malloc'd string + terminated by '\0' (or NULL if said file lacks said attribute). */ +extern char *fileattr_get0 PROTO ((char *filename, char *attrname)); + +/* This is just a string manipulation function; it does not manipulate + file attributes as such. + + LIST is in the format + + ATTRNAME NAMEVALSEP ATTRVAL {ENTSEP ATTRNAME NAMEVALSEP ATTRVAL} + + And we want to put in an attribute with name NAME and value VAL, + replacing the already-present attribute with name NAME if there is + one. Or if VAL is NULL remove attribute NAME. Return a new + malloc'd list; don't muck with the one passed in. If we are removing + the last attribute return NULL. LIST can be NULL to mean that we + started out without any attributes. + + Examples: + + fileattr_modify ("abc=def", "xxx", "val", '=', ';')) => "abc=def;xxx=val" + fileattr_modify ("abc=def", "abc", "val", '=', ';')) => "abc=val" + fileattr_modify ("abc=v1;def=v2", "abc", "val", '=', ';')) + => "abc=val;def=v2" + fileattr_modify ("abc=v1;def=v2", "def", "val", '=', ';')) + => "abc=v1;def=val" + fileattr_modify ("abc=v1;def=v2", "xxx", "val")) + => "abc=v1;def=v2;xxx=val" + fileattr_modify ("abc=v1;def=v2;ghi=v3", "def", "val", '=', ';')) + => "abc=v1;def=val;ghi=v3" +*/ + +extern char *fileattr_modify PROTO ((char *list, char *attrname, + char *attrval, int namevalsep, + int entsep)); + +/* Set attribute ATTRNAME for file FILENAME to ATTRVAL. If ATTRVAL is NULL, + the attribute is removed. Changes are not written to disk until the + next call to fileattr_write. If FILENAME is NULL, set attributes for + files created in the future. If ATTRVAL is NULL, remove that attribute. */ +extern void fileattr_set PROTO ((char *filename, char *attrname, + char *attrval)); + +/* Set the attributes for file FILENAME in whatever manner is appropriate + for a newly created file. */ +extern void fileattr_newfile PROTO ((char *filename)); + +/* Write out all modified attributes. */ +extern void fileattr_write PROTO ((void)); + +/* Free all memory allocated by fileattr_*. */ +extern void fileattr_free PROTO ((void)); + +#define FILEATTR_H 1 +#endif /* fileattr.h */ diff --git a/contrib/cvs/src/filesubr.c b/contrib/cvs/src/filesubr.c new file mode 100644 index 0000000..086da83 --- /dev/null +++ b/contrib/cvs/src/filesubr.c @@ -0,0 +1,662 @@ +/* filesubr.c --- subroutines for dealing with files + Jim Blandy <jimb@cyclic.com> + + This file is part of GNU CVS. + + GNU CVS 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. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. */ + +/* These functions were moved out of subr.c because they need different + definitions under operating systems (like, say, Windows NT) with different + file system semantics. */ + +#include "cvs.h" + +/* + * I don't know of a convenient way to test this at configure time, or else + * I'd certainly do it there. + */ +#if defined(NeXT) +#define LOSING_TMPNAM_FUNCTION +#endif + +static int deep_remove_dir PROTO((const char *path)); + +/* + * Copies "from" to "to". + */ +void +copy_file (from, to) + const char *from; + const char *to; +{ + struct stat sb; + struct utimbuf t; + int fdin, fdout; + + if (trace) +#ifdef SERVER_SUPPORT + (void) fprintf (stderr, "%c-> copy(%s,%s)\n", + (server_active) ? 'S' : ' ', from, to); +#else + (void) fprintf (stderr, "-> copy(%s,%s)\n", from, to); +#endif + 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) + { + char buf[BUFSIZ]; + int n; + + for (;;) + { + n = read (fdin, buf, sizeof(buf)); + if (n == -1) + { +#ifdef EINTR + 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); + } + } + +#ifdef HAVE_FSYNC + 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); + + /* now, set the times for the copied file to match those of the original */ + memset ((char *) &t, 0, sizeof (t)); + t.actime = sb.st_atime; + t.modtime = sb.st_mtime; + (void) utime (to, &t); +} + +/* FIXME-krp: these functions would benefit from caching the char * & + stat buf. */ + +/* + * Returns non-zero if the argument file is a directory, or is a symbolic + * link which points to a directory. + */ +int +isdir (file) + const char *file; +{ + struct stat sb; + + if (stat (file, &sb) < 0) + return (0); + return (S_ISDIR (sb.st_mode)); +} + +/* + * Returns non-zero if the argument file is a symbolic link. + */ +int +islink (file) + const char *file; +{ +#ifdef S_ISLNK + struct stat sb; + + if (lstat (file, &sb) < 0) + return (0); + return (S_ISLNK (sb.st_mode)); +#else + return (0); +#endif +} + +/* + * Returns non-zero if the argument file exists. + */ +int +isfile (file) + const char *file; +{ + return isaccessible(file, F_OK); +} + +/* + * Returns non-zero if the argument file is readable. + */ +int +isreadable (file) + const char *file; +{ + return isaccessible(file, R_OK); +} + +/* + * Returns non-zero if the argument file is writable. + */ +int +iswritable (file) + const char *file; +{ + return isaccessible(file, W_OK); +} + +/* + * Returns non-zero if the argument file is accessable according to + * mode. If compiled with SETXID_SUPPORT also works if cvs has setxid + * bits set. + */ +int +isaccessible (file, mode) + const char *file; + const int mode; +{ +#ifdef SETXID_SUPPORT + struct stat sb; + int umask = 0; + int gmask = 0; + int omask = 0; + int uid; + + if (stat(file, &sb) == -1) + return 0; + if (mode == F_OK) + return 1; + + uid = geteuid(); + if (uid == 0) /* superuser */ + { + if (mode & X_OK) + return sb.st_mode & (S_IXUSR|S_IXGRP|S_IXOTH); + else + return 1; + } + + if (mode & R_OK) + { + umask |= S_IRUSR; + gmask |= S_IRGRP; + omask |= S_IROTH; + } + if (mode & W_OK) + { + umask |= S_IWUSR; + gmask |= S_IWGRP; + omask |= S_IWOTH; + } + if (mode & X_OK) + { + umask |= S_IXUSR; + gmask |= S_IXGRP; + omask |= S_IXOTH; + } + + if (sb.st_uid == uid) + return (sb.st_mode & umask) == umask; + else if (sb.st_gid == getegid()) + return (sb.st_mode & gmask) == gmask; + else + return (sb.st_mode & omask) == omask; +#else + return access(file, mode) == 0; +#endif +} + +/* + * Open a file and die if it fails + */ +FILE * +open_file (name, mode) + const char *name; + const char *mode; +{ + FILE *fp; + + if ((fp = fopen (name, mode)) == NULL) + error (1, errno, "cannot open %s", name); + return (fp); +} + +/* + * Make a directory and die if it fails + */ +void +make_directory (name) + const char *name; +{ + struct stat sb; + + if (stat (name, &sb) == 0 && (!S_ISDIR (sb.st_mode))) + error (0, 0, "%s already exists but is not a directory", name); + if (!noexec && mkdir (name, 0777) < 0) + error (1, errno, "cannot make directory %s", name); +} + +/* + * Make a path to the argument directory, printing a message if something + * goes wrong. + */ +void +make_directories (name) + const char *name; +{ + char *cp; + + if (noexec) + return; + + if (mkdir (name, 0777) == 0 || errno == EEXIST) + return; + if (! existence_error (errno)) + { + error (0, errno, "cannot make path to %s", name); + return; + } + if ((cp = strrchr (name, '/')) == NULL) + return; + *cp = '\0'; + make_directories (name); + *cp++ = '/'; + if (*cp == '\0') + return; + (void) mkdir (name, 0777); +} + +/* + * Change the mode of a file, either adding write permissions, or removing + * all write permissions. Either change honors the current umask setting. + */ +void +xchmod (fname, writable) + char *fname; + int writable; +{ + struct stat sb; + mode_t mode, oumask; + + if (stat (fname, &sb) < 0) + { + if (!noexec) + error (0, errno, "cannot stat %s", fname); + return; + } + oumask = umask (0); + (void) umask (oumask); + if (writable) + { + mode = sb.st_mode | (~oumask + & (((sb.st_mode & S_IRUSR) ? S_IWUSR : 0) + | ((sb.st_mode & S_IRGRP) ? S_IWGRP : 0) + | ((sb.st_mode & S_IROTH) ? S_IWOTH : 0))); + } + else + { + mode = sb.st_mode & ~(S_IWRITE | S_IWGRP | S_IWOTH) & ~oumask; + } + + if (trace) +#ifdef SERVER_SUPPORT + (void) fprintf (stderr, "%c-> chmod(%s,%o)\n", + (server_active) ? 'S' : ' ', fname, + (unsigned int) mode); +#else + (void) fprintf (stderr, "-> chmod(%s,%o)\n", fname, + (unsigned int) mode); +#endif + if (noexec) + return; + + if (chmod (fname, mode) < 0) + error (0, errno, "cannot change mode of file %s", fname); +} + +/* + * Rename a file and die if it fails + */ +void +rename_file (from, to) + const char *from; + const char *to; +{ + if (trace) +#ifdef SERVER_SUPPORT + (void) fprintf (stderr, "%c-> rename(%s,%s)\n", + (server_active) ? 'S' : ' ', from, to); +#else + (void) fprintf (stderr, "-> rename(%s,%s)\n", from, to); +#endif + if (noexec) + return; + + if (rename (from, to) < 0) + error (1, errno, "cannot rename file %s to %s", from, to); +} + +/* + * link a file, if possible. Warning: the Windows NT version of this + * function just copies the file, so only use this function in ways + * that can deal with either a link or a copy. + */ +int +link_file (from, to) + const char *from; + const char *to; +{ + if (trace) +#ifdef SERVER_SUPPORT + (void) fprintf (stderr, "%c-> link(%s,%s)\n", + (server_active) ? 'S' : ' ', from, to); +#else + (void) fprintf (stderr, "-> link(%s,%s)\n", from, to); +#endif + if (noexec) + return (0); + + return (link (from, to)); +} + +/* + * unlink a file, if possible. + */ +int +unlink_file (f) + const char *f; +{ + if (trace) +#ifdef SERVER_SUPPORT + (void) fprintf (stderr, "%c-> unlink(%s)\n", + (server_active) ? 'S' : ' ', f); +#else + (void) fprintf (stderr, "-> unlink(%s)\n", f); +#endif + if (noexec) + return (0); + + return (unlink (f)); +} + +/* + * Unlink a file or dir, if possible. If it is a directory do a deep + * removal of all of the files in the directory. Return -1 on error + * (in which case errno is set). + */ +int +unlink_file_dir (f) + const char *f; +{ + if (trace) +#ifdef SERVER_SUPPORT + (void) fprintf (stderr, "%c-> unlink_file_dir(%s)\n", + (server_active) ? 'S' : ' ', f); +#else + (void) fprintf (stderr, "-> unlink_file_dir(%s)\n", f); +#endif + if (noexec) + return (0); + + /* 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 + doesn't quite work--if someone creates a directory between the + call to isdir() 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 (unlink (f) != 0) + return -1; + } + /* We were able to remove the file from the disk */ + return 0; +} + +/* Remove a directory and everything it contains. Returns 0 for + * success, -1 for failure (in which case errno is set). + */ + +static int +deep_remove_dir (path) + const char *path; +{ + DIR *dirp; + struct dirent *dp; + char buf[PATH_MAX]; + + if (rmdir (path) != 0 && (errno == ENOTEMPTY || errno == EEXIST)) + { + if ((dirp = opendir (path)) == NULL) + /* If unable to open the directory return + * an error + */ + return -1; + + while ((dp = readdir (dirp)) != NULL) + { + if (strcmp (dp->d_name, ".") == 0 || + strcmp (dp->d_name, "..") == 0) + continue; + + sprintf (buf, "%s/%s", path, dp->d_name); + + /* See comment in unlink_file_dir explanation of why we use + isdir instead of just calling unlink and checking the + status. */ + if (isdir(buf)) + { + if (deep_remove_dir(buf)) + { + closedir(dirp); + return -1; + } + } + else + { + if (unlink (buf) != 0) + { + closedir(dirp); + return -1; + } + } + } + closedir (dirp); + return rmdir (path); + } + + /* Was able to remove the directory return 0 */ + return 0; +} + +/* Read NCHARS bytes from descriptor FD into BUF. + Return the number of characters successfully read. + The number returned is always NCHARS unless end-of-file or error. */ +static size_t +block_read (fd, buf, nchars) + int fd; + char *buf; + size_t nchars; +{ + char *bp = buf; + size_t nread; + + do + { + nread = read (fd, bp, nchars); + if (nread == (size_t)-1) + { +#ifdef EINTR + if (errno == EINTR) + continue; +#endif + return (size_t)-1; + } + + if (nread == 0) + break; + + bp += nread; + nchars -= nread; + } while (nchars != 0); + + return bp - buf; +} + + +/* + * Compare "file1" to "file2". Return non-zero if they don't compare exactly. + */ +int +xcmp (file1, file2) + const char *file1; + const char *file2; +{ + char *buf1, *buf2; + struct stat sb1, sb2; + int fd1, fd2; + int ret; + + 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. + But that won't happen in CVS, so we won't bother. */ + + if (sb1.st_size != sb2.st_size) + ret = 1; + else if (sb1.st_size == 0) + ret = 0; + else + { + /* FIXME: compute the optimal buffer size by computing the least + common multiple of the files st_blocks field */ + size_t buf_size = 8 * 1024; + size_t read1; + size_t read2; + + buf1 = xmalloc (buf_size); + buf2 = xmalloc (buf_size); + + do + { + read1 = block_read (fd1, buf1, buf_size); + if (read1 == (size_t)-1) + error (1, errno, "cannot read file %s for comparing", file1); + + read2 = block_read (fd2, buf2, buf_size); + if (read2 == (size_t)-1) + error (1, errno, "cannot read file %s for comparing", file2); + + /* assert (read1 == read2); */ + + ret = memcmp(buf1, buf2, read1); + } while (ret == 0 && read1 == buf_size); + + free (buf1); + free (buf2); + } + + (void) close (fd1); + (void) close (fd2); + return (ret); +} + +#ifdef LOSING_TMPNAM_FUNCTION +char *tmpnam(char *s) +{ + static char value[L_tmpnam+1]; + + if (s){ + strcpy(s,"/tmp/cvsXXXXXX"); + mktemp(s); + return s; + }else{ + strcpy(value,"/tmp/cvsXXXXXX"); + mktemp(s); + return value; + } +} +#endif + +/* Return non-zero iff FILENAME is absolute. + Trivial under Unix, but more complicated under other systems. */ +int +isabsolute (filename) + const char *filename; +{ + return filename[0] == '/'; +} + + +/* Return a pointer into PATH's last component. */ +char * +last_component (path) + char *path; +{ + char *last = strrchr (path, '/'); + + if (last) + return last + 1; + else + return path; +} + +/* Return the home directory. Returns a pointer to storage + managed by this function or its callees (currently getenv). */ +char * +get_homedir () +{ + return getenv ("HOME"); +} + +/* See cvs.h for description. On unix this does nothing, because the + shell expands the wildcards. */ +void +expand_wild (argc, argv, pargc, pargv) + int argc; + char **argv; + int *pargc; + char ***pargv; +{ + int i; + *pargc = argc; + *pargv = (char **) xmalloc (argc * sizeof (char *)); + for (i = 0; i < argc; ++i) + (*pargv)[i] = xstrdup (argv[i]); +} diff --git a/contrib/cvs/src/find_names.c b/contrib/cvs/src/find_names.c new file mode 100644 index 0000000..4885437 --- /dev/null +++ b/contrib/cvs/src/find_names.c @@ -0,0 +1,271 @@ +/* + * Copyright (c) 1992, Brian Berliner and Jeff Polk + * Copyright (c) 1989-1992, Brian Berliner + * + * You may distribute under the terms of the GNU General Public License as + * specified in the README file that comes with the CVS 1.4 kit. + * + * Find Names + * + * Finds all the pertinent file names, both from the administration and from the + * repository + * + * Find Dirs + * + * Finds all pertinent sub-directories of the checked out instantiation and the + * repository (and optionally the attic) + */ + +#include "cvs.h" + +static int find_dirs PROTO((char *dir, List * list, int checkadm)); +static int find_rcs PROTO((char *dir, List * list)); + +static List *filelist; + +/* + * add the key from entry on entries list to the files list + */ +static int add_entries_proc PROTO((Node *, void *)); +static int +add_entries_proc (node, closure) + Node *node; + void *closure; +{ + Node *fnode; + + fnode = getnode (); + fnode->type = FILES; + fnode->key = xstrdup (node->key); + if (addnode (filelist, fnode) != 0) + freenode (fnode); + 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; + int which; + int aflag; + List **optentries; +{ + List *entries; + List *files; + char dir[PATH_MAX]; + + /* make a list for the files */ + files = filelist = getlist (); + + /* look at entries (if necessary) */ + if (which & W_LOCAL) + { + /* parse the entries file (if it exists) */ + entries = Entries_Open (aflag); + if (entries != NULL) + { + /* walk the entries file adding elements to the files list */ + (void) walklist (entries, add_entries_proc, NULL); + + /* if our caller wanted the entries list, return it; else free it */ + if (optentries != NULL) + *optentries = entries; + else + Entries_Close (entries); + } + } + + if ((which & W_REPOS) && repository && !isreadable (CVSADM_ENTSTAT)) + { + /* search the repository */ + if (find_rcs (repository, files) != 0) + error (1, errno, "cannot open directory %s", repository); + + /* search the attic too */ + if (which & W_ATTIC) + { + (void) sprintf (dir, "%s/%s", repository, CVSATTIC); + (void) find_rcs (dir, files); + } + } + + /* sort the list into alphabetical order and return it */ + sortlist (files, fsortcmp); + return (files); +} + +/* + * create a list of directories to traverse from the current directory + */ +List * +Find_Directories (repository, which) + char *repository; + int which; +{ + List *dirlist; + + /* make a list for the directories */ + dirlist = getlist (); + + /* find the local ones */ + if (which & W_LOCAL) + { + /* look only for CVS controlled sub-directories */ + if (find_dirs (".", dirlist, 1) != 0) + error (1, errno, "cannot open current directory"); + } + + /* look for sub-dirs in the repository */ + if ((which & W_REPOS) && repository) + { + /* search the repository */ + if (find_dirs (repository, dirlist, 0) != 0) + error (1, errno, "cannot open directory %s", repository); + +#ifdef ATTIC_DIR_SUPPORT /* XXX - FIXME */ + /* search the attic too */ + if (which & W_ATTIC) + { + char dir[PATH_MAX]; + + (void) sprintf (dir, "%s/%s", repository, CVSATTIC); + (void) find_dirs (dir, dirlist, 0); + } +#endif + } + + /* sort the list into alphabetical order and return it */ + sortlist (dirlist, fsortcmp); + return (dirlist); +} + +/* + * Finds all the ,v files in the argument directory, and adds them to the + * files list. Returns 0 for success and non-zero if the argument directory + * cannot be opened. + */ +static int +find_rcs (dir, list) + char *dir; + List *list; +{ + Node *p; + struct dirent *dp; + DIR *dirp; + + /* set up to read the dir */ + if ((dirp = opendir (dir)) == NULL) + return (1); + + /* read the dir, grabbing the ,v files */ + while ((dp = readdir (dirp)) != NULL) + { + if (fnmatch (RCSPAT, dp->d_name, 0) == 0) + { + char *comma; + + comma = strrchr (dp->d_name, ','); /* strip the ,v */ + *comma = '\0'; + p = getnode (); + p->type = FILES; + p->key = xstrdup (dp->d_name); + if (addnode (list, p) != 0) + freenode (p); + } + } + (void) closedir (dirp); + return (0); +} + +/* + * Finds all the subdirectories of the argument dir and adds them to the + * specified list. Sub-directories without a CVS administration directory + * are optionally ignored Returns 0 for success or 1 on error. + */ +static int +find_dirs (dir, list, checkadm) + char *dir; + List *list; + int checkadm; +{ + Node *p; + char tmp[PATH_MAX]; + struct dirent *dp; + DIR *dirp; + + /* set up to read the dir */ + if ((dirp = opendir (dir)) == NULL) + return (1); + + /* read the dir, grabbing sub-dirs */ + while ((dp = readdir (dirp)) != NULL) + { + if (strcmp (dp->d_name, ".") == 0 || + strcmp (dp->d_name, "..") == 0 || + strcmp (dp->d_name, CVSATTIC) == 0 || + strcmp (dp->d_name, CVSLCK) == 0 || + strcmp (dp->d_name, CVSREP) == 0) + continue; + +#ifdef DT_DIR + if (dp->d_type != DT_DIR) + { + if (dp->d_type != DT_UNKNOWN && dp->d_type != DT_LNK) + continue; +#endif + /* don't bother stating ,v files */ + if (fnmatch (RCSPAT, dp->d_name, 0) == 0) + continue; + + sprintf (tmp, "%s/%s", dir, dp->d_name); + if (!isdir (tmp)) + continue; + +#ifdef DT_DIR + } +#endif + + /* check for administration directories (if needed) */ + if (checkadm) + { + /* blow off symbolic links to dirs in local dir */ +#ifdef DT_DIR + if (dp->d_type != DT_DIR) + { + /* we're either unknown or a symlink at this point */ + if (dp->d_type == DT_LNK) + continue; +#endif + if (islink (tmp)) + continue; +#ifdef DT_DIR + } +#endif + + /* check for new style */ + (void) sprintf (tmp, "%s/%s/%s", dir, dp->d_name, CVSADM); + if (!isdir (tmp)) + continue; + } + + /* put it in the list */ + p = getnode (); + p->type = DIRS; + p->key = xstrdup (dp->d_name); + if (addnode (list, p) != 0) + freenode (p); + } + (void) closedir (dirp); + return (0); +} diff --git a/contrib/cvs/src/hash.c b/contrib/cvs/src/hash.c new file mode 100644 index 0000000..2197db0 --- /dev/null +++ b/contrib/cvs/src/hash.c @@ -0,0 +1,442 @@ +/* + * Copyright (c) 1992, Brian Berliner and Jeff Polk + * + * You may distribute under the terms of the GNU General Public License as + * specified in the README file that comes with the CVS 1.4 kit. + * + * Polk's hash list manager. So cool. + */ + +#include "cvs.h" +#include <assert.h> + +/* global caches */ +static List *listcache = NULL; +static Node *nodecache = NULL; + +static void freenode_mem PROTO((Node * p)); + +/* hash function */ +static int +hashp (key) + const char *key; +{ + unsigned int h = 0; + unsigned int g; + + assert(key != NULL); + + while (*key != 0) + { + unsigned int c = *key++; + /* The FOLD_FN_CHAR is so that findnode_fn works. */ + h = (h << 4) + FOLD_FN_CHAR (c); + if ((g = h & 0xf0000000) != 0) + h = (h ^ (g >> 24)) ^ g; + } + + return (h % HASHSIZE); +} + +/* + * create a new list (or get an old one from the cache) + */ +List * +getlist () +{ + int i; + List *list; + Node *node; + + if (listcache != NULL) + { + /* get a list from the cache and clear it */ + list = listcache; + listcache = listcache->next; + list->next = (List *) NULL; + for (i = 0; i < HASHSIZE; i++) + list->hasharray[i] = (Node *) NULL; + } + else + { + /* make a new list from scratch */ + list = (List *) xmalloc (sizeof (List)); + memset ((char *) list, 0, sizeof (List)); + node = getnode (); + list->list = node; + node->type = HEADER; + node->next = node->prev = node; + } + return (list); +} + +/* + * free up a list + */ +void +dellist (listp) + List **listp; +{ + int i; + Node *p; + + if (*listp == (List *) NULL) + return; + + p = (*listp)->list; + + /* free each node in the list (except header) */ + while (p->next != p) + delnode (p->next); + + /* free any list-private data, without freeing the actual header */ + freenode_mem (p); + + /* free up the header nodes for hash lists (if any) */ + for (i = 0; i < HASHSIZE; i++) + { + if ((p = (*listp)->hasharray[i]) != (Node *) NULL) + { + /* put the nodes into the cache */ + p->type = UNKNOWN; + p->next = nodecache; + nodecache = p; + } + } + + /* put it on the cache */ + (*listp)->next = listcache; + listcache = *listp; + *listp = (List *) NULL; +} + +/* + * get a new list node + */ +Node * +getnode () +{ + Node *p; + + if (nodecache != (Node *) NULL) + { + /* get one from the cache */ + p = nodecache; + nodecache = p->next; + } + else + { + /* make a new one */ + p = (Node *) xmalloc (sizeof (Node)); + } + + /* always make it clean */ + memset ((char *) p, 0, sizeof (Node)); + p->type = UNKNOWN; + + return (p); +} + +/* + * remove a node from it's list (maybe hash list too) and free it + */ +void +delnode (p) + Node *p; +{ + if (p == (Node *) NULL) + return; + + /* take it out of the list */ + p->next->prev = p->prev; + p->prev->next = p->next; + + /* if it was hashed, remove it from there too */ + if (p->hashnext != (Node *) NULL) + { + p->hashnext->hashprev = p->hashprev; + p->hashprev->hashnext = p->hashnext; + } + + /* free up the storage */ + freenode (p); +} + +/* + * free up the storage associated with a node + */ +static void +freenode_mem (p) + Node *p; +{ + if (p->delproc != (void (*) ()) NULL) + p->delproc (p); /* call the specified delproc */ + else + { + if (p->data != NULL) /* otherwise free() it if necessary */ + free (p->data); + } + if (p->key != NULL) /* free the key if necessary */ + free (p->key); + + /* to be safe, re-initialize these */ + p->key = p->data = (char *) NULL; + p->delproc = (void (*) ()) NULL; +} + +/* + * free up the storage associated with a node and recycle it + */ +void +freenode (p) + Node *p; +{ + /* first free the memory */ + freenode_mem (p); + + /* then put it in the cache */ + p->type = UNKNOWN; + p->next = nodecache; + nodecache = p; +} + +/* + * insert item p at end of list "list" (maybe hash it too) if hashing and it + * already exists, return -1 and don't actually put it in the list + * + * return 0 on success + */ +int +addnode (list, p) + List *list; + Node *p; +{ + int hashval; + Node *q; + + if (p->key != NULL) /* hash it too? */ + { + hashval = hashp (p->key); + if (list->hasharray[hashval] == NULL) /* make a header for list? */ + { + q = getnode (); + q->type = HEADER; + list->hasharray[hashval] = q->hashnext = q->hashprev = q; + } + + /* put it into the hash list if it's not already there */ + for (q = list->hasharray[hashval]->hashnext; + q != list->hasharray[hashval]; q = q->hashnext) + { + if (strcmp (p->key, q->key) == 0) + return (-1); + } + q = list->hasharray[hashval]; + p->hashprev = q->hashprev; + p->hashnext = q; + p->hashprev->hashnext = p; + q->hashprev = p; + } + + /* put it into the regular list */ + p->prev = list->list->prev; + p->next = list->list; + list->list->prev->next = p; + list->list->prev = p; + + return (0); +} + +/* Look up an entry in hash list table and return a pointer to the + node. Return NULL if not found. Abort with a fatal error for + errors. */ +Node * +findnode (list, key) + List *list; + const char *key; +{ + Node *head, *p; + + /* This probably should be "assert (list != NULL)" (or if not we + should document the current behavior), but only if we check all + the callers to see if any are relying on this behavior. */ + if ((list == (List *) NULL)) + return ((Node *) NULL); + + assert (key != NULL); + + head = list->hasharray[hashp (key)]; + if (head == (Node *) NULL) + /* Not found. */ + return ((Node *) NULL); + + for (p = head->hashnext; p != head; p = p->hashnext) + if (strcmp (p->key, key) == 0) + return (p); + return ((Node *) NULL); +} + +/* + * Like findnode, but for a filename. + */ +Node * +findnode_fn (list, key) + List *list; + const char *key; +{ + Node *head, *p; + + /* This probably should be "assert (list != NULL)" (or if not we + should document the current behavior), but only if we check all + the callers to see if any are relying on this behavior. */ + if (list == (List *) NULL) + return ((Node *) NULL); + + assert (key != NULL); + + head = list->hasharray[hashp (key)]; + if (head == (Node *) NULL) + return ((Node *) NULL); + + for (p = head->hashnext; p != head; p = p->hashnext) + if (fncmp (p->key, key) == 0) + return (p); + return ((Node *) NULL); +} + +/* + * walk a list with a specific proc + */ +int +walklist (list, proc, closure) + List *list; + int (*proc) PROTO ((Node *, void *)); + void *closure; +{ + Node *head, *p; + int err = 0; + + if (list == NULL) + return (0); + + head = list->list; + for (p = head->next; p != head; p = p->next) + err += proc (p, closure); + return (err); +} + +int +list_isempty (list) + List *list; +{ + return list == NULL || list->list->next == list->list; +} + +/* + * sort the elements of a list (in place) + */ +void +sortlist (list, comp) + List *list; + int (*comp) PROTO ((const Node *, const Node *)); +{ + Node *head, *remain, *p, *q; + + /* save the old first element of the list */ + head = list->list; + remain = head->next; + + /* make the header node into a null list of it's own */ + head->next = head->prev = head; + + /* while there are nodes remaining, do insert sort */ + while (remain != head) + { + /* take one from the list */ + p = remain; + remain = remain->next; + + /* traverse the sorted list looking for the place to insert it */ + for (q = head->next; q != head; q = q->next) + { + if (comp (p, q) < 0) + { + /* p comes before q */ + p->next = q; + p->prev = q->prev; + p->prev->next = p; + q->prev = p; + break; + } + } + if (q == head) + { + /* it belongs at the end of the list */ + p->next = head; + p->prev = head->prev; + p->prev->next = p; + head->prev = p; + } + } +} + +/* Debugging functions. Quite useful to call from within gdb. */ + +char * +nodetypestring (type) + Ntype type; +{ + switch (type) { + case UNKNOWN: return("UNKNOWN"); + case HEADER: return("HEADER"); + case ENTRIES: return("ENTRIES"); + case FILES: return("FILES"); + case LIST: return("LIST"); + case RCSNODE: return("RCSNODE"); + case RCSVERS: return("RCSVERS"); + case DIRS: return("DIRS"); + case UPDATE: return("UPDATE"); + case LOCK: return("LOCK"); + case NDBMNODE: return("NDBMNODE"); + case FILEATTR: return("FILEATTR"); + case VARIABLE: return("VARIABLE"); + } + + return("<trash>"); +} + +static int printnode PROTO ((Node *, void *)); +static int +printnode (node, closure) + Node *node; + void *closure; +{ + if (node == NULL) + { + (void) printf("NULL node.\n"); + return(0); + } + + (void) printf("Node at 0x%p: type = %s, key = 0x%p = \"%s\", data = 0x%p, next = 0x%p, prev = 0x%p\n", + node, nodetypestring(node->type), node->key, node->key, node->data, node->next, node->prev); + + return(0); +} + +void +printlist (list) + List *list; +{ + if (list == NULL) + { + (void) printf("NULL list.\n"); + return; + } + + (void) printf("List at 0x%p: list = 0x%p, HASHSIZE = %d, next = 0x%p\n", + list, list->list, HASHSIZE, list->next); + + (void) walklist(list, printnode, NULL); + + return; +} diff --git a/contrib/cvs/src/hash.h b/contrib/cvs/src/hash.h new file mode 100644 index 0000000..dd83665 --- /dev/null +++ b/contrib/cvs/src/hash.h @@ -0,0 +1,58 @@ +/* $CVSid: @(#)hash.h 1.23 94/10/07 $ */ + +/* + * Copyright (c) 1992, Brian Berliner and Jeff Polk + * + * You may distribute under the terms of the GNU General Public License as + * specified in the README file that comes with the CVS 1.4 kit. + */ + +/* + * The number of buckets for the hash table contained in each list. This + * should probably be prime. + */ +#define HASHSIZE 151 + +/* + * Types of nodes + */ +enum ntype +{ + UNKNOWN, HEADER, ENTRIES, FILES, LIST, RCSNODE, + RCSVERS, DIRS, UPDATE, LOCK, NDBMNODE, FILEATTR, + VARIABLE +}; +typedef enum ntype Ntype; + +struct node +{ + Ntype type; + struct node *next; + struct node *prev; + struct node *hashnext; + struct node *hashprev; + char *key; + char *data; + void (*delproc) (); +}; +typedef struct node Node; + +struct list +{ + Node *list; + Node *hasharray[HASHSIZE]; + struct list *next; +}; +typedef struct list List; + +List *getlist PROTO((void)); +Node *findnode PROTO((List * list, const char *key)); +Node *findnode_fn PROTO((List * list, const char *key)); +Node *getnode PROTO((void)); +int addnode PROTO((List * list, Node * p)); +int walklist PROTO((List * list, int (*)(Node *n, void *closure), void *closure)); +int list_isempty PROTO ((List *list)); +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 *))); diff --git a/contrib/cvs/src/history.c b/contrib/cvs/src/history.c new file mode 100644 index 0000000..81c71ff --- /dev/null +++ b/contrib/cvs/src/history.c @@ -0,0 +1,1484 @@ +/* + * + * You may distribute under the terms of the GNU General Public License + * as specified in the README file that comes with the CVS 1.0 kit. + * + * **************** History of Users and Module **************** + * + * LOGGING: Append record to "${CVSROOT}/CVSROOTADM/CVSROOTADM_HISTORY". + * + * On For each Tag, Add, Checkout, Commit, Update or Release command, + * one line of text is written to a History log. + * + * X date | user | CurDir | special | rev(s) | argument '\n' + * + * where: [The spaces in the example line above are not in the history file.] + * + * X is a single character showing the type of event: + * T "Tag" cmd. + * O "Checkout" cmd. + * F "Release" cmd. + * W "Update" cmd - No User file, Remove from Entries file. + * U "Update" cmd - File was checked out over User file. + * G "Update" cmd - File was merged successfully. + * C "Update" cmd - File was merged and shows overlaps. + * M "Commit" cmd - "Modified" file. + * A "Commit" cmd - "Added" file. + * R "Commit" cmd - "Removed" file. + * + * date is a fixed length 8-char hex representation of a Unix time_t. + * [Starting here, variable fields are delimited by '|' chars.] + * + * user is the username of the person who typed the command. + * + * CurDir The directory where the action occurred. This should be the + * absolute path of the directory which is at the same level as + * the "Repository" field (for W,U,G,C & M,A,R). + * + * Repository For record types [W,U,G,C,M,A,R] this field holds the + * repository read from the administrative data where the + * command was typed. + * T "A" --> New Tag, "D" --> Delete Tag + * Otherwise it is the Tag or Date to modify. + * O,F A "" (null field) + * + * rev(s) Revision number or tag. + * T The Tag to apply. + * O The Tag or Date, if specified, else "" (null field). + * F "" (null field) + * W The Tag or Date, if specified, else "" (null field). + * U The Revision checked out over the User file. + * G,C The Revision(s) involved in merge. + * M,A,R RCS Revision affected. + * + * argument The module (for [TOUF]) or file (for [WUGCMAR]) affected. + * + * + *** Report categories: "User" and "Since" modifiers apply to all reports. + * [For "sort" ordering see the "sort_order" routine.] + * + * Extract list of record types + * + * -e, -x [TOFWUGCMAR] + * + * Extracted records are simply printed, No analysis is performed. + * All "field" modifiers apply. -e chooses all types. + * + * Checked 'O'ut modules + * + * -o, -w + * Checked out modules. 'F' and 'O' records are examined and if + * the last record for a repository/file is an 'O', a line is + * printed. "-w" forces the "working dir" to be used in the + * comparison instead of the repository. + * + * Committed (Modified) files + * + * -c, -l, -w + * All 'M'odified, 'A'dded and 'R'emoved records are examined. + * "Field" modifiers apply. -l forces a sort by file within user + * and shows only the last modifier. -w works as in Checkout. + * + * Warning: Be careful with what you infer from the output of + * "cvs hi -c -l". It means the last time *you* + * changed the file, not the list of files for which + * you were the last changer!!! + * + * Module history for named modules. + * -m module, -l + * + * This is special. If one or more modules are specified, the + * module names are remembered and the files making up the + * modules are remembered. Only records matching exactly those + * files and repositories are shown. Sorting by "module", then + * filename, is implied. If -l ("last modified") is specified, + * then "update" records (types WUCG), tag and release records + * are ignored and the last (by date) "modified" record. + * + * TAG history + * + * -T All Tag records are displayed. + * + *** Modifiers. + * + * Since ... [All records contain a timestamp, so any report + * category can be limited by date.] + * + * -D date - The "date" is parsed into a Unix "time_t" and + * records with an earlier time stamp are ignored. + * -r rev/tag - A "rev" begins with a digit. A "tag" does not. If + * you use this option, every file is searched for the + * indicated rev/tag. + * -t tag - The "tag" is searched for in the history file and no + * record is displayed before the tag is found. An + * error is printed if the tag is never found. + * -b string - Records are printed only back to the last reference + * to the string in the "module", "file" or + * "repository" fields. + * + * Field Selections [Simple comparisons on existing fields. All field + * selections are repeatable.] + * + * -a - All users. + * -u user - If no user is given and '-a' is not given, only + * records for the user typing the command are shown. + * ==> If -a or -u is not specified, just use "self". + * + * -f filematch - Only records in which the "file" field contains the + * string "filematch" are considered. + * + * -p repository - Only records in which the "repository" string is a + * prefix of the "repos" field are considered. + * + * -m modulename - Only records which contain "modulename" in the + * "module" field are considered. + * + * + * EXAMPLES: ("cvs history", "cvs his" or "cvs hi") + * + *** Checked out files for username. (default self, e.g. "dgg") + * cvs hi [equivalent to: "cvs hi -o -u dgg"] + * cvs hi -u user [equivalent to: "cvs hi -o -u user"] + * cvs hi -o [equivalent to: "cvs hi -o -u dgg"] + * + *** Committed (modified) files from the beginning of the file. + * cvs hi -c [-u user] + * + *** Committed (modified) files since Midnight, January 1, 1990: + * cvs hi -c -D 'Jan 1 1990' [-u user] + * + *** Committed (modified) files since tag "TAG" was stored in the history file: + * cvs hi -c -t TAG [-u user] + * + *** Committed (modified) files since tag "TAG" was placed on the files: + * cvs hi -c -r TAG [-u user] + * + *** Who last committed file/repository X? + * cvs hi -c -l -[fp] X + * + *** Modified files since tag/date/file/repos? + * cvs hi -c {-r TAG | -D Date | -b string} + * + *** Tag history + * cvs hi -T + * + *** History of file/repository/module X. + * cvs hi -[fpn] X + * + *** History of user "user". + * cvs hi -e -u user + * + *** Dump (eXtract) specified record types + * cvs hi -x [TOFWUGCMAR] + * + * + * FUTURE: J[Join], I[Import] (Not currently implemented.) + * + */ + +#include "cvs.h" + +static struct hrec +{ + char *type; /* Type of record (In history record) */ + char *user; /* Username (In history record) */ + char *dir; /* "Compressed" Working dir (In history record) */ + char *repos; /* (Tag is special.) Repository (In history record) */ + char *rev; /* Revision affected (In history record) */ + char *file; /* Filename (In history record) */ + char *end; /* Ptr into repository to copy at end of workdir */ + char *mod; /* The module within which the file is contained */ + time_t date; /* Calculated from date stored in record */ + int idx; /* Index of record, for "stable" sort. */ +} *hrec_head; + + +static char *fill_hrec PROTO((char *line, struct hrec * hr)); +static int accept_hrec PROTO((struct hrec * hr, struct hrec * lr)); +static int select_hrec PROTO((struct hrec * hr)); +static int sort_order PROTO((const PTR l, const PTR r)); +static int within PROTO((char *find, char *string)); +static time_t date_and_time PROTO((char *date_str)); +static void expand_modules PROTO((void)); +static void read_hrecs PROTO((char *fname)); +static void report_hrecs PROTO((void)); +static void save_file PROTO((char *dir, char *name, char *module)); +static void save_module PROTO((char *module)); +static void save_user PROTO((char *name)); + +#define ALL_REC_TYPES "TOFWUCGMAR" +#define USER_INCREMENT 2 +#define FILE_INCREMENT 128 +#define MODULE_INCREMENT 5 +#define HREC_INCREMENT 128 + +static short report_count; + +static short extract; +static short v_checkout; +static short modified; +static short tag_report; +static short module_report; +static short working; +static short last_entry; +static short all_users; + +static short user_sort; +static short repos_sort; +static short file_sort; +static short module_sort; + +#ifdef HAVE_RCS5 +static short tz_local; +static time_t tz_seconds_east_of_GMT; +static char *tz_name = "+0000"; +#else +static char tz_name[] = "LT"; +#endif + +static time_t since_date; +static char since_rev[20]; /* Maxrev ~= 99.99.99.999 */ +static char since_tag[64]; +static struct hrec *last_since_tag; +static char backto[128]; +static struct hrec *last_backto; +static char rec_types[20]; + +static int hrec_count; +static int hrec_max; + +static char **user_list; /* Ptr to array of ptrs to user names */ +static int user_max; /* Number of elements allocated */ +static int user_count; /* Number of elements used */ + +static struct file_list_str +{ + char *l_file; + char *l_module; +} *file_list; /* Ptr to array file name structs */ +static int file_max; /* Number of elements allocated */ +static int file_count; /* Number of elements used */ + +static char **mod_list; /* Ptr to array of ptrs to module names */ +static int mod_max; /* Number of elements allocated */ +static int mod_count; /* Number of elements used */ + +static char *histfile; /* Ptr to the history file name */ + +static const char *const history_usg[] = +{ + "Usage: %s %s [-report] [-flags] [-options args] [files...]\n\n", + " Reports:\n", + " -T Produce report on all TAGs\n", + " -c Committed (Modified) files\n", + " -o Checked out modules\n", + " -m <module> Look for specified module (repeatable)\n", + " -x [TOFWUCGMAR] Extract by record type\n", + " Flags:\n", + " -a All users (Default is self)\n", + " -e Everything (same as -x, but all record types)\n", + " -l Last modified (committed or modified report)\n", + " -w Working directory must match\n", + " Options:\n", + " -D <date> Since date (Many formats)\n", + " -b <str> Back to record with str in module/file/repos field\n", + " -f <file> Specified file (same as command line) (repeatable)\n", + " -n <modulename> In module (repeatable)\n", + " -p <repos> In repository (repeatable)\n", + " -r <rev/tag> Since rev or tag (looks inside RCS files!)\n", + " -t <tag> Since tag record placed in history file (by anyone).\n", + " -u <user> For user name (repeatable)\n", + " -z <tz> Output for time zone <tz> (e.g. -z -0700)\n", + NULL}; + +/* Sort routine for qsort: + - If a user is selected at all, sort it first. User-within-file is useless. + - If a module was selected explicitly, sort next on module. + - Then sort by file. "File" is "repository/file" unless "working" is set, + then it is "workdir/file". (Revision order should always track date.) + - Always sort timestamp last. +*/ +static int +sort_order (l, r) + const PTR l; + const PTR r; +{ + int i; + const struct hrec *left = (const struct hrec *) l; + const struct hrec *right = (const struct hrec *) r; + + if (user_sort) /* If Sort by username, compare users */ + { + if ((i = strcmp (left->user, right->user)) != 0) + return (i); + } + if (module_sort) /* If sort by modules, compare module names */ + { + if (left->mod && right->mod) + if ((i = strcmp (left->mod, right->mod)) != 0) + return (i); + } + if (repos_sort) /* If sort by repository, compare them. */ + { + if ((i = strcmp (left->repos, right->repos)) != 0) + return (i); + } + if (file_sort) /* If sort by filename, compare files, NOT dirs. */ + { + if ((i = strcmp (left->file, right->file)) != 0) + return (i); + + if (working) + { + if ((i = strcmp (left->dir, right->dir)) != 0) + return (i); + + if ((i = strcmp (left->end, right->end)) != 0) + return (i); + } + } + + /* + * By default, sort by date, time + * XXX: This fails after 2030 when date slides into sign bit + */ + if ((i = ((long) (left->date) - (long) (right->date))) != 0) + return (i); + + /* For matching dates, keep the sort stable by using record index */ + return (left->idx - right->idx); +} + +static time_t +date_and_time (date_str) + char *date_str; +{ + time_t t; + + t = get_date (date_str, (struct timeb *) NULL); + if (t == (time_t) - 1) + error (1, 0, "Can't parse date/time: %s", date_str); + return (t); +} + +int +history (argc, argv) + int argc; + char **argv; +{ + int i, c; + char fname[PATH_MAX]; + + if (argc == -1) + usage (history_usg); + + optind = 1; + while ((c = getopt (argc, argv, "Tacelow?D:b:f:m:n:p:r:t:u:x:X:z:")) != -1) + { + switch (c) + { + case 'T': /* Tag list */ + report_count++; + tag_report++; + break; + case 'a': /* For all usernames */ + all_users++; + break; + case 'c': + report_count++; + modified = 1; + break; + case 'e': + report_count++; + extract++; + (void) strcpy (rec_types, ALL_REC_TYPES); + break; + case 'l': /* Find Last file record */ + last_entry = 1; + break; + case 'o': + report_count++; + v_checkout = 1; + break; + case 'w': /* Match Working Dir (CurDir) fields */ + working = 1; + break; + case 'X': /* Undocumented debugging flag */ + histfile = optarg; + break; + case 'D': /* Since specified date */ + if (*since_rev || *since_tag || *backto) + { + error (0, 0, "date overriding rev/tag/backto"); + *since_rev = *since_tag = *backto = '\0'; + } + since_date = date_and_time (optarg); + break; + case 'b': /* Since specified file/Repos */ + if (since_date || *since_rev || *since_tag) + { + error (0, 0, "backto overriding date/rev/tag"); + *since_rev = *since_tag = '\0'; + since_date = 0; + } + if (strlen (optarg) >= sizeof (backto)) + { + error (0, 0, "backto truncated to %d bytes", + sizeof (backto) - 1); + optarg[sizeof (backto) - 1] = '\0'; + } + (void) strcpy (backto, optarg); + break; + case 'f': /* For specified file */ + save_file ("", optarg, (char *) NULL); + break; + case 'm': /* Full module report */ + report_count++; + module_report++; + case 'n': /* Look for specified module */ + save_module (optarg); + break; + case 'p': /* For specified directory */ + save_file (optarg, "", (char *) NULL); + break; + case 'r': /* Since specified Tag/Rev */ + if (since_date || *since_tag || *backto) + { + error (0, 0, "rev overriding date/tag/backto"); + *since_tag = *backto = '\0'; + since_date = 0; + } + (void) strcpy (since_rev, optarg); + break; + case 't': /* Since specified Tag/Rev */ + if (since_date || *since_rev || *backto) + { + error (0, 0, "tag overriding date/marker/file/repos"); + *since_rev = *backto = '\0'; + since_date = 0; + } + (void) strcpy (since_tag, optarg); /* tag */ + break; + case 'u': /* For specified username */ + save_user (optarg); + break; + case 'x': + report_count++; + extract++; + { + char *cp; + + for (cp = optarg; *cp; cp++) + if (!strchr (ALL_REC_TYPES, *cp)) + error (1, 0, "%c is not a valid report type", *cp); + } + (void) strcpy (rec_types, optarg); + break; + case 'z': +#ifndef HAVE_RCS5 + error (0, 0, "-z not supported with RCS 4"); +#else + tz_local = + (optarg[0] == 'l' || optarg[0] == 'L') + && (optarg[1] == 't' || optarg[1] == 'T') + && !optarg[2]; + if (tz_local) + tz_name = optarg; + else + { + /* + * Convert a known time with the given timezone to time_t. + * Use the epoch + 23 hours, so timezones east of GMT work. + */ + static char f[] = "1/1/1970 23:00 %s"; + char *buf = xmalloc (sizeof (f) - 2 + strlen (optarg)); + time_t t; + sprintf (buf, f, optarg); + t = get_date (buf, (struct timeb *) NULL); + free (buf); + if (t == (time_t) -1) + error (0, 0, "%s is not a known time zone", optarg); + else + { + /* + * Convert to seconds east of GMT, removing the + * 23-hour offset mentioned above. + */ + tz_seconds_east_of_GMT = (time_t)23 * 60 * 60 - t; + tz_name = optarg; + } + } +#endif + break; + case '?': + default: + usage (history_usg); + break; + } + } + c = optind; /* Save the handled option count */ + + /* ================ Now analyze the arguments a bit */ + if (!report_count) + v_checkout++; + else if (report_count > 1) + error (1, 0, "Only one report type allowed from: \"-Tcomx\"."); + +#ifdef CLIENT_SUPPORT + if (client_active) + { + struct file_list_str *f1; + char **mod; + + /* We're the client side. Fire up the remote server. */ + start_server (); + + ign_setup (); + + if (tag_report) + send_arg("-T"); + if (all_users) + send_arg("-a"); + if (modified) + send_arg("-c"); + if (last_entry) + send_arg("-l"); + if (v_checkout) + send_arg("-o"); + if (working) + send_arg("-w"); + if (histfile) + send_arg("-X"); + if (since_date) + option_with_arg ("-D", asctime (gmtime (&since_date))); + if (backto[0] != '\0') + option_with_arg ("-b", backto); + for (f1 = file_list; f1 < &file_list[file_count]; ++f1) + { + if (f1->l_file[0] == '*') + option_with_arg ("-p", f1->l_file + 1); + else + option_with_arg ("-f", f1->l_file); + } + if (module_report) + send_arg("-m"); + for (mod = mod_list; mod < &mod_list[mod_count]; ++mod) + option_with_arg ("-n", *mod); + if (since_rev != NULL) + option_with_arg ("-r", since_rev); + if (since_tag != NULL) + option_with_arg ("-t", since_tag); + for (mod = user_list; mod < &user_list[user_count]; ++mod) + option_with_arg ("-u", *mod); + if (extract) + option_with_arg ("-x", rec_types); + option_with_arg ("-z", tz_name); + + send_to_server ("history\012", 0); + return get_responses_and_close (); + } +#endif + + if (all_users) + save_user (""); + + if (mod_list) + expand_modules (); + + if (tag_report) + { + if (!strchr (rec_types, 'T')) + (void) strcat (rec_types, "T"); + } + else if (extract) + { + if (user_list) + user_sort++; + } + else if (modified) + { + (void) strcpy (rec_types, "MAR"); + /* + * If the user has not specified a date oriented flag ("Since"), sort + * by Repository/file before date. Default is "just" date. + */ + if (!since_date && !*since_rev && !*since_tag && !*backto) + { + repos_sort++; + file_sort++; + /* + * If we are not looking for last_modified and the user specified + * one or more users to look at, sort by user before filename. + */ + if (!last_entry && user_list) + user_sort++; + } + } + else if (module_report) + { + (void) strcpy (rec_types, last_entry ? "OMAR" : ALL_REC_TYPES); + module_sort++; + repos_sort++; + file_sort++; + working = 0; /* User's workdir doesn't count here */ + } + else + /* Must be "checkout" or default */ + { + (void) strcpy (rec_types, "OF"); + /* See comments in "modified" above */ + if (!last_entry && user_list) + user_sort++; + if (!since_date && !*since_rev && !*since_tag && !*backto) + file_sort++; + } + + /* If no users were specified, use self (-a saves a universal ("") user) */ + if (!user_list) + save_user (getcaller ()); + + /* If we're looking back to a Tag value, must consider "Tag" records */ + if (*since_tag && !strchr (rec_types, 'T')) + (void) strcat (rec_types, "T"); + + argc -= c; + argv += c; + for (i = 0; i < argc; i++) + save_file ("", argv[i], (char *) NULL); + + if (histfile) + (void) strcpy (fname, histfile); + else + (void) sprintf (fname, "%s/%s/%s", CVSroot, + CVSROOTADM, CVSROOTADM_HISTORY); + + read_hrecs (fname); + qsort ((PTR) hrec_head, hrec_count, sizeof (struct hrec), sort_order); + report_hrecs (); + + return (0); +} + +void +history_write (type, update_dir, revs, name, repository) + int type; + char *update_dir; + char *revs; + char *name; + char *repository; +{ + char fname[PATH_MAX], workdir[PATH_MAX], homedir[PATH_MAX]; + char *username = getcaller (); + int fd; + char *line; + char *slash = "", *cp, *cp2, *repos; + int i; + static char *tilde = ""; + static char *PrCurDir = NULL; + + if (logoff) /* History is turned off by cmd line switch */ + return; + (void) sprintf (fname, "%s/%s/%s", CVSroot, CVSROOTADM, CVSROOTADM_HISTORY); + + /* turn off history logging if the history file does not exist */ + if (!isfile (fname)) + { + logoff = 1; + return; + } + + if (trace) +#ifdef SERVER_SUPPORT + fprintf (stderr, "%c-> fopen(%s,a)\n", + (server_active) ? 'S' : ' ', fname); +#else + fprintf (stderr, "-> fopen(%s,a)\n", fname); +#endif + if (noexec) + return; + fd = open (fname, O_WRONLY | O_APPEND | O_CREAT | OPEN_BINARY, 0666); + if (fd < 0) + error (1, errno, "cannot open history file: %s", fname); + + repos = Short_Repository (repository); + + if (!PrCurDir) + { + char *pwdir; + + pwdir = get_homedir (); + PrCurDir = CurDir; + if (pwdir != NULL) + { + /* Assumes neither CurDir nor pwdir ends in '/' */ + i = strlen (pwdir); + if (!strncmp (CurDir, pwdir, i)) + { + PrCurDir += i; /* Point to '/' separator */ + tilde = "~"; + } + else + { + /* Try harder to find a "homedir" */ + if (!getwd (workdir)) + error (1, errno, "can't getwd in history"); + if (chdir (pwdir) < 0) + error (1, errno, "can't chdir(%s)", pwdir); + if (!getwd (homedir)) + error (1, errno, "can't getwd in %s", pwdir); + (void) chdir (workdir); + + i = strlen (homedir); + if (!strncmp (CurDir, homedir, i)) + { + PrCurDir += i; /* Point to '/' separator */ + tilde = "~"; + } + } + } + } + + if (type == 'T') + { + repos = update_dir; + update_dir = ""; + } + else if (update_dir && *update_dir) + slash = "/"; + else + update_dir = ""; + + (void) sprintf (workdir, "%s%s%s%s", tilde, PrCurDir, slash, update_dir); + + /* + * "workdir" is the directory where the file "name" is. ("^~" == $HOME) + * "repos" is the Repository, relative to $CVSROOT where the RCS file is. + * + * "$workdir/$name" is the working file name. + * "$CVSROOT/$repos/$name,v" is the RCS file in the Repository. + * + * First, note that the history format was intended to save space, not + * to be human readable. + * + * The working file directory ("workdir") and the Repository ("repos") + * usually end with the same one or more directory elements. To avoid + * duplication (and save space), the "workdir" field ends with + * an integer offset into the "repos" field. This offset indicates the + * beginning of the "tail" of "repos", after which all characters are + * duplicates. + * + * In other words, if the "workdir" field has a '*' (a very stupid thing + * to put in a filename) in it, then every thing following the last '*' + * is a hex offset into "repos" of the first character from "repos" to + * append to "workdir" to finish the pathname. + * + * It might be easier to look at an example: + * + * M273b3463|dgg|~/work*9|usr/local/cvs/examples|1.2|loginfo + * + * Indicates that the workdir is really "~/work/cvs/examples", saving + * 10 characters, where "~/work*d" would save 6 characters and mean that + * the workdir is really "~/work/examples". It will mean more on + * directories like: usr/local/gnu/emacs/dist-19.17/lisp/term + * + * "workdir" is always an absolute pathname (~/xxx is an absolute path) + * "repos" is always a relative pathname. So we can assume that we will + * never run into the top of "workdir" -- there will always be a '/' or + * a '~' at the head of "workdir" that is not matched by anything in + * "repos". On the other hand, we *can* run off the top of "repos". + * + * Only "compress" if we save characters. + */ + + if (!repos) + repos = ""; + + cp = workdir + strlen (workdir) - 1; + cp2 = repos + strlen (repos) - 1; + for (i = 0; cp2 >= repos && cp > workdir && *cp == *cp2--; cp--) + i++; + + if (i > 2) + { + i = strlen (repos) - i; + (void) sprintf ((cp + 1), "*%x", i); + } + + if (!revs) + revs = ""; + line = xmalloc (strlen (username) + strlen (workdir) + strlen (repos) + + strlen (revs) + strlen (name) + 100); + sprintf (line, "%c%08lx|%s|%s|%s|%s|%s\n", + type, (long) time ((time_t *) NULL), + username, workdir, repos, revs, name); + + /* Lessen some race conditions on non-Posix-compliant hosts. */ + if (lseek (fd, (off_t) 0, SEEK_END) == -1) + error (1, errno, "cannot seek to end of history file: %s", fname); + + if (write (fd, line, strlen (line)) < 0) + error (1, errno, "cannot write to history file: %s", fname); + free (line); + if (close (fd) != 0) + error (1, errno, "cannot close history file: %s", fname); +} + +/* + * save_user() adds a user name to the user list to select. Zero-length + * username ("") matches any user. + */ +static void +save_user (name) + char *name; +{ + if (user_count == user_max) + { + user_max += USER_INCREMENT; + user_list = (char **) xrealloc ((char *) user_list, + (int) user_max * sizeof (char *)); + } + user_list[user_count++] = xstrdup (name); +} + +/* + * save_file() adds file name and associated module to the file list to select. + * + * If "dir" is null, store a file name as is. + * If "name" is null, store a directory name with a '*' on the front. + * Else, store concatenated "dir/name". + * + * Later, in the "select" stage: + * - if it starts with '*', it is prefix-matched against the repository. + * - if it has a '/' in it, it is matched against the repository/file. + * - else it is matched against the file name. + */ +static void +save_file (dir, name, module) + char *dir; + char *name; + char *module; +{ + char *cp; + struct file_list_str *fl; + + if (file_count == file_max) + { + file_max += FILE_INCREMENT; + file_list = (struct file_list_str *) xrealloc ((char *) file_list, + file_max * sizeof (*fl)); + } + fl = &file_list[file_count++]; + fl->l_file = cp = xmalloc (strlen (dir) + strlen (name) + 2); + fl->l_module = module; + + if (dir && *dir) + { + if (name && *name) + { + (void) strcpy (cp, dir); + (void) strcat (cp, "/"); + (void) strcat (cp, name); + } + else + { + *cp++ = '*'; + (void) strcpy (cp, dir); + } + } + else + { + if (name && *name) + { + (void) strcpy (cp, name); + } + else + { + error (0, 0, "save_file: null dir and file name"); + } + } +} + +static void +save_module (module) + char *module; +{ + if (mod_count == mod_max) + { + mod_max += MODULE_INCREMENT; + mod_list = (char **) xrealloc ((char *) mod_list, + mod_max * sizeof (char *)); + } + mod_list[mod_count++] = xstrdup (module); +} + +static void +expand_modules () +{ +} + +/* fill_hrec + * + * Take a ptr to 7-part history line, ending with a newline, for example: + * + * M273b3463|dgg|~/work*9|usr/local/cvs/examples|1.2|loginfo + * + * Split it into 7 parts and drop the parts into a "struct hrec". + * Return a pointer to the character following the newline. + */ + +#define NEXT_BAR(here) do { while (isspace(*line)) line++; hr->here = line; while ((c = *line++) && c != '|') ; if (!c) return(rtn); *(line - 1) = '\0'; } while (0) + +static char * +fill_hrec (line, hr) + char *line; + struct hrec *hr; +{ + char *cp, *rtn; + int c; + int off; + static int idx = 0; + unsigned long date; + + memset ((char *) hr, 0, sizeof (*hr)); + while (isspace (*line)) + line++; + if (!(rtn = strchr (line, '\n'))) + return (""); + *rtn++ = '\0'; + + hr->type = line++; + (void) sscanf (line, "%lx", &date); + hr->date = date; + while (*line && strchr ("0123456789abcdefABCDEF", *line)) + line++; + if (*line == '\0') + return (rtn); + + line++; + NEXT_BAR (user); + NEXT_BAR (dir); + if ((cp = strrchr (hr->dir, '*')) != NULL) + { + *cp++ = '\0'; + (void) sscanf (cp, "%x", &off); + hr->end = line + off; + } + else + hr->end = line - 1; /* A handy pointer to '\0' */ + NEXT_BAR (repos); + NEXT_BAR (rev); + hr->idx = idx++; + if (strchr ("FOT", *(hr->type))) + hr->mod = line; + + NEXT_BAR (file); /* This returns ptr to next line or final '\0' */ + return (rtn); /* If it falls through, go on to next record */ +} + +/* read_hrecs's job is to read the history file and fill in all the "hrec" + * (history record) array elements with the ones we need to print. + * + * Logic: + * - Read the whole history file into a single buffer. + * - Walk through the buffer, parsing lines out of the buffer. + * 1. Split line into pointer and integer fields in the "next" hrec. + * 2. Apply tests to the hrec to see if it is wanted. + * 3. If it *is* wanted, bump the hrec pointer down by one. + */ +static void +read_hrecs (fname) + char *fname; +{ + char *cp, *cp2; + int i, fd; + struct hrec *hr; + struct stat st_buf; + + if ((fd = open (fname, O_RDONLY | OPEN_BINARY)) < 0) + error (1, errno, "cannot open history file: %s", fname); + + if (fstat (fd, &st_buf) < 0) + error (1, errno, "can't stat history file"); + + /* Exactly enough space for lines data */ + if (!(i = st_buf.st_size)) + error (1, 0, "history file is empty"); + cp = xmalloc (i + 2); + + if (read (fd, cp, i) != i) + error (1, errno, "cannot read log file"); + (void) close (fd); + + if (*(cp + i - 1) != '\n') + { + *(cp + i) = '\n'; /* Make sure last line ends in '\n' */ + i++; + } + *(cp + i) = '\0'; + for (cp2 = cp; cp2 - cp < i; cp2++) + { + if (*cp2 != '\n' && !isprint (*cp2)) + *cp2 = ' '; + } + + hrec_max = HREC_INCREMENT; + hrec_head = (struct hrec *) xmalloc (hrec_max * sizeof (struct hrec)); + + while (*cp) + { + if (hrec_count == hrec_max) + { + struct hrec *old_head = hrec_head; + + hrec_max += HREC_INCREMENT; + hrec_head = (struct hrec *) xrealloc ((char *) hrec_head, + hrec_max * sizeof (struct hrec)); + if (hrec_head != old_head) + { + if (last_since_tag) + last_since_tag = hrec_head + (last_since_tag - old_head); + if (last_backto) + last_backto = hrec_head + (last_backto - old_head); + } + } + + hr = hrec_head + hrec_count; + cp = fill_hrec (cp, hr); /* cp == next line or '\0' at end of buffer */ + + if (select_hrec (hr)) + hrec_count++; + } + + /* Special selection problem: If "since_tag" is set, we have saved every + * record from the 1st occurrence of "since_tag", when we want to save + * records since the *last* occurrence of "since_tag". So what we have + * to do is bump hrec_head forward and reduce hrec_count accordingly. + */ + if (last_since_tag) + { + hrec_count -= (last_since_tag - hrec_head); + hrec_head = last_since_tag; + } + + /* Much the same thing is necessary for the "backto" option. */ + if (last_backto) + { + hrec_count -= (last_backto - hrec_head); + hrec_head = last_backto; + } +} + +/* Utility program for determining whether "find" is inside "string" */ +static int +within (find, string) + char *find, *string; +{ + int c, len; + + if (!find || !string) + return (0); + + c = *find++; + len = strlen (find); + + while (*string) + { + if (!(string = strchr (string, c))) + return (0); + string++; + if (!strncmp (find, string, len)) + return (1); + } + return (0); +} + +/* The purpose of "select_hrec" is to apply the selection criteria based on + * the command arguments and defaults and return a flag indicating whether + * this record should be remembered for printing. + */ +static int +select_hrec (hr) + struct hrec *hr; +{ + char **cpp, *cp, *cp2; + struct file_list_str *fl; + int count; + + /* "Since" checking: The argument parser guarantees that only one of the + * following four choices is set: + * + * 1. If "since_date" is set, it contains a Unix time_t specified on the + * command line. hr->date fields earlier than "since_date" are ignored. + * 2. If "since_rev" is set, it contains either an RCS "dotted" revision + * number (which is of limited use) or a symbolic TAG. Each RCS file + * is examined and the date on the specified revision (or the revision + * corresponding to the TAG) in the RCS file (CVSROOT/repos/file) is + * compared against hr->date as in 1. above. + * 3. If "since_tag" is set, matching tag records are saved. The field + * "last_since_tag" is set to the last one of these. Since we don't + * know where the last one will be, all records are saved from the + * first occurrence of the TAG. Later, at the end of "select_hrec" + * records before the last occurrence of "since_tag" are skipped. + * 4. If "backto" is set, all records with a module name or file name + * matching "backto" are saved. In addition, all records with a + * repository field with a *prefix* matching "backto" are saved. + * The field "last_backto" is set to the last one of these. As in + * 3. above, "select_hrec" adjusts to include the last one later on. + */ + if (since_date) + { + if (hr->date < since_date) + return (0); + } + else if (*since_rev) + { + Vers_TS *vers; + time_t t; + + vers = Version_TS (hr->repos, (char *) NULL, since_rev, (char *) NULL, + hr->file, 1, 0, (List *) NULL, (RCSNode *) NULL); + if (vers->vn_rcs) + { + if ((t = RCS_getrevtime (vers->srcfile, vers->vn_rcs, (char *) 0, 0)) + != (time_t) 0) + { + if (hr->date < t) + { + freevers_ts (&vers); + return (0); + } + } + } + freevers_ts (&vers); + } + else if (*since_tag) + { + if (*(hr->type) == 'T') + { + /* + * A 'T'ag record, the "rev" field holds the tag to be set, + * while the "repos" field holds "D"elete, "A"dd or a rev. + */ + if (within (since_tag, hr->rev)) + { + last_since_tag = hr; + return (1); + } + else + return (0); + } + if (!last_since_tag) + return (0); + } + else if (*backto) + { + if (within (backto, hr->file) || within (backto, hr->mod) || + within (backto, hr->repos)) + last_backto = hr; + else + return (0); + } + + /* User checking: + * + * Run down "user_list", match username ("" matches anything) + * If "" is not there and actual username is not there, return failure. + */ + if (user_list && hr->user) + { + for (cpp = user_list, count = user_count; count; cpp++, count--) + { + if (!**cpp) + break; /* null user == accept */ + if (!strcmp (hr->user, *cpp)) /* found listed user */ + break; + } + if (!count) + return (0); /* Not this user */ + } + + /* Record type checking: + * + * 1. If Record type is not in rec_types field, skip it. + * 2. If mod_list is null, keep everything. Otherwise keep only modules + * on mod_list. + * 3. If neither a 'T', 'F' nor 'O' record, run through "file_list". If + * file_list is null, keep everything. Otherwise, keep only files on + * file_list, matched appropriately. + */ + if (!strchr (rec_types, *(hr->type))) + return (0); + if (!strchr ("TFO", *(hr->type))) /* Don't bother with "file" if "TFO" */ + { + if (file_list) /* If file_list is null, accept all */ + { + for (fl = file_list, count = file_count; count; fl++, count--) + { + /* 1. If file_list entry starts with '*', skip the '*' and + * compare it against the repository in the hrec. + * 2. If file_list entry has a '/' in it, compare it against + * the concatenation of the repository and file from hrec. + * 3. Else compare the file_list entry against the hrec file. + */ + char cmpfile[PATH_MAX]; + + if (*(cp = fl->l_file) == '*') + { + cp++; + /* if argument to -p is a prefix of repository */ + if (!strncmp (cp, hr->repos, strlen (cp))) + { + hr->mod = fl->l_module; + break; + } + } + else + { + if (strchr (cp, '/')) + { + (void) sprintf (cp2 = cmpfile, "%s/%s", + hr->repos, hr->file); + } + else + { + cp2 = hr->file; + } + + /* if requested file is found within {repos}/file fields */ + if (within (cp, cp2)) + { + hr->mod = fl->l_module; + break; + } + } + } + if (!count) + return (0); /* String specified and no match */ + } + } + if (mod_list) + { + for (cpp = mod_list, count = mod_count; count; cpp++, count--) + { + if (hr->mod && !strcmp (hr->mod, *cpp)) /* found module */ + break; + } + if (!count) + return (0); /* Module specified & this record is not one of them. */ + } + + return (1); /* Select this record unless rejected above. */ +} + +/* The "sort_order" routine (when handed to qsort) has arranged for the + * hrecs files to be in the right order for the report. + * + * Most of the "selections" are done in the select_hrec routine, but some + * selections are more easily done after the qsort by "accept_hrec". + */ +static void +report_hrecs () +{ + struct hrec *hr, *lr; + struct tm *tm; + int i, count, ty; + char *cp; + int user_len, file_len, rev_len, mod_len, repos_len; + + if (*since_tag && !last_since_tag) + { + (void) printf ("No tag found: %s\n", since_tag); + return; + } + else if (*backto && !last_backto) + { + (void) printf ("No module, file or repository with: %s\n", backto); + return; + } + else if (hrec_count < 1) + { + (void) printf ("No records selected.\n"); + return; + } + + user_len = file_len = rev_len = mod_len = repos_len = 0; + + /* Run through lists and find maximum field widths */ + hr = lr = hrec_head; + hr++; + for (count = hrec_count; count--; lr = hr, hr++) + { + char repos[PATH_MAX]; + + if (!count) + hr = NULL; + if (!accept_hrec (lr, hr)) + continue; + + ty = *(lr->type); + (void) strcpy (repos, lr->repos); + if ((cp = strrchr (repos, '/')) != NULL) + { + if (lr->mod && !strcmp (++cp, lr->mod)) + { + (void) strcpy (cp, "*"); + } + } + if ((i = strlen (lr->user)) > user_len) + user_len = i; + if ((i = strlen (lr->file)) > file_len) + file_len = i; + if (ty != 'T' && (i = strlen (repos)) > repos_len) + repos_len = i; + if (ty != 'T' && (i = strlen (lr->rev)) > rev_len) + rev_len = i; + if (lr->mod && (i = strlen (lr->mod)) > mod_len) + mod_len = i; + } + + /* Walk through hrec array setting "lr" (Last Record) to each element. + * "hr" points to the record following "lr" -- It is NULL in the last + * pass. + * + * There are two sections in the loop below: + * 1. Based on the report type (e.g. extract, checkout, tag, etc.), + * decide whether the record should be printed. + * 2. Based on the record type, format and print the data. + */ + for (lr = hrec_head, hr = (lr + 1); hrec_count--; lr = hr, hr++) + { + char workdir[PATH_MAX], repos[PATH_MAX]; + + if (!hrec_count) + hr = NULL; + if (!accept_hrec (lr, hr)) + continue; + + ty = *(lr->type); +#ifdef HAVE_RCS5 + if (!tz_local) + { + time_t t = lr->date + tz_seconds_east_of_GMT; + tm = gmtime (&t); + } + else +#endif + tm = localtime (&(lr->date)); + (void) printf ("%c %02d/%02d %02d:%02d %s %-*s", ty, tm->tm_mon + 1, + tm->tm_mday, tm->tm_hour, tm->tm_min, tz_name, + user_len, lr->user); + + (void) sprintf (workdir, "%s%s", lr->dir, lr->end); + if ((cp = strrchr (workdir, '/')) != NULL) + { + if (lr->mod && !strcmp (++cp, lr->mod)) + { + (void) strcpy (cp, "*"); + } + } + (void) strcpy (repos, lr->repos); + if ((cp = strrchr (repos, '/')) != NULL) + { + if (lr->mod && !strcmp (++cp, lr->mod)) + { + (void) strcpy (cp, "*"); + } + } + + switch (ty) + { + case 'T': + /* 'T'ag records: repository is a "tag type", rev is the tag */ + (void) printf (" %-*s [%s:%s]", mod_len, lr->mod, lr->rev, + repos); + if (working) + (void) printf (" {%s}", workdir); + break; + case 'F': + case 'O': + if (lr->rev && *(lr->rev)) + (void) printf (" [%s]", lr->rev); + (void) printf (" %-*s =%s%-*s %s", repos_len, repos, lr->mod, + mod_len + 1 - strlen (lr->mod), "=", workdir); + break; + case 'W': + case 'U': + case 'C': + case 'G': + case 'M': + case 'A': + case 'R': + (void) printf (" %-*s %-*s %-*s =%s= %s", rev_len, lr->rev, + file_len, lr->file, repos_len, repos, + lr->mod ? lr->mod : "", workdir); + break; + default: + (void) printf ("Hey! What is this junk? RecType[0x%2.2x]", ty); + break; + } + (void) putchar ('\n'); + } +} + +static int +accept_hrec (lr, hr) + struct hrec *hr, *lr; +{ + int ty; + + ty = *(lr->type); + + if (last_since_tag && ty == 'T') + return (1); + + if (v_checkout) + { + if (ty != 'O') + return (0); /* Only interested in 'O' records */ + + /* We want to identify all the states that cause the next record + * ("hr") to be different from the current one ("lr") and only + * print a line at the allowed boundaries. + */ + + if (!hr || /* The last record */ + strcmp (hr->user, lr->user) || /* User has changed */ + strcmp (hr->mod, lr->mod) ||/* Module has changed */ + (working && /* If must match "workdir" */ + (strcmp (hr->dir, lr->dir) || /* and the 1st parts or */ + strcmp (hr->end, lr->end)))) /* the 2nd parts differ */ + + return (1); + } + else if (modified) + { + if (!last_entry || /* Don't want only last rec */ + !hr || /* Last entry is a "last entry" */ + strcmp (hr->repos, lr->repos) || /* Repository has changed */ + strcmp (hr->file, lr->file))/* File has changed */ + return (1); + + if (working) + { /* If must match "workdir" */ + if (strcmp (hr->dir, lr->dir) || /* and the 1st parts or */ + strcmp (hr->end, lr->end)) /* the 2nd parts differ */ + return (1); + } + } + else if (module_report) + { + if (!last_entry || /* Don't want only last rec */ + !hr || /* Last entry is a "last entry" */ + strcmp (hr->mod, lr->mod) ||/* Module has changed */ + strcmp (hr->repos, lr->repos) || /* Repository has changed */ + strcmp (hr->file, lr->file))/* File has changed */ + return (1); + } + else + { + /* "extract" and "tag_report" always print selected records. */ + return (1); + } + + return (0); +} diff --git a/contrib/cvs/src/ignore.c b/contrib/cvs/src/ignore.c new file mode 100644 index 0000000..25c2269 --- /dev/null +++ b/contrib/cvs/src/ignore.c @@ -0,0 +1,405 @@ +/* + * .cvsignore file support contributed by David G. Grubbs <dgg@odi.com> + */ + +#include "cvs.h" + +/* + * Ignore file section. + * + * "!" may be included any time to reset the list (i.e. ignore nothing); + * "*" may be specified to ignore everything. It stays as the first + * element forever, unless a "!" clears it out. + */ + +static char **ign_list; /* List of files to ignore in update + * and import */ +static char **s_ign_list = NULL; +static int ign_count; /* Number of active entries */ +static int s_ign_count = 0; +static int ign_size; /* This many slots available (plus + * one for a NULL) */ +static int ign_hold; /* Index where first "temporary" item + * is held */ + +const char *ign_default = ". .. core RCSLOG tags TAGS RCS SCCS .make.state\ + .nse_depinfo #* .#* cvslog.* ,* CVS CVS.adm .del-* *.a *.olb *.o *.obj\ + *.so *.Z *~ *.old *.elc *.ln *.bak *.BAK *.orig *.rej *.exe _$* *$"; + +#define IGN_GROW 16 /* grow the list by 16 elements at a + * time */ + +/* Nonzero if we have encountered an -I ! directive, which means one should + no longer ask the server about what is in CVSROOTADM_IGNORE. */ +int ign_inhibit_server; + +/* + * To the "ignore list", add the hard-coded default ignored wildcards above, + * the wildcards found in $CVSROOT/CVSROOT/cvsignore, the wildcards found in + * ~/.cvsignore and the wildcards found in the CVSIGNORE environment + * variable. + */ +void +ign_setup () +{ + struct passwd *pw; + char file[PATH_MAX]; + char *tmp; + + ign_inhibit_server = 0; + + /* Start with default list and special case */ + tmp = xstrdup (ign_default); + ign_add (tmp, 0); + free (tmp); + +#ifdef CLIENT_SUPPORT + /* The client handles another way, by (after it does its own ignore file + processing, and only if !ign_inhibit_server), letting the server + know about the files and letting it decide whether to ignore + them based on CVSROOOTADM_IGNORE. */ + if (!client_active) +#endif + { + /* Then add entries found in repository, if it exists */ + (void) sprintf (file, "%s/%s/%s", CVSroot, CVSROOTADM, + CVSROOTADM_IGNORE); + ign_add_file (file, 0); + } + + /* Then add entries found in home dir, (if user has one) and file exists */ + if ((pw = (struct passwd *) getpwuid (getuid ())) && pw->pw_dir) + { + (void) sprintf (file, "%s/%s", pw->pw_dir, CVSDOTIGNORE); + ign_add_file (file, 0); + } + + /* Then add entries found in CVSIGNORE environment variable. */ + ign_add (getenv (IGNORE_ENV), 0); + + /* Later, add ignore entries found in -I arguments */ +} + +/* + * Open a file and read lines, feeding each line to a line parser. Arrange + * for keeping a temporary list of wildcards at the end, if the "hold" + * argument is set. + */ +void +ign_add_file (file, hold) + char *file; + int hold; +{ + FILE *fp; + char line[1024]; + + /* restore the saved list (if any) */ + if (s_ign_list != NULL) + { + int i; + + for (i = 0; i < s_ign_count; i++) + ign_list[i] = s_ign_list[i]; + ign_count = s_ign_count; + ign_list[ign_count] = NULL; + + s_ign_count = 0; + free (s_ign_list); + s_ign_list = NULL; + } + + /* is this a temporary ignore file? */ + if (hold) + { + /* re-set if we had already done a temporary file */ + if (ign_hold) + { + int i; + + for (i = ign_hold; i < ign_count; i++) + free (ign_list[i]); + ign_count = ign_hold; + ign_list[ign_count] = NULL; + } + else + { + ign_hold = ign_count; + } + } + + /* load the file */ + fp = fopen (file, "r"); + if (fp == NULL) + { + if (! existence_error (errno)) + error (0, errno, "cannot open %s", file); + return; + } + while (fgets (line, sizeof (line), fp)) + ign_add (line, hold); + if (fclose (fp) < 0) + error (0, errno, "cannot close %s", file); +} + +/* Parse a line of space-separated wildcards and add them to the list. */ +void +ign_add (ign, hold) + char *ign; + int hold; +{ + if (!ign || !*ign) + return; + + for (; *ign; ign++) + { + char *mark; + char save; + + /* ignore whitespace before the token */ + if (isspace (*ign)) + continue; + + /* + * if we find a single character !, we must re-set the ignore list + * (saving it if necessary). We also catch * as a special case in a + * global ignore file as an optimization + */ + if ((!*(ign+1) || isspace (*(ign+1))) && (*ign == '!' || *ign == '*')) + { + if (!hold) + { + /* permanently reset the ignore list */ + int i; + + for (i = 0; i < ign_count; i++) + free (ign_list[i]); + ign_count = 0; + ign_list[0] = NULL; + + /* if we are doing a '!', continue; otherwise add the '*' */ + if (*ign == '!') + { + ign_inhibit_server = 1; + continue; + } + } + else if (*ign == '!') + { + /* temporarily reset the ignore list */ + int i; + + if (ign_hold) + { + for (i = ign_hold; i < ign_count; i++) + free (ign_list[i]); + ign_hold = 0; + } + s_ign_list = (char **) xmalloc (ign_count * sizeof (char *)); + for (i = 0; i < ign_count; i++) + s_ign_list[i] = ign_list[i]; + s_ign_count = ign_count; + ign_count = 0; + ign_list[0] = NULL; + continue; + } + } + + /* If we have used up all the space, add some more */ + if (ign_count >= ign_size) + { + ign_size += IGN_GROW; + ign_list = (char **) xrealloc ((char *) ign_list, + (ign_size + 1) * sizeof (char *)); + } + + /* find the end of this token */ + for (mark = ign; *mark && !isspace (*mark); mark++) + /* do nothing */ ; + + save = *mark; + *mark = '\0'; + + ign_list[ign_count++] = xstrdup (ign); + ign_list[ign_count] = NULL; + + *mark = save; + if (save) + ign = mark; + else + ign = mark - 1; + } +} + +/* Set to 1 if ignore file patterns should be matched in a case-insensitive + fashion. */ +int ign_case; + +/* Return 1 if the given filename should be ignored by update or import. */ +int +ign_name (name) + char *name; +{ + char **cpp = ign_list; + + if (cpp == NULL) + return (0); + + if (ign_case) + { + /* We do a case-insensitive match by calling fnmatch on copies of + the pattern and the name which have been converted to + lowercase. */ + char *name_lower; + char *pat_lower; + char *p; + + name_lower = xstrdup (name); + for (p = name_lower; *p != '\0'; ++p) + *p = tolower (*p); + while (*cpp) + { + pat_lower = xstrdup (*cpp++); + for (p = pat_lower; *p != '\0'; ++p) + *p = tolower (*p); + if (fnmatch (pat_lower, name_lower, 0) == 0) + goto matched; + free (pat_lower); + } + free (name_lower); + return 0; + matched: + free (name_lower); + free (pat_lower); + return 1; + } + else + { + while (*cpp) + if (fnmatch (*cpp++, name, 0) == 0) + return 1; + return 0; + } +} + +/* FIXME: This list of dirs to ignore stuff seems not to be used. */ + +static char **dir_ign_list = NULL; +static int dir_ign_max = 0; +static int dir_ign_current = 0; + +/* add a directory to list of dirs to ignore */ +void ign_dir_add (name) + char *name; +{ + /* make sure we've got the space for the entry */ + if (dir_ign_current <= dir_ign_max) + { + dir_ign_max += IGN_GROW; + dir_ign_list = (char **) xrealloc ((char *) dir_ign_list, (dir_ign_max+1) * sizeof(char*)); + } + + dir_ign_list[dir_ign_current] = name; + + dir_ign_current += 1 ; +} + + +/* this function returns 1 (true) if the given directory name is part of + * the list of directories to ignore + */ + +int ignore_directory (name) + char *name; +{ + int i; + + if (!dir_ign_list) + return 0; + + i = dir_ign_current; + while (i--) + { + if (strncmp(name, dir_ign_list[i], strlen(dir_ign_list[i])) == 0) + return 1; + } + + return 0; +} + +/* + * Process the current directory, looking for files not in ILIST and not on + * the global ignore list for this directory. If we find one, call PROC + * passing it the name of the file and the update dir. + */ +void +ignore_files (ilist, update_dir, proc) + List *ilist; + char *update_dir; + Ignore_proc proc; +{ + DIR *dirp; + struct dirent *dp; + struct stat sb; + char *file; + char *xdir; + + /* we get called with update_dir set to "." sometimes... strip it */ + if (strcmp (update_dir, ".") == 0) + xdir = ""; + else + xdir = update_dir; + + dirp = opendir ("."); + if (dirp == NULL) + return; + + ign_add_file (CVSDOTIGNORE, 1); + wrap_add_file (CVSDOTWRAPPER, 1); + + while ((dp = readdir (dirp)) != NULL) + { + file = dp->d_name; + if (strcmp (file, ".") == 0 || strcmp (file, "..") == 0) + continue; + if (findnode_fn (ilist, file) != NULL) + continue; + + if ( +#ifdef DT_DIR + dp->d_type != DT_UNKNOWN || +#endif + lstat(file, &sb) != -1) + { + + if ( +#ifdef DT_DIR + dp->d_type == DT_DIR || dp->d_type == DT_UNKNOWN && +#endif + S_ISDIR(sb.st_mode)) + { + char temp[PATH_MAX]; + + (void) sprintf (temp, "%s/%s", file, CVSADM); + if (isdir (temp)) + continue; + } +#ifdef S_ISLNK + else if ( +#ifdef DT_DIR + dp->d_type == DT_LNK || dp->d_type == DT_UNKNOWN && +#endif + S_ISLNK(sb.st_mode)) + { + continue; + } +#endif + } + + /* We could be ignoring FIFOs and other files which are neither + regular files nor directories here. */ + if (ign_name (file)) + continue; + (*proc) (file, xdir); + } + (void) closedir (dirp); +} diff --git a/contrib/cvs/src/import.c b/contrib/cvs/src/import.c new file mode 100644 index 0000000..98c1635 --- /dev/null +++ b/contrib/cvs/src/import.c @@ -0,0 +1,1207 @@ +/* + * Copyright (c) 1992, Brian Berliner and Jeff Polk + * Copyright (c) 1989-1992, Brian Berliner + * + * You may distribute under the terms of the GNU General Public License as + * specified in the README file that comes with the CVS 1.4 kit. + * + * "import" checks in the vendor release located in the current directory into + * the CVS source repository. The CVS vendor branch support is utilized. + * + * At least three arguments are expected to follow the options: + * repository Where the source belongs relative to the CVSROOT + * VendorTag Vendor's major tag + * VendorReleTag Tag for this particular release + * + * Additional arguments specify more Vendor Release Tags. + */ + +#include "cvs.h" +#include "savecwd.h" + +#define FILE_HOLDER ".#cvsxxx" + +static char *get_comment PROTO((char *user)); +static int add_rcs_file PROTO((char *message, char *rcs, char *user, char *vtag, + int targc, char *targv[])); +static int expand_at_signs PROTO((char *buf, off_t size, FILE *fp)); +static int add_rev PROTO((char *message, char *rcs, char *vfile, char *vers)); +static int add_tags PROTO((char *rcs, char *vfile, char *vtag, int targc, + char *targv[])); +static int import_descend PROTO((char *message, char *vtag, int targc, char *targv[])); +static int import_descend_dir PROTO((char *message, char *dir, char *vtag, + int targc, char *targv[])); +static int process_import_file PROTO((char *message, char *vfile, char *vtag, + int targc, char *targv[])); +static int update_rcs_file PROTO((char *message, char *vfile, char *vtag, int targc, + char *targv[], int inattic)); +static void add_log PROTO((int ch, char *fname)); + +static int repos_len; +static char vhead[50]; +static char vbranch[50]; +static FILE *logfp; +static char repository[PATH_MAX]; +static int conflicts; +static int use_file_modtime; +static char *keyword_opt = NULL; + +static const char *const import_usage[] = +{ + "Usage: %s %s [-d] [-k subst] [-I ign] [-m msg] [-b branch]\n", + " [-W spec] repository vendor-tag release-tags...\n", + "\t-d\tUse the file's modification time as the time of import.\n", + "\t-k sub\tSet default RCS keyword substitution mode.\n", + "\t-I ign\tMore files to ignore (! to reset).\n", + "\t-b bra\tVendor branch id.\n", + "\t-m msg\tLog message.\n", + "\t-W spec\tWrappers specification line.\n", + NULL +}; + +int +import (argc, argv) + int argc; + char **argv; +{ + char *message = NULL; + char tmpfile[L_tmpnam+1]; + char *cp; + int i, c, msglen, err; + List *ulist; + Node *p; + + if (argc == -1) + usage (import_usage); + + ign_setup (); + wrap_setup (); + + (void) strcpy (vbranch, CVSBRANCH); + optind = 1; + while ((c = getopt (argc, argv, "Qqdb:m:I:k:W:")) != -1) + { + switch (c) + { + case 'Q': + case 'q': +#ifdef SERVER_SUPPORT + /* The CVS 1.5 client sends these options (in addition to + Global_option requests), so we must ignore them. */ + if (!server_active) +#endif + error (1, 0, + "-q or -Q must be specified before \"%s\"", + command_name); + break; + case 'd': + use_file_modtime = 1; + break; + case 'b': + (void) strcpy (vbranch, optarg); + break; + case 'm': +#ifdef FORCE_USE_EDITOR + use_editor = TRUE; +#else + use_editor = FALSE; +#endif + message = xstrdup(optarg); + break; + case 'I': + ign_add (optarg, 0); + break; + case 'k': + /* RCS_check_kflag returns strings of the form -kxx. We + only use it for validation, so we can free the value + as soon as it is returned. */ + free (RCS_check_kflag(optarg)); + keyword_opt = optarg; + break; + case 'W': + wrap_add (optarg, 0); + break; + case '?': + default: + usage (import_usage); + break; + } + } + argc -= optind; + argv += optind; + if (argc < 3) + usage (import_usage); + + for (i = 1; i < argc; i++) /* check the tags for validity */ + RCS_check_tag (argv[i]); + + /* XXX - this should be a module, not just a pathname */ + if (! isabsolute (argv[0])) + { + if (CVSroot == NULL) + { + error (0, 0, "missing CVSROOT environment variable\n"); + error (1, 0, "Set it or specify the '-d' option to %s.", + program_name); + } + (void) sprintf (repository, "%s/%s", CVSroot, argv[0]); + repos_len = strlen (CVSroot); + } + else + { + (void) strcpy (repository, argv[0]); + repos_len = 0; + } + + /* + * Consistency checks on the specified vendor branch. It must be + * composed of only numbers and dots ('.'). Also, for now we only + * support branching to a single level, so the specified vendor branch + * must only have two dots in it (like "1.1.1"). + */ + for (cp = vbranch; *cp != '\0'; cp++) + if (!isdigit (*cp) && *cp != '.') + error (1, 0, "%s is not a numeric branch", vbranch); + if (numdots (vbranch) != 2) + error (1, 0, "Only branches with two dots are supported: %s", vbranch); + (void) strcpy (vhead, vbranch); + cp = strrchr (vhead, '.'); + *cp = '\0'; + +#ifdef CLIENT_SUPPORT + if (client_active) + { + /* Do this now; don't ask for a log message if we can't talk to the + server. But if there is a syntax error in the options, give + an error message without connecting. */ + start_server (); + } +#endif + + if (use_editor) + { + do_editor ((char *) NULL, &message, repository, + (List *) NULL); + } + + msglen = message == NULL ? 0 : strlen (message); + if (msglen == 0 || message[msglen - 1] != '\n') + { + char *nm = xmalloc (msglen + 2); + if (message != NULL) + { + (void) strcpy (nm, message); + free (message); + } + (void) strcat (nm + msglen, "\n"); + message = nm; + } + +#ifdef CLIENT_SUPPORT + if (client_active) + { + int err; + + if (use_file_modtime) + send_arg("-d"); + + if (vbranch[0] != '\0') + option_with_arg ("-b", vbranch); + if (message) + option_with_arg ("-m", message); + if (keyword_opt != NULL) + option_with_arg ("-k", keyword_opt); + /* The only ignore processing which takes place on the server side + is the CVSROOT/cvsignore file. But if the user specified -I !, + the documented behavior is to not process said file. */ + if (ign_inhibit_server) + { + send_arg ("-I"); + send_arg ("!"); + } + + { + int i; + for (i = 0; i < argc; ++i) + send_arg (argv[i]); + } + + logfp = stdin; + client_import_setup (repository); + err = import_descend (message, argv[1], argc - 2, argv + 2); + client_import_done (); + send_to_server ("import\012", 0); + err += get_responses_and_close (); + return err; + } +#endif + + /* + * Make all newly created directories writable. Should really use a more + * sophisticated security mechanism here. + */ + (void) umask (cvsumask); + make_directories (repository); + + /* Create the logfile that will be logged upon completion */ + if ((logfp = fopen (tmpnam (tmpfile), "w+")) == NULL) + error (1, errno, "cannot create temporary file `%s'", tmpfile); + (void) unlink (tmpfile); /* to be sure it goes away */ + (void) fprintf (logfp, "\nVendor Tag:\t%s\n", argv[1]); + (void) fprintf (logfp, "Release Tags:\t"); + for (i = 2; i < argc; i++) + (void) fprintf (logfp, "%s\n\t\t", argv[i]); + (void) fprintf (logfp, "\n"); + + /* Just Do It. */ + err = import_descend (message, argv[1], argc - 2, argv + 2); + if (conflicts) + { + if (!really_quiet) + { + char buf[80]; + sprintf (buf, "\n%d conflicts created by this import.\n", + conflicts); + cvs_output (buf, 0); + cvs_output ("Use the following command to help the merge:\n\n", + 0); + cvs_output ("\t", 1); + cvs_output (program_name, 0); + cvs_output (" checkout -j", 0); + cvs_output (argv[1], 0); + cvs_output (":yesterday -j", 0); + cvs_output (argv[1], 0); + cvs_output (" ", 1); + cvs_output (argv[0], 0); + cvs_output ("\n\n", 0); + } + + (void) fprintf (logfp, "\n%d conflicts created by this import.\n", + conflicts); + (void) fprintf (logfp, + "Use the following command to help the merge:\n\n"); + (void) fprintf (logfp, "\t%s checkout -j%s:yesterday -j%s %s\n\n", + program_name, argv[1], argv[1], argv[0]); + } + else + { + if (!really_quiet) + cvs_output ("\nNo conflicts created by this import\n\n", 0); + (void) fprintf (logfp, "\nNo conflicts created by this import\n\n"); + } + + /* + * Write out the logfile and clean up. + */ + ulist = getlist (); + p = getnode (); + p->type = UPDATE; + p->delproc = update_delproc; + p->key = xstrdup ("- Imported sources"); + p->data = (char *) T_TITLE; + (void) addnode (ulist, p); + Update_Logfile (repository, message, vbranch, logfp, ulist); + dellist (&ulist); + (void) fclose (logfp); + + /* Make sure the temporary file goes away, even on systems that don't let + you delete a file that's in use. */ + unlink (tmpfile); + + if (message) + free (message); + + return (err); +} + +/* + * process all the files in ".", then descend into other directories. + */ +static int +import_descend (message, vtag, targc, targv) + char *message; + char *vtag; + int targc; + char *targv[]; +{ + DIR *dirp; + struct dirent *dp; + int err = 0; + List *dirlist = NULL; + + /* first, load up any per-directory ignore lists */ + ign_add_file (CVSDOTIGNORE, 1); + wrap_add_file (CVSDOTWRAPPER, 1); + + if ((dirp = opendir (".")) == NULL) + { + err++; + } + else + { + while ((dp = readdir (dirp)) != NULL) + { + if (strcmp (dp->d_name, ".") == 0 || strcmp (dp->d_name, "..") == 0) + continue; +#ifdef SERVER_SUPPORT + /* CVS directories are created in the temp directory by + server.c because it doesn't special-case import. So + don't print a message about them, regardless of -I!. */ + if (server_active && strcmp (dp->d_name, CVSADM) == 0) + continue; +#endif + if (ign_name (dp->d_name)) + { + add_log ('I', dp->d_name); + continue; + } + + if ( +#ifdef DT_DIR + (dp->d_type == DT_DIR + || (dp->d_type == DT_UNKNOWN && isdir (dp->d_name))) +#else + isdir (dp->d_name) +#endif + && !wrap_name_has (dp->d_name, WRAP_TOCVS) + ) + { + Node *n; + + if (dirlist == NULL) + dirlist = getlist(); + + n = getnode(); + n->key = xstrdup (dp->d_name); + addnode(dirlist, n); + } + else if ( +#ifdef DT_DIR + dp->d_type == DT_LNK || dp->d_type == DT_UNKNOWN && +#endif + islink (dp->d_name)) + { + add_log ('L', dp->d_name); + err++; + } + else + { +#ifdef CLIENT_SUPPORT + if (client_active) + err += client_process_import_file (message, dp->d_name, + vtag, targc, targv, + repository); + else +#endif + err += process_import_file (message, dp->d_name, + vtag, targc, targv); + } + } + (void) closedir (dirp); + } + + if (dirlist != NULL) + { + Node *head, *p; + + head = dirlist->list; + for (p = head->next; p != head; p = p->next) + { + err += import_descend_dir (message, p->key, vtag, targc, targv); + } + + dellist(&dirlist); + } + + return (err); +} + +/* + * Process the argument import file. + */ +static int +process_import_file (message, vfile, vtag, targc, targv) + char *message; + char *vfile; + char *vtag; + int targc; + char *targv[]; +{ + char attic_name[PATH_MAX]; + char rcs[PATH_MAX]; + int inattic = 0; + + (void) sprintf (rcs, "%s/%s%s", repository, vfile, RCSEXT); + if (!isfile (rcs)) + { + (void) sprintf (attic_name, "%s/%s/%s%s", repository, CVSATTIC, + vfile, RCSEXT); + if (!isfile (attic_name)) + { + + /* + * A new import source file; it doesn't exist as a ,v within the + * repository nor in the Attic -- create it anew. + */ + add_log ('N', vfile); + return (add_rcs_file (message, rcs, vfile, vtag, targc, targv)); + } + inattic = 1; + } + + /* + * an rcs file exists. have to do things the official, slow, way. + */ + return (update_rcs_file (message, vfile, vtag, targc, targv, inattic)); +} + +/* + * The RCS file exists; update it by adding the new import file to the + * (possibly already existing) vendor branch. + */ +static int +update_rcs_file (message, vfile, vtag, targc, targv, inattic) + char *message; + char *vfile; + char *vtag; + int targc; + char *targv[]; + int inattic; +{ + Vers_TS *vers; + int letter; + int ierrno; + char *tmpdir; + char *tocvsPath; + + vers = Version_TS (repository, (char *) NULL, vbranch, (char *) NULL, vfile, + 1, 0, (List *) NULL, (RCSNode *) NULL); + if (vers->vn_rcs != NULL + && !RCS_isdead(vers->srcfile, vers->vn_rcs)) + { + char xtmpfile[PATH_MAX]; + int different; + int retcode = 0; + + tmpdir = getenv ("TMPDIR"); + if (tmpdir == NULL || tmpdir[0] == '\0') + tmpdir = "/tmp"; + + (void) sprintf (xtmpfile, "%s/cvs-imp%ld", tmpdir, (long) getpid()); + + /* + * The rcs file does have a revision on the vendor branch. Compare + * this revision with the import file; if they match exactly, there + * is no need to install the new import file as a new revision to the + * branch. Just tag the revision with the new import tags. + * + * This is to try to cut down the number of "C" conflict messages for + * locally modified import source files. + */ + /* Why is RCS_FLAGS_FORCE here? I wouldn't think that it would have any + effect in conjunction with passing NULL for workfile (i.e. to stdout). */ + retcode = RCS_checkout (vers->srcfile->path, NULL, vers->vn_rcs, +#ifdef HAVE_RCS5 + "-ko", +#else + NULL, +#endif + xtmpfile, RCS_FLAGS_FORCE, 0); + if (retcode != 0) + { + ierrno = errno; + fperror (logfp, 0, retcode == -1 ? ierrno : 0, + "ERROR: cannot co revision %s of file %s", vers->vn_rcs, + vers->srcfile->path); + error (0, retcode == -1 ? ierrno : 0, + "ERROR: cannot co revision %s of file %s", vers->vn_rcs, + vers->srcfile->path); + (void) unlink_file (xtmpfile); + return (1); + } + + tocvsPath = wrap_tocvs_process_file (vfile); + different = xcmp (xtmpfile, vfile); + if (tocvsPath) + if (unlink_file_dir (tocvsPath) < 0) + error (0, errno, "cannot remove %s", tocvsPath); + + (void) unlink_file (xtmpfile); + if (!different) + { + int retval = 0; + + /* + * The two files are identical. Just update the tags, print the + * "U", signifying that the file has changed, but needs no + * attention, and we're done. + */ + if (add_tags (vers->srcfile->path, vfile, vtag, targc, targv)) + retval = 1; + add_log ('U', vfile); + freevers_ts (&vers); + return (retval); + } + } + + /* We may have failed to parse the RCS file; check just in case */ + if (vers->srcfile == NULL || + add_rev (message, vers->srcfile->path, vfile, vers->vn_rcs) || + add_tags (vers->srcfile->path, vfile, vtag, targc, targv)) + { + freevers_ts (&vers); + return (1); + } + + if (vers->srcfile->branch == NULL || inattic || + strcmp (vers->srcfile->branch, vbranch) != 0) + { + conflicts++; + letter = 'C'; + } + else + letter = 'U'; + add_log (letter, vfile); + + freevers_ts (&vers); + return (0); +} + +/* + * Add the revision to the vendor branch + */ +static int +add_rev (message, rcs, vfile, vers) + char *message; + char *rcs; + char *vfile; + char *vers; +{ + int locked, status, ierrno; + char *tocvsPath; + + if (noexec) + return (0); + + locked = 0; + if (vers != NULL) + { + /* Before RCS_lock existed, we were directing stdout, as well as + stderr, from the RCS command, to DEVNULL. I wouldn't guess that + was necessary, but I don't know for sure. */ + if (RCS_lock (rcs, vbranch, 1) != 0) + { + error (0, errno, "fork failed"); + return (1); + } + locked = 1; + } + tocvsPath = wrap_tocvs_process_file (vfile); + if (tocvsPath == NULL) + { + /* We play with hard links rather than passing -u to ci to avoid + expanding RCS keywords (see test 106.5 in sanity.sh). */ + if (link_file (vfile, FILE_HOLDER) < 0) + { + if (errno == EEXIST) + { + (void) unlink_file (FILE_HOLDER); + (void) link_file (vfile, FILE_HOLDER); + } + else + { + ierrno = errno; + fperror (logfp, 0, ierrno, + "ERROR: cannot create link to %s", vfile); + error (0, ierrno, "ERROR: cannot create link to %s", vfile); + return (1); + } + } + } + + status = RCS_checkin (rcs, tocvsPath == NULL ? vfile : tocvsPath, + message, vbranch, + (RCS_FLAGS_QUIET + | (use_file_modtime ? RCS_FLAGS_MODTIME : 0)), + 0); + ierrno = errno; + + if (tocvsPath == NULL) + rename_file (FILE_HOLDER, vfile); + else + if (unlink_file_dir (tocvsPath) < 0) + error (0, errno, "cannot remove %s", tocvsPath); + + if (status) + { + if (!noexec) + { + fperror (logfp, 0, status == -1 ? ierrno : 0, "ERROR: Check-in of %s failed", rcs); + error (0, status == -1 ? ierrno : 0, "ERROR: Check-in of %s failed", rcs); + } + if (locked) + { + (void) RCS_unlock(rcs, vbranch, 0); + } + return (1); + } + return (0); +} + +/* + * Add the vendor branch tag and all the specified import release tags to the + * RCS file. The vendor branch tag goes on the branch root (1.1.1) while the + * vendor release tags go on the newly added leaf of the branch (1.1.1.1, + * 1.1.1.2, ...). + */ +static int +add_tags (rcs, vfile, vtag, targc, targv) + char *rcs; + char *vfile; + char *vtag; + int targc; + char *targv[]; +{ + int i, ierrno; + Vers_TS *vers; + int retcode = 0; + + if (noexec) + return (0); + + if ((retcode = RCS_settag(rcs, vtag, vbranch)) != 0) + { + ierrno = errno; + fperror (logfp, 0, retcode == -1 ? ierrno : 0, + "ERROR: Failed to set tag %s in %s", vtag, rcs); + error (0, retcode == -1 ? ierrno : 0, + "ERROR: Failed to set tag %s in %s", vtag, rcs); + return (1); + } + vers = Version_TS (repository, (char *) NULL, vtag, (char *) NULL, vfile, + 1, 0, (List *) NULL, (RCSNode *) NULL); + for (i = 0; i < targc; i++) + { + if ((retcode = RCS_settag (rcs, targv[i], vers->vn_rcs)) != 0) + { + ierrno = errno; + fperror (logfp, 0, retcode == -1 ? ierrno : 0, + "WARNING: Couldn't add tag %s to %s", targv[i], rcs); + error (0, retcode == -1 ? ierrno : 0, + "WARNING: Couldn't add tag %s to %s", targv[i], rcs); + } + } + freevers_ts (&vers); + return (0); +} + +/* + * Stolen from rcs/src/rcsfnms.c, and adapted/extended. + */ +struct compair +{ + char *suffix, *comlead; +}; + +static const struct compair comtable[] = +{ + +/* + * comtable pairs each filename suffix with a comment leader. The comment + * leader is placed before each line generated by the $Log keyword. This + * table is used to guess the proper comment leader from the working file's + * suffix during initial ci (see InitAdmin()). Comment leaders are needed for + * languages without multiline comments; for others they are optional. + */ + {"a", "-- "}, /* Ada */ + {"ada", "-- "}, + {"adb", "-- "}, + {"asm", ";; "}, /* assembler (MS-DOS) */ + {"ads", "-- "}, /* Ada */ + {"bas", "' "}, /* Visual Basic code */ + {"bat", ":: "}, /* batch (MS-DOS) */ + {"body", "-- "}, /* Ada */ + {"c", " * "}, /* C */ + {"c++", "// "}, /* C++ in all its infinite guises */ + {"cc", "// "}, + {"cpp", "// "}, + {"cxx", "// "}, + {"m", "// "}, /* Objective-C */ + {"cl", ";;; "}, /* Common Lisp */ + {"cmd", ":: "}, /* command (OS/2) */ + {"cmf", "c "}, /* CM Fortran */ + {"cs", " * "}, /* C* */ + {"csh", "# "}, /* shell */ + {"dlg", " * "}, /* MS Windows dialog file */ + {"e", "# "}, /* efl */ + {"epsf", "% "}, /* encapsulated postscript */ + {"epsi", "% "}, /* encapsulated postscript */ + {"el", "; "}, /* Emacs Lisp */ + {"f", "c "}, /* Fortran */ + {"for", "c "}, + {"frm", "' "}, /* Visual Basic form */ + {"h", " * "}, /* C-header */ + {"hh", "// "}, /* C++ header */ + {"hpp", "// "}, + {"hxx", "// "}, + {"in", "# "}, /* for Makefile.in */ + {"l", " * "}, /* lex (conflict between lex and + * franzlisp) */ + {"mac", ";; "}, /* macro (DEC-10, MS-DOS, PDP-11, + * VMS, etc) */ + {"mak", "# "}, /* makefile, e.g. Visual C++ */ + {"me", ".\\\" "}, /* me-macros t/nroff */ + {"ml", "; "}, /* mocklisp */ + {"mm", ".\\\" "}, /* mm-macros t/nroff */ + {"ms", ".\\\" "}, /* ms-macros t/nroff */ + {"man", ".\\\" "}, /* man-macros t/nroff */ + {"1", ".\\\" "}, /* feeble attempt at man pages... */ + {"2", ".\\\" "}, + {"3", ".\\\" "}, + {"4", ".\\\" "}, + {"5", ".\\\" "}, + {"6", ".\\\" "}, + {"7", ".\\\" "}, + {"8", ".\\\" "}, + {"9", ".\\\" "}, + {"p", " * "}, /* pascal */ + {"pas", " * "}, + {"pl", "# "}, /* perl (conflict with Prolog) */ + {"ps", "% "}, /* postscript */ + {"psw", "% "}, /* postscript wrap */ + {"pswm", "% "}, /* postscript wrap */ + {"r", "# "}, /* ratfor */ + {"rc", " * "}, /* Microsoft Windows resource file */ + {"red", "% "}, /* psl/rlisp */ +#ifdef sparc + {"s", "! "}, /* assembler */ +#endif +#ifdef mc68000 + {"s", "| "}, /* assembler */ +#endif +#ifdef pdp11 + {"s", "/ "}, /* assembler */ +#endif +#ifdef vax + {"s", "# "}, /* assembler */ +#endif +#ifdef __ksr__ + {"s", "# "}, /* assembler */ + {"S", "# "}, /* Macro assembler */ +#endif + {"sh", "# "}, /* shell */ + {"sl", "% "}, /* psl */ + {"spec", "-- "}, /* Ada */ + {"tex", "% "}, /* tex */ + {"y", " * "}, /* yacc */ + {"ye", " * "}, /* yacc-efl */ + {"yr", " * "}, /* yacc-ratfor */ + {"", "# "}, /* default for empty suffix */ + {NULL, "# "} /* default for unknown suffix; */ +/* must always be last */ +}; + +static char * +get_comment (user) + char *user; +{ + char *cp, *suffix; + char suffix_path[PATH_MAX]; + int i; + + cp = strrchr (user, '.'); + if (cp != NULL) + { + cp++; + + /* + * Convert to lower-case, since we are not concerned about the + * case-ness of the suffix. + */ + (void) strcpy (suffix_path, cp); + for (cp = suffix_path; *cp; cp++) + if (isupper (*cp)) + *cp = tolower (*cp); + suffix = suffix_path; + } + else + suffix = ""; /* will use the default */ + for (i = 0;; i++) + { + if (comtable[i].suffix == NULL) /* default */ + return (comtable[i].comlead); + if (strcmp (suffix, comtable[i].suffix) == 0) + return (comtable[i].comlead); + } +} + +static int +add_rcs_file (message, rcs, user, vtag, targc, targv) + char *message; + char *rcs; + char *user; + char *vtag; + int targc; + char *targv[]; +{ + FILE *fprcs, *fpuser; + struct stat sb; + struct tm *ftm; + time_t now; + char altdate1[50]; +#ifndef HAVE_RCS5 + char altdate2[50]; +#endif + char *author; + int i, ierrno, err = 0; + mode_t mode; + char *tocvsPath; + char *userfile; + + if (noexec) + return (0); + + /* FIXME? We always import files as text files (note that means + that files get stored with straight linefeeds). There isn't an + obvious, clean, way to let people specify which files are binary. + Maybe based on the file name.... */ + tocvsPath = wrap_tocvs_process_file (user); + userfile = (tocvsPath == NULL ? user : tocvsPath); + fpuser = fopen (userfile, "r"); + if (fpuser == NULL) + { + /* not fatal, continue import */ + fperror (logfp, 0, errno, "ERROR: cannot read file %s", userfile); + error (0, errno, "ERROR: cannot read file %s", userfile); + goto read_error; + } + fprcs = fopen (rcs, "w+b"); + if (fprcs == NULL) + { + ierrno = errno; + goto write_error_noclose; + } + + /* + * putadmin() + */ + if (fprintf (fprcs, "head %s;\012", vhead) < 0 || + fprintf (fprcs, "branch %s;\012", vbranch) < 0 || + fprintf (fprcs, "access ;\012") < 0 || + fprintf (fprcs, "symbols ") < 0) + { + goto write_error; + } + + for (i = targc - 1; i >= 0; i--) /* RCS writes the symbols backwards */ + if (fprintf (fprcs, "%s:%s.1 ", targv[i], vbranch) < 0) + goto write_error; + + if (fprintf (fprcs, "%s:%s;\012", vtag, vbranch) < 0 || + fprintf (fprcs, "locks ; strict;\012") < 0 || + /* XXX - make sure @@ processing works in the RCS file */ + fprintf (fprcs, "comment @%s@;\012", get_comment (user)) < 0) + { + goto write_error; + } + + if (keyword_opt != NULL) + if (fprintf (fprcs, "expand @%s@;\012", keyword_opt) < 0) + { + goto write_error; + } + + if (fprintf (fprcs, "\012") < 0) + goto write_error; + + /* + * puttree() + */ + if (fstat (fileno (fpuser), &sb) < 0) + error (1, errno, "cannot fstat %s", user); + if (use_file_modtime) + now = sb.st_mtime; + else + (void) time (&now); +#ifdef HAVE_RCS5 + ftm = gmtime (&now); +#else + ftm = localtime (&now); +#endif + (void) sprintf (altdate1, DATEFORM, + ftm->tm_year + (ftm->tm_year < 100 ? 0 : 1900), + ftm->tm_mon + 1, ftm->tm_mday, ftm->tm_hour, + ftm->tm_min, ftm->tm_sec); +#ifdef HAVE_RCS5 +#define altdate2 altdate1 +#else + /* + * If you don't have RCS V5 or later, you need to lie about the ci + * time, since RCS V4 and earlier insist that the times differ. + */ + now++; + ftm = localtime (&now); + (void) sprintf (altdate2, DATEFORM, + ftm->tm_year + (ftm->tm_year < 100 ? 0 : 1900), + ftm->tm_mon + 1, ftm->tm_mday, ftm->tm_hour, + ftm->tm_min, ftm->tm_sec); +#endif + author = getcaller (); + + if (fprintf (fprcs, "\012%s\012", vhead) < 0 || + fprintf (fprcs, "date %s; author %s; state Exp;\012", + altdate1, author) < 0 || + fprintf (fprcs, "branches %s.1;\012", vbranch) < 0 || + fprintf (fprcs, "next ;\012") < 0 || + fprintf (fprcs, "\012%s.1\012", vbranch) < 0 || + fprintf (fprcs, "date %s; author %s; state Exp;\012", + altdate2, author) < 0 || + fprintf (fprcs, "branches ;\012") < 0 || + fprintf (fprcs, "next ;\012\012") < 0 || + /* + * putdesc() + */ + fprintf (fprcs, "\012desc\012") < 0 || + fprintf (fprcs, "@@\012\012\012") < 0 || + /* + * putdelta() + */ + fprintf (fprcs, "\012%s\012", vhead) < 0 || + fprintf (fprcs, "log\012") < 0 || + fprintf (fprcs, "@Initial revision\012@\012") < 0 || + fprintf (fprcs, "text\012@") < 0) + { + goto write_error; + } + + /* Now copy over the contents of the file, expanding at signs. */ + { + unsigned char buf[8192]; + unsigned int len; + + while (1) + { + len = fread (buf, 1, sizeof buf, fpuser); + if (len == 0) + { + if (ferror (fpuser)) + error (1, errno, "cannot read file %s for copying", user); + break; + } + if (expand_at_signs (buf, len, fprcs) < 0) + goto write_error; + } + } + if (fprintf (fprcs, "@\012\012") < 0 || + fprintf (fprcs, "\012%s.1\012", vbranch) < 0 || + fprintf (fprcs, "log\012@") < 0 || + expand_at_signs (message, (off_t) strlen (message), fprcs) < 0 || + fprintf (fprcs, "@\012text\012") < 0 || + fprintf (fprcs, "@@\012") < 0) + { + goto write_error; + } + if (fclose (fprcs) == EOF) + { + ierrno = errno; + goto write_error_noclose; + } + (void) fclose (fpuser); + + /* + * Fix the modes on the RCS files. The user modes of the original + * user file are propagated to the group and other modes as allowed + * by the repository umask, except that all write permissions are + * turned off. + */ + mode = (sb.st_mode | + (sb.st_mode & S_IRWXU) >> 3 | + (sb.st_mode & S_IRWXU) >> 6) & + ~cvsumask & + ~(S_IWRITE | S_IWGRP | S_IWOTH); + if (chmod (rcs, mode) < 0) + { + ierrno = errno; + fperror (logfp, 0, ierrno, + "WARNING: cannot change mode of file %s", rcs); + error (0, ierrno, "WARNING: cannot change mode of file %s", rcs); + err++; + } + if (tocvsPath) + if (unlink_file_dir (tocvsPath) < 0) + error (0, errno, "cannot remove %s", tocvsPath); + return (err); + +write_error: + ierrno = errno; + (void) fclose (fprcs); +write_error_noclose: + (void) fclose (fpuser); + fperror (logfp, 0, ierrno, "ERROR: cannot write file %s", rcs); + error (0, ierrno, "ERROR: cannot write file %s", rcs); + if (ierrno == ENOSPC) + { + (void) unlink (rcs); + fperror (logfp, 0, 0, "ERROR: out of space - aborting"); + error (1, 0, "ERROR: out of space - aborting"); + } +read_error: + if (tocvsPath) + if (unlink_file_dir (tocvsPath) < 0) + error (0, errno, "cannot remove %s", tocvsPath); + + return (err + 1); +} + +/* + * Write SIZE bytes at BUF to FP, expanding @ signs into double @ + * signs. If an error occurs, return a negative value and set errno + * to indicate the error. If not, return a nonnegative value. + */ +static int +expand_at_signs (buf, size, fp) + char *buf; + off_t size; + FILE *fp; +{ + char *cp, *end; + + errno = 0; + for (cp = buf, end = buf + size; cp < end; cp++) + { + if (*cp == '@') + { + if (putc ('@', fp) == EOF && errno != 0) + return EOF; + } + if (putc (*cp, fp) == EOF && errno != 0) + return (EOF); + } + return (1); +} + +/* + * Write an update message to (potentially) the screen and the log file. + */ +static void +add_log (ch, fname) + int ch; + char *fname; +{ + if (!really_quiet) /* write to terminal */ + { + char buf[2]; + buf[0] = ch; + buf[1] = ' '; + cvs_output (buf, 2); + if (repos_len) + { + cvs_output (repository + repos_len + 1, 0); + cvs_output ("/", 1); + } + else if (repository[0] != '\0') + { + cvs_output (repository, 0); + cvs_output ("/", 1); + } + cvs_output (fname, 0); + cvs_output ("\n", 1); + } + + if (repos_len) /* write to logfile */ + (void) fprintf (logfp, "%c %s/%s\n", ch, + repository + repos_len + 1, fname); + else if (repository[0]) + (void) fprintf (logfp, "%c %s/%s\n", ch, repository, fname); + else + (void) fprintf (logfp, "%c %s\n", ch, fname); +} + +/* + * This is the recursive function that walks the argument directory looking + * for sub-directories that have CVS administration files in them and updates + * them recursively. + * + * Note that we do not follow symbolic links here, which is a feature! + */ +static int +import_descend_dir (message, dir, vtag, targc, targv) + char *message; + char *dir; + char *vtag; + int targc; + char *targv[]; +{ + struct saved_cwd cwd; + char *cp; + int ierrno, err; + + if (islink (dir)) + return (0); + if (save_cwd (&cwd)) + { + fperror (logfp, 0, 0, "ERROR: cannot get working directory"); + return (1); + } + if (repository[0] == '\0') + (void) strcpy (repository, dir); + else + { + (void) strcat (repository, "/"); + (void) strcat (repository, dir); + } +#ifdef CLIENT_SUPPORT + if (!quiet && !client_active) +#else + if (!quiet) +#endif + error (0, 0, "Importing %s", repository); + + if (chdir (dir) < 0) + { + ierrno = errno; + fperror (logfp, 0, ierrno, "ERROR: cannot chdir to %s", repository); + error (0, ierrno, "ERROR: cannot chdir to %s", repository); + err = 1; + goto out; + } +#ifdef CLIENT_SUPPORT + if (!client_active && !isdir (repository)) +#else + if (!isdir (repository)) +#endif + { + char rcs[PATH_MAX]; + + (void) sprintf (rcs, "%s%s", repository, RCSEXT); + if (isfile (repository) || isfile(rcs)) + { + fperror (logfp, 0, 0, "ERROR: %s is a file, should be a directory!", + repository); + error (0, 0, "ERROR: %s is a file, should be a directory!", + repository); + err = 1; + goto out; + } + if (noexec == 0 && CVS_MKDIR (repository, 0777) < 0) + { + ierrno = errno; + fperror (logfp, 0, ierrno, + "ERROR: cannot mkdir %s -- not added", repository); + error (0, ierrno, + "ERROR: cannot mkdir %s -- not added", repository); + err = 1; + goto out; + } + } + err = import_descend (message, vtag, targc, targv); + out: + if ((cp = strrchr (repository, '/')) != NULL) + *cp = '\0'; + else + repository[0] = '\0'; + if (restore_cwd (&cwd, NULL)) + exit (EXIT_FAILURE); + free_cwd (&cwd); + return (err); +} diff --git a/contrib/cvs/src/lock.c b/contrib/cvs/src/lock.c new file mode 100644 index 0000000..7e35aed --- /dev/null +++ b/contrib/cvs/src/lock.c @@ -0,0 +1,639 @@ +/* + * Copyright (c) 1992, Brian Berliner and Jeff Polk + * Copyright (c) 1989-1992, Brian Berliner + * + * You may distribute under the terms of the GNU General Public License as + * specified in the README file that comes with the CVS 1.4 kit. + * + * Set Lock + * + * Lock file support for CVS. + */ + +#include "cvs.h" + +static int readers_exist PROTO((char *repository)); +static int set_lock PROTO((char *repository, int will_wait)); +static void clear_lock PROTO((void)); +static void set_lockers_name PROTO((struct stat *statp)); +static int set_writelock_proc PROTO((Node * p, void *closure)); +static int unlock_proc PROTO((Node * p, void *closure)); +static int write_lock PROTO((char *repository)); +static void lock_simple_remove PROTO((char *repository)); +static void lock_wait PROTO((char *repository)); +static int Check_Owner PROTO((char *lockdir)); + +static char lockers_name[20]; +static char *repository; +static char readlock[PATH_MAX], writelock[PATH_MAX], masterlock[PATH_MAX]; +static int cleanup_lckdir; +static List *locklist; + +#define L_OK 0 /* success */ +#define L_ERROR 1 /* error condition */ +#define L_LOCKED 2 /* lock owned by someone else */ + +/* + * Clean up all outstanding locks + */ +void +Lock_Cleanup () +{ + /* clean up simple locks (if any) */ + if (repository != NULL) + { + lock_simple_remove (repository); + repository = (char *) NULL; + } + + /* clean up multiple locks (if any) */ + if (locklist != (List *) NULL) + { + (void) walklist (locklist, unlock_proc, NULL); + locklist = (List *) NULL; + } +} + +/* + * walklist proc for removing a list of locks + */ +static int +unlock_proc (p, closure) + Node *p; + void *closure; +{ + lock_simple_remove (p->key); + return (0); +} + +/* + * Remove the lock files (without complaining if they are not there), + */ +static void +lock_simple_remove (repository) + char *repository; +{ + char tmp[PATH_MAX]; + + if (readlock[0] != '\0') + { + (void) sprintf (tmp, "%s/%s", repository, readlock); + if (unlink (tmp) < 0 && ! existence_error (errno)) + error (0, errno, "failed to remove lock %s", tmp); + } + + if (writelock[0] != '\0') + { + (void) sprintf (tmp, "%s/%s", repository, writelock); + if (unlink (tmp) < 0 && ! existence_error (errno)) + error (0, errno, "failed to remove lock %s", tmp); + } + + /* + * Only remove the lock directory if it is ours, note that this does + * lead to the limitation that one user ID should not be committing + * files into the same Repository directory at the same time. Oh well. + */ + if (writelock[0] != '\0' || (readlock[0] != '\0' && cleanup_lckdir)) + { + (void) sprintf (tmp, "%s/%s", repository, CVSLCK); + if (Check_Owner(tmp)) + { +#ifdef AFSCVS + char rmuidlock[PATH_MAX]; + sprintf(rmuidlock, "rm -f %s/uidlock%d", tmp, geteuid() ); + system(rmuidlock); +#endif + (void) rmdir (tmp); + } + } + cleanup_lckdir = 0; +} + +/* + * Check the owner of a lock. Returns 1 if we own it, 0 otherwise. + */ +static int +Check_Owner(lockdir) + char *lockdir; +{ + struct stat sb; + +#ifdef AFSCVS + /* In the Andrew File System (AFS), user ids from stat don't match + those from geteuid(). The AFSCVS code can deal with either AFS or + non-AFS repositories; the non-AFSCVS code is faster. */ + char uidlock[PATH_MAX]; + + /* Check if the uidlock is in the lock directory */ + sprintf(uidlock, "%s/uidlock%d", lockdir, geteuid() ); + if( stat(uidlock, &sb) != -1) + return 1; /* The file exists, therefore we own the lock */ + else + return 0; /* The file didn't exist or some other error. + * Assume that we don't own it. + */ +#else + if (stat (lockdir, &sb) != -1 && sb.st_uid == geteuid ()) + return 1; + else + return 0; +#endif +} /* end Check_Owner() */ + + +/* + * Create a lock file for readers + */ +int +Reader_Lock (xrepository) + char *xrepository; +{ + int err = 0; + FILE *fp; + char tmp[PATH_MAX]; + + if (noexec) + return (0); + + /* we only do one directory at a time for read locks! */ + if (repository != NULL) + { + error (0, 0, "Reader_Lock called while read locks set - Help!"); + return (1); + } + + if (readlock[0] == '\0') + (void) sprintf (readlock, +#ifdef HAVE_LONG_FILE_NAMES + "%s.%s.%ld", CVSRFL, hostname, +#else + "%s.%ld", CVSRFL, +#endif + (long) getpid ()); + + /* remember what we're locking (for lock_cleanup) */ + repository = xrepository; + + /* get the lock dir for our own */ + if (set_lock (xrepository, 1) != L_OK) + { + error (0, 0, "failed to obtain dir lock in repository `%s'", + xrepository); + readlock[0] = '\0'; + return (1); + } + + /* write a read-lock */ + (void) sprintf (tmp, "%s/%s", xrepository, readlock); + if ((fp = fopen (tmp, "w+")) == NULL || fclose (fp) == EOF) + { + error (0, errno, "cannot create read lock in repository `%s'", + xrepository); + readlock[0] = '\0'; + err = 1; + } + + /* free the lock dir */ + clear_lock(); + + return (err); +} + +/* + * Lock a list of directories for writing + */ +static char *lock_error_repos; +static int lock_error; +int +Writer_Lock (list) + List *list; +{ + if (noexec) + return (0); + + /* We only know how to do one list at a time */ + if (locklist != (List *) NULL) + { + error (0, 0, "Writer_Lock called while write locks set - Help!"); + return (1); + } + + for (;;) + { + /* try to lock everything on the list */ + lock_error = L_OK; /* init for set_writelock_proc */ + lock_error_repos = (char *) NULL; /* init for set_writelock_proc */ + locklist = list; /* init for Lock_Cleanup */ + (void) strcpy (lockers_name, "unknown"); + + (void) walklist (list, set_writelock_proc, NULL); + + switch (lock_error) + { + case L_ERROR: /* Real Error */ + Lock_Cleanup (); /* clean up any locks we set */ + error (0, 0, "lock failed - giving up"); + return (1); + + case L_LOCKED: /* Someone already had a lock */ + Lock_Cleanup (); /* clean up any locks we set */ + lock_wait (lock_error_repos); /* sleep a while and try again */ + continue; + + case L_OK: /* we got the locks set */ + return (0); + + default: + error (0, 0, "unknown lock status %d in Writer_Lock", + lock_error); + return (1); + } + } +} + +/* + * walklist proc for setting write locks + */ +static int +set_writelock_proc (p, closure) + Node *p; + void *closure; +{ + /* if some lock was not OK, just skip this one */ + if (lock_error != L_OK) + return (0); + + /* apply the write lock */ + lock_error_repos = p->key; + lock_error = write_lock (p->key); + return (0); +} + +/* + * Create a lock file for writers returns L_OK if lock set ok, L_LOCKED if + * lock held by someone else or L_ERROR if an error occurred + */ +static int +write_lock (repository) + char *repository; +{ + int status; + FILE *fp; + char tmp[PATH_MAX]; + + if (writelock[0] == '\0') + (void) sprintf (writelock, +#ifdef HAVE_LONG_FILE_NAMES + "%s.%s.%ld", CVSWFL, hostname, +#else + "%s.%ld", CVSWFL, +#endif + (long) getpid()); + + /* make sure the lock dir is ours (not necessarily unique to us!) */ + status = set_lock (repository, 0); + if (status == L_OK) + { + /* we now own a writer - make sure there are no readers */ + if (readers_exist (repository)) + { + /* clean up the lock dir if we created it */ + if (status == L_OK) + { + clear_lock(); + } + + /* indicate we failed due to read locks instead of error */ + return (L_LOCKED); + } + + /* write the write-lock file */ + (void) sprintf (tmp, "%s/%s", repository, writelock); + if ((fp = fopen (tmp, "w+")) == NULL || fclose (fp) == EOF) + { + int xerrno = errno; + + if (unlink (tmp) < 0 && ! existence_error (errno)) + error (0, errno, "failed to remove lock %s", tmp); + + /* free the lock dir if we created it */ + if (status == L_OK) + { + clear_lock(); + } + + /* return the error */ + error (0, xerrno, "cannot create write lock in repository `%s'", + repository); + return (L_ERROR); + } + return (L_OK); + } + else + return (status); +} + +/* + * readers_exist() returns 0 if there are no reader lock files remaining in + * the repository; else 1 is returned, to indicate that the caller should + * sleep a while and try again. + */ +static int +readers_exist (repository) + char *repository; +{ + char *line; + DIR *dirp; + struct dirent *dp; + struct stat sb; + int ret = 0; + +#ifdef CVS_FUDGELOCKS +again: +#endif + + if ((dirp = opendir (repository)) == NULL) + error (1, 0, "cannot open directory %s", repository); + + errno = 0; + while ((dp = readdir (dirp)) != NULL) + { + if (fnmatch (CVSRFLPAT, dp->d_name, 0) == 0) + { +#ifdef CVS_FUDGELOCKS + time_t now; + (void) time (&now); +#endif + + line = xmalloc (strlen (repository) + strlen (dp->d_name) + 5); + (void) sprintf (line, "%s/%s", repository, dp->d_name); + if (stat (line, &sb) != -1) + { +#ifdef CVS_FUDGELOCKS + /* + * If the create time of the file is more than CVSLCKAGE + * seconds ago, try to clean-up the lock file, and if + * successful, re-open the directory and try again. + */ + if (now >= (sb.st_ctime + CVSLCKAGE) && unlink (line) != -1) + { + (void) closedir (dirp); + free (line); + goto again; + } +#endif + set_lockers_name (&sb); + } + else + { + /* If the file doesn't exist, it just means that it disappeared + between the time we did the readdir and the time we did + the stat. */ + if (!existence_error (errno)) + error (0, errno, "cannot stat %s", line); + } + errno = 0; + free (line); + + ret = 1; + break; + } + errno = 0; + } + if (errno != 0) + error (0, errno, "error reading directory %s", repository); + + closedir (dirp); + return (ret); +} + +/* + * Set the static variable lockers_name appropriately, based on the stat + * structure passed in. + */ +static void +set_lockers_name (statp) + struct stat *statp; +{ + struct passwd *pw; + + if ((pw = (struct passwd *) getpwuid (statp->st_uid)) != + (struct passwd *) NULL) + { + (void) strcpy (lockers_name, pw->pw_name); + } + else + (void) sprintf (lockers_name, "uid%lu", (unsigned long) statp->st_uid); +} + +/* + * Persistently tries to make the directory "lckdir",, which serves as a + * lock. If the create time on the directory is greater than CVSLCKAGE + * seconds old, just try to remove the directory. + */ +static int +set_lock (repository, will_wait) + char *repository; + int will_wait; +{ + struct stat sb; + mode_t omask; +#ifdef CVS_FUDGELOCKS + time_t now; +#endif + + (void) sprintf (masterlock, "%s/%s", repository, CVSLCK); + + /* + * Note that it is up to the callers of set_lock() to arrange for signal + * handlers that do the appropriate things, like remove the lock + * directory before they exit. + */ + cleanup_lckdir = 0; + for (;;) + { + int status = -1; + omask = umask (cvsumask); + SIG_beginCrSect (); + if (CVS_MKDIR (masterlock, 0777) == 0) + { +#ifdef AFSCVS + char uidlock[PATH_MAX]; + FILE *fp; + + sprintf(uidlock, "%s/uidlock%d", masterlock, geteuid() ); + if ((fp = fopen(uidlock, "w+")) == NULL) + { + /* We failed to create the uidlock, + so rm masterlock and leave */ + rmdir(masterlock); + SIG_endCrSect (); + status = L_ERROR; + goto out; + } + + /* We successfully created the uid lock, so close the file */ + fclose(fp); +#endif + cleanup_lckdir = 1; + SIG_endCrSect (); + status = L_OK; + goto out; + } + SIG_endCrSect (); + out: + (void) umask (omask); + if (status != -1) + return status; + + if (errno != EEXIST) + { + error (0, errno, + "failed to create lock directory in repository `%s'", + repository); + return (L_ERROR); + } + + /* + * stat the dir - if it is non-existent, re-try the loop since + * someone probably just removed it (thus releasing the lock) + */ + if (stat (masterlock, &sb) < 0) + { + if (existence_error (errno)) + continue; + + error (0, errno, "couldn't stat lock directory `%s'", masterlock); + return (L_ERROR); + } + +#ifdef CVS_FUDGELOCKS + /* + * If the create time of the directory is more than CVSLCKAGE seconds + * ago, try to clean-up the lock directory, and if successful, just + * quietly retry to make it. + */ + (void) time (&now); + if (now >= (sb.st_ctime + CVSLCKAGE)) + { +#ifdef AFSCVS + /* Remove the uidlock first */ + char rmuidlock[PATH_MAX]; + sprintf(rmuidlock, "rm -f %s/uidlock%d", masterlock, geteuid() ); + system(rmuidlock); +#endif + if (rmdir (masterlock) >= 0) + continue; + } +#endif + + /* set the lockers name */ + set_lockers_name (&sb); + + /* if he wasn't willing to wait, return an error */ + if (!will_wait) + return (L_LOCKED); + lock_wait (repository); + } +} + +/* + * Clear master lock. We don't have to recompute the lock name since + * clear_lock is never called except after a successful set_lock(). + */ +static void +clear_lock() +{ +#ifdef AFSCVS + /* Remove the uidlock first */ + char rmuidlock[PATH_MAX]; + sprintf(rmuidlock, "rm -f %s/uidlock%d", masterlock, geteuid() ); + system(rmuidlock); +#endif + if (rmdir (masterlock) < 0) + error (0, errno, "failed to remove lock dir `%s'", masterlock); + cleanup_lckdir = 0; +} + +/* + * Print out a message that the lock is still held, then sleep a while. + */ +static void +lock_wait (repos) + char *repos; +{ + time_t now; + + (void) time (&now); + error (0, 0, "[%8.8s] waiting for %s's lock in %s", ctime (&now) + 11, + lockers_name, repos); + (void) sleep (CVSLCKSLEEP); +} + +static int lock_filesdoneproc PROTO ((int err, char *repository, + char *update_dir)); +static int fsortcmp PROTO((const Node * p, const Node * q)); + +static List *lock_tree_list; + +/* + * Create a list of repositories to lock + */ +/* ARGSUSED */ +static int +lock_filesdoneproc (err, repository, update_dir) + int err; + char *repository; + char *update_dir; +{ + Node *p; + + p = getnode (); + p->type = LOCK; + p->key = xstrdup (repository); + /* FIXME-KRP: this error condition should not simply be passed by. */ + if (p->key == NULL || addnode (lock_tree_list, p) != 0) + freenode (p); + 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; + char **argv; + int local; + int aflag; +{ + int err; + /* + * Run the recursion processor to find all the dirs to lock and lock all + * the dirs + */ + lock_tree_list = getlist (); + err = start_recursion ((FILEPROC) NULL, lock_filesdoneproc, + (DIRENTPROC) NULL, (DIRLEAVEPROC) NULL, argc, + argv, local, W_LOCAL, aflag, 0, (char *) NULL, 0, + 0); + sortlist (lock_tree_list, fsortcmp); + if (Writer_Lock (lock_tree_list) != 0) + error (1, 0, "lock failed - giving up"); +} + +void +lock_tree_cleanup () +{ + Lock_Cleanup (); + dellist (&lock_tree_list); +} diff --git a/contrib/cvs/src/log.c b/contrib/cvs/src/log.c new file mode 100644 index 0000000..f167d92 --- /dev/null +++ b/contrib/cvs/src/log.c @@ -0,0 +1,163 @@ +/* + * Copyright (c) 1992, Brian Berliner and Jeff Polk + * Copyright (c) 1989-1992, Brian Berliner + * + * You may distribute under the terms of the GNU General Public License as + * specified in the README file that comes with the CVS 1.4 kit. + * + * Print Log Information + * + * This line exists solely to test some pcl-cvs/ChangeLog stuff. You + * can delete it, if indeed it's still here when you read it. -Karl + * + * Prints the RCS "log" (rlog) information for the specified files. With no + * argument, prints the log information for all the files in the directory + * (recursive by default). + */ + +#include "cvs.h" + +static Dtype log_dirproc PROTO((char *dir, char *repository, char *update_dir)); +static int log_fileproc PROTO((struct file_info *finfo)); + +static const char *const log_usage[] = +{ + "Usage: %s %s [-l] [rlog-options] [files...]\n", + "\t-l\tLocal directory only, no recursion.\n", + NULL +}; + +static int ac; +static char **av; + +int +cvslog (argc, argv) + int argc; + char **argv; +{ + int i; + int err = 0; + int local = 0; + + if (argc == -1) + usage (log_usage); + + /* + * All 'log' command options except -l are passed directly on to 'rlog' + */ + for (i = 1; i < argc && argv[i][0] == '-'; i++) + if (argv[i][1] == 'l') + local = 1; + + wrap_setup (); + +#ifdef CLIENT_SUPPORT + if (client_active) { + /* We're the local client. Fire up the remote server. */ + start_server (); + + ign_setup (); + + for (i = 1; i < argc && argv[i][0] == '-'; i++) + send_arg (argv[i]); + + send_file_names (argc - i, argv + i, SEND_EXPAND_WILD); +/* FIXME: We shouldn't have to send current files to get log entries, but it + doesn't work yet and I haven't debugged it. So send the files -- + it's slower but it works. gnu@cygnus.com Apr94 */ + send_files (argc - i, argv + i, local, 0); + + send_to_server ("log\012", 0); + err = get_responses_and_close (); + return err; + } + + ac = argc; + av = argv; +#endif + + err = start_recursion (log_fileproc, (FILESDONEPROC) NULL, log_dirproc, + (DIRLEAVEPROC) NULL, argc - i, argv + i, local, + W_LOCAL | W_REPOS | W_ATTIC, 0, 1, + (char *) NULL, 1, 0); + return (err); +} + + +/* + * Do an rlog on a file + */ +/* ARGSUSED */ +static int +log_fileproc (finfo) + struct file_info *finfo; +{ + Node *p; + RCSNode *rcsfile; + int retcode = 0; + + if ((rcsfile = finfo->rcs) == NULL) + { + /* no rcs file. What *do* we know about this file? */ + p = findnode (finfo->entries, finfo->file); + if (p != NULL) + { + Entnode *e; + + e = (Entnode *) p->data; + if (e->version[0] == '0' || e->version[1] == '\0') + { + if (!really_quiet) + error (0, 0, "%s has been added, but not committed", + finfo->file); + return(0); + } + } + + if (!really_quiet) + error (0, 0, "nothing known about %s", finfo->file); + + return (1); + } + + run_setup ("%s%s -x,v/", Rcsbin, RCS_RLOG); + { + int i; + for (i = 1; i < ac && av[i][0] == '-'; i++) + if (av[i][1] != 'l') + run_arg (av[i]); + } + run_arg (rcsfile->path); + + if (*finfo->update_dir) + { + char *workfile = xmalloc (strlen (finfo->update_dir) + strlen (finfo->file) + 2); + sprintf (workfile, "%s/%s", finfo->update_dir, finfo->file); + run_arg (workfile); + free (workfile); + } + + if ((retcode = run_exec (RUN_TTY, RUN_TTY, RUN_TTY, RUN_REALLY)) == -1) + { + error (1, errno, "fork failed for rlog on %s", finfo->file); + } + return (retcode); +} + +/* + * Print a warm fuzzy message + */ +/* ARGSUSED */ +static Dtype +log_dirproc (dir, repository, update_dir) + char *dir; + char *repository; + char *update_dir; +{ + if (!isdir (dir)) + return (R_SKIP_ALL); + + if (!quiet) + error (0, 0, "Logging %s", update_dir); + return (R_PROCESS); +} diff --git a/contrib/cvs/src/login.c b/contrib/cvs/src/login.c new file mode 100644 index 0000000..fc3a178 --- /dev/null +++ b/contrib/cvs/src/login.c @@ -0,0 +1,392 @@ +/* + * Copyright (c) 1995, Cyclic Software, Bloomington, IN, USA + * + * You may distribute under the terms of the GNU General Public License as + * specified in the README file that comes with CVS. + * + * Allow user to log in for an authenticating server. + */ + +#include "cvs.h" +#include "getline.h" + +#ifdef AUTH_CLIENT_SUPPORT /* This covers the rest of the file. */ + +extern char *getpass (); + +#ifndef CVS_PASSWORD_FILE +#define CVS_PASSWORD_FILE ".cvspass" +#endif + +/* If non-NULL, get_cvs_password() will just return this. */ +static char *cvs_password = NULL; + +/* The return value will need to be freed. */ +char * +construct_cvspass_filename () +{ + char *homedir; + char *passfile; + + /* Environment should override file. */ + if ((passfile = getenv ("CVS_PASSFILE")) != NULL) + return xstrdup (passfile); + + /* Construct absolute pathname to user's password file. */ + /* todo: does this work under OS/2 ? */ + homedir = get_homedir (); + if (! homedir) + { + error (1, errno, "could not find out home directory"); + return (char *) NULL; + } + + passfile = + (char *) xmalloc (strlen (homedir) + strlen (CVS_PASSWORD_FILE) + 3); + strcpy (passfile, homedir); + strcat (passfile, "/"); + strcat (passfile, CVS_PASSWORD_FILE); + + /* Safety first and last, Scouts. */ + if (isfile (passfile)) + /* xchmod() is too polite. */ + chmod (passfile, 0600); + + return passfile; +} + + +/* Prompt for a password, and store it in the file "CVS/.cvspass". + * + * Because the user might be accessing multiple repositories, with + * different passwords for each one, the format of ~/.cvspass is: + * + * user@host:/path Acleartext_password + * user@host:/path Acleartext_password + * ... + * + * Of course, the "user@" might be left off -- it's just based on the + * value of CVSroot. + * + * The "A" before "cleartext_password" is a literal capital A. It's a + * version number indicating which form of scrambling we're doing on + * the password -- someday we might provide something more secure than + * the trivial encoding we do now, and when that day comes, it would + * be nice to remain backward-compatible. + * + * Like .netrc, the file's permissions are the only thing preventing + * it from being read by others. Unlike .netrc, we will not be + * fascist about it, at most issuing a warning, and never refusing to + * work. + */ +int +login (argc, argv) + int argc; + char **argv; +{ + char *passfile; + FILE *fp; + char *typed_password, *found_password; + char *linebuf = (char *) NULL; + size_t linebuf_len; + int root_len, already_entered = 0; + + /* Make this a "fully-qualified" CVSroot if necessary. */ + if (! strchr (CVSroot, '@')) + { + /* We need to prepend "user@host:". */ + char *tmp; + + printf ("Repository \"%s\" not fully-qualified.\n", CVSroot); + printf ("Please enter \"user@host:/path\": "); + fflush (stdout); + getline (&linebuf, &linebuf_len, stdin); + + tmp = xmalloc (strlen (linebuf) + 1); + + /* Give it some permanent storage. */ + strcpy (tmp, linebuf); + tmp[strlen (linebuf) - 1] = '\0'; + CVSroot = tmp; + + /* Reset. */ + free (linebuf); + linebuf = (char *) NULL; + } + + if (CVSroot[0] != ':') + { + /* Then we need to prepend ":pserver:". */ + char *tmp; + + tmp = xmalloc (strlen (":pserver:") + strlen (CVSroot) + 1); + strcpy (tmp, ":pserver:"); + strcat (tmp, CVSroot); + CVSroot = tmp; + } + + /* Check to make sure it's fully-qualified before going on. + * Fully qualified in this context means it has both a user and a + * host:repos portion. + */ + { + char *r; + + /* After confirming that CVSroot is non-NULL, we skip past the + initial ":pserver:" to test the rest of it. */ + + if (! CVSroot) + error (1, 0, "CVSroot is NULL"); + else if (! strchr ((r = (CVSroot + strlen (":pserver:"))), '@')) + goto not_fqrn; + else if (! strchr (r, ':')) + goto not_fqrn; + + if (0) /* Lovely. */ + { + not_fqrn: + error (0, 0, "CVSroot not fully-qualified: %s", CVSroot); + error (1, 0, "should be format user@host:/path/to/repository"); + } + } + + /* CVSroot is now fully qualified and has ":pserver:" prepended. + We'll print out most of it so user knows exactly what is being + dealt with here. */ + { + char *s; + s = strchr (CVSroot, ':'); + s++; + s = strchr (s, ':'); + s++; + + if (s == NULL) + error (1, 0, "NULL CVSroot"); + + printf ("(Logging in to %s)\n", s); + fflush (stdout); + } + + passfile = construct_cvspass_filename (); + typed_password = getpass ("CVS password: "); + typed_password = scramble (typed_password); + + /* Force get_cvs_password() to use this one (when the client + * confirms the new password with the server), instead of consulting + * the file. We make a new copy because cvs_password will get + * zeroed by connect_to_server(). + */ + cvs_password = xstrdup (typed_password); + + if (connect_to_pserver (NULL, NULL, 1) == 0) + { + /* The password is wrong, according to the server. */ + error (1, 0, "incorrect password"); + } + + /* IF we have a password for this "[user@]host:/path" already + * THEN + * IF it's the same as the password we read from the prompt + * THEN + * do nothing + * ELSE + * replace the old password with the new one + * ELSE + * append new entry to the end of the file. + */ + + root_len = strlen (CVSroot); + + /* Yes, the method below reads the user's password file twice. It's + inefficient, but we're not talking about a gig of data here. */ + + fp = fopen (passfile, "r"); + /* FIXME: should be printing a message if fp == NULL and not + existence_error (errno). */ + if (fp != NULL) + { + /* Check each line to see if we have this entry already. */ + while (getline (&linebuf, &linebuf_len, fp) >= 0) + { + if (strncmp (CVSroot, linebuf, root_len) == 0) + { + already_entered = 1; + break; + } + else + { + free (linebuf); + linebuf = (char *) NULL; + } + } + fclose (fp); + } + + if (already_entered) + { + /* This user/host has a password in the file already. */ + + strtok (linebuf, " "); + found_password = strtok (NULL, "\n"); + if (strcmp (found_password, typed_password)) + { + /* typed_password and found_password don't match, so we'll + * have to update passfile. We replace the old password + * with the new one by writing a tmp file whose contents are + * exactly the same as passfile except that this one entry + * gets typed_password instead of found_password. Then we + * rename the tmp file on top of passfile. + */ + char *tmp_name; + FILE *tmp_fp; + + tmp_name = tmpnam (NULL); + if ((tmp_fp = fopen (tmp_name, "w")) == NULL) + { + error (1, errno, "unable to open temp file %s", tmp_name); + return 1; + } + chmod (tmp_name, 0600); + + fp = fopen (passfile, "r"); + if (fp == NULL) + { + error (1, errno, "unable to open %s", passfile); + return 1; + } + /* I'm not paranoid, they really ARE out to get me: */ + chmod (passfile, 0600); + + free (linebuf); + linebuf = (char *) NULL; + while (getline (&linebuf, &linebuf_len, fp) >= 0) + { + if (strncmp (CVSroot, linebuf, root_len)) + fprintf (tmp_fp, "%s", linebuf); + else + fprintf (tmp_fp, "%s %s\n", CVSroot, typed_password); + + free (linebuf); + linebuf = (char *) NULL; + } + fclose (tmp_fp); + fclose (fp); + rename_file (tmp_name, passfile); + chmod (passfile, 0600); + } + } + else + { + if ((fp = fopen (passfile, "a")) == NULL) + { + error (1, errno, "could not open %s", passfile); + free (passfile); + return 1; + } + + fprintf (fp, "%s %s\n", CVSroot, typed_password); + fclose (fp); + } + + /* Utter, total, raving paranoia, I know. */ + chmod (passfile, 0600); + memset (typed_password, 0, strlen (typed_password)); + free (typed_password); + + free (passfile); + free (cvs_password); + cvs_password = NULL; + return 0; +} + +/* todo: "cvs logout" could erase an entry from the file. + * But to what purpose? + */ + +/* Returns the _scrambled_ password. The server must descramble + before hashing and comparing. */ +char * +get_cvs_password () +{ + int found_it = 0; + int root_len; + char *password; + char *linebuf = (char *) NULL; + size_t linebuf_len; + FILE *fp; + char *passfile; + + /* If someone (i.e., login()) is calling connect_to_pserver() out of + context, then assume they have supplied the correct, scrambled + password. */ + if (cvs_password) + return cvs_password; + + /* Environment should override file. */ + if ((password = getenv ("CVS_PASSWORD")) != NULL) + { + char *p; + p = xstrdup (password); + /* If we got it from the environment, then it wasn't properly + scrambled. Since unscrambling is done on the server side, we + need to transmit it scrambled. */ + p = scramble (p); + return p; + } + + /* Else get it from the file. */ + passfile = construct_cvspass_filename (); + fp = fopen (passfile, "r"); + if (fp == NULL) + { + error (0, errno, "could not open %s", passfile); + free (passfile); + error (1, 0, "use \"cvs login\" to log in first"); + } + + root_len = strlen (CVSroot); + + /* Check each line to see if we have this entry already. */ + while (getline (&linebuf, &linebuf_len, fp) >= 0) + { + if (strncmp (CVSroot, linebuf, root_len) == 0) + { + /* This is it! So break out and deal with linebuf. */ + found_it = 1; + break; + } + else + { + free (linebuf); + linebuf = (char *) NULL; + } + } + + if (found_it) + { + /* linebuf now contains the line with the password. */ + char *tmp; + + strtok (linebuf, " "); + password = strtok (NULL, "\n"); + + /* Give it permanent storage. */ + tmp = xmalloc (strlen (password) + 1); + strcpy (tmp, password); + tmp[strlen (password)] = '\0'; + memset (password, 0, strlen (password)); + free (linebuf); + return tmp; + } + else + { + error (0, 0, "cannot find password"); + error (0, 0, "use \"cvs login\" to log in first"); + error (1, 0, "or set the CVS_PASSWORD environment variable"); + } + /* NOTREACHED */ + return NULL; +} + +#endif /* AUTH_CLIENT_SUPPORT from beginning of file. */ + diff --git a/contrib/cvs/src/logmsg.c b/contrib/cvs/src/logmsg.c new file mode 100644 index 0000000..370ceab --- /dev/null +++ b/contrib/cvs/src/logmsg.c @@ -0,0 +1,521 @@ +/* + * Copyright (c) 1992, Brian Berliner and Jeff Polk + * Copyright (c) 1989-1992, Brian Berliner + * + * You may distribute under the terms of the GNU General Public License as + * specified in the README file that comes with the CVS 1.4 kit. + */ + +#include "cvs.h" +#include "getline.h" + +static int find_type PROTO((Node * p, void *closure)); +static int fmt_proc PROTO((Node * p, void *closure)); +static int logfile_write PROTO((char *repository, char *filter, char *title, + char *message, char *revision, FILE * logfp, + List * changes)); +static int rcsinfo_proc PROTO((char *repository, char *template)); +static int title_proc PROTO((Node * p, void *closure)); +static int update_logfile_proc PROTO((char *repository, char *filter)); +static void setup_tmpfile PROTO((FILE * xfp, char *xprefix, List * changes)); +static int editinfo_proc PROTO((char *repository, char *template)); + +static FILE *fp; +static char *str_list; +static char *editinfo_editor; +static Ctype type; + +/* + * Puts a standard header on the output which is either being prepared for an + * editor session, or being sent to a logfile program. The modified, added, + * and removed files are included (if any) and formatted to look pretty. */ +static char *prefix; +static int col; +static void +setup_tmpfile (xfp, xprefix, changes) + FILE *xfp; + char *xprefix; + List *changes; +{ + /* set up statics */ + fp = xfp; + prefix = xprefix; + + type = T_MODIFIED; + if (walklist (changes, find_type, NULL) != 0) + { + (void) fprintf (fp, "%sModified Files:\n", prefix); + (void) fprintf (fp, "%s\t", prefix); + col = 8; + (void) walklist (changes, fmt_proc, NULL); + (void) fprintf (fp, "\n"); + } + type = T_ADDED; + if (walklist (changes, find_type, NULL) != 0) + { + (void) fprintf (fp, "%sAdded Files:\n", prefix); + (void) fprintf (fp, "%s\t", prefix); + col = 8; + (void) walklist (changes, fmt_proc, NULL); + (void) fprintf (fp, "\n"); + } + type = T_REMOVED; + if (walklist (changes, find_type, NULL) != 0) + { + (void) fprintf (fp, "%sRemoved Files:\n", prefix); + (void) fprintf (fp, "%s\t", prefix); + col = 8; + (void) walklist (changes, fmt_proc, NULL); + (void) fprintf (fp, "\n"); + } +} + +/* + * Looks for nodes of a specified type and returns 1 if found + */ +static int +find_type (p, closure) + Node *p; + void *closure; +{ + if (p->data == (char *) type) + return (1); + else + return (0); +} + +/* + * Breaks the files list into reasonable sized lines to avoid line wrap... + * all in the name of pretty output. It only works on nodes whose types + * match the one we're looking for + */ +static int +fmt_proc (p, closure) + Node *p; + void *closure; +{ + if (p->data == (char *) type) + { + if ((col + (int) strlen (p->key)) > 70) + { + (void) fprintf (fp, "\n%s\t", prefix); + col = 8; + } + (void) fprintf (fp, "%s ", p->key); + col += strlen (p->key) + 1; + } + return (0); +} + +/* + * Builds a temporary file using setup_tmpfile() and invokes the user's + * editor on the file. The header garbage in the resultant file is then + * stripped and the log message is stored in the "message" argument. + * + * If REPOSITORY is non-NULL, process rcsinfo for that repository; if it + * is NULL, use the CVSADM_TEMPLATE file instead. + */ +void +do_editor (dir, messagep, repository, changes) + char *dir; + char **messagep; + char *repository; + List *changes; +{ + static int reuse_log_message = 0; + char *line; + int line_length; + size_t line_chars_allocated; + char fname[L_tmpnam+1]; + struct stat pre_stbuf, post_stbuf; + int retcode = 0; + char *p; + + if (noexec || reuse_log_message) + return; + + /* Abort creation of temp file if no editor is defined */ + if (strcmp (Editor, "") == 0 && !editinfo_editor) + error(1, 0, "no editor defined, must use -e or -m"); + + /* Create a temporary file */ + (void) tmpnam (fname); + again: + if ((fp = fopen (fname, "w+")) == NULL) + error (1, 0, "cannot create temporary file %s", fname); + + if (*messagep) + { + (void) fprintf (fp, "%s", *messagep); + + if ((*messagep)[strlen (*messagep) - 1] != '\n') + (void) fprintf (fp, "\n"); + } + else + (void) fprintf (fp, "\n"); + + if (repository != NULL) + /* tack templates on if necessary */ + (void) Parse_Info (CVSROOTADM_RCSINFO, repository, rcsinfo_proc, 1); + else + { + FILE *tfp; + char buf[1024]; + char *p; + size_t n; + size_t nwrite; + + /* Why "b"? */ + tfp = fopen (CVSADM_TEMPLATE, "rb"); + if (tfp == NULL) + { + if (!existence_error (errno)) + error (1, errno, "cannot read %s", CVSADM_TEMPLATE); + } + else + { + while (!feof (tfp)) + { + n = fread (buf, 1, sizeof buf, tfp); + nwrite = n; + p = buf; + while (nwrite > 0) + { + n = fwrite (p, 1, nwrite, fp); + nwrite -= n; + p += n; + } + if (ferror (tfp)) + error (1, errno, "cannot read %s", CVSADM_TEMPLATE); + } + if (fclose (tfp) < 0) + error (0, errno, "cannot close %s", CVSADM_TEMPLATE); + } + } + + (void) fprintf (fp, + "%s----------------------------------------------------------------------\n", + CVSEDITPREFIX); + (void) fprintf (fp, + "%sEnter Log. Lines beginning with `%s' are removed automatically\n%s\n", + CVSEDITPREFIX, CVSEDITPREFIX, CVSEDITPREFIX); + if (dir != NULL && *dir) + (void) fprintf (fp, "%sCommitting in %s\n%s\n", CVSEDITPREFIX, + dir, CVSEDITPREFIX); + if (changes != NULL) + setup_tmpfile (fp, CVSEDITPREFIX, changes); + (void) fprintf (fp, + "%s----------------------------------------------------------------------\n", + CVSEDITPREFIX); + + /* finish off the temp file */ + if (fclose (fp) == EOF) + error (1, errno, "%s", fname); + if (stat (fname, &pre_stbuf) == -1) + pre_stbuf.st_mtime = 0; + + if (editinfo_editor) + free (editinfo_editor); + editinfo_editor = (char *) NULL; + if (repository != NULL) + (void) Parse_Info (CVSROOTADM_EDITINFO, repository, editinfo_proc, 0); + + /* run the editor */ + run_setup ("%s", editinfo_editor ? editinfo_editor : Editor); + run_arg (fname); + if ((retcode = run_exec (RUN_TTY, RUN_TTY, RUN_TTY, + RUN_NORMAL | RUN_SIGIGNORE)) != 0) + error (editinfo_editor ? 1 : 0, retcode == -1 ? errno : 0, + editinfo_editor ? "Logfile verification failed" : + "warning: editor session failed"); + + /* put the entire message back into the *messagep variable */ + + fp = open_file (fname, "r"); + + if (*messagep) + free (*messagep); + + if (stat (fname, &post_stbuf) != 0) + error (1, errno, "cannot find size of temp file %s", fname); + + if (post_stbuf.st_size == 0) + *messagep = NULL; + else + { + /* On NT, we might read less than st_size bytes, but we won't + read more. So this works. */ + *messagep = (char *) xmalloc (post_stbuf.st_size + 1); + *messagep[0] = '\0'; + } + + line = NULL; + line_chars_allocated = 0; + + if (*messagep) + { + p = *messagep; + while (1) + { + line_length = getline (&line, &line_chars_allocated, fp); + if (line_length == -1) + { + if (ferror (fp)) + error (0, errno, "warning: cannot read %s", fname); + break; + } + if (strncmp (line, CVSEDITPREFIX, sizeof (CVSEDITPREFIX) - 1) == 0) + continue; + (void) strcpy (p, line); + p += line_length; + } + } + if (fclose (fp) < 0) + error (0, errno, "warning: cannot close %s", fname); + + if (pre_stbuf.st_mtime == post_stbuf.st_mtime || + *messagep == NULL || + strcmp (*messagep, "\n") == 0) + { + for (;;) + { + (void) printf ("\nLog message unchanged or not specified\n"); + (void) printf ("a)bort, c)ontinue, e)dit, !)reuse this message unchanged for remaining dirs\n"); + (void) printf ("Action: (continue) "); + (void) fflush (stdout); + line_length = getline (&line, &line_chars_allocated, stdin); + if (line_length <= 0 + || *line == '\n' || *line == 'c' || *line == 'C') + break; + if (*line == 'a' || *line == 'A') + error (1, 0, "aborted by user"); + if (*line == 'e' || *line == 'E') + goto again; + if (*line == '!') + { + reuse_log_message = 1; + break; + } + (void) printf ("Unknown input\n"); + } + } + if (line) + free (line); + if (unlink_file (fname) < 0) + error (0, errno, "warning: cannot remove temp file %s", fname); +} + +/* + * callback proc for Parse_Info for rcsinfo templates this routine basically + * copies the matching template onto the end of the tempfile we are setting + * up + */ +/* ARGSUSED */ +static int +rcsinfo_proc (repository, template) + char *repository; + char *template; +{ + static char *last_template; + FILE *tfp; + + /* nothing to do if the last one included is the same as this one */ + if (last_template && strcmp (last_template, template) == 0) + return (0); + if (last_template) + free (last_template); + last_template = xstrdup (template); + + if ((tfp = fopen (template, "r")) != NULL) + { + char *line = NULL; + size_t line_chars_allocated = 0; + + while (getline (&line, &line_chars_allocated, tfp) >= 0) + (void) fputs (line, fp); + if (ferror (tfp)) + error (0, errno, "warning: cannot read %s", template); + if (fclose (tfp) < 0) + error (0, errno, "warning: cannot close %s", template); + if (line) + free (line); + return (0); + } + else + { + error (0, errno, "Couldn't open rcsinfo template file %s", template); + return (1); + } +} + +/* + * Uses setup_tmpfile() to pass the updated message on directly to any + * logfile programs that have a regular expression match for the checked in + * directory in the source repository. The log information is fed into the + * specified program as standard input. + */ +static char *title; +static FILE *logfp; +static char *message; +static char *revision; +static List *changes; + +void +Update_Logfile (repository, xmessage, xrevision, xlogfp, xchanges) + char *repository; + char *xmessage; + char *xrevision; + FILE *xlogfp; + List *xchanges; +{ + char *srepos; + + /* nothing to do if the list is empty */ + if (xchanges == NULL || xchanges->list->next == xchanges->list) + return; + + /* set up static vars for update_logfile_proc */ + message = xmessage; + revision = xrevision; + logfp = xlogfp; + changes = xchanges; + + /* figure out a good title string */ + srepos = Short_Repository (repository); + + /* allocate a chunk of memory to hold the title string */ + if (!str_list) + str_list = xmalloc (MAXLISTLEN); + str_list[0] = '\0'; + + type = T_TITLE; + (void) walklist (changes, title_proc, NULL); + type = T_ADDED; + (void) walklist (changes, title_proc, NULL); + type = T_MODIFIED; + (void) walklist (changes, title_proc, NULL); + type = T_REMOVED; + (void) walklist (changes, title_proc, NULL); + title = xmalloc (strlen (srepos) + strlen (str_list) + 1 + 2); /* for 's */ + (void) sprintf (title, "'%s%s'", srepos, str_list); + + /* to be nice, free up this chunk of memory */ + free (str_list); + str_list = (char *) NULL; + + /* call Parse_Info to do the actual logfile updates */ + (void) Parse_Info (CVSROOTADM_LOGINFO, repository, update_logfile_proc, 1); + + /* clean up */ + free (title); +} + +/* + * callback proc to actually do the logfile write from Update_Logfile + */ +static int +update_logfile_proc (repository, filter) + char *repository; + char *filter; +{ + return (logfile_write (repository, filter, title, message, revision, + logfp, changes)); +} + +/* + * concatenate each name onto str_list + */ +static int +title_proc (p, closure) + Node *p; + void *closure; +{ + if (p->data == (char *) type) + { + (void) strcat (str_list, " "); + (void) strcat (str_list, p->key); + } + return (0); +} + +/* + * Since some systems don't define this... + */ +#ifndef MAXHOSTNAMELEN +#define MAXHOSTNAMELEN 256 +#endif + +/* + * Writes some stuff to the logfile "filter" and returns the status of the + * filter program. + */ +static int +logfile_write (repository, filter, title, message, revision, logfp, changes) + char *repository; + char *filter; + char *title; + char *message; + char *revision; + FILE *logfp; + List *changes; +{ + char cwd[PATH_MAX]; + FILE *pipefp; + char *prog = xmalloc (MAXPROGLEN); + char *cp; + int c; + int pipestatus; + + /* + * Only 1 %s argument is supported in the filter + */ + (void) sprintf (prog, filter, title); + if ((pipefp = run_popen (prog, "w")) == NULL) + { + if (!noexec) + error (0, 0, "cannot write entry to log filter: %s", prog); + free (prog); + return (1); + } + (void) fprintf (pipefp, "Update of %s\n", repository); + (void) fprintf (pipefp, "In directory %s:%s\n\n", hostname, + ((cp = getwd (cwd)) != NULL) ? cp : cwd); + if (revision && *revision) + (void) fprintf (pipefp, "Revision/Branch: %s\n\n", revision); + setup_tmpfile (pipefp, "", changes); + (void) fprintf (pipefp, "Log Message:\n%s\n", message); + if (logfp != (FILE *) 0) + { + (void) fprintf (pipefp, "Status:\n"); + rewind (logfp); + while ((c = getc (logfp)) != EOF) + (void) putc ((char) c, pipefp); + } + free (prog); + pipestatus = pclose (pipefp); + return ((pipestatus == -1) || (pipestatus == 127)) ? 1 : 0; +} + +/* + * We choose to use the *last* match within the editinfo file for this + * repository. This allows us to have a global editinfo program for the + * root of some hierarchy, for example, and different ones within different + * sub-directories of the root (like a special checker for changes made to + * the "src" directory versus changes made to the "doc" or "test" + * directories. + */ +/* ARGSUSED */ +static int +editinfo_proc(repository, editor) + char *repository; + char *editor; +{ + /* nothing to do if the last match is the same as this one */ + if (editinfo_editor && strcmp (editinfo_editor, editor) == 0) + return (0); + if (editinfo_editor) + free (editinfo_editor); + + editinfo_editor = xstrdup (editor); + return (0); +} diff --git a/contrib/cvs/src/main.c b/contrib/cvs/src/main.c new file mode 100644 index 0000000..daa0230 --- /dev/null +++ b/contrib/cvs/src/main.c @@ -0,0 +1,814 @@ +/* + * Copyright (c) 1992, Brian Berliner and Jeff Polk + * Copyright (c) 1989-1992, Brian Berliner + * + * You may distribute under the terms of the GNU General Public License + * as specified in the README file that comes with the CVS 1.4 kit. + * + * This is the main C driver for the CVS system. + * + * Credit to Dick Grune, Vrije Universiteit, Amsterdam, for writing + * the shell-script CVS system that this is based on. + * + * Usage: + * cvs [options] command [options] [files/modules...] + * + * Where "command" is composed of: + * admin RCS command + * checkout Check out a module/dir/file + * export Like checkout, but used for exporting sources + * update Brings work tree in sync with repository + * commit Checks files into the repository + * diff Runs diffs between revisions + * log Prints "rlog" information for files + * login Record user, host, repos, password + * add Adds an entry to the repository + * remove Removes an entry from the repository + * status Status info on the revisions + * rdiff "patch" format diff listing between releases + * tag Add/delete a symbolic tag to the RCS file + * rtag Add/delete a symbolic tag to the RCS file + * import Import sources into CVS, using vendor branches + * release Indicate that Module is no longer in use. + * history Display history of Users and Modules. + */ + +#include "cvs.h" + +#ifdef HAVE_WINSOCK_H +#include <winsock.h> +#else +extern int gethostname (); +#endif + +#if HAVE_KERBEROS +#include <sys/socket.h> +#include <netinet/in.h> +#include <krb.h> +#ifndef HAVE_KRB_GET_ERR_TEXT +#define krb_get_err_text(status) krb_err_txt[status] +#endif +#endif + +char *program_name; +char *program_path; +/* + * Initialize comamnd_name to "cvs" so that the first call to + * read_cvsrc tries to find global cvs options. + */ +char *command_name = ""; + +/* + * Since some systems don't define this... + */ +#ifndef MAXHOSTNAMELEN +#define MAXHOSTNAMELEN 256 +#endif + +char hostname[MAXHOSTNAMELEN]; + +#ifdef AUTH_CLIENT_SUPPORT +int use_authenticating_server = FALSE; +#endif /* AUTH_CLIENT_SUPPORT */ +int use_editor = TRUE; +int use_cvsrc = TRUE; +int cvswrite = !CVSREAD_DFLT; +int really_quiet = FALSE; +int quiet = FALSE; +int trace = FALSE; +int noexec = FALSE; +int logoff = FALSE; +mode_t cvsumask = UMASK_DFLT; + +char *CurDir; + +/* + * Defaults, for the environment variables that are not set + */ +char *Rcsbin = RCSBIN_DFLT; +char *Editor = EDITOR_DFLT; +char *CVSroot = CVSROOT_DFLT; +/* + * The path found in CVS/Root must match $CVSROOT and/or 'cvs -d root' + */ +char *CVSADM_Root = CVSROOT_DFLT; + +int add PROTO((int argc, char **argv)); +int admin PROTO((int argc, char **argv)); +int checkout PROTO((int argc, char **argv)); +int commit PROTO((int argc, char **argv)); +int diff PROTO((int argc, char **argv)); +int history PROTO((int argc, char **argv)); +int import PROTO((int argc, char **argv)); +int cvslog PROTO((int argc, char **argv)); +#ifdef AUTH_CLIENT_SUPPORT +int login PROTO((int argc, char **argv)); +#endif /* AUTH_CLIENT_SUPPORT */ +int patch PROTO((int argc, char **argv)); +int release PROTO((int argc, char **argv)); +int cvsremove PROTO((int argc, char **argv)); +int rtag PROTO((int argc, char **argv)); +int status PROTO((int argc, char **argv)); +int tag PROTO((int argc, char **argv)); +int update PROTO((int argc, char **argv)); + +const struct cmd +{ + char *fullname; /* Full name of the function (e.g. "commit") */ + char *nick1; /* alternate name (e.g. "ci") */ + char *nick2; /* another alternate names (e.g. "ci") */ + int (*func) (); /* Function takes (argc, argv) arguments. */ +#ifdef CLIENT_SUPPORT + int (*client_func) (); /* Function to do it via the protocol. */ +#endif +} cmds[] = + +{ +#ifdef CLIENT_SUPPORT +#define CMD_ENTRY(n1, n2, n3, f1, f2) { n1, n2, n3, f1, f2 } +#else +#define CMD_ENTRY(n1, n2, n3, f1, f2) { n1, n2, n3, f1 } +#endif + + CMD_ENTRY("add", "ad", "new", add, client_add), + CMD_ENTRY("admin", "adm", "rcs", admin, client_admin), + CMD_ENTRY("annotate", NULL, NULL, annotate, client_annotate), + CMD_ENTRY("checkout", "co", "get", checkout, client_checkout), + CMD_ENTRY("commit", "ci", "com", commit, client_commit), + CMD_ENTRY("diff", "di", "dif", diff, client_diff), + CMD_ENTRY("edit", "edit", "edit", edit, client_edit), + CMD_ENTRY("editors", "editors","editors",editors, client_editors), + CMD_ENTRY("export", "exp", "ex", checkout, client_export), + CMD_ENTRY("history", "hi", "his", history, client_history), + CMD_ENTRY("import", "im", "imp", import, client_import), + CMD_ENTRY("init", NULL, NULL, init, client_init), + CMD_ENTRY("log", "lo", "rlog", cvslog, client_log), +#ifdef AUTH_CLIENT_SUPPORT + CMD_ENTRY("login", "logon", "lgn", login, login), +#endif /* AUTH_CLIENT_SUPPORT */ + CMD_ENTRY("rdiff", "patch", "pa", patch, client_rdiff), + CMD_ENTRY("release", "re", "rel", release, client_release), + CMD_ENTRY("remove", "rm", "delete", cvsremove, client_remove), + CMD_ENTRY("status", "st", "stat", status, client_status), + CMD_ENTRY("rtag", "rt", "rfreeze", rtag, client_rtag), + CMD_ENTRY("tag", "ta", "freeze", tag, client_tag), + CMD_ENTRY("unedit", "unedit","unedit", unedit, client_unedit), + CMD_ENTRY("update", "up", "upd", update, client_update), + CMD_ENTRY("watch", "watch", "watch", watch, client_watch), + CMD_ENTRY("watchers", "watchers","watchers",watchers,client_watchers), +#ifdef SERVER_SUPPORT + /* + * The client_func is also server because we might have picked up a + * CVSROOT environment variable containing a colon. The client will send + * the real root later. + */ + CMD_ENTRY("server", "server", "server", server, server), +#endif + CMD_ENTRY(NULL, NULL, NULL, NULL, NULL), + +#undef CMD_ENTRY +}; + +static const char *const usg[] = +{ + "Usage: %s [cvs-options] command [command-options] [files...]\n", + " Where 'cvs-options' are:\n", + " -H Displays Usage information for command\n", + " -Q Cause CVS to be really quiet.\n", + " -q Cause CVS to be somewhat quiet.\n", + " -r Make checked-out files read-only\n", + " -w Make checked-out files read-write (default)\n", + " -l Turn History logging off\n", + " -n Do not execute anything that will change the disk\n", + " -t Show trace of program execution -- Try with -n\n", + " -v CVS version and copyright\n", + " -b bindir Find RCS programs in 'bindir'\n", + " -e editor Use 'editor' for editing log information\n", + " -d CVS_root Overrides $CVSROOT as the root of the CVS tree\n", + " -f Do not use the ~/.cvsrc file\n", +#ifdef CLIENT_SUPPORT + " -z # Use 'gzip -#' for net traffic if possible.\n", +#endif + " -s VAR=VAL Set CVS user variable.\n", + "\n", + " and where 'command' is: add, admin, etc. (use the --help-commands\n", + " option for a list of commands)\n", + NULL, +}; + +static const char *const cmd_usage[] = +{ + "CVS commands are:\n", + " add Adds a new file/directory to the repository\n", + " admin Administration front end for rcs\n", + " annotate Show revision where each line was modified\n", + " checkout Checkout sources for editing\n", + " commit Checks files into the repository\n", + " diff Runs diffs between revisions\n", + " edit Get ready to edit a watched file\n", + " editors See who is editing a watched file\n", + " history Shows status of files and users\n", + " import Import sources into CVS, using vendor branches\n", + " export Export sources from CVS, similar to checkout\n", + " log Prints out 'rlog' information for files\n", +#ifdef AUTH_CLIENT_SUPPORT + " login Prompt for password for authenticating server.\n", +#endif /* AUTH_CLIENT_SUPPORT */ + " rdiff 'patch' format diffs between releases\n", + " release Indicate that a Module is no longer in use\n", + " remove Removes an entry from the repository\n", + " status Status info on the revisions\n", + " tag Add a symbolic tag to checked out version of RCS file\n", + " unedit Undo an edit command\n", + " rtag Add a symbolic tag to the RCS file\n", + " update Brings work tree in sync with repository\n", + " watch Set watches\n", + " watchers See who is watching a file\n", + NULL, +}; + +static RETSIGTYPE +main_cleanup () +{ + exit (EXIT_FAILURE); +} + +static void +error_cleanup PROTO((void)) +{ + Lock_Cleanup(); +#ifdef SERVER_SUPPORT + if (server_active) + server_cleanup (0); +#endif +} + +int +main (argc, argv) + int argc; + char **argv; +{ + extern char *version_string; + extern char *config_string; + char *cp, *end; + const struct cmd *cm; + int c, err = 0; + static int help = FALSE; + static int version_flag = FALSE; + static int help_commands = FALSE; + int rcsbin_update_env, cvs_update_env = 0; + static struct option long_options[] = + { + {"help", 0, &help, TRUE}, + {"version", 0, &version_flag, TRUE}, + {"help-commands", 0, &help_commands, TRUE}, + {0, 0, 0, 0} + }; + /* `getopt_long' stores the option index here, but right now we + don't use it. */ + int option_index = 0; + + error_set_cleanup (error_cleanup); + +/* The socket subsystems on NT and OS2 must be initialized before use */ +#ifdef INITIALIZE_SOCKET_SUBSYSTEM + INITIALIZE_SOCKET_SUBSYSTEM(); +#endif /* INITIALIZE_SOCKET_SUBSYSTEM */ + + /* + * Just save the last component of the path for error messages + */ + program_path = xstrdup (argv[0]); + program_name = last_component (argv[0]); + + CurDir = xmalloc (PATH_MAX); +#ifndef SERVER_SUPPORT + if (!getwd (CurDir)) + error (1, 0, "cannot get working directory: %s", CurDir); +#endif + + /* + * Query the environment variables up-front, so that + * they can be overridden by command line arguments + */ + rcsbin_update_env = *Rcsbin; /* RCSBIN_DFLT must be set */ + cvs_update_env = 0; + if ((cp = getenv (RCSBIN_ENV)) != NULL) + { + Rcsbin = cp; + rcsbin_update_env = 0; /* it's already there */ + } + if ((cp = getenv (EDITOR1_ENV)) != NULL) + Editor = cp; + else if ((cp = getenv (EDITOR2_ENV)) != NULL) + Editor = cp; + else if ((cp = getenv (EDITOR3_ENV)) != NULL) + Editor = cp; + if ((cp = getenv (CVSROOT_ENV)) != NULL) + { + CVSroot = cp; + cvs_update_env = 0; /* it's already there */ + } + if (getenv (CVSREAD_ENV) != NULL) + cvswrite = FALSE; + if ((cp = getenv (CVSUMASK_ENV)) != NULL) + { + /* FIXME: Should be accepting symbolic as well as numeric mask. */ + cvsumask = strtol (cp, &end, 8) & 0777; + if (*end != '\0') + error (1, errno, "invalid umask value in %s (%s)", + CVSUMASK_ENV, cp); + } + + /* This has the effect of setting getopt's ordering to REQUIRE_ORDER, + which is what we need to distinguish between global options and + command options. FIXME: It would appear to be possible to do this + much less kludgily by passing "+" as the first character to the + option string we pass to getopt_long. */ + optind = 1; + + + /* We have to parse the options twice because else there is no + chance to avoid reading the global options from ".cvsrc". Set + opterr to 0 for avoiding error messages about invalid options. + */ + opterr = 0; + + while ((c = getopt_long + (argc, argv, "f", NULL, NULL)) + != EOF) + { + if (c == 'f') + use_cvsrc = FALSE; + } + + /* + * Scan cvsrc file for global options. + */ + if (use_cvsrc) + read_cvsrc (&argc, &argv, "cvs"); + + optind = 1; + opterr = 1; + + while ((c = getopt_long + (argc, argv, "Qqrwtnlvb:e:d:Hfz:s:", long_options, &option_index)) + != EOF) + { + switch (c) + { + case 0: + /* getopt_long took care of setting the flag. */ + break; + case 'Q': + really_quiet = TRUE; + /* FALL THROUGH */ + case 'q': + quiet = TRUE; + break; + case 'r': + cvswrite = FALSE; + break; + case 'w': + cvswrite = TRUE; + break; + case 't': + trace = TRUE; + break; + case 'n': + noexec = TRUE; + case 'l': /* Fall through */ + logoff = TRUE; + break; + case 'v': + version_flag = TRUE; + break; + case 'b': + Rcsbin = optarg; + rcsbin_update_env = 1; /* need to update environment */ + break; + case 'e': + Editor = optarg; + break; + case 'd': + CVSroot = optarg; + cvs_update_env = 1; /* need to update environment */ + break; + case 'H': + use_cvsrc = FALSE; /* this ensure that cvs -H works */ + help = TRUE; + break; + case 'f': + use_cvsrc = FALSE; + break; + case 'z': +#ifdef CLIENT_SUPPORT + gzip_level = atoi (optarg); + if (gzip_level <= 0 || gzip_level > 9) + error (1, 0, + "gzip compression level must be between 1 and 9"); +#endif + /* If no CLIENT_SUPPORT, we just silently ignore the gzip + level, so that users can have it in their .cvsrc and not + cause any trouble. */ + break; + case 's': + variable_set (optarg); + break; + case '?': + default: + usage (usg); + } + } + + if (version_flag == TRUE) + { + (void) fputs (version_string, stdout); + (void) fputs (config_string, stdout); + (void) fputs ("\n", stdout); + (void) fputs ("Copyright (c) 1993-1994 Brian Berliner\n", stdout); + (void) fputs ("Copyright (c) 1993-1994 david d `zoo' zuhn\n", stdout); + (void) fputs ("Copyright (c) 1992, Brian Berliner and Jeff Polk\n", stdout); + (void) fputs ("Copyright (c) 1989-1992, Brian Berliner\n", stdout); + (void) fputs ("\n", stdout); + (void) fputs ("CVS may be copied only under the terms of the GNU General Public License,\n", stdout); + (void) fputs ("a copy of which can be found with the CVS distribution kit.\n", stdout); + exit (0); + } + else if (help_commands) + usage (cmd_usage); + + argc -= optind; + argv += optind; + if (argc < 1) + usage (usg); + +#ifdef HAVE_KERBEROS + /* If we are invoked with a single argument "kserver", then we are + running as Kerberos server as root. Do the authentication as + the very first thing, to minimize the amount of time we are + running as root. */ + if (strcmp (argv[0], "kserver") == 0) + { + int status; + char instance[INST_SZ]; + struct sockaddr_in peer; + struct sockaddr_in laddr; + int len; + KTEXT_ST ticket; + AUTH_DAT auth; + char version[KRB_SENDAUTH_VLEN]; + Key_schedule sched; + char user[ANAME_SZ]; + struct passwd *pw; + + strcpy (instance, "*"); + len = sizeof peer; + if (getpeername (STDIN_FILENO, (struct sockaddr *) &peer, &len) < 0 + || getsockname (STDIN_FILENO, (struct sockaddr *) &laddr, + &len) < 0) + { + printf ("E Fatal error, aborting.\n\ +error %s getpeername or getsockname failed\n", strerror (errno)); + exit (EXIT_FAILURE); + } + + status = krb_recvauth (KOPT_DO_MUTUAL, STDIN_FILENO, &ticket, "rcmd", + instance, &peer, &laddr, &auth, "", sched, + version); + if (status != KSUCCESS) + { + printf ("E Fatal error, aborting.\n\ +error 0 kerberos: %s\n", krb_get_err_text(status)); + exit (EXIT_FAILURE); + } + + /* Get the local name. */ + status = krb_kntoln (&auth, user); + if (status != KSUCCESS) + { + printf ("E Fatal error, aborting.\n\ +error 0 kerberos: can't get local name: %s\n", krb_get_err_text(status)); + exit (EXIT_FAILURE); + } + + pw = getpwnam (user); + if (pw == NULL) + { + printf ("E Fatal error, aborting.\n\ +error 0 %s: no such user\n", user); + exit (EXIT_FAILURE); + } + + initgroups (pw->pw_name, pw->pw_gid); + setgid (pw->pw_gid); + setuid (pw->pw_uid); + /* Inhibit access by randoms. Don't want people randomly + changing our temporary tree before we check things in. */ + umask (077); + +#if HAVE_PUTENV + /* Set LOGNAME and USER in the environment, in case they are + already set to something else. */ + { + char *env; + + env = xmalloc (sizeof "LOGNAME=" + strlen (user)); + (void) sprintf (env, "LOGNAME=%s", user); + (void) putenv (env); + + env = xmalloc (sizeof "USER=" + strlen (user)); + (void) sprintf (env, "USER=%s", user); + (void) putenv (env); + } +#endif + + /* Pretend we were invoked as a plain server. */ + argv[0] = "server"; + } +#endif /* HAVE_KERBEROS */ + + +#if defined(AUTH_SERVER_SUPPORT) && defined(SERVER_SUPPORT) + if (strcmp (argv[0], "pserver") == 0) + { + /* Gets username and password from client, authenticates, then + switches to run as that user and sends an ACK back to the + client. */ + authenticate_connection (); + + /* Pretend we were invoked as a plain server. */ + argv[0] = "server"; + } +#endif /* AUTH_SERVER_SUPPORT && SERVER_SUPPORT */ + + + /* + * See if we are able to find a 'better' value for CVSroot in the + * CVSADM_ROOT directory. + */ +#ifdef SERVER_SUPPORT + if (strcmp (argv[0], "server") == 0 && CVSroot == NULL) + CVSADM_Root = NULL; + else + CVSADM_Root = Name_Root((char *) NULL, (char *) NULL); +#else /* No SERVER_SUPPORT */ + CVSADM_Root = Name_Root((char *) NULL, (char *) NULL); +#endif /* No SERVER_SUPPORT */ + if (CVSADM_Root != NULL) + { + if (CVSroot == NULL || !cvs_update_env) + { + CVSroot = CVSADM_Root; + cvs_update_env = 1; /* need to update environment */ + } +#ifdef CLIENT_SUPPORT + else if (!getenv ("CVS_IGNORE_REMOTE_ROOT")) +#else /* ! CLIENT_SUPPORT */ + else +#endif /* CLIENT_SUPPORT */ + { + /* + * Now for the hard part, compare the two directories. If they + * are not identical, then abort this command. + */ + if ((fncmp (CVSroot, CVSADM_Root) != 0) && + !same_directories(CVSroot, CVSADM_Root)) + { + error (0, 0, "%s value for CVS Root found in %s", + CVSADM_Root, CVSADM_ROOT); + error (0, 0, "does not match command line -d %s setting", + CVSroot); + error (1, 0, + "you may wish to try the cvs command again without the -d option "); + } + } + } + + /* CVSroot may need fixing up, if an access-method was specified, + * but not a user. Later code assumes that if CVSroot contains an + * access-method, then it also has a user. We print a warning and + * die if we can't guarantee that. + */ + if (CVSroot + && *CVSroot + && (CVSroot[0] == ':') + && (strchr (CVSroot, '@') == NULL)) + { + error (1, 0, + "must also give a username if specifying access method"); + } + + /* + * Specifying just the '-H' flag to the sub-command causes a Usage + * message to be displayed. + */ + command_name = cp = argv[0]; + if (help == TRUE || (argc > 1 && strcmp (argv[1], "-H") == 0)) + argc = -1; + else + { + /* + * Check to see if we can write into the history file. If not, + * we assume that we can't work in the repository. + * BUT, only if the history file exists. + */ +#ifdef SERVER_SUPPORT + if (strcmp (command_name, "server") != 0 || CVSroot != NULL) +#endif + { + char path[PATH_MAX]; + int save_errno; + + if (!CVSroot || !*CVSroot) + error (1, 0, "You don't have a %s environment variable", + CVSROOT_ENV); + (void) sprintf (path, "%s/%s", CVSroot, CVSROOTADM); + if (!isaccessible (path, R_OK | X_OK)) + { + save_errno = errno; + /* If this is "cvs init", the root need not exist yet. */ + if (strcmp (command_name, "init") != 0 +#ifdef CLIENT_SUPPORT + /* If we are a remote client, the root need not exist + on the client machine (FIXME: we should also skip + the check for CVSROOTADM_HISTORY being writable; + it shouldn't matter if there is a read-only file + which happens to have the same name on the client + machine). */ + && strchr (CVSroot, ':') == NULL) +#endif + { + error (0, 0, + "Sorry, you don't have sufficient access to %s", CVSroot); + error (1, save_errno, "%s", path); + } + } + (void) strcat (path, "/"); + (void) strcat (path, CVSROOTADM_HISTORY); + if (isfile (path) && !isaccessible (path, R_OK | W_OK)) + { + save_errno = errno; + error (0, 0, + "Sorry, you don't have read/write access to the history file"); + error (1, save_errno, "%s", path); + } + } + } + +#ifdef SERVER_SUPPORT + if (strcmp (command_name, "server") == 0) + /* This is only used for writing into the history file. Might + be nice to have hostname and/or remote path, on the other hand + I'm not sure whether it is worth the trouble. */ + strcpy (CurDir, "<remote>"); + else if (!getwd (CurDir)) + error (1, 0, "cannot get working directory: %s", CurDir); +#endif + +#ifdef HAVE_PUTENV + /* Now, see if we should update the environment with the Rcsbin value */ + if (cvs_update_env) + { + char *env; + + env = xmalloc (strlen (CVSROOT_ENV) + strlen (CVSroot) + 1 + 1); + (void) sprintf (env, "%s=%s", CVSROOT_ENV, CVSroot); + (void) putenv (env); + /* do not free env, as putenv has control of it */ + } + if (rcsbin_update_env) + { + char *env; + + env = xmalloc (strlen (RCSBIN_ENV) + strlen (Rcsbin) + 1 + 1); + (void) sprintf (env, "%s=%s", RCSBIN_ENV, Rcsbin); + (void) putenv (env); + /* do not free env, as putenv has control of it */ + } +#endif + + /* + * If Rcsbin is set to something, make sure it is terminated with + * a slash character. If not, add one. + */ + if (*Rcsbin) + { + int len = strlen (Rcsbin); + char *rcsbin; + + if (Rcsbin[len - 1] != '/') + { + rcsbin = Rcsbin; + Rcsbin = xmalloc (len + 2); /* one for '/', one for NULL */ + (void) strcpy (Rcsbin, rcsbin); + (void) strcat (Rcsbin, "/"); + } + } + + for (cm = cmds; cm->fullname; cm++) + { + if (cm->nick1 && !strcmp (cp, cm->nick1)) + break; + if (cm->nick2 && !strcmp (cp, cm->nick2)) + break; + if (!strcmp (cp, cm->fullname)) + break; + } + + if (!cm->fullname) + usage (usg); /* no match */ + else + { + command_name = cm->fullname; /* Global pointer for later use */ + + /* make sure we clean up on error */ +#ifdef SIGHUP + (void) SIG_register (SIGHUP, main_cleanup); + (void) SIG_register (SIGHUP, Lock_Cleanup); +#endif +#ifdef SIGINT + (void) SIG_register (SIGINT, main_cleanup); + (void) SIG_register (SIGINT, Lock_Cleanup); +#endif +#ifdef SIGQUIT + (void) SIG_register (SIGQUIT, main_cleanup); + (void) SIG_register (SIGQUIT, Lock_Cleanup); +#endif +#ifdef SIGPIPE + (void) SIG_register (SIGPIPE, main_cleanup); + (void) SIG_register (SIGPIPE, Lock_Cleanup); +#endif +#ifdef SIGTERM + (void) SIG_register (SIGTERM, main_cleanup); + (void) SIG_register (SIGTERM, Lock_Cleanup); +#endif + + gethostname(hostname, sizeof (hostname)); + +#ifdef HAVE_SETVBUF + /* + * Make stdout line buffered, so 'tail -f' can monitor progress. + * Patch creates too much output to monitor and it runs slowly. + */ + if (strcmp (cm->fullname, "patch")) + (void) setvbuf (stdout, (char *) NULL, _IOLBF, 0); +#endif + + if (use_cvsrc) + read_cvsrc (&argc, &argv, command_name); + +#ifdef CLIENT_SUPPORT + /* If cvsroot contains a colon, try to do it via the protocol. */ + { + char *p = CVSroot == NULL ? NULL : strchr (CVSroot, ':'); + if (p) + err = (*(cm->client_func)) (argc, argv); + else + err = (*(cm->func)) (argc, argv); + } +#else /* No CLIENT_SUPPORT */ + err = (*(cm->func)) (argc, argv); + +#endif /* No CLIENT_SUPPORT */ + } + Lock_Cleanup (); + if (err) + return (EXIT_FAILURE); + return 0; +} + +char * +Make_Date (rawdate) + char *rawdate; +{ + struct tm *ftm; + time_t unixtime; + char date[256]; /* XXX bigger than we'll ever need? */ + char *ret; + + unixtime = get_date (rawdate, (struct timeb *) NULL); + if (unixtime == (time_t) - 1) + error (1, 0, "Can't parse date/time: %s", rawdate); +#ifdef HAVE_RCS5 + ftm = gmtime (&unixtime); +#else + ftm = localtime (&unixtime); +#endif + (void) sprintf (date, DATEFORM, + ftm->tm_year + (ftm->tm_year < 100 ? 0 : 1900), + ftm->tm_mon + 1, ftm->tm_mday, ftm->tm_hour, + ftm->tm_min, ftm->tm_sec); + ret = xstrdup (date); + return (ret); +} + +void +usage (cpp) + register const char *const *cpp; +{ + (void) fprintf (stderr, *cpp++, program_name, command_name); + for (; *cpp; cpp++) + (void) fprintf (stderr, *cpp); + exit (EXIT_FAILURE); +} diff --git a/contrib/cvs/src/mkmodules.c b/contrib/cvs/src/mkmodules.c new file mode 100644 index 0000000..bdd27eb --- /dev/null +++ b/contrib/cvs/src/mkmodules.c @@ -0,0 +1,742 @@ +/* + * Copyright (c) 1992, Brian Berliner and Jeff Polk + * Copyright (c) 1989-1992, Brian Berliner + * + * You may distribute under the terms of the GNU General Public License as + * specified in the README file that comes with the CVS kit. */ + +#include "cvs.h" +#include "savecwd.h" + +#ifndef DBLKSIZ +#define DBLKSIZ 4096 /* since GNU ndbm doesn't define it */ +#endif + +static int checkout_file PROTO((char *file, char *temp)); +static void make_tempfile PROTO((char *temp)); +static void rename_rcsfile PROTO((char *temp, char *real)); + +#ifndef MY_NDBM +static void rename_dbmfile PROTO((char *temp)); +static void write_dbmfile PROTO((char *temp)); +#endif /* !MY_NDBM */ + +/* Structure which describes an administrative file. */ +struct admin_file { + /* Name of the file, within the CVSROOT directory. */ + char *filename; + + /* This is a one line description of what the file is for. It is not + currently used, although one wonders whether it should be, somehow. + If NULL, then don't process this file in mkmodules (FIXME: a bit of + a kludge; probably should replace this with a flags field). */ + char *errormsg; + + /* Contents which the file should have in a new repository. To avoid + problems with brain-dead compilers which choke on long string constants, + this is a pointer to an array of char * terminated by NULL--each of + the strings is concatenated. */ + const char * const *contents; +}; + +static const char *const loginfo_contents[] = { + "# The \"loginfo\" file is used to control where \"cvs commit\" log information\n", + "# is sent. The first entry on a line is a regular expression which is tested\n", + "# against the directory that the change is being made to, relative to the\n", + "# $CVSROOT. For the first match that is found, then the remainder of the\n", + "# line is a filter program that should expect log information on its standard\n", + "# input.\n", + "#\n", + "# If the repository name does not match any of the regular expressions in the\n", + "# first field of this file, the \"DEFAULT\" line is used, if it is specified.\n", + "#\n", + "# If the name \"ALL\" appears as a regular expression it is always used\n", + "# in addition to the first matching regex or \"DEFAULT\".\n", + "#\n", + "# The filter program may use one and only one \"%s\" modifier (ala printf). If\n", + "# such a \"%s\" is specified in the filter program, a brief title is included\n", + "# (as one argument, enclosed in single quotes) showing the relative directory\n", + "# name and listing the modified file names.\n", + "#\n", + "# For example:\n", + "#DEFAULT (echo \"\"; who am i; date; cat) >> $CVSROOT/CVSROOT/commitlog\n", + NULL +}; + +static const char *const rcsinfo_contents[] = { + "# The \"rcsinfo\" file is used to control templates with which the editor\n", + "# is invoked on commit and import.\n", + "#\n", + "# The first entry on a line is a regular expression which is tested\n", + "# against the directory that the change is being made to, relative to the\n", + "# $CVSROOT. For the first match that is found, then the remainder of the\n", + "# line is the name of the file that contains the template.\n", + "#\n", + "# If the repository name does not match any of the regular expressions in this\n", + "# file, the \"DEFAULT\" line is used, if it is specified.\n", + "#\n", + "# If the name \"ALL\" appears as a regular expression it is always used\n", + "# in addition to the first matching regex or \"DEFAULT\".\n", + NULL +}; + +static const char *const editinfo_contents[] = { + "# The \"editinfo\" file is used to allow verification of logging\n", + "# information. It works best when a template (as specified in the\n", + "# rcsinfo file) is provided for the logging procedure. Given a\n", + "# template with locations for, a bug-id number, a list of people who\n", + "# reviewed the code before it can be checked in, and an external\n", + "# process to catalog the differences that were code reviewed, the\n", + "# following test can be applied to the code:\n", + "#\n", + "# Making sure that the entered bug-id number is correct.\n", + "# Validating that the code that was reviewed is indeed the code being\n", + "# checked in (using the bug-id number or a seperate review\n", + "# number to identify this particular code set.).\n", + "#\n", + "# If any of the above test failed, then the commit would be aborted.\n", + "#\n", + "# Actions such as mailing a copy of the report to each reviewer are\n", + "# better handled by an entry in the loginfo file.\n", + "#\n", + "# One thing that should be noted is the the ALL keyword is not\n", + "# supported. There can be only one entry that matches a given\n", + "# repository.\n", + NULL +}; + +static const char *const commitinfo_contents[] = { + "# The \"commitinfo\" file is used to control pre-commit checks.\n", + "# The filter on the right is invoked with the repository and a list \n", + "# of files to check. A non-zero exit of the filter program will \n", + "# cause the commit to be aborted.\n", + "#\n", + "# The first entry on a line is a regular expression which is tested\n", + "# against the directory that the change is being committed to, relative\n", + "# to the $CVSROOT. For the first match that is found, then the remainder\n", + "# of the line is the name of the filter to run.\n", + "#\n", + "# If the repository name does not match any of the regular expressions in this\n", + "# file, the \"DEFAULT\" line is used, if it is specified.\n", + "#\n", + "# If the name \"ALL\" appears as a regular expression it is always used\n", + "# in addition to the first matching regex or \"DEFAULT\".\n", + NULL +}; + +static const char *const taginfo_contents[] = { + "# The \"taginfo\" file is used to control pre-tag checks.\n", + "# The filter on the right is invoked with the following arguments:\n", + "#\n", + "# $1 -- tagname\n", + "# $2 -- operation \"add\" for tag, \"mov\" for tag -F, and \"del\" for tag -d\n", + "# $3 -- repository\n", + "# $4-> file revision [file revision ...]\n", + "#\n", + "# A non-zero exit of the filter program will cause the tag to be aborted.\n", + "#\n", + "# The first entry on a line is a regular expression which is tested\n", + "# against the directory that the change is being committed to, relative\n", + "# to the $CVSROOT. For the first match that is found, then the remainder\n", + "# of the line is the name of the filter to run.\n", + "#\n", + "# If the repository name does not match any of the regular expressions in this\n", + "# file, the \"DEFAULT\" line is used, if it is specified.\n", + "#\n", + "# If the name \"ALL\" appears as a regular expression it is always used\n", + "# in addition to the first matching regex or \"DEFAULT\".\n", + NULL +}; + +static const char *const checkoutlist_contents[] = { + "# The \"checkoutlist\" file is used to support additional version controlled\n", + "# administrative files in $CVSROOT/CVSROOT, such as template files.\n", + "#\n", + "# The first entry on a line is a filename which will be checked out from\n", + "# the corresponding RCS file in the $CVSROOT/CVSROOT directory.\n", + "# The remainder of the line is an error message to use if the file cannot\n", + "# be checked out.\n", + "#\n", + "# File format:\n", + "#\n", + "# [<whitespace>]<filename><whitespace><error message><end-of-line>\n", + "#\n", + "# comment lines begin with '#'\n", + NULL +}; + +static const char *const cvswrappers_contents[] = { + "# This file describes wrappers and other binary files to CVS.\n", + "#\n", + "# Wrappers are the concept where directories of files are to be\n", + "# treated as a single file. The intended use is to wrap up a wrapper\n", + "# into a single tar such that the tar archive can be treated as a\n", + "# single binary file in CVS.\n", + "#\n", + "# To solve the problem effectively, it was also necessary to be able to\n", + "# prevent rcsmerge from merging these files.\n", + "#\n", + "# Format of wrapper file ($CVSROOT/CVSROOT/cvswrappers or .cvswrappers)\n", + "#\n", + "# wildcard [option value][option value]...\n", + "#\n", + "# where option is one of\n", + "# -f from cvs filter value: path to filter\n", + "# -t to cvs filter value: path to filter\n", + "# -m update methodology value: MERGE or COPY\n", + "#\n", + "# and value is a single-quote delimited value.\n", + "#\n", + "# For example:\n", + NULL +}; + +static const char *const notify_contents[] = { + "# The \"notify\" file controls where notifications from watches set by\n", + "# \"cvs watch add\" or \"cvs edit\" are sent. The first entry on a line is\n", + "# a regular expression which is tested against the directory that the\n", + "# change is being made to, relative to the $CVSROOT. If it matches,\n", + "# then the remainder of the line is a filter program that should contain\n", + "# one occurrence of %s for the user to notify, and information on its\n", + "# standard input.\n", + "#\n", + "# \"ALL\" or \"DEFAULT\" can be used in place of the regular expression.\n", + "#\n", + "# For example:\n", + "#ALL mail %s -s \"CVS notification\"\n", + NULL +}; + +static const char *const modules_contents[] = { + "# Three different line formats are valid:\n", + "# key -a aliases...\n", + "# key [options] directory\n", + "# key [options] directory files...\n", + "#\n", + "# Where \"options\" are composed of:\n", + "# -i prog Run \"prog\" on \"cvs commit\" from top-level of module.\n", + "# -o prog Run \"prog\" on \"cvs checkout\" of module.\n", + "# -e prog Run \"prog\" on \"cvs export\" of module.\n", + "# -t prog Run \"prog\" on \"cvs rtag\" of module.\n", + "# -u prog Run \"prog\" on \"cvs update\" of module.\n", + "# -d dir Place module in directory \"dir\" instead of module name.\n", + "# -l Top-level directory only -- do not recurse.\n", + "#\n", + "# And \"directory\" is a path to a directory relative to $CVSROOT.\n", + "#\n", + "# The \"-a\" option specifies an alias. An alias is interpreted as if\n", + "# everything on the right of the \"-a\" had been typed on the command line.\n", + "#\n", + "# You can encode a module within a module by using the special '&'\n", + "# character to interpose another module into the current module. This\n", + "# can be useful for creating a module that consists of many directories\n", + "# spread out over the entire source repository.\n", + NULL +}; + +static const struct admin_file filelist[] = { + {CVSROOTADM_LOGINFO, + "no logging of 'cvs commit' messages is done without a %s file", + &loginfo_contents[0]}, + {CVSROOTADM_RCSINFO, + "a %s file can be used to configure 'cvs commit' templates", + rcsinfo_contents}, + {CVSROOTADM_EDITINFO, + "a %s file can be used to validate log messages", + editinfo_contents}, + {CVSROOTADM_COMMITINFO, + "a %s file can be used to configure 'cvs commit' checking", + commitinfo_contents}, + {CVSROOTADM_TAGINFO, + "a %s file can be used to configure 'cvs tag' checking", + taginfo_contents}, + {CVSROOTADM_IGNORE, + "a %s file can be used to specify files to ignore", + NULL}, + {CVSROOTADM_CHECKOUTLIST, + "a %s file can specify extra CVSROOT files to auto-checkout", + checkoutlist_contents}, + {CVSROOTADM_WRAPPER, + "a %s file can be used to specify files to treat as wrappers", + cvswrappers_contents}, + {CVSROOTADM_NOTIFY, + "a %s file can be used to specify where notifications go", + notify_contents}, + {CVSROOTADM_MODULES, + /* modules is special-cased in mkmodules. */ + NULL, + modules_contents}, + {NULL, NULL} +}; + +/* Rebuild the checked out administrative files in directory DIR. */ +int +mkmodules (dir) + char *dir; +{ + struct saved_cwd cwd; + /* FIXME: arbitrary limit */ + char temp[PATH_MAX]; + char *cp, *last, *fname; +#ifdef MY_NDBM + DBM *db; +#endif + FILE *fp; + /* FIXME: arbitrary limit */ + char line[512]; + const struct admin_file *fileptr; + + if (save_cwd (&cwd)) + exit (EXIT_FAILURE); + + if (chdir (dir) < 0) + error (1, errno, "cannot chdir to %s", dir); + + /* + * First, do the work necessary to update the "modules" database. + */ + make_tempfile (temp); + switch (checkout_file (CVSROOTADM_MODULES, temp)) + { + + case 0: /* everything ok */ +#ifdef MY_NDBM + /* open it, to generate any duplicate errors */ + if ((db = dbm_open (temp, O_RDONLY, 0666)) != NULL) + dbm_close (db); +#else + write_dbmfile (temp); + rename_dbmfile (temp); +#endif + rename_rcsfile (temp, CVSROOTADM_MODULES); + break; + + case -1: /* fork failed */ + (void) unlink_file (temp); + exit (EXIT_FAILURE); + /* NOTREACHED */ + + default: + error (0, 0, + "'cvs checkout' is less functional without a %s file", + CVSROOTADM_MODULES); + break; + } /* switch on checkout_file() */ + + (void) unlink_file (temp); + + /* Checkout the files that need it in CVSROOT dir */ + for (fileptr = filelist; fileptr && fileptr->filename; fileptr++) { + if (fileptr->errormsg == NULL) + continue; + make_tempfile (temp); + if (checkout_file (fileptr->filename, temp) == 0) + rename_rcsfile (temp, fileptr->filename); +#if 0 + /* + * If there was some problem other than the file not existing, + * checkout_file already printed a real error message. If the + * file does not exist, it is harmless--it probably just means + * that the repository was created with an old version of CVS + * which didn't have so many files in CVSROOT. + */ + else if (fileptr->errormsg) + error (0, 0, fileptr->errormsg, fileptr->filename); +#endif + (void) unlink_file (temp); + } + + /* Use 'fopen' instead of 'open_file' because we want to ignore error */ + fp = fopen (CVSROOTADM_CHECKOUTLIST, "r"); + if (fp) + { + /* + * File format: + * [<whitespace>]<filename><whitespace><error message><end-of-line> + * + * comment lines begin with '#' + */ + while (fgets (line, sizeof (line), fp) != NULL) + { + /* skip lines starting with # */ + if (line[0] == '#') + continue; + + if ((last = strrchr (line, '\n')) != NULL) + *last = '\0'; /* strip the newline */ + + /* Skip leading white space. */ + for (fname = line; *fname && isspace(*fname); fname++) + ; + + /* Find end of filename. */ + for (cp = fname; *cp && !isspace(*cp); cp++) + ; + *cp = '\0'; + + make_tempfile (temp); + if (checkout_file (fname, temp) == 0) + { + rename_rcsfile (temp, fname); + } + else + { + for (cp++; cp < last && *last && isspace(*last); cp++) + ; + if (cp < last && *cp) + error (0, 0, cp, fname); + } + } + (void) fclose (fp); + } + + if (restore_cwd (&cwd, NULL)) + exit (EXIT_FAILURE); + free_cwd (&cwd); + + return (0); +} + +/* + * Yeah, I know, there are NFS race conditions here. + */ +static void +make_tempfile (temp) + char *temp; +{ + static int seed = 0; + int fd; + + if (seed == 0) + seed = getpid (); + while (1) + { + (void) sprintf (temp, "%s%d", BAKPREFIX, seed++); + if ((fd = open (temp, O_CREAT|O_EXCL|O_RDWR, 0666)) != -1) + break; + if (errno != EEXIST) + error (1, errno, "cannot create temporary file %s", temp); + } + if (close(fd) < 0) + error(1, errno, "cannot close temporary file %s", temp); +} + +static int +checkout_file (file, temp) + char *file; + char *temp; +{ + char rcs[PATH_MAX]; + int retcode = 0; + + (void) sprintf (rcs, "%s%s", file, RCSEXT); + if (!isfile (rcs)) + return (1); + run_setup ("%s%s -x,v/ -q -p", Rcsbin, RCS_CO); + run_arg (rcs); + if ((retcode = run_exec (RUN_TTY, temp, RUN_TTY, RUN_NORMAL)) != 0) + { + error (0, retcode == -1 ? errno : 0, "failed to check out %s file", file); + } + return (retcode); +} + +#ifndef MY_NDBM + +static void +write_dbmfile (temp) + char *temp; +{ + char line[DBLKSIZ], value[DBLKSIZ]; + FILE *fp; + DBM *db; + char *cp, *vp; + datum key, val; + int len, cont, err = 0; + + fp = open_file (temp, "r"); + if ((db = dbm_open (temp, O_RDWR | O_CREAT | O_TRUNC, 0666)) == NULL) + error (1, errno, "cannot open dbm file %s for creation", temp); + for (cont = 0; fgets (line, sizeof (line), fp) != NULL;) + { + if ((cp = strrchr (line, '\n')) != NULL) + *cp = '\0'; /* strip the newline */ + + /* + * Add the line to the value, at the end if this is a continuation + * line; otherwise at the beginning, but only after any trailing + * backslash is removed. + */ + vp = value; + if (cont) + vp += strlen (value); + + /* + * 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]; + else + cp = line; + if (*cp == '\\') + { + cont = 1; + *cp = '\0'; + } + else + { + cont = 0; + } + (void) strcpy (vp, line); + if (value[0] == '#') + continue; /* comment line */ + vp = value; + while (*vp && isspace (*vp)) + vp++; + if (*vp == '\0') + continue; /* empty line */ + + /* + * If this was not a continuation line, add the entry to the database + */ + if (!cont) + { + key.dptr = vp; + while (*vp && !isspace (*vp)) + vp++; + key.dsize = vp - key.dptr; + *vp++ = '\0'; /* NULL terminate the key */ + while (*vp && isspace (*vp)) + vp++; /* skip whitespace to value */ + if (*vp == '\0') + { + error (0, 0, "warning: NULL value for key `%s'", key.dptr); + continue; + } + val.dptr = vp; + val.dsize = strlen (vp); + if (dbm_store (db, key, val, DBM_INSERT) == 1) + { + error (0, 0, "duplicate key found for `%s'", key.dptr); + err++; + } + } + } + dbm_close (db); + (void) fclose (fp); + if (err) + { + char dotdir[50], dotpag[50], dotdb[50]; + + (void) sprintf (dotdir, "%s.dir", temp); + (void) sprintf (dotpag, "%s.pag", temp); + (void) sprintf (dotdb, "%s.db", temp); + (void) unlink_file (dotdir); + (void) unlink_file (dotpag); + (void) unlink_file (dotdb); + error (1, 0, "DBM creation failed; correct above errors"); + } +} + +static void +rename_dbmfile (temp) + char *temp; +{ + char newdir[50], newpag[50], newdb[50]; + char dotdir[50], dotpag[50], dotdb[50]; + char bakdir[50], bakpag[50], bakdb[50]; + + (void) sprintf (dotdir, "%s.dir", CVSROOTADM_MODULES); + (void) sprintf (dotpag, "%s.pag", CVSROOTADM_MODULES); + (void) sprintf (dotdb, "%s.db", CVSROOTADM_MODULES); + (void) sprintf (bakdir, "%s%s.dir", BAKPREFIX, CVSROOTADM_MODULES); + (void) sprintf (bakpag, "%s%s.pag", BAKPREFIX, CVSROOTADM_MODULES); + (void) sprintf (bakdb, "%s%s.db", BAKPREFIX, CVSROOTADM_MODULES); + (void) sprintf (newdir, "%s.dir", temp); + (void) sprintf (newpag, "%s.pag", temp); + (void) sprintf (newdb, "%s.db", temp); + + (void) chmod (newdir, 0666); + (void) chmod (newpag, 0666); + (void) chmod (newdb, 0666); + + /* don't mess with me */ + SIG_beginCrSect (); + + (void) unlink_file (bakdir); /* rm .#modules.dir .#modules.pag */ + (void) unlink_file (bakpag); + (void) unlink_file (bakdb); + (void) rename (dotdir, bakdir); /* mv modules.dir .#modules.dir */ + (void) rename (dotpag, bakpag); /* mv modules.pag .#modules.pag */ + (void) rename (dotdb, bakdb); /* mv modules.db .#modules.db */ + (void) rename (newdir, dotdir); /* mv "temp".dir modules.dir */ + (void) rename (newpag, dotpag); /* mv "temp".pag modules.pag */ + (void) rename (newdb, dotdb); /* mv "temp".db modules.db */ + + /* OK -- make my day */ + SIG_endCrSect (); +} + +#endif /* !MY_NDBM */ + +static void +rename_rcsfile (temp, real) + char *temp; + char *real; +{ + char bak[50]; + struct stat statbuf; + char rcs[PATH_MAX]; + + /* Set "x" bits if set in original. */ + (void) sprintf (rcs, "%s%s", real, RCSEXT); + statbuf.st_mode = 0; /* in case rcs file doesn't exist, but it should... */ + (void) stat (rcs, &statbuf); + + if (chmod (temp, 0444 | (statbuf.st_mode & 0111)) < 0) + error (0, errno, "warning: cannot chmod %s", temp); + (void) sprintf (bak, "%s%s", BAKPREFIX, real); + (void) unlink_file (bak); /* rm .#loginfo */ + (void) rename (real, bak); /* mv loginfo .#loginfo */ + (void) rename (temp, real); /* mv "temp" loginfo */ +} + +const char *const init_usage[] = { + "Usage: %s %s\n", + NULL +}; + +/* Create directory NAME if it does not already exist; fatal error for + other errors. FIXME: This should be in filesubr.c or thereabouts, + probably. Perhaps it should be further abstracted, though (for example + to handle CVSUMASK where appropriate?). */ +static void +mkdir_if_needed (name) + char *name; +{ + if (CVS_MKDIR (name, 0777) < 0) + { + if (errno != EEXIST +#ifdef EACCESS + /* OS/2; see longer comment in client.c. */ + && errno != EACCESS +#endif + ) + error (1, errno, "cannot mkdir %s", name); + } +} + +int +init (argc, argv) + int argc; + char **argv; +{ + /* Name of CVSROOT directory. */ + char *adm; + /* Name of this administrative file. */ + char *info; + /* Name of ,v file for this administrative file. */ + char *info_v; + + const struct admin_file *fileptr; + + umask (cvsumask); + + if (argc > 1) + usage (init_usage); + + if (client_active) + { + start_server (); + + ign_setup (); + send_init_command (); + return get_responses_and_close (); + } + + /* Note: we do *not* create parent directories as needed like the + old cvsinit.sh script did. Few utilities do that, and a + non-existent parent directory is as likely to be a typo as something + which needs to be created. */ + mkdir_if_needed (CVSroot); + + adm = xmalloc (strlen (CVSroot) + sizeof (CVSROOTADM) + 10); + strcpy (adm, CVSroot); + strcat (adm, "/"); + strcat (adm, CVSROOTADM); + mkdir_if_needed (adm); + + /* This is needed by the call to "ci" below. */ + if (chdir (adm) < 0) + error (1, errno, "cannot change to directory %s", adm); + + /* 80 is long enough for all the administrative file names, plus + "/" and so on. */ + info = xmalloc (strlen (adm) + 80); + info_v = xmalloc (strlen (adm) + 80); + for (fileptr = filelist; fileptr && fileptr->filename; ++fileptr) + { + if (fileptr->contents == NULL) + continue; + strcpy (info, adm); + strcat (info, "/"); + strcat (info, fileptr->filename); + strcpy (info_v, info); + strcat (info_v, RCSEXT); + if (isfile (info_v)) + /* We will check out this file in the mkmodules step. + Nothing else is required. */ + ; + else + { + int retcode; + + if (!isfile (info)) + { + FILE *fp; + const char * const *p; + + fp = open_file (info, "w"); + for (p = fileptr->contents; *p != NULL; ++p) + if (fputs (*p, fp) < 0) + error (1, errno, "cannot write %s", info); + if (fclose (fp) < 0) + error (1, errno, "cannot close %s", info); + } + /* Now check the file in. FIXME: we could be using + add_rcs_file from import.c which is faster (if it were + tweaked slightly). */ + run_setup ("%s%s -x,v/ -q -u -t-", Rcsbin, RCS_CI); + run_args ("-minitial checkin of %s", fileptr->filename); + run_arg (fileptr->filename); + retcode = run_exec (RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL); + if (retcode != 0) + error (1, retcode == -1 ? errno : 0, + "failed to check in %s", info); + } + } + + /* Turn on history logging by default. The user can remove the file + to disable it. */ + strcpy (info, adm); + strcat (info, "/"); + strcat (info, CVSROOTADM_HISTORY); + if (!isfile (info)) + { + FILE *fp; + + fp = open_file (info, "w"); + if (fclose (fp) < 0) + error (1, errno, "cannot close %s", info); + } + + free (info); + free (info_v); + + mkmodules (adm); + + free (adm); + return 0; +} diff --git a/contrib/cvs/src/modules.c b/contrib/cvs/src/modules.c new file mode 100644 index 0000000..0e07c0b --- /dev/null +++ b/contrib/cvs/src/modules.c @@ -0,0 +1,900 @@ +/* + * Copyright (c) 1992, Brian Berliner and Jeff Polk + * Copyright (c) 1989-1992, Brian Berliner + * + * You may distribute under the terms of the GNU General Public License + * as specified in the README file that comes with the CVS 1.4 kit. + * + * Modules + * + * Functions for accessing the modules file. + * + * The modules file supports basically three formats of lines: + * key [options] directory files... [ -x directory [files] ] ... + * key [options] directory [ -x directory [files] ] ... + * key -a aliases... + * + * The -a option allows an aliasing step in the parsing of the modules + * file. The "aliases" listed on a line following the -a are + * processed one-by-one, as if they were specified as arguments on the + * command line. + */ + +#include "cvs.h" +#include "savecwd.h" + + +/* Defines related to the syntax of the modules file. */ + +/* Options in modules file. Note that it is OK to use GNU getopt features; + we already are arranging to make sure we are using the getopt distributed + with CVS. */ +#define CVSMODULE_OPTS "+ad:i:lo:e:s:t:u:" + +/* Special delimiter. */ +#define CVSMODULE_SPEC '&' + +struct sortrec +{ + char *modname; + char *status; + char *rest; + char *comment; +}; + +static int sort_order PROTO((const PTR l, const PTR r)); +static void save_d PROTO((char *k, int ks, char *d, int ds)); + + +/* + * Open the modules file, and die if the CVSROOT environment variable + * was not set. If the modules file does not exist, that's fine, and + * a warning message is displayed and a NULL is returned. + */ +DBM * +open_module () +{ + char mfile[PATH_MAX]; + + if (CVSroot == NULL) + { + (void) fprintf (stderr, + "%s: must set the CVSROOT environment variable\n", + program_name); + error (1, 0, "or specify the '-d' option to %s", program_name); + } + (void) sprintf (mfile, "%s/%s/%s", CVSroot, CVSROOTADM, CVSROOTADM_MODULES); + return (dbm_open (mfile, O_RDONLY, 0666)); +} + +/* + * Close the modules file, if the open succeeded, that is + */ +void +close_module (db) + DBM *db; +{ + if (db != NULL) + dbm_close (db); +} + +/* + * This is the recursive function that processes a module name. + * It calls back the passed routine for each directory of a module + * It runs the post checkout or post tag proc from the modules file + */ +int +do_module (db, mname, m_type, msg, callback_proc, where, + shorten, local_specified, run_module_prog, extra_arg) + DBM *db; + char *mname; + enum mtype m_type; + char *msg; + CALLBACKPROC callback_proc; + char *where; + int shorten; + int local_specified; + int run_module_prog; + char *extra_arg; +{ + char *checkin_prog = NULL; + char *checkout_prog = NULL; + char *export_prog = NULL; + char *tag_prog = NULL; + char *update_prog = NULL; + struct saved_cwd cwd; + char *line; + int modargc; + int xmodargc; + char **modargv; + char *xmodargv[MAXFILEPERDIR]; + char *value; + char *zvalue; + char *mwhere = NULL; + char *mfile = NULL; + char *spec_opt = NULL; + char xvalue[PATH_MAX]; + int alias = 0; + datum key, val; + char *cp; + int c, err = 0; + +#ifdef SERVER_SUPPORT + if (trace) + { + fprintf (stderr, "%s%c-> do_module (%s, %s, %s, %s)\n", + error_use_protocol ? "E " : "", + (server_active) ? 'S' : ' ', + mname, msg, where ? where : "", + extra_arg ? extra_arg : ""); + } +#endif + + /* if this is a directory to ignore, add it to that list */ + if (mname[0] == '!' && mname[1] != '\0') + { + ign_dir_add (mname+1); + return(err); + } + + /* strip extra stuff from the module name */ + strip_path (mname); + + /* + * Look up the module using the following scheme: + * 1) look for mname as a module name + * 2) look for mname as a directory + * 3) look for mname as a file + * 4) take mname up to the first slash and look it up as a module name + * (this is for checking out only part of a module) + */ + + /* look it up as a module name */ + key.dptr = mname; + key.dsize = strlen (key.dptr); + if (db != NULL) + val = dbm_fetch (db, key); + else + val.dptr = NULL; + if (val.dptr != NULL) + { + /* null terminate the value XXX - is this space ours? */ + val.dptr[val.dsize] = '\0'; + + /* If the line ends in a comment, strip it off */ + if ((cp = strchr (val.dptr, '#')) != NULL) + { + do + *cp-- = '\0'; + while (isspace (*cp)); + } + else + { + /* Always strip trailing spaces */ + cp = strchr (val.dptr, '\0'); + while (cp > val.dptr && isspace(*--cp)) + *cp = '\0'; + } + + value = val.dptr; + mwhere = xstrdup (mname); + goto found; + } + else + { + char file[PATH_MAX]; + char attic_file[PATH_MAX]; + char *acp; + + /* check to see if mname is a directory or file */ + + (void) sprintf (file, "%s/%s", CVSroot, mname); + if ((acp = strrchr (mname, '/')) != NULL) + { + *acp = '\0'; + (void) sprintf (attic_file, "%s/%s/%s/%s%s", CVSroot, mname, + CVSATTIC, acp + 1, RCSEXT); + *acp = '/'; + } + else + (void) sprintf (attic_file, "%s/%s/%s%s", CVSroot, CVSATTIC, + mname, RCSEXT); + + if (isdir (file)) + { + value = mname; + goto found; + } + else + { + (void) strcat (file, RCSEXT); + if (isfile (file) || isfile (attic_file)) + { + /* if mname was a file, we have to split it into "dir file" */ + if ((cp = strrchr (mname, '/')) != NULL && cp != mname) + { + char *slashp; + + /* put the ' ' in a copy so we don't mess up the original */ + value = strcpy (xvalue, mname); + slashp = strrchr (value, '/'); + *slashp = ' '; + } + else + { + /* + * the only '/' at the beginning or no '/' at all + * means the file we are interested in is in CVSROOT + * itself so the directory should be '.' + */ + if (cp == mname) + { + /* drop the leading / if specified */ + value = strcpy (xvalue, ". "); + (void) strcat (xvalue, mname + 1); + } + else + { + /* otherwise just copy it */ + value = strcpy (xvalue, ". "); + (void) strcat (xvalue, mname); + } + } + goto found; + } + } + } + + /* look up everything to the first / as a module */ + if (mname[0] != '/' && (cp = strchr (mname, '/')) != NULL) + { + /* Make the slash the new end of the string temporarily */ + *cp = '\0'; + key.dptr = mname; + key.dsize = strlen (key.dptr); + + /* do the lookup */ + if (db != NULL) + val = dbm_fetch (db, key); + else + val.dptr = NULL; + + /* if we found it, clean up the value and life is good */ + if (val.dptr != NULL) + { + char *cp2; + + /* null terminate the value XXX - is this space ours? */ + val.dptr[val.dsize] = '\0'; + + /* If the line ends in a comment, strip it off */ + if ((cp2 = strchr (val.dptr, '#')) != NULL) + { + do + *cp2-- = '\0'; + while (isspace (*cp2)); + } + value = val.dptr; + + /* mwhere gets just the module name */ + mwhere = xstrdup (mname); + mfile = cp + 1; + + /* put the / back in mname */ + *cp = '/'; + + goto found; + } + + /* put the / back in mname */ + *cp = '/'; + } + + /* if we got here, we couldn't find it using our search, so give up */ + error (0, 0, "cannot find module `%s' - ignored", mname); + err++; + if (mwhere) + free (mwhere); + return (err); + + + /* + * At this point, we found what we were looking for in one + * of the many different forms. + */ + found: + + /* remember where we start */ + if (save_cwd (&cwd)) + exit (EXIT_FAILURE); + + /* copy value to our own string since if we go recursive we'll be + really screwed if we do another dbm lookup */ + zvalue = xstrdup (value); + value = zvalue; + + /* search the value for the special delimiter and save for later */ + if ((cp = strchr (value, CVSMODULE_SPEC)) != NULL) + { + *cp = '\0'; /* null out the special char */ + spec_opt = cp + 1; /* save the options for later */ + + if (cp != value) /* strip whitespace if necessary */ + while (isspace (*--cp)) + *cp = '\0'; + + if (cp == value) + { + /* + * we had nothing but special options, so skip arg + * parsing and regular stuff entirely + * + * If there were only special ones though, we must + * make the appropriate directory and cd to it + */ + char *dir; + + /* XXX - XXX - MAJOR HACK - DO NOT SHIP - this needs to + be !pipeout, but we don't know that here yet */ + if (!run_module_prog) + goto out; + + dir = where ? where : mname; + /* XXX - think about making null repositories at each dir here + instead of just at the bottom */ + make_directories (dir); + if (chdir (dir) < 0) + { + error (0, errno, "cannot chdir to %s", dir); + spec_opt = NULL; + err++; + goto out; + } + if (!isfile (CVSADM)) + { + char nullrepos[PATH_MAX]; + + (void) sprintf (nullrepos, "%s/%s/%s", CVSroot, + CVSROOTADM, CVSNULLREPOS); + if (!isfile (nullrepos)) + { + mode_t omask; + omask = umask (cvsumask); + (void) CVS_MKDIR (nullrepos, 0777); + (void) umask (omask); + } + if (!isdir (nullrepos)) + error (1, 0, "there is no repository %s", nullrepos); + + Create_Admin (".", dir, + nullrepos, (char *) NULL, (char *) NULL); + if (!noexec) + { + FILE *fp; + + fp = open_file (CVSADM_ENTSTAT, "w+"); + if (fclose (fp) == EOF) + error (1, errno, "cannot close %s", CVSADM_ENTSTAT); +#ifdef SERVER_SUPPORT + if (server_active) + server_set_entstat (dir, nullrepos); +#endif + } + } + out: + goto do_special; + } + } + + /* don't do special options only part of a module was specified */ + if (mfile != NULL) + spec_opt = NULL; + + /* + * value now contains one of the following: + * 1) dir + * 2) dir file + * 3) the value from modules without any special args + * [ args ] dir [file] [file] ... + * or -a module [ module ] ... + */ + + /* Put the value on a line with XXX prepended for getopt to eat */ + line = xmalloc (strlen (value) + 10); + (void) sprintf (line, "%s %s", "XXX", value); + + /* turn the line into an argv[] array */ + line2argv (&xmodargc, xmodargv, line); + free (line); + modargc = xmodargc; + modargv = xmodargv; + + /* parse the args */ + optind = 1; + while ((c = getopt (modargc, modargv, CVSMODULE_OPTS)) != -1) + { + switch (c) + { + case 'a': + alias = 1; + break; + case 'd': + if (mwhere) + free (mwhere); + mwhere = xstrdup (optarg); + break; + case 'i': + checkin_prog = optarg; + break; + case 'l': + local_specified = 1; + break; + case 'o': + checkout_prog = optarg; + break; + case 'e': + export_prog = optarg; + break; + case 't': + tag_prog = optarg; + break; + case 'u': + update_prog = optarg; + break; + case '?': + error (0, 0, + "modules file has invalid option for key %s value %s", + key.dptr, val.dptr); + err++; + if (mwhere) + free (mwhere); + free (zvalue); + free_cwd (&cwd); + return (err); + } + } + modargc -= optind; + modargv += optind; + if (modargc == 0) + { + error (0, 0, "modules file missing directory for module %s", mname); + if (mwhere) + free (mwhere); + free (zvalue); + free_cwd (&cwd); + return (++err); + } + + /* if this was an alias, call ourselves recursively for each module */ + if (alias) + { + int i; + + for (i = 0; i < modargc; i++) + { + if (strcmp (mname, modargv[i]) == 0) + error (0, 0, + "module `%s' in modules file contains infinite loop", + mname); + else + err += do_module (db, modargv[i], m_type, msg, callback_proc, + where, shorten, local_specified, + run_module_prog, extra_arg); + } + if (mwhere) + free (mwhere); + free (zvalue); + free_cwd (&cwd); + return (err); + } + + /* otherwise, process this module */ + err += callback_proc (&modargc, modargv, where, mwhere, mfile, shorten, + local_specified, mname, msg); + +#if 0 + /* FIXME: I've fixed this so that the correct arguments are called, + but now this fails because there is code below this point that + uses optarg values extracted from the arg vector. */ + free_names (&xmodargc, xmodargv); +#endif + + /* if there were special include args, process them now */ + + do_special: + + /* blow off special options if -l was specified */ + if (local_specified) + spec_opt = NULL; + + while (spec_opt != NULL) + { + char *next_opt; + + cp = strchr (spec_opt, CVSMODULE_SPEC); + if (cp != NULL) + { + /* save the beginning of the next arg */ + next_opt = cp + 1; + + /* strip whitespace off the end */ + do + *cp = '\0'; + while (isspace (*--cp)); + } + else + next_opt = NULL; + + /* strip whitespace from front */ + while (isspace (*spec_opt)) + spec_opt++; + + if (*spec_opt == '\0') + error (0, 0, "Mal-formed %c option for module %s - ignored", + CVSMODULE_SPEC, mname); + else + err += do_module (db, spec_opt, m_type, msg, callback_proc, + (char *) NULL, 0, local_specified, + run_module_prog, extra_arg); + spec_opt = next_opt; + } + + /* write out the checkin/update prog files if necessary */ +#ifdef SERVER_SUPPORT + if (err == 0 && !noexec && m_type == CHECKOUT && server_expanding) + { + if (checkin_prog != NULL) + server_prog (where ? where : mname, checkin_prog, PROG_CHECKIN); + if (update_prog != NULL) + server_prog (where ? where : mname, update_prog, PROG_UPDATE); + } + else +#endif + if (err == 0 && !noexec && m_type == CHECKOUT && run_module_prog) + { + FILE *fp; + + if (checkin_prog != NULL) + { + fp = open_file (CVSADM_CIPROG, "w+"); + (void) fprintf (fp, "%s\n", checkin_prog); + if (fclose (fp) == EOF) + error (1, errno, "cannot close %s", CVSADM_CIPROG); + } + if (update_prog != NULL) + { + fp = open_file (CVSADM_UPROG, "w+"); + (void) fprintf (fp, "%s\n", update_prog); + if (fclose (fp) == EOF) + error (1, errno, "cannot close %s", CVSADM_UPROG); + } + } + + /* cd back to where we started */ + if (restore_cwd (&cwd, NULL)) + exit (EXIT_FAILURE); + free_cwd (&cwd); + + /* run checkout or tag prog if appropriate */ + if (err == 0 && run_module_prog) + { + if ((m_type == TAG && tag_prog != NULL) || + (m_type == CHECKOUT && checkout_prog != NULL) || + (m_type == EXPORT && export_prog != NULL)) + { + /* + * If a relative pathname is specified as the checkout, tag + * or export proc, try to tack on the current "where" value. + * if we can't find a matching program, just punt and use + * whatever is specified in the modules file. + */ + char real_prog[PATH_MAX]; + char *prog = (m_type == TAG ? tag_prog : + (m_type == CHECKOUT ? checkout_prog : export_prog)); + char *real_where = (where != NULL ? where : mwhere); + + if ((*prog != '/') && (*prog != '.')) + { + (void) sprintf (real_prog, "%s/%s", real_where, prog); + if (isfile (real_prog)) + prog = real_prog; + } + + run_setup ("%s %s", prog, real_where); + if (extra_arg) + run_arg (extra_arg); + + if (!quiet) + { + (void) printf ("%s %s: Executing '", program_name, + command_name); + run_print (stdout); + (void) printf ("'\n"); + } + err += run_exec (RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL); + } + } + + /* clean up */ + if (mwhere) + free (mwhere); + free (zvalue); + + return (err); +} + +/* - Read all the records from the modules database into an array. + - Sort the array depending on what format is desired. + - Print the array in the format desired. + + Currently, there are only two "desires": + + 1. Sort by module name and format the whole entry including switches, + files and the comment field: (Including aliases) + + modulename -s switches, one per line, even if + -i it has many switches. + Directories and files involved, formatted + to cover multiple lines if necessary. + # Comment, also formatted to cover multiple + # lines if necessary. + + 2. Sort by status field string and print: (*not* including aliases) + + modulename STATUS Directories and files involved, formatted + to cover multiple lines if necessary. + # Comment, also formatted to cover multiple + # lines if necessary. +*/ + +static struct sortrec *s_head; + +static int s_max = 0; /* Number of elements allocated */ +static int s_count = 0; /* Number of elements used */ + +static int Status; /* Nonzero if the user is + interested in status + information as well as + module name */ +static char def_status[] = "NONE"; + +/* Sort routine for qsort: + - If we want the "Status" field to be sorted, check it first. + - Then compare the "module name" fields. Since they are unique, we don't + have to look further. +*/ +static int +sort_order (l, r) + const PTR l; + const PTR r; +{ + int i; + const struct sortrec *left = (const struct sortrec *) l; + const struct sortrec *right = (const struct sortrec *) r; + + if (Status) + { + /* If Sort by status field, compare them. */ + if ((i = strcmp (left->status, right->status)) != 0) + return (i); + } + return (strcmp (left->modname, right->modname)); +} + +static void +save_d (k, ks, d, ds) + char *k; + int ks; + char *d; + int ds; +{ + char *cp, *cp2; + struct sortrec *s_rec; + + if (Status && *d == '-' && *(d + 1) == 'a') + return; /* We want "cvs co -s" and it is an alias! */ + + if (s_count == s_max) + { + s_max += 64; + s_head = (struct sortrec *) xrealloc ((char *) s_head, s_max * sizeof (*s_head)); + } + s_rec = &s_head[s_count]; + s_rec->modname = cp = xmalloc (ks + 1); + (void) strncpy (cp, k, ks); + *(cp + ks) = '\0'; + + s_rec->rest = cp2 = xmalloc (ds + 1); + cp = d; + *(cp + ds) = '\0'; /* Assumes an extra byte at end of static dbm buffer */ + + while (isspace (*cp)) + cp++; + /* Turn <spaces> into one ' ' -- makes the rest of this routine simpler */ + while (*cp) + { + if (isspace (*cp)) + { + *cp2++ = ' '; + while (isspace (*cp)) + cp++; + } + else + *cp2++ = *cp++; + } + *cp2 = '\0'; + + /* Look for the "-s statusvalue" text */ + if (Status) + { + s_rec->status = def_status; + + /* Minor kluge, but general enough to maintain */ + for (cp = s_rec->rest; (cp2 = strchr (cp, '-')) != NULL; cp = ++cp2) + { + if (*(cp2 + 1) == 's' && *(cp2 + 2) == ' ') + { + s_rec->status = (cp2 += 3); + while (*cp2 != ' ') + cp2++; + *cp2++ = '\0'; + cp = cp2; + break; + } + } + } + else + cp = s_rec->rest; + + /* Find comment field, clean up on all three sides & compress blanks */ + if ((cp2 = cp = strchr (cp, '#')) != NULL) + { + if (*--cp2 == ' ') + *cp2 = '\0'; + if (*++cp == ' ') + cp++; + s_rec->comment = cp; + } + else + s_rec->comment = ""; + + s_count++; +} + +/* Print out the module database as we know it. If STATUS is + non-zero, print out status information for each module. */ + +void +cat_module (status) + int status; +{ + DBM *db; + datum key, val; + int i, c, wid, argc, cols = 80, indent, fill; + int moduleargc; + struct sortrec *s_h; + char *cp, *cp2, **argv; + char *line; + char *moduleargv[MAXFILEPERDIR]; + +#ifdef sun +#ifdef TIOCGSIZE + struct ttysize ts; + + (void) ioctl (0, TIOCGSIZE, &ts); + cols = ts.ts_cols; +#endif +#else +#ifdef TIOCGWINSZ + struct winsize ws; + + (void) ioctl (0, TIOCGWINSZ, &ws); + cols = ws.ws_col; +#endif +#endif + + Status = status; + + /* Read the whole modules file into allocated records */ + if (!(db = open_module ())) + error (1, 0, "failed to open the modules file"); + + for (key = dbm_firstkey (db); key.dptr != NULL; key = dbm_nextkey (db)) + { + val = dbm_fetch (db, key); + if (val.dptr != NULL) + save_d (key.dptr, key.dsize, val.dptr, val.dsize); + } + + /* Sort the list as requested */ + qsort ((PTR) s_head, s_count, sizeof (struct sortrec), sort_order); + + /* + * Run through the sorted array and format the entries + * indent = space for modulename + space for status field + */ + indent = 12 + (status * 12); + fill = cols - (indent + 2); + for (s_h = s_head, i = 0; i < s_count; i++, s_h++) + { + /* Print module name (and status, if wanted) */ + (void) printf ("%-12s", s_h->modname); + if (status) + { + (void) printf (" %-11s", s_h->status); + if (s_h->status != def_status) + *(s_h->status + strlen (s_h->status)) = ' '; + } + + /* Parse module file entry as command line and print options */ + line = xmalloc (strlen (s_h->modname) + strlen (s_h->rest) + 10); + (void) sprintf (line, "%s %s", s_h->modname, s_h->rest); + line2argv (&moduleargc, moduleargv, line); + free (line); + argc = moduleargc; + argv = moduleargv; + + optind = 0; + wid = 0; + while ((c = getopt (argc, argv, CVSMODULE_OPTS)) != -1) + { + if (!status) + { + if (c == 'a' || c == 'l') + { + (void) printf (" -%c", c); + wid += 3; /* Could just set it to 3 */ + } + else + { + if (strlen (optarg) + 4 + wid > (unsigned) fill) + { + (void) printf ("\n%*s", indent, ""); + wid = 0; + } + (void) printf (" -%c %s", c, optarg); + wid += strlen (optarg) + 4; + } + } + } + argc -= optind; + argv += optind; + + /* Format and Print all the files and directories */ + for (; argc--; argv++) + { + if (strlen (*argv) + wid > (unsigned) fill) + { + (void) printf ("\n%*s", indent, ""); + wid = 0; + } + (void) printf (" %s", *argv); + wid += strlen (*argv) + 1; + } + (void) printf ("\n"); + + /* Format the comment field -- save_d (), compressed spaces */ + for (cp2 = cp = s_h->comment; *cp; cp2 = cp) + { + (void) printf ("%*s # ", indent, ""); + if (strlen (cp2) < (unsigned) (fill - 2)) + { + (void) printf ("%s\n", cp2); + break; + } + cp += fill - 2; + while (*cp != ' ' && cp > cp2) + cp--; + if (cp == cp2) + { + (void) printf ("%s\n", cp2); + break; + } + + *cp++ = '\0'; + (void) printf ("%s\n", cp2); + } + + free_names(&moduleargc, moduleargv); + } +} diff --git a/contrib/cvs/src/myndbm.c b/contrib/cvs/src/myndbm.c new file mode 100644 index 0000000..527f7ee --- /dev/null +++ b/contrib/cvs/src/myndbm.c @@ -0,0 +1,288 @@ +/* + * Copyright (c) 1992, Brian Berliner + * + * You may distribute under the terms of the GNU General Public License as + * specified in the README file that comes with the CVS 1.4 kit. + * + * A simple ndbm-emulator for CVS. It parses a text file of the format: + * + * key value + * + * at dbm_open time, and loads the entire file into memory. As such, it is + * probably only good for fairly small modules files. Ours is about 30K in + * size, and this code works fine. + */ + +#include <assert.h> +#include "cvs.h" +#include "getline.h" + +#ifdef MY_NDBM + +static void mydbm_load_file (); + +/* ARGSUSED */ +DBM * +mydbm_open (file, flags, mode) + char *file; + int flags; + int mode; +{ + FILE *fp; + DBM *db; + + fp = fopen (file, FOPEN_BINARY_READ); + if (fp == NULL && !(existence_error (errno) && (flags & O_CREAT))) + return ((DBM *) 0); + + db = (DBM *) xmalloc (sizeof (*db)); + db->dbm_list = getlist (); + db->modified = 0; + db->name = xstrdup (file); + + if (fp != NULL) + { + mydbm_load_file (fp, db->dbm_list); + if (fclose (fp) < 0) + error (0, errno, "cannot close %s", file); + } + return (db); +} + +static int write_item PROTO ((Node *, void *)); + +static int +write_item (node, data) + Node *node; + void *data; +{ + FILE *fp = (FILE *)data; + fputs (node->key, fp); + fputs (" ", fp); + fputs (node->data, fp); + fputs ("\012", fp); + return 0; +} + +void +mydbm_close (db) + DBM *db; +{ + if (db->modified) + { + FILE *fp; + fp = fopen (db->name, FOPEN_BINARY_WRITE); + if (fp == NULL) + error (1, errno, "cannot write %s", db->name); + walklist (db->dbm_list, write_item, (void *)fp); + if (fclose (fp) < 0) + error (0, errno, "cannot close %s", db->name); + } + free (db->name); + dellist (&db->dbm_list); + free ((char *) db); +} + +datum +mydbm_fetch (db, key) + DBM *db; + datum key; +{ + Node *p; + char *s; + datum val; + + /* make sure it's null-terminated */ + s = xmalloc (key.dsize + 1); + (void) strncpy (s, key.dptr, key.dsize); + s[key.dsize] = '\0'; + + p = findnode (db->dbm_list, s); + if (p) + { + val.dptr = p->data; + val.dsize = strlen (p->data); + } + else + { + val.dptr = (char *) NULL; + val.dsize = 0; + } + free (s); + return (val); +} + +datum +mydbm_firstkey (db) + DBM *db; +{ + Node *head, *p; + datum key; + + head = db->dbm_list->list; + p = head->next; + if (p != head) + { + key.dptr = p->key; + key.dsize = strlen (p->key); + } + else + { + key.dptr = (char *) NULL; + key.dsize = 0; + } + db->dbm_next = p->next; + return (key); +} + +datum +mydbm_nextkey (db) + DBM *db; +{ + Node *head, *p; + datum key; + + head = db->dbm_list->list; + p = db->dbm_next; + if (p != head) + { + key.dptr = p->key; + key.dsize = strlen (p->key); + } + else + { + key.dptr = (char *) NULL; + key.dsize = 0; + } + db->dbm_next = p->next; + return (key); +} + +/* Note: only updates the in-memory copy, which is written out at + mydbm_close time. Note: Also differs from DBM in that on duplication, + it gives a warning, rather than either DBM_INSERT or DBM_REPLACE + behavior. */ +int +mydbm_store (db, key, value, flags) + DBM *db; + datum key; + datum value; + int flags; +{ + Node *node; + + node = getnode (); + node->type = NDBMNODE; + + node->key = xmalloc (key.dsize + 1); + strncpy (node->key, key.dptr, key.dsize); + node->key[key.dsize] = '\0'; + + node->data = xmalloc (value.dsize + 1); + strncpy (node->data, value.dptr, value.dsize); + node->data[value.dsize] = '\0'; + + db->modified = 1; + if (addnode (db->dbm_list, node) == -1) + { + error (0, 0, "attempt to insert duplicate key `%s'", node->key); + freenode (node); + return 0; + } + return 0; +} + +static void +mydbm_load_file (fp, list) + FILE *fp; + List *list; +{ + char *line = NULL; + size_t line_len; + /* FIXME: arbitrary limit. */ + char value[MAXLINELEN]; + char *cp, *vp; + int len, cont; + + for (cont = 0; getline (&line, &line_len, fp) >= 0;) + { + if ((cp = strrchr (line, '\012')) != NULL) + *cp = '\0'; /* strip the newline */ + cp = line + strlen (line); + if (cp > line && cp[-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'; + + /* + * Add the line to the value, at the end if this is a continuation + * line; otherwise at the beginning, but only after any trailing + * backslash is removed. + */ + vp = value; + if (cont) + vp += strlen (value); + + /* + * 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]; + else + cp = line; + if (*cp == '\\') + { + cont = 1; + *cp = '\0'; + } + else + { + cont = 0; + } + (void) strcpy (vp, line); + if (value[0] == '#') + continue; /* comment line */ + vp = value; + while (*vp && isspace (*vp)) + vp++; + if (*vp == '\0') + continue; /* empty line */ + + /* + * If this was not a continuation line, add the entry to the database + */ + if (!cont) + { + Node *p = getnode (); + char *kp; + + kp = vp; + while (*vp && !isspace (*vp)) + vp++; + *vp++ = '\0'; /* NULL terminate the key */ + p->type = NDBMNODE; + p->key = xstrdup (kp); + while (*vp && isspace (*vp)) + vp++; /* skip whitespace to value */ + if (*vp == '\0') + { + error (0, 0, "warning: NULL value for key `%s'", p->key); + freenode (p); + continue; + } + p->data = xstrdup (vp); + if (addnode (list, p) == -1) + { + error (0, 0, "duplicate key found for `%s'", p->key); + freenode (p); + } + } + } + free (line); +} + +#endif /* MY_NDBM */ diff --git a/contrib/cvs/src/myndbm.h b/contrib/cvs/src/myndbm.h new file mode 100644 index 0000000..0431e15 --- /dev/null +++ b/contrib/cvs/src/myndbm.h @@ -0,0 +1,47 @@ +/* $CVSid: @(#)myndbm.h 1.4 94/09/21 $ */ + +#ifdef MY_NDBM + +#define DBLKSIZ 4096 + +typedef struct +{ + List *dbm_list; /* cached database */ + Node *dbm_next; /* next key to return for nextkey() */ + + /* Name of the file to write to if modified is set. malloc'd. */ + char *name; + + /* Nonzero if the database has been modified and dbm_close needs to + write it out to disk. */ + int modified; +} DBM; + +typedef struct +{ + char *dptr; + int dsize; +} datum; + +/* + * So as not to conflict with other dbm_open, etc., routines that may + * be included by someone's libc, all of my emulation routines are prefixed + * by "my" and we define the "standard" ones to be "my" ones here. + */ +#define dbm_open mydbm_open +#define dbm_close mydbm_close +#define dbm_fetch mydbm_fetch +#define dbm_firstkey mydbm_firstkey +#define dbm_nextkey mydbm_nextkey +#define dbm_store mydbm_store +#define DBM_INSERT 0 +#define DBM_REPLACE 1 + +DBM *mydbm_open PROTO((char *file, int flags, int mode)); +void mydbm_close PROTO((DBM * db)); +datum mydbm_fetch PROTO((DBM * db, datum key)); +datum mydbm_firstkey PROTO((DBM * db)); +datum mydbm_nextkey PROTO((DBM * db)); +extern int mydbm_store PROTO ((DBM *, datum, datum, int)); + +#endif /* MY_NDBM */ diff --git a/contrib/cvs/src/no_diff.c b/contrib/cvs/src/no_diff.c new file mode 100644 index 0000000..a0d00f5 --- /dev/null +++ b/contrib/cvs/src/no_diff.c @@ -0,0 +1,129 @@ +/* + * Copyright (c) 1992, Brian Berliner and Jeff Polk + * Copyright (c) 1989-1992, Brian Berliner + * + * You may distribute under the terms of the GNU General Public License as + * specified in the README file that comes with the CVS 1.4 kit. + * + * No Difference + * + * The user file looks modified judging from its time stamp; however it needn't + * be. No_difference() finds out whether it is or not. If it is not, it + * updates the administration. + * + * returns 0 if no differences are found and non-zero otherwise + */ + +#include "cvs.h" + +int +No_Difference (file, vers, entries, repository, update_dir) + char *file; + Vers_TS *vers; + List *entries; + char *repository; + char *update_dir; +{ + Node *p; + char tmp[L_tmpnam+1]; + int ret; + char *ts, *options; + int retcode = 0; + char *tocvsPath; + + if (!vers->srcfile || !vers->srcfile->path) + return (-1); /* different since we couldn't tell */ + + if (vers->entdata && vers->entdata->options) + options = xstrdup (vers->entdata->options); + else + options = xstrdup (""); + + retcode = RCS_checkout (vers->srcfile->path, NULL, vers->vn_user, options, + tmpnam (tmp), 0, 0); + if (retcode == 0) + { +#if 0 + /* Why would we want to munge the modes? And only if the timestamps + are different? And even for commands like "cvs status"???? */ + if (!iswritable (file)) /* fix the modes as a side effect */ + xchmod (file, 1); +#endif + + tocvsPath = wrap_tocvs_process_file (file); + + /* do the byte by byte compare */ + if (xcmp (tocvsPath == NULL ? file : tocvsPath, tmp) == 0) + { +#if 0 + /* Why would we want to munge the modes? And only if the + timestamps are different? And even for commands like + "cvs status"???? */ + if (cvswrite == FALSE) /* fix the modes as a side effect */ + xchmod (file, 0); +#endif + + /* no difference was found, so fix the entries file */ + ts = time_stamp (file); + Register (entries, file, + vers->vn_user ? vers->vn_user : vers->vn_rcs, ts, + options, vers->tag, vers->date, (char *) 0); +#ifdef SERVER_SUPPORT + if (server_active) + { + /* We need to update the entries line on the client side. */ + server_update_entries + (file, update_dir, repository, SERVER_UPDATED); + } +#endif + free (ts); + + /* update the entdata pointer in the vers_ts structure */ + p = findnode (entries, file); + vers->entdata = (Entnode *) p->data; + + ret = 0; + } + else + ret = 1; /* files were really different */ + if (tocvsPath) + { + /* Need to call unlink myself because the noexec variable + * has been set to 1. */ + if (trace) + (void) fprintf (stderr, "%c-> unlink (%s)\n", +#ifdef SERVER_SUPPORT + (server_active) ? 'S' : ' ', +#else + ' ', +#endif + tocvsPath); + if (unlink (tocvsPath) < 0) + error (0, errno, "could not remove %s", tocvsPath); + } + } + else + { + if (update_dir[0] == '\0') + error (0, retcode == -1 ? errno : 0, + "could not check out revision %s of %s", + vers->vn_user, file); + else + error (0, retcode == -1 ? errno : 0, + "could not check out revision %s of %s/%s", + vers->vn_user, update_dir, file); + ret = -1; /* different since we couldn't tell */ + } + + if (trace) +#ifdef SERVER_SUPPORT + (void) fprintf (stderr, "%c-> unlink2 (%s)\n", + (server_active) ? 'S' : ' ', tmp); +#else + (void) fprintf (stderr, "-> unlink (%s)\n", tmp); +#endif + if (unlink (tmp) < 0) + error (0, errno, "could not remove %s", tmp); + free (options); + return (ret); +} diff --git a/contrib/cvs/src/options.h.in b/contrib/cvs/src/options.h.in new file mode 100644 index 0000000..7cb58dc --- /dev/null +++ b/contrib/cvs/src/options.h.in @@ -0,0 +1,275 @@ +/* + * Copyright (c) 1992, Brian Berliner and Jeff Polk + * Copyright (c) 1989-1992, Brian Berliner + * + * You may distribute under the terms of the GNU General Public License as + * specified in the README file that comes with the CVS 1.4 kit. + * + * This file holds (most of) the configuration tweaks that can be made to + * customize CVS for your site. CVS comes configured for a typical SunOS 4.x + * environment. The comments for each configurable item are intended to be + * self-explanatory. All #defines are tested first to see if an over-riding + * option was specified on the "make" command line. + * + * If special libraries are needed, you will have to edit the Makefile.in file + * or the configure script directly. Sorry. + */ + +/* + * CVS provides the most features when used in conjunction with the Version-5 + * release of RCS. Thus, it is the default. This also assumes that GNU diff + * Version-1.15 is being used as well -- you will have to configure your RCS + * V5 release separately to make this the case. If you do not have RCS V5 and + * GNU diff V1.15, comment out this define. You should not try mixing and + * matching other combinations of these tools. + */ +#ifndef HAVE_RCS5 +#define HAVE_RCS5 +#endif + +/* + * If, before installing this version of CVS, you were running RCS V4 AND you + * are installing this CVS and RCS V5 and GNU diff 1.15 all at the same time, + * you should turn on the following define. It only exists to try to do + * reasonable things with your existing checked out files when you upgrade to + * RCS V5, since the keyword expansion formats have changed with RCS V5. + * + * If you already have been running with RCS5, or haven't been running with CVS + * yet at all, or are sticking with RCS V4 for now, leave the commented out. + */ +#ifndef HAD_RCS4 +/* #define HAD_RCS4 */ +#endif + +/* + * For portability and heterogeneity reasons, CVS is shipped by default using + * my own text-file version of the ndbm database library in the src/myndbm.c + * file. If you want better performance and are not concerned about + * heterogeneous hosts accessing your modules file, turn this option off. + */ +#ifndef MY_NDBM +#define MY_NDBM +#endif + +/* + * The "diff" program to execute when creating patch output. This "diff" + * must support the "-c" option for context diffing. Specify a full + * pathname if your site wants to use a particular diff. Note that unlike + * the diff used with RCS, you *must not* supply -a here (doing so will cause + * the server to generate patches which patch cannot handle in some cases). + * + * NOTE: this program is only used for the ``patch'' sub-command (and + * for ``update'' if you are using the server). The other commands + * use rcsdiff which will use whatever version of diff was specified + * when rcsdiff was built on your system. + */ + +#ifndef DIFF +#define DIFF "diff" +#endif + +/* + * The "grep" program to execute when checking to see if a merged file had + * any conflicts. This "grep" must support a standard basic + * regular expression as an argument. Specify a full pathname if your site + * wants to use a particular grep. + */ + +#ifndef GREP +#define GREP "grep" +#endif + +/* + * The "patch" program to run when using the CVS server and accepting + * patches across the network. Specify a full pathname if your site + * wants to use a particular patch. + */ +#ifndef PATCH_PROGRAM +#define PATCH_PROGRAM "patch" +#endif + +/* + * By default, RCS programs are executed with the shell or through execlp(), + * so the user's PATH environment variable is searched. If you'd like to + * bind all RCS programs to a certain directory (perhaps one not in most + * people's PATH) then set the default in RCSBIN_DFLT. Note that setting + * this here will cause all RCS programs to be executed from this directory, + * unless the user overrides the default with the RCSBIN environment variable + * or the "-b" option to CVS. + * + * If you use the password-authenticating server, then you need to + * make sure that the server can find the RCS programs to invoke them. + * The authenticating server starts out running as root, and then + * switches to run as the appropriate user once authentication is + * complete. But no actual shell is ever started by that user, so the + * PATH environment variable may not contain the directory with the + * RCS binaries, even though if that user logged in normally, PATH + * would include the directory. + * + * One way to solve this problem is to set RCSBIN_DFLT here. An + * alternative is to make sure that root has the right directory in + * its path already. Another, probably better alternative is to + * specify -b in /etc/inetd.conf. + * + * This define should be either the empty string ("") or a full + * pathname to the directory containing all the installed programs + * from the RCS distribution. + */ +#ifndef RCSBIN_DFLT +#define RCSBIN_DFLT "" +#endif + +/* + * The default editor to use, if one does not specify the "-e" option to cvs, + * or does not have an EDITOR environment variable. I set this to just "vi", + * and use the shell to find where "vi" actually is. This allows sites with + * /usr/bin/vi or /usr/ucb/vi to work equally well (assuming that your PATH + * is reasonable). + */ +#ifndef EDITOR_DFLT +#define EDITOR_DFLT "vi" +#endif + +/* + * The default umask to use when creating or otherwise setting file or + * directory permissions in the repository. Must be a value in the + * range of 0 through 0777. For example, a value of 002 allows group + * rwx access and world rx access; a value of 007 allows group rwx + * access but no world access. This value is overridden by the value + * of the CVSUMASK environment variable, which is interpreted as an + * octal number. + */ +#ifndef UMASK_DFLT +#define UMASK_DFLT 002 +#endif + +/* + * The cvs admin command is restricted to the members of the group + * CVS_ADMIN_GROUP. If this group does not exist, all users are + * allowed to run cvs admin. To disable the cvs admin for all users, + * create an empty group CVS_ADMIN_GROUP. To disable access control for + * cvs admin, comment out the define below. + */ +#ifndef CVS_ADMIN_GROUP +#define CVS_ADMIN_GROUP "cvsadmin" +#endif + +/* + * The Repository file holds the path to the directory within the source + * repository that contains the RCS ,v files for each CVS working directory. + * This path is either a full-path or a path relative to CVSROOT. + * + * The only advantage that I can see to having a relative path is that One can + * change the physical location of the master source repository, change one's + * CVSROOT environment variable, and CVS will work without problems. I + * recommend using full-paths. + */ +#ifndef RELATIVE_REPOS +/* #define RELATIVE_REPOS */ +#endif + +/* + * When committing or importing files, you must enter a log message. + * Normally, you can do this either via the -m flag on the command line or an + * editor will be started for you. If you like to use logging templates (the + * rcsinfo file within the $CVSROOT/CVSROOT directory), you might want to + * force people to use the editor even if they specify a message with -m. + * Enabling FORCE_USE_EDITOR will cause the -m message to be appended to the + * temp file when the editor is started. + */ +#ifndef FORCE_USE_EDITOR +/* #define FORCE_USE_EDITOR */ +#endif + +/* + * When locking the repository, some sites like to remove locks and assume + * the program that created them went away if the lock has existed for a long + * time. This used to be the default for previous versions of CVS. CVS now + * attempts to be much more robust, so lock files should not be left around + * by mistake. The new behaviour will never remove old locks (they must now + * be removed by hand). Enabling CVS_FUDGELOCKS will cause CVS to remove + * locks that are older than CVSLCKAGE seconds. + * Use of this option is NOT recommended. + */ +#ifndef CVS_FUDGELOCKS +/* #define CVS_FUDGELOCKS */ +#endif + +/* + * When committing a permanent change, CVS and RCS make a log entry of + * who committed the change. If you are committing the change logged in + * as "root" (not under "su" or other root-priv giving program), CVS/RCS + * cannot determine who is actually making the change. + * + * As such, by default, CVS disallows changes to be committed by users + * logged in as "root". You can disable this option by commenting + * out the lines below. + */ +#ifndef CVS_BADROOT +#define CVS_BADROOT +#endif + +/* + * The "cvs diff" command accepts all the single-character options that GNU + * diff (1.15) accepts. Except -D. GNU diff uses -D as a way to put + * cpp-style #define's around the output differences. CVS, by default, uses + * -D to specify a free-form date (like "cvs diff -D '1 week ago'"). If + * you would prefer that the -D option of "cvs diff" work like the GNU diff + * option, then comment out this define. + */ +#ifndef CVS_DIFFDATE +#define CVS_DIFFDATE +#endif + +/* Define this to enable the SETXID support. The way to use this is + to create a group with no users in it (except perhaps cvs + administrators), set the cvs executable to setgid that group, chown + all the repository files to that group, and change all directory + permissions in the repository to 770. The last person to modify a + file will own it, but as long as directory permissions are set + right that won't matter. You'll need a system which inherits file + groups from the parent directory. I don't know how carefully this + has been inspected for security holes. */ + +#ifndef SETXID_SUPPORT +/* #define SETXID_SUPPORT */ +#endif + +/* Should we build the password-authenticating client? Whether to + include the password-authenticating _server_, on the other hand, is + set in config.h. */ +#define AUTH_CLIENT_SUPPORT 1 + +/* + * If you are working with a large remote repository and a 'cvs checkout' is + * swamping your network and memory, define these to enable flow control. + * You will end up with even less guarantees of a consistant checkout, + * but that may be better than no checkout at all. The master server process + * will monitor how far it is getting behind, if it reaches the high water + * mark, it will signal the child process to stop generating data when + * convenient (ie: no locks are held, currently at the beginning of a + * new directory). Once the buffer has drained sufficiently to reach the + * low water mark, it will be signalled to start again. + * -- EXPERIMENTAL! -- A better solution may be in the works. + * You may override the default hi/low watermarks here too. + */ +#ifndef SERVER_FLOWCONTROL +/* #define SERVER_FLOWCONTROL */ +/* #define SERVER_HI_WATER (2 * 1024 * 1024) */ +/* #define SERVER_LO_WATER (1 * 1024 * 1024) */ +#endif + +/* End of CVS configuration section */ + +/* + * Externs that are included in libc, but are used frequently enough to + * warrant defining here. + */ +#ifndef STDC_HEADERS +extern void exit (); +#endif + +#ifndef getwd +extern char *getwd (); +#endif + diff --git a/contrib/cvs/src/parseinfo.c b/contrib/cvs/src/parseinfo.c new file mode 100644 index 0000000..c567ef8 --- /dev/null +++ b/contrib/cvs/src/parseinfo.c @@ -0,0 +1,162 @@ +/* + * Copyright (c) 1992, Brian Berliner and Jeff Polk + * Copyright (c) 1989-1992, Brian Berliner + * + * You may distribute under the terms of the GNU General Public License as + * specified in the README file that comes with the CVS 1.4 kit. + */ + +#include "cvs.h" + +/* + * Parse the INFOFILE file for the specified REPOSITORY. Invoke CALLPROC for + * the first line in the file that matches the REPOSITORY, or if ALL != 0, any lines + * matching "ALL", or if no lines match, the last line matching "DEFAULT". + * + * Return 0 for success, -1 if there was not an INFOFILE, and >0 for failure. + */ +int +Parse_Info (infofile, repository, callproc, all) + char *infofile; + char *repository; + CALLPROC callproc; + int all; +{ + int err = 0; + FILE *fp_info; + char infopath[PATH_MAX]; + char line[MAXLINELEN]; + char *default_value = NULL; + char *expanded_value= NULL; + int callback_done, line_number; + char *cp, *exp, *value, *srepos; + const char *regex_err; + + if (CVSroot == NULL) + { + /* XXX - should be error maybe? */ + error (0, 0, "CVSROOT variable not set"); + return (1); + } + + /* find the info file and open it */ + (void) sprintf (infopath, "%s/%s/%s", CVSroot, + CVSROOTADM, infofile); + if ((fp_info = fopen (infopath, "r")) == NULL) + return (0); /* no file -> nothing special done */ + + /* strip off the CVSROOT if repository was absolute */ + srepos = Short_Repository (repository); + + if (trace) + (void) fprintf (stderr, "-> ParseInfo(%s, %s, %s)\n", + infopath, srepos, all ? "ALL" : "not ALL"); + + /* search the info file for lines that match */ + callback_done = line_number = 0; + while (fgets (line, sizeof (line), fp_info) != NULL) + { + line_number++; + + /* skip lines starting with # */ + if (line[0] == '#') + continue; + + /* skip whitespace at beginning of line */ + for (cp = line; *cp && isspace (*cp); cp++) + ; + + /* if *cp is null, the whole line was blank */ + if (*cp == '\0') + continue; + + /* the regular expression is everything up to the first space */ + for (exp = cp; *cp && !isspace (*cp); cp++) + ; + if (*cp != '\0') + *cp++ = '\0'; + + /* skip whitespace up to the start of the matching value */ + while (*cp && isspace (*cp)) + cp++; + + /* no value to match with the regular expression is an error */ + if (*cp == '\0') + { + error (0, 0, "syntax error at line %d file %s; ignored", + line_number, infofile); + continue; + } + value = cp; + + /* strip the newline off the end of the value */ + if ((cp = strrchr (value, '\n')) != NULL) + *cp = '\0'; + + expanded_value = expand_path (value, infofile, line_number); + if (!expanded_value) + { + continue; + } + + /* + * At this point, exp points to the regular expression, and value + * points to the value to call the callback routine with. Evaluate + * the regular expression against srepos and callback with the value + * if it matches. + */ + + /* save the default value so we have it later if we need it */ + if (strcmp (exp, "DEFAULT") == 0) + { + default_value = xstrdup (expanded_value); + continue; + } + + /* + * For a regular expression of "ALL", do the callback always We may + * execute lots of ALL callbacks in addition to *one* regular matching + * callback or default + */ + if (strcmp (exp, "ALL") == 0) + { + if (all) + err += callproc (repository, expanded_value); + else + error(0, 0, "Keyword `ALL' is ignored at line %d in %s file", + line_number, infofile); + continue; + } + + if (callback_done) + /* only first matching, plus "ALL"'s */ + continue; + + /* see if the repository matched this regular expression */ + if ((regex_err = re_comp (exp)) != NULL) + { + error (0, 0, "bad regular expression at line %d file %s: %s", + line_number, infofile, regex_err); + continue; + } + if (re_exec (srepos) == 0) + continue; /* no match */ + + /* it did, so do the callback and note that we did one */ + err += callproc (repository, expanded_value); + callback_done = 1; + } + (void) fclose (fp_info); + + /* if we fell through and didn't callback at all, do the default */ + if (callback_done == 0 && default_value != NULL) + err += callproc (repository, default_value); + + /* free up space if necessary */ + if (default_value != NULL) + free (default_value); + if (expanded_value != NULL) + free (expanded_value); + + return (err); +} diff --git a/contrib/cvs/src/patch.c b/contrib/cvs/src/patch.c new file mode 100644 index 0000000..39b4e64 --- /dev/null +++ b/contrib/cvs/src/patch.c @@ -0,0 +1,604 @@ +/* + * Copyright (c) 1992, Brian Berliner and Jeff Polk + * Copyright (c) 1989-1992, Brian Berliner + * + * You may distribute under the terms of the GNU General Public License as + * specified in the README file that comes with the CVS 1.4 kit. + * + * Patch + * + * Create a Larry Wall format "patch" file between a previous release and the + * current head of a module, or between two releases. Can specify the + * release as either a date or a revision number. + */ + +#include "cvs.h" +#include "getline.h" + +static RETSIGTYPE patch_cleanup PROTO((void)); +static Dtype patch_dirproc PROTO((char *dir, char *repos, char *update_dir)); +static int patch_fileproc PROTO((struct file_info *finfo)); +static int patch_proc PROTO((int *pargc, char **argv, char *xwhere, + char *mwhere, char *mfile, int shorten, + int local_specified, char *mname, char *msg)); + +static int force_tag_match = 1; +static int patch_short = 0; +static int toptwo_diffs = 0; +static int local = 0; +static char *options = NULL; +static char *rev1 = NULL; +static int rev1_validated = 1; +static char *rev2 = NULL; +static int rev2_validated = 1; +static char *date1 = NULL; +static char *date2 = NULL; +static char tmpfile1[L_tmpnam+1], tmpfile2[L_tmpnam+1], tmpfile3[L_tmpnam+1]; +static int unidiff = 0; + +static const char *const patch_usage[] = +{ + "Usage: %s %s [-fl] [-c|-u] [-s|-t] [-V %%d]\n", + " -r rev|-D date [-r rev2 | -D date2] modules...\n", + "\t-f\tForce a head revision match if tag/date not found.\n", + "\t-l\tLocal directory only, not recursive\n", + "\t-c\tContext diffs (default)\n", + "\t-u\tUnidiff format.\n", + "\t-s\tShort patch - one liner per file.\n", + "\t-t\tTop two diffs - last change made to the file.\n", + "\t-D date\tDate.\n", + "\t-r rev\tRevision - symbolic or numeric.\n", + "\t-V vers\tUse RCS Version \"vers\" for keyword expansion.\n", + NULL +}; + +int +patch (argc, argv) + int argc; + char **argv; +{ + register int i; + int c; + int err = 0; + DBM *db; + + if (argc == -1) + usage (patch_usage); + + optind = 1; + while ((c = getopt (argc, argv, "V:k:cuftsQqlRD:r:")) != -1) + { + switch (c) + { + case 'Q': + case 'q': +#ifdef SERVER_SUPPORT + /* The CVS 1.5 client sends these options (in addition to + Global_option requests), so we must ignore them. */ + if (!server_active) +#endif + error (1, 0, + "-q or -Q must be specified before \"%s\"", + command_name); + break; + case 'f': + force_tag_match = 0; + break; + case 'l': + local = 1; + break; + case 'R': + local = 0; + break; + case 't': + toptwo_diffs = 1; + break; + case 's': + patch_short = 1; + break; + case 'D': + if (rev2 != NULL || date2 != NULL) + error (1, 0, + "no more than two revisions/dates can be specified"); + if (rev1 != NULL || date1 != NULL) + date2 = Make_Date (optarg); + else + date1 = Make_Date (optarg); + break; + case 'r': + if (rev2 != NULL || date2 != NULL) + error (1, 0, + "no more than two revisions/dates can be specified"); + if (rev1 != NULL || date1 != NULL) + rev2 = optarg; + else + rev1 = optarg; + break; + case 'k': + if (options) + free (options); + options = RCS_check_kflag (optarg); + break; + case 'V': + if (atoi (optarg) <= 0) + error (1, 0, "must specify a version number to -V"); + if (options) + free (options); + options = xmalloc (strlen (optarg) + 1 + 2); /* for the -V */ + (void) sprintf (options, "-V%s", optarg); + break; + case 'u': + unidiff = 1; /* Unidiff */ + break; + case 'c': /* Context diff */ + unidiff = 0; + break; + case '?': + default: + usage (patch_usage); + break; + } + } + argc -= optind; + argv += optind; + + /* Sanity checks */ + if (argc < 1) + usage (patch_usage); + + if (toptwo_diffs && patch_short) + error (1, 0, "-t and -s options are mutually exclusive"); + if (toptwo_diffs && (date1 != NULL || date2 != NULL || + rev1 != NULL || rev2 != NULL)) + error (1, 0, "must not specify revisions/dates with -t option!"); + + if (!toptwo_diffs && (date1 == NULL && date2 == NULL && + rev1 == NULL && rev2 == NULL)) + error (1, 0, "must specify at least one revision/date!"); + if (date1 != NULL && date2 != NULL) + if (RCS_datecmp (date1, date2) >= 0) + error (1, 0, "second date must come after first date!"); + + /* if options is NULL, make it a NULL string */ + if (options == NULL) + options = xstrdup (""); + +#ifdef CLIENT_SUPPORT + if (client_active) + { + /* We're the client side. Fire up the remote server. */ + start_server (); + + ign_setup (); + + if (local) + send_arg("-l"); + if (force_tag_match) + send_arg("-f"); + if (toptwo_diffs) + send_arg("-t"); + if (patch_short) + send_arg("-s"); + if (unidiff) + send_arg("-u"); + + if (rev1) + option_with_arg ("-r", rev1); + if (date1) + client_senddate (date1); + if (rev2) + option_with_arg ("-r", rev2); + if (date2) + client_senddate (date2); + if (options[0] != '\0') + send_arg (options); + + { + int i; + for (i = 0; i < argc; ++i) + send_arg (argv[i]); + } + + send_to_server ("rdiff\012", 0); + return get_responses_and_close (); + } +#endif + + /* clean up if we get a signal */ +#ifdef SIGHUP + (void) SIG_register (SIGHUP, patch_cleanup); +#endif +#ifdef SIGINT + (void) SIG_register (SIGINT, patch_cleanup); +#endif +#ifdef SIGQUIT + (void) SIG_register (SIGQUIT, patch_cleanup); +#endif +#ifdef SIGPIPE + (void) SIG_register (SIGPIPE, patch_cleanup); +#endif +#ifdef SIGTERM + (void) SIG_register (SIGTERM, patch_cleanup); +#endif + + db = open_module (); + for (i = 0; i < argc; i++) + err += do_module (db, argv[i], PATCH, "Patching", patch_proc, + (char *) NULL, 0, 0, 0, (char *) NULL); + close_module (db); + free (options); + patch_cleanup (); + return (err); +} + +/* + * callback proc for doing the real work of patching + */ +/* ARGSUSED */ +static char where[PATH_MAX]; +static int +patch_proc (pargc, argv, xwhere, mwhere, mfile, shorten, local_specified, + mname, msg) + int *pargc; + char **argv; + char *xwhere; + char *mwhere; + char *mfile; + int shorten; + int local_specified; + char *mname; + char *msg; +{ + int err = 0; + int which; + char repository[PATH_MAX]; + + (void) sprintf (repository, "%s/%s", CVSroot, argv[0]); + (void) strcpy (where, argv[0]); + + /* if mfile isn't null, we need to set up to do only part of the module */ + if (mfile != NULL) + { + char *cp; + char path[PATH_MAX]; + + /* if the portion of the module is a path, put the dir part on repos */ + if ((cp = strrchr (mfile, '/')) != NULL) + { + *cp = '\0'; + (void) strcat (repository, "/"); + (void) strcat (repository, mfile); + (void) strcat (where, "/"); + (void) strcat (where, mfile); + mfile = cp + 1; + } + + /* take care of the rest */ + (void) sprintf (path, "%s/%s", repository, mfile); + if (isdir (path)) + { + /* directory means repository gets the dir tacked on */ + (void) strcpy (repository, path); + (void) strcat (where, "/"); + (void) strcat (where, mfile); + } + else + { + int i; + + /* a file means muck argv */ + for (i = 1; i < *pargc; i++) + free (argv[i]); + argv[1] = xstrdup (mfile); + (*pargc) = 2; + } + } + + /* cd to the starting repository */ + if (chdir (repository) < 0) + { + error (0, errno, "cannot chdir to %s", repository); + return (1); + } + + if (force_tag_match) + which = W_REPOS | W_ATTIC; + else + which = W_REPOS; + + if (rev1 != NULL && !rev1_validated) + { + tag_check_valid (rev1, *pargc - 1, argv + 1, local, 0, NULL); + rev1_validated = 1; + } + if (rev2 != NULL && !rev2_validated) + { + tag_check_valid (rev2, *pargc - 1, argv + 1, local, 0, NULL); + rev2_validated = 1; + } + + /* start the recursion processor */ + err = start_recursion (patch_fileproc, (FILESDONEPROC) NULL, patch_dirproc, + (DIRLEAVEPROC) NULL, *pargc - 1, argv + 1, local, + which, 0, 1, where, 1, 1); + + return (err); +} + +/* + * Called to examine a particular RCS file, as appropriate with the options + * that were set above. + */ +/* ARGSUSED */ +static int +patch_fileproc (finfo) + struct file_info *finfo; +{ + struct utimbuf t; + char *vers_tag, *vers_head; + char rcsspace[1][PATH_MAX]; + char *rcs = rcsspace[0]; + RCSNode *rcsfile; + FILE *fp1, *fp2, *fp3; + int ret = 0; + int isattic = 0; + int retcode = 0; + char file1[PATH_MAX], file2[PATH_MAX], strippath[PATH_MAX]; + char *line1, *line2; + size_t line1_chars_allocated; + size_t line2_chars_allocated; + char *cp1, *cp2; + FILE *fp; + + /* find the parsed rcs file */ + if ((rcsfile = finfo->rcs) == NULL) + return (1); + if ((rcsfile->flags & VALID) && (rcsfile->flags & INATTIC)) + isattic = 1; + + (void) sprintf (rcs, "%s%s", finfo->file, RCSEXT); + + /* if vers_head is NULL, may have been removed from the release */ + if (isattic && rev2 == NULL && date2 == NULL) + vers_head = NULL; + else + vers_head = RCS_getversion (rcsfile, rev2, date2, force_tag_match, 0); + + if (toptwo_diffs) + { + if (vers_head == NULL) + return (1); + + if (!date1) + date1 = xmalloc (50); /* plenty big :-) */ + *date1 = '\0'; + if (RCS_getrevtime (rcsfile, vers_head, date1, 1) == -1) + { + if (!really_quiet) + error (0, 0, "cannot find date in rcs file %s revision %s", + rcs, vers_head); + return (1); + } + } + vers_tag = RCS_getversion (rcsfile, rev1, date1, force_tag_match, 0); + + if (vers_tag == NULL && (vers_head == NULL || isattic)) + return (0); /* nothing known about specified revs */ + + if (vers_tag && vers_head && strcmp (vers_head, vers_tag) == 0) + return (0); /* not changed between releases */ + + if (patch_short) + { + (void) printf ("File %s ", finfo->fullname); + if (vers_tag == NULL) + (void) printf ("is new; current revision %s\n", vers_head); + else if (vers_head == NULL) + { + (void) printf ("is removed; not included in "); + if (rev2 != NULL) + (void) printf ("release tag %s", rev2); + else if (date2 != NULL) + (void) printf ("release date %s", date2); + else + (void) printf ("current release"); + (void) printf ("\n"); + } + else + (void) printf ("changed from revision %s to %s\n", + vers_tag, vers_head); + return (0); + } + if ((fp1 = fopen (tmpnam (tmpfile1), "w+")) != NULL) + (void) fclose (fp1); + if ((fp2 = fopen (tmpnam (tmpfile2), "w+")) != NULL) + (void) fclose (fp2); + if ((fp3 = fopen (tmpnam (tmpfile3), "w+")) != NULL) + (void) fclose (fp3); + if (fp1 == NULL || fp2 == NULL || fp3 == NULL) + { + error (0, 0, "cannot create temporary files"); + ret = 1; + goto out; + } + if (vers_tag != NULL) + { + retcode = RCS_checkout (rcsfile->path, NULL, vers_tag, options, tmpfile1, + 0, 0); + 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); + ret = 1; + goto out; + } + memset ((char *) &t, 0, sizeof (t)); + if ((t.actime = t.modtime = RCS_getrevtime (rcsfile, vers_tag, + (char *) 0, 0)) != -1) + (void) utime (tmpfile1, &t); + } + else if (toptwo_diffs) + { + ret = 1; + goto out; + } + if (vers_head != NULL) + { + retcode = RCS_checkout (rcsfile->path, NULL, vers_head, options, tmpfile2, 0, 0); + 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); + ret = 1; + goto out; + } + if ((t.actime = t.modtime = RCS_getrevtime (rcsfile, vers_head, + (char *) 0, 0)) != -1) + (void) utime (tmpfile2, &t); + } + run_setup ("%s -%c", DIFF, unidiff ? 'u' : 'c'); + run_arg (tmpfile1); + run_arg (tmpfile2); + + line1 = NULL; + line1_chars_allocated = 0; + line2 = NULL; + line2_chars_allocated = 0; + + switch (run_exec (RUN_TTY, tmpfile3, RUN_TTY, RUN_REALLY)) + { + case -1: /* fork/wait failure */ + error (1, errno, "fork for diff failed on %s", rcs); + break; + case 0: /* nothing to do */ + break; + case 1: + /* + * The two revisions are really different, so read the first two + * lines of the diff output file, and munge them to include more + * reasonable file names that "patch" will understand. + */ + + /* Output an "Index:" line for patch to use */ + (void) fflush (stdout); + (void) printf ("Index: %s\n", finfo->fullname); + (void) fflush (stdout); + + fp = open_file (tmpfile3, "r"); + 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); + ret = 1; + (void) fclose (fp); + goto out; + } + if (!unidiff) + { + if (strncmp (line1, "*** ", 4) != 0 || + strncmp (line2, "--- ", 4) != 0 || + (cp1 = strchr (line1, '\t')) == NULL || + (cp2 = strchr (line2, '\t')) == NULL) + { + error (0, 0, "invalid diff header for %s", rcs); + ret = 1; + (void) fclose (fp); + goto out; + } + } + else + { + if (strncmp (line1, "--- ", 4) != 0 || + strncmp (line2, "+++ ", 4) != 0 || + (cp1 = strchr (line1, '\t')) == NULL || + (cp2 = strchr (line2, '\t')) == NULL) + { + error (0, 0, "invalid unidiff header for %s", rcs); + ret = 1; + (void) fclose (fp); + goto out; + } + } + if (CVSroot != NULL) + (void) sprintf (strippath, "%s/", CVSroot); + else + (void) strcpy (strippath, REPOS_STRIP); + if (strncmp (rcs, strippath, strlen (strippath)) == 0) + rcs += strlen (strippath); + if (vers_tag != NULL) + { + (void) sprintf (file1, "%s:%s", finfo->fullname, vers_tag); + } + else + { + (void) strcpy (file1, DEVNULL); + } + (void) sprintf (file2, "%s:%s", finfo->fullname, + vers_head ? vers_head : "removed"); + + /* Note that this prints "diff" not DIFF. The format of a diff + does not depend on the name of the program which happens to + have produced it. */ + if (unidiff) + { + (void) printf ("diff -u %s %s\n", file1, file2); + (void) printf ("--- %s%s+++ ", file1, cp1); + } + else + { + (void) printf ("diff -c %s %s\n", file1, file2); + (void) printf ("*** %s%s--- ", file1, cp1); + } + + (void) printf ("%s%s", finfo->fullname, cp2); + /* spew the rest of the diff out */ + while (getline (&line1, &line1_chars_allocated, fp) >= 0) + (void) fputs (line1, stdout); + (void) fclose (fp); + break; + default: + error (0, 0, "diff failed for %s", finfo->fullname); + } + out: + if (line1) + free (line1); + if (line2) + free (line2); + /* FIXME: should be checking for errors. */ + (void) unlink (tmpfile1); + (void) unlink (tmpfile2); + (void) unlink (tmpfile3); + return (ret); +} + +/* + * Print a warm fuzzy message + */ +/* ARGSUSED */ +static Dtype +patch_dirproc (dir, repos, update_dir) + char *dir; + char *repos; + char *update_dir; +{ + if (!quiet) + error (0, 0, "Diffing %s", update_dir); + return (R_PROCESS); +} + +/* + * Clean up temporary files + */ +static RETSIGTYPE +patch_cleanup () +{ + if (tmpfile1[0] != '\0') + (void) unlink_file (tmpfile1); + if (tmpfile2[0] != '\0') + (void) unlink_file (tmpfile2); + if (tmpfile3[0] != '\0') + (void) unlink_file (tmpfile3); +} diff --git a/contrib/cvs/src/rcs.c b/contrib/cvs/src/rcs.c new file mode 100644 index 0000000..c68c255 --- /dev/null +++ b/contrib/cvs/src/rcs.c @@ -0,0 +1,2262 @@ +/* + * Copyright (c) 1992, Brian Berliner and Jeff Polk + * + * You may distribute under the terms of the GNU General Public License as + * specified in the README file that comes with the CVS 1.4 kit. + * + * The routines contained in this file do all the rcs file parsing and + * manipulation + */ + +#include <assert.h> +#include "cvs.h" + +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)); +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)); +static void rcsvers_delproc PROTO((Node * p)); + +/* + * We don't want to use isspace() from the C library because: + * + * 1. The definition of "whitespace" in RCS files includes ASCII + * backspace, but the C locale doesn't. + * 2. isspace is an very expensive function call in some implementations + * due to the addition of wide character support. + */ +static const char spacetab[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, /* 0x00 - 0x0f */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x10 - 0x1f */ + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x20 - 0x2f */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x30 - 0x3f */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x40 - 0x4f */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x50 - 0x5f */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x60 - 0x8f */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x70 - 0x7f */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x80 - 0x8f */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x90 - 0x9f */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0xa0 - 0xaf */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0xb0 - 0xbf */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0xc0 - 0xcf */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0xd0 - 0xdf */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0xe0 - 0xef */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 /* 0xf0 - 0xff */ +}; + +#define whitespace(c) (spacetab[(unsigned char)c] != 0) + + +/* + * Parse an rcsfile given a user file name and a repository + */ +RCSNode * +RCS_parse (file, repos) + const char *file; + const char *repos; +{ + RCSNode *rcs; + FILE *fp; + char rcsfile[PATH_MAX]; + + (void) sprintf (rcsfile, "%s/%s%s", repos, file, RCSEXT); + if ((fp = fopen (rcsfile, FOPEN_BINARY_READ)) != NULL) + { + rcs = RCS_parsercsfile_i(fp, rcsfile); + if (rcs != NULL) + rcs->flags |= VALID; + + fclose (fp); + return (rcs); + } + else if (! existence_error (errno)) + { + error (0, errno, "cannot open %s", rcsfile); + return NULL; + } + + (void) sprintf (rcsfile, "%s/%s/%s%s", repos, CVSATTIC, file, RCSEXT); + if ((fp = fopen (rcsfile, FOPEN_BINARY_READ)) != NULL) + { + rcs = RCS_parsercsfile_i(fp, rcsfile); + if (rcs != NULL) + { + rcs->flags |= INATTIC; + rcs->flags |= VALID; + } + + fclose (fp); + return (rcs); + } + else if (! existence_error (errno)) + { + error (0, errno, "cannot open %s", rcsfile); + return NULL; + } + + return (NULL); +} + +/* + * Parse a specific rcsfile. + */ +RCSNode * +RCS_parsercsfile (rcsfile) + char *rcsfile; +{ + FILE *fp; + RCSNode *rcs; + + /* open the rcsfile */ + if ((fp = fopen (rcsfile, FOPEN_BINARY_READ)) == NULL) + { + error (0, errno, "Couldn't open rcs file `%s'", rcsfile); + return (NULL); + } + + rcs = RCS_parsercsfile_i (fp, rcsfile); + + fclose (fp); + return (rcs); +} + + +/* + */ +static RCSNode * +RCS_parsercsfile_i (fp, rcsfile) + FILE *fp; + const char *rcsfile; +{ + RCSNode *rdata; + char *key, *value; + + /* make a node */ + rdata = (RCSNode *) xmalloc (sizeof (RCSNode)); + memset ((char *) rdata, 0, sizeof (RCSNode)); + rdata->refcount = 1; + rdata->path = xstrdup (rcsfile); + + /* Process HEAD and BRANCH keywords from the RCS header. + * + * Most cvs operatations on the main branch don't need any more + * information. Those that do call XXX to completely parse the + * RCS file. */ + + if (getrcskey (fp, &key, &value) == -1 || key == NULL) + goto l_error; + if (strcmp (key, RCSDESC) == 0) + goto l_error; + + if (strcmp (RCSHEAD, key) == 0 && value != NULL) + rdata->head = xstrdup (value); + + if (getrcskey (fp, &key, &value) == -1 || key == NULL) + goto l_error; + if (strcmp (key, RCSDESC) == 0) + goto l_error; + + if (strcmp (RCSBRANCH, key) == 0 && value != NULL) + { + char *cp; + + rdata->branch = xstrdup (value); + if ((numdots (rdata->branch) & 1) != 0) + { + /* turn it into a branch if it's a revision */ + cp = strrchr (rdata->branch, '.'); + *cp = '\0'; + } + } + + rdata->flags |= PARTIAL; + return rdata; + +l_error: + if (!really_quiet) + { + if (ferror(fp)) + { + error (1, 0, "error reading `%s'", rcsfile); + } + else + { + error (0, 0, "`%s' does not appear to be a valid rcs file", + rcsfile); + } + } + freercsnode (&rdata); + return (NULL); +} + + +/* Do the real work of parsing an RCS file. + + On error, die with a fatal error; if it returns at all it was successful. + + If PFP is NULL, close the file when done. Otherwise, leave it open + and store the FILE * in *PFP. */ +static void +RCS_reparsercsfile (rdata, pfp) + RCSNode *rdata; + FILE **pfp; +{ + FILE *fp; + char *rcsfile; + + Node *q; + RCSVers *vnode; + int n; + char *cp; + char *key, *value; + + assert (rdata != NULL); + rcsfile = rdata->path; + + fp = fopen(rcsfile, FOPEN_BINARY_READ); + if (fp == NULL) + error (1, 0, "unable to reopen `%s'", rcsfile); + + /* make a node */ + rdata->versions = getlist (); + + /* + * process all the special header information, break out when we get to + * the first revision delta + */ + for (;;) + { + /* 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) == -1 || key == NULL + || strcmp (key, RCSDESC) == 0) + { + if (ferror(fp)) + { + error (1, 0, "error reading `%s'", rcsfile); + } + else + { + error (1, 0, "`%s' does not appear to be a valid rcs file", + rcsfile); + } + } + + if (strcmp (RCSSYMBOLS, key) == 0) + { + if (value != NULL) + { + rdata->symbols_data = xstrdup(value); + continue; + } + } + + if (strcmp (RCSEXPAND, key) == 0) + { + rdata->expand = xstrdup (value); + continue; + } + + /* + * check key for '.''s and digits (probably a rev) if it is a + * revision, we are done with the headers and are down to the + * revision deltas, so we break out of the loop + */ + for (cp = key; (isdigit (*cp) || *cp == '.') && *cp != '\0'; cp++) + /* do nothing */ ; + if (*cp == '\0' && strncmp (RCSDATE, value, strlen (RCSDATE)) == 0) + break; + + /* 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 + */ + for (;;) + { + char *valp; + + vnode = (RCSVers *) xmalloc (sizeof (RCSVers)); + memset (vnode, 0, sizeof (RCSVers)); + + /* fill in the version before we forget it */ + vnode->version = xstrdup (key); + + /* grab the value of the date from value */ + valp = value + strlen (RCSDATE);/* skip the "date" keyword */ + while (whitespace (*valp)) /* take space off front of value */ + valp++; + + vnode->date = xstrdup (valp); + + /* Get author field. */ + (void) getrcskey (fp, &key, &value); + /* FIXME: should be using errno in case of ferror. */ + if (key == NULL || strcmp (key, "author") != 0) + error (1, 0, "\ +unable to parse rcs file; `author' not in the expected place"); + vnode->author = xstrdup (value); + + /* Get state field. */ + (void) getrcskey (fp, &key, &value); + /* FIXME: should be using errno in case of ferror. */ + if (key == NULL || strcmp (key, "state") != 0) + error (1, 0, "\ +unable to parse rcs file; `state' not in the expected place"); + if (strcmp (value, "dead") == 0) + { + vnode->dead = 1; + } + + /* fill in the branch list (if any branches exist) */ + (void) getrcskey (fp, &key, &value); + /* FIXME: should be handling various error conditions better. */ + if (key != NULL && strcmp (key, RCSDESC) == 0) + value = NULL; + if (value != (char *) NULL) + { + vnode->branches = getlist (); + do_branches (vnode->branches, value); + } + + /* fill in the next field if there is a next revision */ + (void) getrcskey (fp, &key, &value); + /* FIXME: should be handling various error conditions better. */ + if (key != NULL && strcmp (key, RCSDESC) == 0) + value = NULL; + if (value != (char *) NULL) + vnode->next = xstrdup (value); + + /* + * at this point, we skip any user defined fields XXX - this is where + * we put the symbolic link stuff??? + */ + /* FIXME: Does not correctly handle errors, e.g. from stdio. */ + while ((n = getrcskey (fp, &key, &value)) >= 0) + { + assert (key != NULL); + + if (strcmp (key, RCSDESC) == 0) + { + n = -1; + 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) + { + vnode->dead = 1; + continue; + } + /* if we have a revision, break and do it */ + for (cp = key; (isdigit (*cp) || *cp == '.') && *cp != '\0'; cp++) + /* do nothing */ ; + if (*cp == '\0' && strncmp (RCSDATE, value, strlen (RCSDATE)) == 0) + break; + } + + /* get the node */ + q = getnode (); + q->type = RCSVERS; + q->delproc = rcsvers_delproc; + q->data = (char *) vnode; + q->key = vnode->version; + + /* add the nodes to the list */ + if (addnode (rdata->versions, q) != 0) + { +#if 0 + purify_printf("WARNING: Adding duplicate version: %s (%s)\n", + q->key, rcsfile); + freenode (q); +#endif + } + + /* + * if we left the loop because there were no more keys, we break out + * of the revision processing loop + */ + if (n < 0) + break; + } + + if (pfp == NULL) + { + if (fclose (fp) < 0) + error (0, errno, "cannot close %s", rcsfile); + } + else + { + *pfp = fp; + } + rdata->flags &= ~PARTIAL; +} + +/* + * freercsnode - free up the info for an RCSNode + */ +void +freercsnode (rnodep) + RCSNode **rnodep; +{ + if (rnodep == NULL || *rnodep == NULL) + return; + + ((*rnodep)->refcount)--; + if ((*rnodep)->refcount != 0) + { + *rnodep = (RCSNode *) NULL; + return; + } + free ((*rnodep)->path); + dellist (&(*rnodep)->versions); + if ((*rnodep)->symbols != (List *) NULL) + dellist (&(*rnodep)->symbols); + if ((*rnodep)->symbols_data != (char *) NULL) + free ((*rnodep)->symbols_data); + if ((*rnodep)->expand != NULL) + free ((*rnodep)->expand); + if ((*rnodep)->head != (char *) NULL) + free ((*rnodep)->head); + if ((*rnodep)->branch != (char *) NULL) + free ((*rnodep)->branch); + free ((char *) *rnodep); + *rnodep = (RCSNode *) NULL; +} + +/* + * rcsvers_delproc - free up an RCSVers type node + */ +static void +rcsvers_delproc (p) + Node *p; +{ + RCSVers *rnode; + + rnode = (RCSVers *) p->data; + + if (rnode->branches != (List *) NULL) + dellist (&rnode->branches); + if (rnode->date != (char *) NULL) + free (rnode->date); + if (rnode->next != (char *) NULL) + free (rnode->next); + free ((char *) rnode); +} + +/* + * 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. + */ + +static char *key = NULL; +static char *value = NULL; +static size_t keysize = 0; +static size_t valsize = 0; + +#define ALLOCINCR 1024 + +static int +getrcskey (fp, keyp, valp) + FILE *fp; + char **keyp; + char **valp; +{ + char *cur, *max; + int c; + + /* skip leading whitespace */ + do + { + c = getc (fp); + if (c == EOF) + { + *keyp = (char *) NULL; + *valp = (char *) NULL; + return (-1); + } + } while (whitespace (c)); + + /* fill in key */ + cur = key; + max = key + keysize; + while (!whitespace (c) && c != ';') + { + if (cur >= max) + { + key = xrealloc (key, keysize + ALLOCINCR); + cur = key + keysize; + keysize += ALLOCINCR; + max = key + keysize; + } + *cur++ = c; + + c = getc (fp); + if (c == EOF) + { + *keyp = (char *) NULL; + *valp = (char *) NULL; + return (-1); + } + } + if (cur >= max) + { + key = xrealloc (key, keysize + ALLOCINCR); + cur = key + keysize; + keysize += ALLOCINCR; + max = key + keysize; + } + *cur = '\0'; + + /* skip whitespace between key and val */ + while (whitespace (c)) + { + c = getc (fp); + if (c == EOF) + { + *keyp = (char *) NULL; + *valp = (char *) NULL; + return (-1); + } + } + + /* if we ended key with a semicolon, there is no value */ + if (c == ';') + { + *keyp = key; + *valp = (char *) NULL; + return (0); + } + + /* otherwise, there might be a value, so fill it in */ + cur = value; + max = value + valsize; + + /* process the value */ + for (;;) + { + /* handle RCS "strings" */ + if (c == '@') + { + for (;;) + { + c = getc (fp); + if (c == EOF) + { + *keyp = (char *) NULL; + *valp = (char *) NULL; + return (-1); + } + + if (c == '@') + { + c = getc (fp); + if (c == EOF) + { + *keyp = (char *) NULL; + *valp = (char *) NULL; + return (-1); + } + + if (c != '@') + break; + } + + if (cur >= max) + { + value = xrealloc (value, valsize + ALLOCINCR); + cur = value + valsize; + valsize += ALLOCINCR; + max = value + valsize; + } + *cur++ = c; + } + } + + /* The syntax for some key-value pairs is different; they + don't end with a semicolon. */ + if (strcmp (key, RCSDESC) == 0 + || strcmp (key, "text") == 0 + || strcmp (key, "log") == 0) + break; + + /* compress whitespace down to a single space */ + if (whitespace (c)) + { + do { + c = getc (fp); + if (c == EOF) + { + *keyp = (char *) NULL; + *valp = (char *) NULL; + return (-1); + } + } while (whitespace (c)); + + if (cur >= max) + { + value = xrealloc (value, valsize + ALLOCINCR); + cur = value + valsize; + valsize += ALLOCINCR; + max = value + valsize; + } + *cur++ = ' '; + } + + /* if we got a semi-colon we are done with the entire value */ + if (c == ';') + break; + + if (cur >= max) + { + value = xrealloc (value, valsize + ALLOCINCR); + cur = value + valsize; + valsize += ALLOCINCR; + max = value + valsize; + } + *cur++ = c; + + c = getc (fp); + if (c == EOF) + { + *keyp = (char *) NULL; + *valp = (char *) NULL; + return (-1); + } + } + + /* terminate the string */ + if (cur >= max) + { + value = xrealloc (value, valsize + ALLOCINCR); + cur = value + valsize; + valsize += ALLOCINCR; + max = value + valsize; + } + *cur = '\0'; + + /* if the string is empty, make it null */ + if (value && *value != '\0') + *valp = value; + else + *valp = NULL; + *keyp = key; + return (0); +} + +static void getrcsrev PROTO ((FILE *fp, char **revp)); + +/* 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. */ +static void +getrcsrev (fp, revp) + FILE *fp; + char **revp; +{ + char *cur; + char *max; + int c; + + do { + c = getc (fp); + if (c == EOF) + /* FIXME: should be including filename in error message. */ + error (1, errno, "cannot read rcs file"); + } 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"); + + cur = key; + max = key + keysize; + while (isdigit (c) || c == '.') + { + if (cur >= max) + { + key = xrealloc (key, keysize + ALLOCINCR); + cur = key + keysize; + keysize += ALLOCINCR; + max = key + keysize; + } + *cur++ = c; + + c = getc (fp); + if (c == EOF) + { + /* FIXME: should be including filename in error message. */ + error (1, errno, "cannot read rcs file"); + } + } + + if (cur >= max) + { + key = xrealloc (key, keysize + ALLOCINCR); + cur = key + keysize; + keysize += ALLOCINCR; + max = key + keysize; + } + *cur = '\0'; + *revp = key; +} + +/* + * process the symbols list of the rcs file + */ +static void +do_symbols (list, val) + List *list; + char *val; +{ + Node *p; + char *cp = val; + char *tag, *rev; + + for (;;) + { + /* skip leading whitespace */ + while (whitespace (*cp)) + cp++; + + /* if we got to the end, we are done */ + if (*cp == '\0') + break; + + /* split it up into tag and rev */ + tag = cp; + cp = strchr (cp, ':'); + *cp++ = '\0'; + rev = cp; + while (!whitespace (*cp) && *cp != '\0') + cp++; + if (*cp != '\0') + *cp++ = '\0'; + + /* make a new node and add it to the list */ + p = getnode (); + p->key = xstrdup (tag); + p->data = xstrdup (rev); + (void) addnode (list, p); + } +} + +/* + * process the branches list of a revision delta + */ +static void +do_branches (list, val) + List *list; + char *val; +{ + Node *p; + char *cp = val; + char *branch; + + for (;;) + { + /* skip leading whitespace */ + while (whitespace (*cp)) + cp++; + + /* if we got to the end, we are done */ + if (*cp == '\0') + break; + + /* find the end of this branch */ + branch = cp; + while (!whitespace (*cp) && *cp != '\0') + cp++; + if (*cp != '\0') + *cp++ = '\0'; + + /* make a new node and add it to the list */ + p = getnode (); + p->key = xstrdup (branch); + (void) addnode (list, p); + } +} + +/* + * Version Number + * + * Returns the requested version number of the RCS file, satisfying tags and/or + * dates, and walking branches, if necessary. + * + * The result is returned; null-string if error. + */ +char * +RCS_getversion (rcs, tag, date, force_tag_match, return_both) + RCSNode *rcs; + char *tag; + char *date; + int force_tag_match; + int return_both; +{ + /* make sure we have something to look at... */ + assert (rcs != NULL); + + if (tag && date) + { + char *cp, *rev, *tagrev; + + /* + * first lookup the tag; if that works, turn the revision into + * a branch and lookup the date. + */ + tagrev = RCS_gettag (rcs, tag, force_tag_match, 0); + if (tagrev == NULL) + return ((char *) NULL); + + if ((cp = strrchr (tagrev, '.')) != NULL) + *cp = '\0'; + rev = RCS_getdatebranch (rcs, date, tagrev); + free (tagrev); + return (rev); + } + else if (tag) + return (RCS_gettag (rcs, tag, force_tag_match, return_both)); + else if (date) + return (RCS_getdate (rcs, date, force_tag_match)); + else + return (RCS_head (rcs)); + +} + +/* + * Find the revision for a specific tag. + * If force_tag_match is set, return NULL if an exact match is not + * possible otherwise return RCS_head (). We are careful to look for + * and handle "magic" revisions specially. + * + * If the matched tag is a branch tag, find the head of the branch. + */ +char * +RCS_gettag (rcs, symtag, force_tag_match, return_both) + RCSNode *rcs; + char *symtag; + int force_tag_match; + int return_both; +{ + Node *p; + char *tag = symtag; + + /* make sure we have something to look at... */ + assert (rcs != NULL); + + /* XXX this is probably not necessary, --jtc */ + if (rcs->flags & PARTIAL) + RCS_reparsercsfile (rcs, NULL); + + /* If tag is "HEAD", special case to get head RCS revision */ + if (tag && (strcmp (tag, TAG_HEAD) == 0 || *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 */ + else +#endif + return (RCS_head (rcs)); + + if (!isdigit (tag[0])) + { + /* If we got a symbolic tag, resolve it to a numeric */ + if (rcs == NULL) + p = NULL; + else { + p = findnode (RCS_symbols(rcs), tag); + } + if (p != NULL) + { + int dots; + char *magic, *branch, *cp; + + tag = p->data; + + /* + * If this is a magic revision, we turn it into either its + * physical branch equivalent (if one exists) or into + * its base revision, which we assume exists. + */ + dots = numdots (tag); + if (dots > 2 && (dots & 1) != 0) + { + branch = strrchr (tag, '.'); + cp = branch++ - 1; + while (*cp != '.') + cp--; + + /* see if we have .magic-branch. (".0.") */ + magic = xmalloc (strlen (tag) + 1); + (void) sprintf (magic, ".%d.", RCS_MAGIC_BRANCH); + if (strncmp (magic, cp, strlen (magic)) == 0) + { + char *xtag; + + /* it's magic. See if the branch exists */ + *cp = '\0'; /* turn it into a revision */ + xtag = xstrdup (tag); + *cp = '.'; /* and back again */ + (void) sprintf (magic, "%s.%s", xtag, branch); + branch = RCS_getbranch (rcs, magic, 1); + free (magic); + if (branch != NULL) + { + free (xtag); + return (branch); + } + return (xtag); + } + free (magic); + } + } + else + { + /* The tag wasn't there, so return the head or NULL */ + if (force_tag_match) + return (NULL); + else + return (RCS_head (rcs)); + } + } + + /* + * numeric tag processing: + * 1) revision number - just return it + * 2) branch number - find head of branch + */ + + /* strip trailing dots */ + while (tag[strlen (tag) - 1] == '.') + tag[strlen (tag) - 1] = '\0'; + + if ((numdots (tag) & 1) == 0) + { + /* we have a branch tag, so we need to walk the branch */ + return (RCS_getbranch (rcs, tag, force_tag_match)); + } + else + { + /* we have a revision tag, so make sure it exists */ + if (rcs == NULL) + p = NULL; + else + p = findnode (rcs->versions, tag); + if (p != NULL) + { + /* + * we have found a numeric revision for the revision tag. + * To support expanding the RCS keyword Name, return both + * the numeric tag and the supplied tag (which might be + * symbolic). They are separated with a ':' which is not + * a valid tag char. The variable return_both is only set + * if this function is called through Version_TS -> + * RCS_getversion. + */ + if (return_both) + { + char *both = xmalloc(strlen(tag) + 2 + strlen(symtag)); + sprintf(both, "%s:%s", tag, symtag); + return both; + } + else + return (xstrdup (tag)); + } + else + { + /* The revision wasn't there, so return the head or NULL */ + if (force_tag_match) + return (NULL); + else + return (RCS_head (rcs)); + } + } +} + +/* + * Return a "magic" revision as a virtual branch off of REV for the RCS file. + * A "magic" revision is one which is unique in the RCS file. By unique, I + * mean we return a revision which: + * - has a branch of 0 (see rcs.h RCS_MAGIC_BRANCH) + * - has a revision component which is not an existing branch off REV + * - has a revision component which is not an existing magic revision + * - is an even-numbered revision, to avoid conflicts with vendor branches + * The first point is what makes it "magic". + * + * As an example, if we pass in 1.37 as REV, we will look for an existing + * branch called 1.37.2. If it did not exist, we would look for an + * existing symbolic tag with a numeric part equal to 1.37.0.2. If that + * didn't exist, then we know that the 1.37.2 branch can be reserved by + * creating a symbolic tag with 1.37.0.2 as the numeric part. + * + * This allows us to fork development with very little overhead -- just a + * symbolic tag is used in the RCS file. When a commit is done, a physical + * branch is dynamically created to hold the new revision. + * + * Note: We assume that REV is an RCS revision and not a branch number. + */ +static char *check_rev; +char * +RCS_magicrev (rcs, rev) + RCSNode *rcs; + char *rev; +{ + int rev_num; + char *xrev, *test_branch; + + xrev = xmalloc (strlen (rev) + 14); /* enough for .0.number */ + check_rev = xrev; + + /* only look at even numbered branches */ + for (rev_num = 2; ; rev_num += 2) + { + /* see if the physical branch exists */ + (void) sprintf (xrev, "%s.%d", rev, rev_num); + test_branch = RCS_getbranch (rcs, xrev, 1); + if (test_branch != NULL) /* it did, so keep looking */ + { + free (test_branch); + continue; + } + + /* now, create a "magic" revision */ + (void) sprintf (xrev, "%s.%d.%d", rev, RCS_MAGIC_BRANCH, rev_num); + + /* walk the symbols list to see if a magic one already exists */ + if (walklist (RCS_symbols(rcs), checkmagic_proc, NULL) != 0) + continue; + + /* we found a free magic branch. Claim it as ours */ + return (xrev); + } +} + +/* + * walklist proc to look for a match in the symbols list. + * Returns 0 if the symbol does not match, 1 if it does. + */ +static int +checkmagic_proc (p, closure) + Node *p; + void *closure; +{ + if (strcmp (check_rev, p->data) == 0) + return (1); + else + return (0); +} + +/* + * Given an RCSNode, returns non-zero if the specified revision number + * or symbolic tag resolves to a "branch" within the rcs file. + * + * FIXME: this is the same as RCS_nodeisbranch except for the special + * case for handling a null rcsnode. + */ +int +RCS_isbranch (rcs, rev) + RCSNode *rcs; + const char *rev; +{ + /* numeric revisions are easy -- even number of dots is a branch */ + if (isdigit (*rev)) + return ((numdots (rev) & 1) == 0); + + /* assume a revision if you can't find the RCS info */ + if (rcs == NULL) + return (0); + + /* now, look for a match in the symbols list */ + return (RCS_nodeisbranch (rcs, rev)); +} + +/* + * Given an RCSNode, returns non-zero if the specified revision number + * or symbolic tag resolves to a "branch" within the rcs file. We do + * take into account any magic branches as well. + */ +int +RCS_nodeisbranch (rcs, rev) + RCSNode *rcs; + const char *rev; +{ + int dots; + Node *p; + + /* numeric revisions are easy -- even number of dots is a branch */ + if (isdigit (*rev)) + return ((numdots (rev) & 1) == 0); + + p = findnode (RCS_symbols(rcs), rev); + if (p == NULL) + return (0); + dots = numdots (p->data); + if ((dots & 1) == 0) + return (1); + + /* got a symbolic tag match, but it's not a branch; see if it's magic */ + if (dots > 2) + { + char *magic; + char *branch = strrchr (p->data, '.'); + char *cp = branch - 1; + while (*cp != '.') + cp--; + + /* see if we have .magic-branch. (".0.") */ + magic = xmalloc (strlen (p->data) + 1); + (void) sprintf (magic, ".%d.", RCS_MAGIC_BRANCH); + if (strncmp (magic, cp, strlen (magic)) == 0) + { + free (magic); + return (1); + } + free (magic); + } + return (0); +} + +/* + * Returns a pointer to malloc'ed memory which contains the branch + * for the specified *symbolic* tag. Magic branches are handled correctly. + */ +char * +RCS_whatbranch (rcs, rev) + RCSNode *rcs; + const char *rev; +{ + Node *p; + int dots; + + /* assume no branch if you can't find the RCS info */ + if (rcs == NULL) + return ((char *) NULL); + + /* now, look for a match in the symbols list */ + p = findnode (RCS_symbols(rcs), rev); + if (p == NULL) + return ((char *) NULL); + dots = numdots (p->data); + if ((dots & 1) == 0) + return (xstrdup (p->data)); + + /* got a symbolic tag match, but it's not a branch; see if it's magic */ + if (dots > 2) + { + char *magic; + char *branch = strrchr (p->data, '.'); + char *cp = branch++ - 1; + while (*cp != '.') + cp--; + + /* see if we have .magic-branch. (".0.") */ + magic = xmalloc (strlen (p->data) + 1); + (void) sprintf (magic, ".%d.", RCS_MAGIC_BRANCH); + if (strncmp (magic, cp, strlen (magic)) == 0) + { + /* yep. it's magic. now, construct the real branch */ + *cp = '\0'; /* turn it into a revision */ + (void) sprintf (magic, "%s.%s", p->data, branch); + *cp = '.'; /* and turn it back */ + return (magic); + } + free (magic); + } + return ((char *) NULL); +} + +/* + * Get the head of the specified branch. If the branch does not exist, + * return NULL or RCS_head depending on force_tag_match + */ +char * +RCS_getbranch (rcs, tag, force_tag_match) + RCSNode *rcs; + char *tag; + int force_tag_match; +{ + Node *p, *head; + RCSVers *vn; + char *xtag; + char *nextvers; + char *cp; + + /* make sure we have something to look at... */ + assert (rcs != NULL); + + if (rcs->flags & PARTIAL) + RCS_reparsercsfile (rcs, NULL); + + /* find out if the tag contains a dot, or is on the trunk */ + cp = strrchr (tag, '.'); + + /* trunk processing is the special case */ + if (cp == NULL) + { + xtag = xmalloc (strlen (tag) + 1 + 1); /* +1 for an extra . */ + (void) strcpy (xtag, tag); + (void) strcat (xtag, "."); + for (cp = rcs->head; cp != NULL;) + { + if (strncmp (xtag, cp, strlen (xtag)) == 0) + break; + p = findnode (rcs->versions, cp); + if (p == NULL) + { + free (xtag); + if (force_tag_match) + return (NULL); + else + return (RCS_head (rcs)); + } + vn = (RCSVers *) p->data; + cp = vn->next; + } + free (xtag); + if (cp == NULL) + { + if (force_tag_match) + return (NULL); + else + return (RCS_head (rcs)); + } + return (xstrdup (cp)); + } + + /* if it had a `.', terminate the string so we have the base revision */ + *cp = '\0'; + + /* look up the revision this branch is based on */ + p = findnode (rcs->versions, tag); + + /* put the . back so we have the branch again */ + *cp = '.'; + + if (p == NULL) + { + /* if the base revision didn't exist, return head or NULL */ + if (force_tag_match) + return (NULL); + else + return (RCS_head (rcs)); + } + + /* find the first element of the branch we are looking for */ + vn = (RCSVers *) p->data; + if (vn->branches == NULL) + return (NULL); + xtag = xmalloc (strlen (tag) + 1 + 1); /* 1 for the extra '.' */ + (void) strcpy (xtag, tag); + (void) strcat (xtag, "."); + head = vn->branches->list; + for (p = head->next; p != head; p = p->next) + if (strncmp (p->key, xtag, strlen (xtag)) == 0) + break; + free (xtag); + + if (p == head) + { + /* we didn't find a match so return head or NULL */ + if (force_tag_match) + return (NULL); + else + return (RCS_head (rcs)); + } + + /* now walk the next pointers of the branch */ + nextvers = p->key; + do + { + p = findnode (rcs->versions, nextvers); + if (p == NULL) + { + /* a link in the chain is missing - return head or NULL */ + if (force_tag_match) + return (NULL); + else + return (RCS_head (rcs)); + } + vn = (RCSVers *) p->data; + nextvers = vn->next; + } while (nextvers != NULL); + + /* we have the version in our hand, so go for it */ + return (xstrdup (vn->version)); +} + +/* + * Get the head of the RCS file. If branch is set, this is the head of the + * branch, otherwise the real head + */ +char * +RCS_head (rcs) + RCSNode *rcs; +{ + /* make sure we have something to look at... */ + assert (rcs != NULL); + + /* + * NOTE: we call getbranch with force_tag_match set to avoid any + * possibility of recursion + */ + if (rcs->branch) + return (RCS_getbranch (rcs, rcs->branch, 1)); + else + return (xstrdup (rcs->head)); +} + +/* + * Get the most recent revision, based on the supplied date, but use some + * funky stuff and follow the vendor branch maybe + */ +char * +RCS_getdate (rcs, date, force_tag_match) + RCSNode *rcs; + char *date; + int force_tag_match; +{ + char *cur_rev = NULL; + char *retval = NULL; + Node *p; + RCSVers *vers = NULL; + + /* make sure we have something to look at... */ + assert (rcs != NULL); + + if (rcs->flags & PARTIAL) + RCS_reparsercsfile (rcs, NULL); + + /* if the head is on a branch, try the branch first */ + if (rcs->branch != NULL) + retval = RCS_getdatebranch (rcs, date, rcs->branch); + + /* if we found a match, we are done */ + if (retval != NULL) + return (retval); + + /* otherwise if we have a trunk, try it */ + if (rcs->head) + { + p = findnode (rcs->versions, rcs->head); + while (p != NULL) + { + /* if the date of this one is before date, take it */ + vers = (RCSVers *) p->data; + if (RCS_datecmp (vers->date, date) <= 0) + { + cur_rev = vers->version; + break; + } + + /* if there is a next version, find the node */ + if (vers->next != NULL) + p = findnode (rcs->versions, vers->next); + else + p = (Node *) NULL; + } + } + + /* + * at this point, either we have the revision we want, or we have the + * first revision on the trunk (1.1?) in our hands + */ + + /* 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) + return (xstrdup (cur_rev)); + + /* look on the vendor branch */ + retval = RCS_getdatebranch (rcs, date, CVSBRANCH); + + /* + * if we found a match, return it; otherwise, we return the first + * revision on the trunk or NULL depending on force_tag_match and the + * date of the first rev + */ + if (retval != NULL) + return (retval); + + if (!force_tag_match || RCS_datecmp (vers->date, date) <= 0) + return (xstrdup (vers->version)); + else + return (NULL); +} + +/* + * Look up the last element on a branch that was put in before the specified + * date (return the rev or NULL) + */ +static char * +RCS_getdatebranch (rcs, date, branch) + RCSNode *rcs; + char *date; + char *branch; +{ + char *cur_rev = NULL; + char *cp; + char *xbranch, *xrev; + Node *p; + RCSVers *vers; + + /* look up the first revision on the branch */ + xrev = xstrdup (branch); + cp = strrchr (xrev, '.'); + if (cp == NULL) + { + free (xrev); + return (NULL); + } + *cp = '\0'; /* turn it into a revision */ + + assert (rcs != NULL); + + if (rcs->flags & PARTIAL) + RCS_reparsercsfile (rcs, NULL); + + p = findnode (rcs->versions, xrev); + free (xrev); + if (p == NULL) + return (NULL); + vers = (RCSVers *) p->data; + + /* if no branches list, return NULL */ + if (vers->branches == NULL) + return (NULL); + + /* walk the branches list looking for the branch number */ + xbranch = xmalloc (strlen (branch) + 1 + 1); /* +1 for the extra dot */ + (void) strcpy (xbranch, branch); + (void) strcat (xbranch, "."); + for (p = vers->branches->list->next; p != vers->branches->list; p = p->next) + if (strncmp (p->key, xbranch, strlen (xbranch)) == 0) + break; + free (xbranch); + if (p == vers->branches->list) + return (NULL); + + p = findnode (rcs->versions, p->key); + + /* walk the next pointers until you find the end, or the date is too late */ + while (p != NULL) + { + vers = (RCSVers *) p->data; + if (RCS_datecmp (vers->date, date) <= 0) + cur_rev = vers->version; + else + break; + + /* if there is a next version, find the node */ + if (vers->next != NULL) + p = findnode (rcs->versions, vers->next); + else + p = (Node *) NULL; + } + + /* if we found something acceptable, return it - otherwise NULL */ + if (cur_rev != NULL) + return (xstrdup (cur_rev)); + else + return (NULL); +} + +/* + * Compare two dates in RCS format. Beware the change in format on January 1, + * 2000, when years go from 2-digit to full format. + */ +int +RCS_datecmp (date1, date2) + char *date1, *date2; +{ + int length_diff = strlen (date1) - strlen (date2); + + return (length_diff ? length_diff : strcmp (date1, date2)); +} + +/* + * Lookup the specified revision in the ,v file and return, in the date + * argument, the date specified for the revision *minus one second*, so that + * the logically previous revision will be found later. + * + * Returns zero on failure, RCS revision time as a Unix "time_t" on success. + */ +time_t +RCS_getrevtime (rcs, rev, date, fudge) + RCSNode *rcs; + char *rev; + char *date; + int fudge; +{ + char tdate[MAXDATELEN]; + struct tm xtm, *ftm; + time_t revdate = 0; + Node *p; + RCSVers *vers; + + /* make sure we have something to look at... */ + assert (rcs != NULL); + + if (rcs->flags & PARTIAL) + RCS_reparsercsfile (rcs, NULL); + + /* look up the revision */ + p = findnode (rcs->versions, rev); + if (p == NULL) + return (-1); + vers = (RCSVers *) p->data; + + /* split up the date */ + ftm = &xtm; + (void) sscanf (vers->date, SDATEFORM, &ftm->tm_year, &ftm->tm_mon, + &ftm->tm_mday, &ftm->tm_hour, &ftm->tm_min, + &ftm->tm_sec); + + /* If the year is from 1900 to 1999, RCS files contain only two + digits, and sscanf gives us a year from 0-99. If the year is + 2000+, RCS files contain all four digits and we subtract 1900, + because the tm_year field should contain years since 1900. */ + + if (ftm->tm_year > 1900) + ftm->tm_year -= 1900; + + /* put the date in a form getdate can grok */ +#ifdef HAVE_RCS5 + (void) sprintf (tdate, "%d/%d/%d GMT %d:%d:%d", ftm->tm_mon, + ftm->tm_mday, ftm->tm_year, ftm->tm_hour, + ftm->tm_min, ftm->tm_sec); +#else + (void) sprintf (tdate, "%d/%d/%d %d:%d:%d", ftm->tm_mon, + ftm->tm_mday, ftm->tm_year, ftm->tm_hour, + ftm->tm_min, ftm->tm_sec); +#endif + + /* turn it into seconds since the epoch */ + revdate = get_date (tdate, (struct timeb *) NULL); + if (revdate != (time_t) -1) + { + revdate -= fudge; /* remove "fudge" seconds */ + if (date) + { + /* put an appropriate string into ``date'' if we were given one */ +#ifdef HAVE_RCS5 + ftm = gmtime (&revdate); +#else + ftm = localtime (&revdate); +#endif + (void) sprintf (date, DATEFORM, + ftm->tm_year + (ftm->tm_year < 100 ? 0 : 1900), + ftm->tm_mon + 1, ftm->tm_mday, ftm->tm_hour, + ftm->tm_min, ftm->tm_sec); + } + } + return (revdate); +} + +List * +RCS_symbols(rcs) + RCSNode *rcs; +{ + assert(rcs != NULL); + + if (rcs->flags & PARTIAL) + RCS_reparsercsfile (rcs, NULL); + + if (rcs->symbols_data) { + rcs->symbols = getlist (); + do_symbols (rcs->symbols, rcs->symbols_data); + free(rcs->symbols_data); + rcs->symbols_data = NULL; + } + + return rcs->symbols; +} + +/* + * The argument ARG is the getopt remainder of the -k option specified on the + * command line. This function returns malloc'ed space that can be used + * directly in calls to RCS V5, with the -k flag munged correctly. + */ +char * +RCS_check_kflag (arg) + const char *arg; +{ + static const char *const kflags[] = + {"kv", "kvl", "k", "v", "o", "b", (char *) NULL}; + static const char *const keyword_usage[] = + { + "%s %s: invalid RCS keyword expansion mode\n", + "Valid expansion modes include:\n", + " -kkv\tGenerate keywords using the default form.\n", + " -kkvl\tLike -kkv, except locker's name inserted.\n", + " -kk\tGenerate only keyword names in keyword strings.\n", + " -kv\tGenerate only keyword values in keyword strings.\n", + " -ko\tGenerate the old keyword string (no changes from checked in file).\n", + " -kb\tGenerate binary file unmodified (merges not allowed) (RCS 5.7).\n", + NULL, + }; + char karg[10]; + char const *const *cpp = NULL; + +#ifndef HAVE_RCS5 + error (1, 0, "%s %s: your version of RCS does not support the -k option", + program_name, command_name); +#endif + + if (arg) + { + for (cpp = kflags; *cpp != NULL; cpp++) + { + if (strcmp (arg, *cpp) == 0) + break; + } + } + + if (arg == NULL || *cpp == NULL) + { + usage (keyword_usage); + } + + (void) sprintf (karg, "-k%s", *cpp); + return (xstrdup (karg)); +} + +/* + * Do some consistency checks on the symbolic tag... These should equate + * pretty close to what RCS checks, though I don't know for certain. + */ +void +RCS_check_tag (tag) + const char *tag; +{ + char *invalid = "$,.:;@"; /* invalid RCS tag characters */ + const char *cp; + + /* + * The first character must be an alphabetic letter. The remaining + * characters cannot be non-visible graphic characters, and must not be + * in the set of "invalid" RCS identifier characters. + */ + if (isalpha (*tag)) + { + for (cp = tag; *cp; cp++) + { + if (!isgraph (*cp)) + error (1, 0, "tag `%s' has non-visible graphic characters", + tag); + if (strchr (invalid, *cp)) + error (1, 0, "tag `%s' must not contain the characters `%s'", + tag, invalid); + } + } + else + error (1, 0, "tag `%s' must start with a letter", tag); +} + +/* + * Return true if RCS revision with TAG is a dead revision. + */ +int +RCS_isdead (rcs, tag) + RCSNode *rcs; + const char *tag; +{ + Node *p; + RCSVers *version; + + if (rcs->flags & PARTIAL) + RCS_reparsercsfile (rcs, NULL); + + p = findnode (rcs->versions, tag); + if (p == NULL) + return (0); + + version = (RCSVers *) p->data; + return (version->dead); +} + +/* Return the RCS keyword expansion mode. For example "b" for binary. + Returns a pointer into storage which is allocated and freed along with + the rest of the RCS information; the caller should not modify this + storage. Returns NULL if the RCS file does not specify a keyword + expansion mode; for all other errors, die with a fatal error. */ +char * +RCS_getexpand (rcs) + RCSNode *rcs; +{ + assert (rcs != NULL); + if (rcs->flags & PARTIAL) + RCS_reparsercsfile (rcs, NULL); + return rcs->expand; +} + +/* Stuff related to annotate command. This should perhaps be split + into the stuff which knows about the guts of RCS files, and the + command parsing type stuff. */ + +/* Linked list of allocated blocks. Seems kind of silly to + reinvent the obstack wheel, and this isn't as nice as obstacks + in some ways, but obstacks are pretty baroque. */ +struct allocblock +{ + char *text; + struct allocblock *next; +}; +struct allocblock *blocks; + +static void *block_alloc PROTO ((size_t)); + +static void * +block_alloc (n) + size_t n; +{ + struct allocblock *blk; + blk = (struct allocblock *) xmalloc (sizeof (struct allocblock)); + blk->text = xmalloc (n); + blk->next = blocks; + blocks = blk; + return blk->text; +} + +static void block_free PROTO ((void)); + +static void +block_free () +{ + struct allocblock *p; + struct allocblock *q; + + p = blocks; + while (p != NULL) + { + free (p->text); + q = p->next; + free (p); + p = q; + } + blocks = NULL; +} + +struct line +{ + /* Text of this line, terminated by \n or \0. */ + char *text; + /* Version in which it was introduced. */ + RCSVers *vers; + /* Nonzero if this line ends with \n. This will always be true + except possibly for the last line. */ + int has_newline; +}; + +struct linevector +{ + /* How many lines in use for this linevector? */ + unsigned int nlines; + /* How many lines allocated for this linevector? */ + unsigned int lines_alloced; + /* Pointer to array containing a pointer to each line. */ + struct line **vector; +}; + +static void linevector_init PROTO ((struct linevector *)); + +/* Initialize *VEC to be a linevector with no lines. */ +static void +linevector_init (vec) + struct linevector *vec; +{ + vec->lines_alloced = 10; + vec->nlines = 0; + vec->vector = (struct line **) + xmalloc (vec->lines_alloced * sizeof (*vec->vector)); +} + +static void linevector_add PROTO ((struct linevector *vec, char *text, + RCSVers *vers, unsigned int pos)); + +/* Given some text TEXT, add each of its lines to VEC before line POS + (where line 0 is the first line). The last line in TEXT may or may + not be \n terminated. All \n in TEXT are changed to \0. Set the + version for each of the new lines to VERS. */ +static void +linevector_add (vec, text, vers, pos) + struct linevector *vec; + char *text; + RCSVers *vers; + unsigned int pos; +{ + unsigned int i; + unsigned int nnew; + char *p; + struct line *lines; + + assert (vec->lines_alloced > 0); + + /* Count the number of lines we will need to add. */ + nnew = 1; + for (p = text; *p != '\0'; ++p) + if (*p == '\n' && p[1] != '\0') + ++nnew; + /* Allocate the struct line's. */ + lines = block_alloc (nnew * sizeof (struct line)); + + /* Expand VEC->VECTOR if needed. */ + if (vec->nlines + nnew >= vec->lines_alloced) + { + while (vec->nlines + nnew >= vec->lines_alloced) + vec->lines_alloced *= 2; + vec->vector = xrealloc (vec->vector, + vec->lines_alloced * sizeof (*vec->vector)); + } + + /* Make room for the new lines in VEC->VECTOR. */ + for (i = vec->nlines + nnew - 1; i >= pos + nnew; --i) + vec->vector[i] = vec->vector[i - nnew]; + + if (pos > vec->nlines) + error (1, 0, "invalid rcs file: line to add out of range"); + + /* Actually add the lines, to LINES and VEC->VECTOR. */ + i = pos; + lines[0].text = text; + lines[0].vers = vers; + lines[0].has_newline = 0; + vec->vector[i++] = &lines[0]; + for (p = text; *p != '\0'; ++p) + if (*p == '\n') + { + *p = '\0'; + lines[i - pos - 1].has_newline = 1; + if (p[1] == '\0') + /* If there are no characters beyond the last newline, we + don't consider it another line. */ + break; + lines[i - pos].text = p + 1; + lines[i - pos].vers = vers; + lines[i - pos].has_newline = 0; + vec->vector[i] = &lines[i - pos]; + ++i; + } + vec->nlines += nnew; +} + +static void linevector_delete PROTO ((struct linevector *, unsigned int, + unsigned int)); + +/* Remove NLINES lines from VEC at position POS (where line 0 is the + first line). */ +static void +linevector_delete (vec, pos, nlines) + struct linevector *vec; + unsigned int pos; + unsigned int nlines; +{ + unsigned int i; + unsigned int last; + + last = vec->nlines - nlines; + for (i = pos; i < last; ++i) + vec->vector[i] = vec->vector[i + nlines]; + vec->nlines -= nlines; +} + +static void linevector_copy PROTO ((struct linevector *, struct linevector *)); + +/* Copy FROM to TO, copying the vectors but not the lines pointed to. */ +static void +linevector_copy (to, from) + struct linevector *to; + struct linevector *from; +{ + if (from->nlines > to->lines_alloced) + { + while (from->nlines > to->lines_alloced) + to->lines_alloced *= 2; + to->vector = (struct line **) + xrealloc (to->vector, to->lines_alloced * sizeof (*to->vector)); + } + memcpy (to->vector, from->vector, + from->nlines * sizeof (*to->vector)); + to->nlines = from->nlines; +} + +static void linevector_free PROTO ((struct linevector *)); + +/* Free storage associated with linevector (that is, the vector but + not the lines pointed to). */ +static void +linevector_free (vec) + struct linevector *vec; +{ + free (vec->vector); +} + +static char *month_printname PROTO ((char *)); + +/* Given a textual string giving the month (1-12), terminated with any + character not recognized by atoi, return the 3 character name to + print it with. I do not think it is a good idea to change these + strings based on the locale; they are standard abbreviations (for + example in rfc822 mail messages) which should be widely understood. + Returns a pointer into static readonly storage. */ +static char * +month_printname (month) + char *month; +{ + static const char *const months[] = + {"Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}; + int mnum; + + mnum = atoi (month); + if (mnum < 1 || mnum > 12) + return "???"; + return (char *)months[mnum - 1]; +} + +static int annotate_fileproc PROTO ((struct file_info *)); + +static int +annotate_fileproc (finfo) + struct file_info *finfo; +{ + FILE *fp; + char *key; + char *value; + RCSVers *vers; + RCSVers *prev_vers; + int n; + int ishead; + Node *node; + struct linevector headlines; + struct linevector curlines; + + if (finfo->rcs == NULL) + return (1); + + /* Distinguish output for various files if we are processing + several files. */ + cvs_outerr ("Annotations for ", 0); + cvs_outerr (finfo->fullname, 0); + cvs_outerr ("\n***************\n", 0); + + if (!(finfo->rcs->flags & PARTIAL)) + /* We are leaking memory by calling RCS_reparsefile again. */ + error (0, 0, "internal warning: non-partial rcs in annotate_fileproc"); + RCS_reparsercsfile (finfo->rcs, &fp); + + ishead = 1; + vers = NULL; + + do { + getrcsrev (fp, &key); + + /* Stash the previous version. */ + prev_vers = vers; + + /* look up the revision */ + node = findnode (finfo->rcs->versions, key); + if (node == NULL) + error (1, 0, "mismatch in rcs file %s between deltas and deltatexts", + finfo->rcs->path); + vers = (RCSVers *) node->data; + + while ((n = getrcskey (fp, &key, &value)) >= 0) + { + if (strcmp (key, "text") == 0) + { + if (ishead) + { + char *p; + + p = block_alloc (strlen (value) + 1); + strcpy (p, value); + + linevector_init (&headlines); + linevector_init (&curlines); + linevector_add (&headlines, p, NULL, 0); + linevector_copy (&curlines, &headlines); + ishead = 0; + } + else + { + char *p; + char *q; + int op; + /* The RCS format throws us for a loop in that the + deltafrags (if we define a deltafrag as an + add or a delete) need to be applied in reverse + order. So we stick them into a linked list. */ + struct deltafrag { + enum {ADD, DELETE} type; + unsigned long pos; + unsigned long nlines; + char *new_lines; + struct deltafrag *next; + }; + struct deltafrag *dfhead; + struct deltafrag *df; + + dfhead = NULL; + for (p = value; p != NULL && *p != '\0'; ) + { + op = *p++; + if (op != 'a' && op != 'd') + /* Can't just skip over the deltafrag, because + the value of op determines the syntax. */ + error (1, 0, "unrecognized operation '%c' in %s", + op, finfo->rcs->path); + df = (struct deltafrag *) + xmalloc (sizeof (struct deltafrag)); + df->next = dfhead; + dfhead = df; + df->pos = strtoul (p, &q, 10); + + if (p == q) + error (1, 0, "number expected in %s", + finfo->rcs->path); + p = q; + if (*p++ != ' ') + error (1, 0, "space expected in %s", + finfo->rcs->path); + df->nlines = strtoul (p, &q, 10); + if (p == q) + error (1, 0, "number expected in %s", + finfo->rcs->path); + p = q; + if (*p++ != '\012') + error (1, 0, "linefeed expected in %s", + finfo->rcs->path); + + if (op == 'a') + { + unsigned int i; + + df->type = ADD; + i = df->nlines; + /* The text we want is the number of lines + specified, or until the end of the value, + whichever comes first (it will be the former + except in the case where we are adding a line + which does not end in newline). */ + for (q = p; i != 0; ++q) + if (*q == '\n') + --i; + else if (*q == '\0') + { + if (i != 1) + error (1, 0, "\ +invalid rcs file %s: premature end of value", + finfo->rcs->path); + else + break; + } + + /* Copy the text we are adding into allocated + space. */ + df->new_lines = block_alloc (q - p + 1); + strncpy (df->new_lines, p, q - p); + df->new_lines[q - p] = '\0'; + + p = q; + } + else + { + /* Correct for the fact that line numbers in RCS + files start with 1. */ + --df->pos; + + assert (op == 'd'); + df->type = DELETE; + } + } + for (df = dfhead; df != NULL;) + { + unsigned int ln; + + switch (df->type) + { + case ADD: + linevector_add (&curlines, df->new_lines, + NULL, df->pos); + break; + case DELETE: + if (df->pos > curlines.nlines + || df->pos + df->nlines > curlines.nlines) + error (1, 0, "\ +invalid rcs file %s (`d' operand out of range)", + finfo->rcs->path); + for (ln = df->pos; ln < df->pos + df->nlines; ++ln) + curlines.vector[ln]->vers = prev_vers; + linevector_delete (&curlines, df->pos, df->nlines); + break; + } + df = df->next; + free (dfhead); + dfhead = df; + } + } + break; + } + } + if (n < 0) + goto l_error; + } while (vers->next != NULL); + + if (fclose (fp) < 0) + error (0, errno, "cannot close %s", finfo->rcs->path); + + /* Now print out the data we have just computed. */ + { + unsigned int ln; + + for (ln = 0; ln < headlines.nlines; ++ln) + { + char buf[80]; + /* Period which separates year from month in date. */ + char *ym; + /* Period which separates month from day in date. */ + char *md; + RCSVers *prvers; + + prvers = headlines.vector[ln]->vers; + if (prvers == NULL) + prvers = vers; + + sprintf (buf, "%-12s (%-8.8s ", + prvers->version, + prvers->author); + cvs_output (buf, 0); + + /* Now output the date. */ + ym = strchr (prvers->date, '.'); + if (ym == NULL) + cvs_output ("??-???-??", 0); + else + { + md = strchr (ym + 1, '.'); + if (md == NULL) + cvs_output ("??", 0); + else + cvs_output (md + 1, 2); + + cvs_output ("-", 1); + cvs_output (month_printname (ym + 1), 0); + cvs_output ("-", 1); + /* Only output the last two digits of the year. Our output + lines are long enough as it is without printing the + century. */ + cvs_output (ym - 2, 2); + } + cvs_output ("): ", 0); + cvs_output (headlines.vector[ln]->text, 0); + cvs_output ("\n", 1); + } + } + + if (!ishead) + { + linevector_free (&curlines); + linevector_free (&headlines); + } + block_free (); + return 0; + + l_error: + if (ferror (fp)) + error (1, errno, "cannot read %s", finfo->rcs->path); + else + error (1, 0, "%s does not appear to be a valid rcs file", + finfo->rcs->path); + /* Shut up gcc -Wall. */ + return 0; +} + +static const char *const annotate_usage[] = +{ + "Usage: %s %s [-l] [files...]\n", + "\t-l\tLocal directory only, no recursion.\n", + NULL +}; + +/* Command to show the revision, date, and author where each line of a + file was modified. Currently it will only show the trunk, all the + way to the head, but it would be useful to enhance it to (a) allow + one to specify a revision, and display only as far as that (easy; + just have annotate_fileproc set all the ->vers fields to NULL when + you hit that revision), and (b) handle branches (not as easy, but + doable). The user interface for both (a) and (b) could be a -r + option. */ + +int +annotate (argc, argv) + int argc; + char **argv; +{ + int local = 0; + int c; + + if (argc == -1) + usage (annotate_usage); + + optind = 0; + while ((c = getopt (argc, argv, "+l")) != -1) + { + switch (c) + { + case 'l': + local = 1; + break; + case '?': + default: + usage (annotate_usage); + break; + } + } + argc -= optind; + argv += optind; + +#ifdef CLIENT_SUPPORT + if (client_active) + { + start_server (); + ign_setup (); + + if (local) + send_arg ("-l"); + send_file_names (argc, argv, SEND_EXPAND_WILD); + /* FIXME: We shouldn't have to send current files, but I'm not sure + whether it works. So send the files -- + it's slower but it works. */ + send_files (argc, argv, local, 0); + send_to_server ("annotate\012", 0); + return get_responses_and_close (); + } +#endif /* CLIENT_SUPPORT */ + + return start_recursion (annotate_fileproc, (FILESDONEPROC) NULL, + (DIRENTPROC) NULL, (DIRLEAVEPROC) NULL, + argc, argv, local, W_LOCAL, 0, 1, (char *)NULL, + 1, 0); +} diff --git a/contrib/cvs/src/rcs.h b/contrib/cvs/src/rcs.h new file mode 100644 index 0000000..698a3b1 --- /dev/null +++ b/contrib/cvs/src/rcs.h @@ -0,0 +1,104 @@ +/* $CVSid: @(#)rcs.h 1.18 94/09/23 $ */ + +/* + * Copyright (c) 1992, Brian Berliner and Jeff Polk + * Copyright (c) 1989-1992, Brian Berliner + * + * You may distribute under the terms of the GNU General Public License as + * specified in the README file that comes with the CVS 1.4 kit. + * + * RCS source control definitions needed by rcs.c and friends + */ + +#define RCS "rcs" +#define RCS_CI "ci" +#define RCS_CO "co" +#define RCS_RLOG "rlog" +#define RCS_DIFF "rcsdiff" +#define RCS_RCSMERGE "rcsmerge" +#define RCS_MERGE_PAT "^>>>>>>> " /* runs "grep" with this pattern */ +#define RCSEXT ",v" +#define RCSPAT "*,v" +#define RCSHEAD "head" +#define RCSBRANCH "branch" +#define RCSSYMBOLS "symbols" +#define RCSDATE "date" +#define RCSDESC "desc" +#define RCSEXPAND "expand" + +/* Used by the version of death support which resulted from old + versions of CVS (e.g. 1.5 if you define DEATH_SUPPORT and not + DEATH_STATE). Only a hacked up RCS (used by those old versions of + CVS) will put this into RCS files. Considered obsolete. */ +#define RCSDEAD "dead" + +#define DATEFORM "%02d.%02d.%02d.%02d.%02d.%02d" +#define SDATEFORM "%d.%d.%d.%d.%d.%d" + +/* + * Opaque structure definitions used by RCS specific lookup routines + */ +#define VALID 0x1 /* flags field contains valid data */ +#define INATTIC 0x2 /* RCS file is located in the Attic */ +#define PARTIAL 0x4 /* RCS file not completly parsed */ + +struct rcsnode +{ + int refcount; + int flags; + char *path; + char *head; + char *branch; + char *symbols_data; + char *expand; + List *symbols; + List *versions; +}; + +typedef struct rcsnode RCSNode; + +struct rcsversnode +{ + char *version; + char *date; + char *author; + char *next; + int dead; + List *branches; +}; +typedef struct rcsversnode RCSVers; + +/* + * CVS reserves all even-numbered branches for its own use. "magic" branches + * (see rcs.c) are contained as virtual revision numbers (within symbolic + * tags only) off the RCS_MAGIC_BRANCH, which is 0. CVS also reserves the + * ".1" branch for vendor revisions. So, if you do your own branching, you + * should limit your use to odd branch numbers starting at 3. + */ +#define RCS_MAGIC_BRANCH 0 + +/* + * exported interfaces + */ +RCSNode *RCS_parse PROTO((const char *file, const char *repos)); +RCSNode *RCS_parsercsfile PROTO((char *rcsfile)); +char *RCS_check_kflag PROTO((const char *arg)); +char *RCS_getdate PROTO((RCSNode * rcs, char *date, int force_tag_match)); +char *RCS_gettag PROTO((RCSNode * rcs, char *symtag, int force_tag_match, + int return_both)); +char *RCS_getversion PROTO((RCSNode * rcs, char *tag, char *date, + int force_tag_match, int return_both)); +char *RCS_magicrev PROTO((RCSNode *rcs, char *rev)); +int RCS_isbranch PROTO((RCSNode *rcs, const char *rev)); +int RCS_nodeisbranch PROTO((RCSNode *rcs, const char *tag)); +char *RCS_whatbranch PROTO((RCSNode *rcs, const char *tag)); +char *RCS_head PROTO((RCSNode * rcs)); +int RCS_datecmp PROTO((char *date1, char *date2)); +time_t RCS_getrevtime PROTO((RCSNode * rcs, char *rev, char *date, int fudge)); +List *RCS_symbols PROTO((RCSNode *rcs)); +void RCS_check_tag PROTO((const char *tag)); +void freercsnode PROTO((RCSNode ** rnodep)); +char *RCS_getbranch PROTO((RCSNode * rcs, char *tag, int force_tag_match)); + +int RCS_isdead PROTO((RCSNode *, const char *)); +char *RCS_getexpand PROTO ((RCSNode *)); diff --git a/contrib/cvs/src/rcscmds.c b/contrib/cvs/src/rcscmds.c new file mode 100644 index 0000000..66aea57 --- /dev/null +++ b/contrib/cvs/src/rcscmds.c @@ -0,0 +1,188 @@ +/* + * Copyright (c) 1992, Brian Berliner and Jeff Polk + * Copyright (c) 1989-1992, Brian Berliner + * + * You may distribute under the terms of the GNU General Public License as + * specified in the README file that comes with the CVS 1.4 kit. + * + * The functions in this file provide an interface for performing + * operations directly on RCS files. + */ + +#include "cvs.h" +#include <assert.h> + +/* For RCS file PATH, make symbolic tag TAG point to revision REV. + This validates that TAG is OK for a user to use. 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. */ + +int +RCS_settag(path, tag, rev) + const char *path; + const char *tag; + const char *rev; +{ + if (strcmp (tag, TAG_BASE) == 0 + || strcmp (tag, TAG_HEAD) == 0) + { + /* Print the name of the tag might be considered redundant + with the caller, which also prints it. Perhaps this helps + clarify why the tag name is considered reserved, I don't + know. */ + error (0, 0, "Attempt to add reserved tag name %s", tag); + return 1; + } + + run_setup ("%s%s -x,v/ -q -N%s:%s", Rcsbin, RCS, tag, rev); + run_arg (path); + return run_exec (RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL); +} + +/* NOERR is 1 to suppress errors--FIXME it would + be better to avoid the errors or some cleaner solution. */ +int +RCS_deltag(path, tag, noerr) + const char *path; + const char *tag; + int noerr; +{ + run_setup ("%s%s -x,v/ -q -N%s", Rcsbin, RCS, tag); + run_arg (path); + return run_exec (RUN_TTY, RUN_TTY, noerr ? DEVNULL : RUN_TTY, RUN_NORMAL); +} + +/* set RCS branch to REV */ +int +RCS_setbranch(path, rev) + const char *path; + const char *rev; +{ + run_setup ("%s%s -x,v/ -q -b%s", Rcsbin, RCS, rev ? rev : ""); + run_arg (path); + return run_exec (RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL); +} + +/* Lock revision REV. NOERR is 1 to suppress errors--FIXME it would + be better to avoid the errors or some cleaner solution. */ +int +RCS_lock(path, rev, noerr) + const char *path; + const char *rev; + int noerr; +{ + run_setup ("%s%s -x,v/ -q -l%s", Rcsbin, RCS, rev ? rev : ""); + run_arg (path); + return run_exec (RUN_TTY, RUN_TTY, noerr ? DEVNULL : RUN_TTY, RUN_NORMAL); +} + +/* Unlock revision REV. NOERR is 1 to suppress errors--FIXME it would + be better to avoid the errors or some cleaner solution. */ +int +RCS_unlock(path, rev, noerr) + const char *path; + const char *rev; + int noerr; +{ + run_setup ("%s%s -x,v/ -q -u%s", Rcsbin, RCS, rev ? rev : ""); + run_arg (path); + return run_exec (RUN_TTY, RUN_TTY, noerr ? DEVNULL : RUN_TTY, RUN_NORMAL); +} + +/* Merge revisions REV1 and REV2. */ +int +RCS_merge(path, options, rev1, rev2) + const char *path; + const char *options; + const char *rev1; + const char *rev2; +{ + int status; + + /* XXX - Do merge by hand instead of using rcsmerge, due to -k handling */ + + run_setup ("%s%s -x,v/ %s -r%s -r%s %s", Rcsbin, RCS_RCSMERGE, + options, rev1, rev2, path); + status = run_exec (RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL); +#ifndef HAVE_RCS5 + if (status == 0) + { + /* Run GREP to see if there appear to be conflicts in the file */ + run_setup ("%s", GREP); + run_arg (RCS_MERGE_PAT); + run_arg (path); + status = (run_exec (RUN_TTY, DEVNULL, RUN_TTY, RUN_NORMAL) == 0); + + } +#endif + return status; +} + +/* Check out a revision from RCSFILE into WORKFILE, or to standard output + if WORKFILE is NULL. If WORKFILE is "", let RCS pick the working file + name. TAG is the tag to check out, or NULL if one should check out + the head of the default branch. OPTIONS is a string such as + -kb or -kkv, for keyword expansion options, or NULL if there are none. + If WORKFILE is NULL, run regardless of noexec; if non-NULL, noexec + inhibits execution. SOUT is what to do with standard output + (typically RUN_TTY). If FLAGS & RCS_FLAGS_LOCK, lock it. If + FLAGS & RCS_FLAGS_FORCE, check out even on top of an existing file. + If NOERR is nonzero, suppress errors. */ +int +RCS_checkout (rcsfile, workfile, tag, options, sout, flags, noerr) + char *rcsfile; + char *workfile; + char *tag; + char *options; + char *sout; + int flags; + int noerr; +{ + run_setup ("%s%s -x,v/ -q %s%s", Rcsbin, RCS_CO, + tag ? "-r" : "", tag ? tag : ""); + if (options != NULL && options[0] != '\0') + run_arg (options); + if (workfile == NULL) + run_arg ("-p"); + if (flags & RCS_FLAGS_LOCK) + run_arg ("-l"); + if (flags & RCS_FLAGS_FORCE) + run_arg ("-f"); + run_arg (rcsfile); + if (workfile != NULL && workfile[0] != '\0') + run_arg (workfile); + return run_exec (RUN_TTY, sout, noerr ? DEVNULL : RUN_TTY, + workfile == NULL ? (RUN_NORMAL | RUN_REALLY) : RUN_NORMAL); +} + +/* 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 NOERR, do not + report errors. If FLAGS & RCS_FLAGS_QUIET suppress errors somewhat more + selectively. 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. */ +int +RCS_checkin (rcsfile, workfile, message, rev, flags, noerr) + char *rcsfile; + char *workfile; + char *message; + char *rev; + int flags; + int noerr; +{ + run_setup ("%s%s -x,v/ -f %s%s", Rcsbin, RCS_CI, + rev ? "-r" : "", rev ? rev : ""); + if (flags & RCS_FLAGS_DEAD) + run_arg ("-sdead"); + if (flags & RCS_FLAGS_QUIET) + run_arg ("-q"); + if (flags & RCS_FLAGS_MODTIME) + run_arg ("-d"); + run_args ("-m%s", make_message_rcslegal (message)); + if (workfile != NULL) + run_arg (workfile); + run_arg (rcsfile); + return run_exec (RUN_TTY, RUN_TTY, noerr ? DEVNULL : RUN_TTY, RUN_NORMAL); +} diff --git a/contrib/cvs/src/recurse.c b/contrib/cvs/src/recurse.c new file mode 100644 index 0000000..ec51a98 --- /dev/null +++ b/contrib/cvs/src/recurse.c @@ -0,0 +1,714 @@ +/* + * Copyright (c) 1992, Brian Berliner and Jeff Polk + * + * You may distribute under the terms of the GNU General Public License as + * specified in the README file that comes with the CVS 1.4 kit. + * + * General recursion handler + * + */ + +#include "cvs.h" +#include "savecwd.h" +#include "fileattr.h" +#include "edit.h" + +static int do_dir_proc PROTO((Node * p, void *closure)); +static int do_file_proc PROTO((Node * p, void *closure)); +static void addlist PROTO((List ** listp, char *key)); +static int unroll_files_proc PROTO((Node *p, void *closure)); +static void addfile PROTO((List **listp, char *dir, char *file)); + + +/* + * Local static versions eliminates the need for globals + */ +static FILEPROC fileproc; +static FILESDONEPROC filesdoneproc; +static DIRENTPROC direntproc; +static DIRLEAVEPROC dirleaveproc; +static int which; +static Dtype flags; +static int aflag; +static int readlock; +static int dosrcs; +static char update_dir[PATH_MAX]; +static char *repository = NULL; +static List *filelist = NULL; /* holds list of files on which to operate */ +static List *dirlist = NULL; /* holds list of directories on which to operate */ + +struct recursion_frame { + FILEPROC fileproc; + FILESDONEPROC filesdoneproc; + DIRENTPROC direntproc; + DIRLEAVEPROC dirleaveproc; + Dtype flags; + int which; + int aflag; + int readlock; + int dosrcs; +}; + +/* + * Called to start a recursive command. + * + * Command line arguments dictate the directories and files on which + * we operate. In the special case of no arguments, we default to + * ".". + * + * The general algorithm is as follows. + */ +int +start_recursion (fileproc, filesdoneproc, direntproc, dirleaveproc, + argc, argv, local, which, aflag, readlock, + update_preload, dosrcs, wd_is_repos) + FILEPROC fileproc; + FILESDONEPROC filesdoneproc; + DIRENTPROC direntproc; + DIRLEAVEPROC dirleaveproc; + int argc; + char **argv; + int local; + int which; + int aflag; + int readlock; + char *update_preload; + int dosrcs; + int wd_is_repos; /* Set if caller has already cd'd to the repository */ +{ + int i, err = 0; + Dtype flags; + List *files_by_dir = NULL; + struct recursion_frame frame; + + expand_wild (argc, argv, &argc, &argv); + + if (update_preload == NULL) + update_dir[0] = '\0'; + else + (void) strcpy (update_dir, update_preload); + + if (local) + flags = R_SKIP_DIRS; + else + flags = R_PROCESS; + + /* clean up from any previous calls to start_recursion */ + if (repository) + { + free (repository); + repository = (char *) NULL; + } + if (filelist) + dellist (&filelist); /* FIXME-krp: no longer correct. */ +/* FIXME-krp: clean up files_by_dir */ + if (dirlist) + dellist (&dirlist); + + if (argc == 0) + { + + /* + * There were no arguments, so we'll probably just recurse. The + * exception to the rule is when we are called from a directory + * without any CVS administration files. That has always meant to + * process each of the sub-directories, so we pretend like we were + * called with the list of sub-dirs of the current dir as args + */ + if ((which & W_LOCAL) && !isdir (CVSADM)) + dirlist = Find_Directories ((char *) NULL, W_LOCAL); + else + addlist (&dirlist, "."); + + err += do_recursion (fileproc, filesdoneproc, direntproc, + dirleaveproc, flags, which, aflag, + readlock, dosrcs); + return(err); + } + + + /* + * There were arguments, so we have to handle them by hand. To do + * that, we set up the filelist and dirlist with the arguments and + * call do_recursion. do_recursion recognizes the fact that the + * lists are non-null when it starts and doesn't update them. + * + * explicitly named directories are stored in dirlist. + * explicitly named files are stored in filelist. + * other possibility is named entities whicha are not currently in + * the working directory. + */ + + for (i = 0; i < argc; i++) + { + /* if this argument is a directory, then add it to the list of + directories. */ + + if (!wrap_name_has (argv[i], WRAP_TOCVS) && isdir (argv[i])) + addlist (&dirlist, argv[i]); + else + { + /* otherwise, split argument into directory and component names. */ + char *dir; + char *comp; + char tmp[PATH_MAX]; + char *file_to_try; + + /* Now break out argv[i] into directory part (DIR) and file part (COMP). + DIR and COMP will each point to a newly malloc'd string. */ + dir = xstrdup (argv[i]); + comp = last_component (dir); + if (comp == dir) + { + /* no dir component. What we have is an implied "./" */ + dir = xstrdup("."); + } + else + { + char *p = comp; + + p[-1] = '\0'; + comp = xstrdup (p); + } + + /* if this argument exists as a file in the current + working directory tree, then add it to the files list. */ + + if (wd_is_repos) + { + /* If doing rtag, we've done a chdir to the repository. */ + sprintf (tmp, "%s%s", argv[i], RCSEXT); + file_to_try = tmp; + } + else + file_to_try = argv[i]; + + if(isfile(file_to_try)) + addfile (&files_by_dir, dir, comp); + else if (isdir (dir)) + { + if (isdir (CVSADM)) + { + /* otherwise, look for it in the repository. */ + char *save_update_dir; + char *repos; + + /* save & set (aka push) update_dir */ + save_update_dir = xstrdup (update_dir); + + if (*update_dir != '\0') + (void) strcat (update_dir, "/"); + + (void) strcat (update_dir, dir); + + /* look for it in the repository. */ + repos = Name_Repository (dir, update_dir); + (void) sprintf (tmp, "%s/%s", repos, comp); + free (repos); + + if (!wrap_name_has (comp, WRAP_TOCVS) && isdir(tmp)) + addlist (&dirlist, argv[i]); + else + addfile (&files_by_dir, dir, comp); + + (void) sprintf (update_dir, "%s", save_update_dir); + free (save_update_dir); + } + else + addfile (&files_by_dir, dir, comp); + } + else + error (1, 0, "no such directory `%s'", dir); + + free (dir); + free (comp); + } + } + + /* At this point we have looped over all named arguments and built + a coupla lists. Now we unroll the lists, setting up and + calling do_recursion. */ + + frame.fileproc = fileproc; + frame.filesdoneproc = filesdoneproc; + frame.direntproc = direntproc; + frame.dirleaveproc = dirleaveproc; + frame.flags = flags; + frame.which = which; + frame.aflag = aflag; + frame.readlock = readlock; + frame.dosrcs = dosrcs; + err += walklist (files_by_dir, unroll_files_proc, (void *) &frame); + + /* then do_recursion on the dirlist. */ + if (dirlist != NULL) + err += do_recursion (frame.fileproc, frame.filesdoneproc, + frame.direntproc, frame.dirleaveproc, + frame.flags, frame.which, frame.aflag, + frame.readlock, frame.dosrcs); + + /* Free the data which expand_wild allocated. */ + for (i = 0; i < argc; ++i) + free (argv[i]); + free (argv); + + return (err); +} + +/* + * Implement the recursive policies on the local directory. This may be + * called directly, or may be called by start_recursion + */ +int +do_recursion (xfileproc, xfilesdoneproc, xdirentproc, xdirleaveproc, + xflags, xwhich, xaflag, xreadlock, xdosrcs) + FILEPROC xfileproc; + FILESDONEPROC xfilesdoneproc; + DIRENTPROC xdirentproc; + DIRLEAVEPROC xdirleaveproc; + Dtype xflags; + int xwhich; + int xaflag; + int xreadlock; + int xdosrcs; +{ + int err = 0; + int dodoneproc = 1; + char *srepository; + List *entries = NULL; + + /* do nothing if told */ + if (xflags == R_SKIP_ALL) + return (0); + + /* set up the static vars */ + fileproc = xfileproc; + filesdoneproc = xfilesdoneproc; + direntproc = xdirentproc; + dirleaveproc = xdirleaveproc; + flags = xflags; + which = xwhich; + aflag = xaflag; + readlock = noexec ? 0 : xreadlock; + dosrcs = xdosrcs; + + /* The fact that locks are not active here is what makes us fail to have + the + + If someone commits some changes in one cvs command, + then an update by someone else will either get all the + changes, or none of them. + + property (see node Concurrency in cvs.texinfo). + + The most straightforward fix would just to readlock the whole + tree before starting an update, but that means that if a commit + gets blocked on a big update, it might need to wait a *long* + time. + + A more adequate fix would be a two-pass design for update, + checkout, etc. The first pass would go through the repository, + with the whole tree readlocked, noting what versions of each + file we want to get. The second pass would release all locks + (except perhaps short-term locks on one file at a + time--although I think RCS already deals with this) and + actually get the files, specifying the particular versions it wants. + + This could be sped up by separating out the data needed for the + first pass into a separate file(s)--for example a file + attribute for each file whose value contains the head revision + for each branch. The structure should be designed so that + commit can relatively quickly update the information for a + single file or a handful of files (file attributes, as + implemented in Jan 96, are probably acceptable; improvements + would be possible such as branch attributes which are in + separate files for each branch). */ + +#if defined(SERVER_SUPPORT) && defined(SERVER_FLOWCONTROL) + /* + * Now would be a good time to check to see if we need to stop + * generating data, to give the buffers a chance to drain to the + * remote client. We should not have locks active at this point. + */ + if (server_active + /* If there are writelocks around, we cannot pause here. */ + && (readlock || noexec)) + server_pause_check(); +#endif + + /* + * Fill in repository with the current repository + */ + if (which & W_LOCAL) + { + if (isdir (CVSADM)) + repository = Name_Repository ((char *) NULL, update_dir); + else + repository = NULL; + } + else + { + repository = xmalloc (PATH_MAX); + (void) getwd (repository); + } + srepository = repository; /* remember what to free */ + + fileattr_startdir (repository); + + /* + * The filesdoneproc needs to be called for each directory where files + * processed, or each directory that is processed by a call where no + * directories were passed in. In fact, the only time we don't want to + * call back the filesdoneproc is when we are processing directories that + * were passed in on the command line (or in the special case of `.' when + * we were called with no args + */ + if (dirlist != NULL && filelist == NULL) + dodoneproc = 0; + + /* + * If filelist or dirlist is already set, we don't look again. Otherwise, + * find the files and directories + */ + if (filelist == NULL && dirlist == NULL) + { + /* both lists were NULL, so start from scratch */ + if (fileproc != NULL && flags != R_SKIP_FILES) + { + int lwhich = which; + + /* be sure to look in the attic if we have sticky tags/date */ + if ((lwhich & W_ATTIC) == 0) + if (isreadable (CVSADM_TAG)) + lwhich |= W_ATTIC; + + /* find the files and fill in entries if appropriate */ + filelist = Find_Names (repository, lwhich, aflag, &entries); + } + + /* find sub-directories if we will recurse */ + if (flags != R_SKIP_DIRS) + dirlist = Find_Directories (repository, which); + } + else + { + /* something was passed on the command line */ + if (filelist != NULL && fileproc != NULL) + { + /* we will process files, so pre-parse entries */ + if (which & W_LOCAL) + entries = Entries_Open (aflag); + } + } + + /* process the files (if any) */ + if (filelist != NULL && fileproc) + { + struct file_info finfo_struct; + + /* read lock it if necessary */ + if (readlock && repository && Reader_Lock (repository) != 0) + error (1, 0, "read lock failed - giving up"); + +#ifdef CLIENT_SUPPORT + /* For the server, we handle notifications in a completely different + place (server_notify). For local, we can't do them here--we don't + have writelocks in place, and there is no way to get writelocks + here. */ + if (client_active) + notify_check (repository, update_dir); +#endif /* CLIENT_SUPPORT */ + + finfo_struct.repository = repository; + finfo_struct.update_dir = update_dir; + finfo_struct.entries = entries; + /* do_file_proc will fill in finfo_struct.file. */ + + /* process the files */ + err += walklist (filelist, do_file_proc, &finfo_struct); + + /* unlock it */ + if (readlock) + Lock_Cleanup (); + + /* clean up */ + dellist (&filelist); + } + + if (entries) + { + Entries_Close (entries); + entries = NULL; + } + + /* call-back files done proc (if any) */ + if (dodoneproc && filesdoneproc != NULL) + err = filesdoneproc (err, repository, update_dir[0] ? update_dir : "."); + + fileattr_write (); + fileattr_free (); + + /* process the directories (if necessary) */ + if (dirlist != NULL) + err += walklist (dirlist, do_dir_proc, NULL); +#ifdef notdef + else if (dirleaveproc != NULL) + err += dirleaveproc(".", err, "."); +#endif + dellist (&dirlist); + + /* free the saved copy of the pointer if necessary */ + if (srepository) + { + free (srepository); + repository = (char *) NULL; + } + + return (err); +} + +/* + * Process each of the files in the list with the callback proc + */ +static int +do_file_proc (p, closure) + Node *p; + void *closure; +{ + struct file_info *finfo = (struct file_info *)closure; + int ret; + + finfo->file = p->key; + finfo->fullname = xmalloc (strlen (finfo->file) + + strlen (finfo->update_dir) + + 2); + finfo->fullname[0] = '\0'; + if (finfo->update_dir[0] != '\0') + { + strcat (finfo->fullname, finfo->update_dir); + strcat (finfo->fullname, "/"); + } + strcat (finfo->fullname, finfo->file); + + if (dosrcs && repository) + finfo->rcs = RCS_parse (finfo->file, repository); + else + finfo->rcs = (RCSNode *) NULL; + ret = fileproc (finfo); + + freercsnode(&finfo->rcs); + free (finfo->fullname); + + return (ret); +} + +/* + * Process each of the directories in the list (recursing as we go) + */ +static int +do_dir_proc (p, closure) + Node *p; + void *closure; +{ + char *dir = p->key; + char newrepos[PATH_MAX]; + List *sdirlist; + char *srepository; + char *cp; + Dtype dir_return = R_PROCESS; + int stripped_dot = 0; + int err = 0; + struct saved_cwd cwd; + + /* set up update_dir - skip dots if not at start */ + if (strcmp (dir, ".") != 0) + { + if (update_dir[0] != '\0') + { + (void) strcat (update_dir, "/"); + (void) strcat (update_dir, dir); + } + else + (void) strcpy (update_dir, dir); + + /* + * Here we need a plausible repository name for the sub-directory. We + * create one by concatenating the new directory name onto the + * previous repository name. The only case where the name should be + * used is in the case where we are creating a new sub-directory for + * update -d and in that case the generated name will be correct. + */ + if (repository == NULL) + newrepos[0] = '\0'; + else + (void) sprintf (newrepos, "%s/%s", repository, dir); + } + else + { + if (update_dir[0] == '\0') + (void) strcpy (update_dir, dir); + + if (repository == NULL) + newrepos[0] = '\0'; + else + (void) strcpy (newrepos, repository); + } + + /* call-back dir entry proc (if any) */ + if (direntproc != NULL) + dir_return = direntproc (dir, newrepos, update_dir); + + /* only process the dir if the return code was 0 */ + if (dir_return != R_SKIP_ALL) + { + /* save our current directory and static vars */ + if (save_cwd (&cwd)) + exit (EXIT_FAILURE); + sdirlist = dirlist; + srepository = repository; + dirlist = NULL; + + /* cd to the sub-directory */ + if (chdir (dir) < 0) + error (1, errno, "could not chdir to %s", dir); + + /* honor the global SKIP_DIRS (a.k.a. local) */ + if (flags == R_SKIP_DIRS) + dir_return = R_SKIP_DIRS; + + /* remember if the `.' will be stripped for subsequent dirs */ + if (strcmp (update_dir, ".") == 0) + { + update_dir[0] = '\0'; + stripped_dot = 1; + } + + /* make the recursive call */ + err += do_recursion (fileproc, filesdoneproc, direntproc, dirleaveproc, + dir_return, which, aflag, readlock, dosrcs); + + /* put the `.' back if necessary */ + if (stripped_dot) + (void) strcpy (update_dir, "."); + + /* call-back dir leave proc (if any) */ + if (dirleaveproc != NULL) + err = dirleaveproc (dir, err, update_dir); + + /* get back to where we started and restore state vars */ + if (restore_cwd (&cwd, NULL)) + exit (EXIT_FAILURE); + free_cwd (&cwd); + dirlist = sdirlist; + repository = srepository; + } + + /* put back update_dir */ + cp = last_component (update_dir); + if (cp > update_dir) + cp[-1] = '\0'; + else + update_dir[0] = '\0'; + + return (err); +} + +/* + * Add a node to a list allocating the list if necessary. + */ +static void +addlist (listp, key) + List **listp; + char *key; +{ + Node *p; + + if (*listp == NULL) + *listp = getlist (); + p = getnode (); + p->type = FILES; + p->key = xstrdup (key); + if (addnode (*listp, p) != 0) + freenode (p); +} + +static void +addfile (listp, dir, file) + List **listp; + char *dir; + char *file; +{ + Node *n; + + /* add this dir. */ + addlist (listp, dir); + + n = findnode (*listp, dir); + if (n == NULL) + { + error (1, 0, "can't find recently added dir node `%s' in start_recursion.", + dir); + } + + n->type = DIRS; + addlist ((List **) &n->data, file); + return; +} + +static int +unroll_files_proc (p, closure) + Node *p; + void *closure; +{ + Node *n; + struct recursion_frame *frame = (struct recursion_frame *) closure; + int err = 0; + List *save_dirlist; + char *save_update_dir = NULL; + struct saved_cwd cwd; + + /* if this dir was also an explicitly named argument, then skip + it. We'll catch it later when we do dirs. */ + n = findnode (dirlist, p->key); + if (n != NULL) + return (0); + + /* otherwise, call dorecusion for this list of files. */ + filelist = (List *) p->data; + save_dirlist = dirlist; + dirlist = NULL; + + if (strcmp(p->key, ".") != 0) + { + if (save_cwd (&cwd)) + exit (EXIT_FAILURE); + if (chdir (p->key) < 0) + error (1, errno, "could not chdir to %s", p->key); + + save_update_dir = xstrdup (update_dir); + + if (*update_dir != '\0') + (void) strcat (update_dir, "/"); + + (void) strcat (update_dir, p->key); + } + + err += do_recursion (frame->fileproc, frame->filesdoneproc, + frame->direntproc, frame->dirleaveproc, + frame->flags, frame->which, frame->aflag, + frame->readlock, frame->dosrcs); + + if (save_update_dir != NULL) + { + (void) strcpy (update_dir, save_update_dir); + free (save_update_dir); + + if (restore_cwd (&cwd, NULL)) + exit (EXIT_FAILURE); + free_cwd (&cwd); + } + + dirlist = save_dirlist; + filelist = NULL; + return(err); +} diff --git a/contrib/cvs/src/release.c b/contrib/cvs/src/release.c new file mode 100644 index 0000000..b3ebb2b --- /dev/null +++ b/contrib/cvs/src/release.c @@ -0,0 +1,286 @@ +/* + * Release: "cancel" a checkout in the history log. + * + * - Don't allow release if anything is active - Don't allow release if not + * above or inside repository. - Don't allow release if ./CVS/Repository is + * not the same as the directory specified in the module database. + * + * - Enter a line in the history log indicating the "release". - If asked to, + * delete the local working directory. + */ + +#include "cvs.h" + +static void release_delete PROTO((char *dir)); + +static const char *const release_usage[] = +{ + "Usage: %s %s [-d] modules...\n", + "\t-d\tDelete the given directory.\n", + NULL +}; + +static short delete_flag; + +/* FIXME: This implementation is cheezy in quite a few ways: + + 1. The whole "cvs update" junk could be checked locally with a + fairly simple start_recursion/classify_file loop--a win for + portability, performance, and cleanliness. + + 2. Should be like edit/unedit in terms of working well if disconnected + from the network, and then sending a delayed notification. + + 3. Way too many network turnarounds. More than one for each argument. + Puh-leeze. + + 4. Oh, and as a purely stylistic nit, break this out into separate + functions for client/local and for server. Those #ifdefs are a mess. */ + +int +release (argc, argv) + int argc; + char **argv; +{ + FILE *fp; + register int i, c; + char *repository, *srepos; + char line[PATH_MAX], update_cmd[PATH_MAX]; + char *thisarg; + int arg_start_idx; + int err = 0; + +#ifdef SERVER_SUPPORT + if (!server_active) + { +#endif /* SERVER_SUPPORT */ + if (argc == -1) + usage (release_usage); + optind = 1; + while ((c = getopt (argc, argv, "Qdq")) != -1) + { + switch (c) + { + case 'Q': + case 'q': +#ifdef SERVER_SUPPORT + /* The CVS 1.5 client sends these options (in addition to + Global_option requests), so we must ignore them. */ + if (!server_active) +#endif + error (1, 0, + "-q or -Q must be specified before \"%s\"", + command_name); + break; + case 'd': + delete_flag++; + break; + case '?': + default: + usage (release_usage); + break; + } + } + argc -= optind; + argv += optind; +#ifdef SERVER_SUPPORT + } +#endif /* SERVER_SUPPORT */ + + /* We're going to run "cvs -n -q update" and check its output; if + * the output is sufficiently unalarming, then we release with no + * questions asked. Else we prompt, then maybe release. + */ + /* Construct the update command. */ + sprintf (update_cmd, "%s -n -q -d %s update", + program_path, CVSroot); + +#ifdef CLIENT_SUPPORT + /* Start the server; we'll close it after looping. */ + if (client_active) + { + start_server (); + ign_setup (); + } +#endif /* CLIENT_SUPPORT */ + + /* If !server_active, we already skipped over argv[0] in the "argc + -= optind;" statement above. But if server_active, we need to + skip it now. */ +#ifdef SERVER_SUPPORT + if (server_active) + arg_start_idx = 1; + else +#endif /* SERVER_SUPPORT */ + arg_start_idx = 0; + + for (i = arg_start_idx; i < argc; i++) + { + thisarg = argv[i]; + +#ifdef SERVER_SUPPORT + if (server_active) + { + /* Just log the release -- all the interesting stuff happened + * on the client. + */ + history_write ('F', thisarg, "", thisarg, ""); /* F == Free */ + } + else + { +#endif /* SERVER_SUPPORT */ + + /* + * If we are in a repository, do it. Else if we are in the parent of + * a directory with the same name as the module, "cd" into it and + * look for a repository there. + */ + if (isdir (thisarg)) + { + if (chdir (thisarg) < 0) + { + if (!really_quiet) + error (0, 0, "can't chdir to: %s", thisarg); + continue; + } + if (!isdir (CVSADM)) + { + if (!really_quiet) + error (0, 0, "no repository module: %s", thisarg); + continue; + } + } + else + { + if (!really_quiet) + error (0, 0, "no such directory: %s", thisarg); + continue; + } + + repository = Name_Repository ((char *) NULL, (char *) NULL); + srepos = Short_Repository (repository); + + if (!really_quiet) + { + /* The "release" command piggybacks on "update", which + * does the real work of finding out if anything is not + * up-to-date with the repository. Then "release" prompts + * the user, telling her how many files have been + * modified, and asking if she still wants to do the + * release. + */ + fp = run_popen (update_cmd, "r"); + c = 0; + + while (fgets (line, sizeof (line), fp)) + { + if (strchr ("MARCZ", *line)) + c++; + (void) printf (line); + } + + /* If the update exited with an error, then we just want to + * complain and go on to the next arg. Especially, we do + * not want to delete the local copy, since it's obviously + * not what the user thinks it is. + */ + if ((pclose (fp)) != 0) + { + error (0, 0, "unable to release `%s'", thisarg); + continue; + } + + (void) printf ("You have [%d] altered files in this repository.\n", + c); + (void) printf ("Are you sure you want to release %smodule `%s': ", + delete_flag ? "(and delete) " : "", thisarg); + c = !yesno (); + if (c) /* "No" */ + { + (void) fprintf (stderr, "** `%s' aborted by user choice.\n", + command_name); + free (repository); + continue; + } + } + + if (1 +#ifdef SERVER_SUPPORT + && !server_active +#endif +#ifdef CLIENT_SUPPORT + && !(client_active + && (!supported_request ("noop") + || !supported_request ("Notify"))) +#endif + ) + { + /* We are chdir'ed into the directory in question. + So don't pass args to unedit. */ + int argc = 1; + char *argv[3]; + argv[0] = "dummy"; + argv[1] = NULL; + err += unedit (argc, argv); + } + +#ifdef CLIENT_SUPPORT + if (client_active) + { + send_to_server ("Argument ", 0); + send_to_server (thisarg, 0); + send_to_server ("\012", 1); + send_to_server ("release\012", 0); + } + else + { +#endif /* CLIENT_SUPPORT */ + history_write ('F', thisarg, "", thisarg, ""); /* F == Free */ +#ifdef CLIENT_SUPPORT + } /* else client not active */ +#endif /* CLIENT_SUPPORT */ + + free (repository); + if (delete_flag) release_delete (thisarg); + +#ifdef CLIENT_SUPPORT + if (client_active) + return get_responses_and_close (); + else +#endif /* CLIENT_SUPPORT */ + return (0); + +#ifdef SERVER_SUPPORT + } /* else server not active */ +#endif /* SERVER_SUPPORT */ + } /* `for' loop */ + return err; +} + + +/* We want to "rm -r" the working directory, but let us be a little + paranoid. */ +static void +release_delete (dir) + char *dir; +{ + struct stat st; + ino_t ino; + + (void) stat (".", &st); + ino = st.st_ino; + (void) chdir (".."); + (void) stat (dir, &st); + if (ino != st.st_ino) + { + error (0, 0, + "Parent dir on a different disk, delete of %s aborted", dir); + return; + } + /* + * XXX - shouldn't this just delete the CVS-controlled files and, perhaps, + * the files that would normally be ignored and leave everything else? + */ + if (unlink_file_dir (dir) < 0) + error (0, errno, "deletion of directory %s failed", dir); +} diff --git a/contrib/cvs/src/remove.c b/contrib/cvs/src/remove.c new file mode 100644 index 0000000..2911bf4 --- /dev/null +++ b/contrib/cvs/src/remove.c @@ -0,0 +1,200 @@ +/* + * Copyright (c) 1992, Brian Berliner and Jeff Polk + * Copyright (c) 1989-1992, Brian Berliner + * + * You may distribute under the terms of the GNU General Public License as + * specified in the README file that comes with the CVS 1.4 kit. + * + * Remove a File + * + * Removes entries from the present version. The entries will be removed from + * the RCS repository upon the next "commit". + * + * "remove" accepts no options, only file names that are to be removed. The + * file must not exist in the current directory for "remove" to work + * correctly. + */ + +#include "cvs.h" + +static int remove_fileproc PROTO((struct file_info *finfo)); +static Dtype remove_dirproc PROTO((char *dir, char *repos, char *update_dir)); + +static int force; +static int local; +static int removed_files; +static int existing_files; + +static const char *const remove_usage[] = +{ + "Usage: %s %s [-flR] [files...]\n", + "\t-f\tDelete the file before removing it.\n", + "\t-l\tProcess this directory only (not recursive).\n", + "\t-R\tProcess directories recursively.\n", + NULL +}; + +int +cvsremove (argc, argv) + int argc; + char **argv; +{ + int c, err; + + if (argc == -1) + usage (remove_usage); + + optind = 1; + while ((c = getopt (argc, argv, "flR")) != -1) + { + switch (c) + { + case 'f': + force = 1; + break; + case 'l': + local = 1; + break; + case 'R': + local = 0; + break; + case '?': + default: + usage (remove_usage); + break; + } + } + argc -= optind; + argv += optind; + + wrap_setup (); + +#ifdef CLIENT_SUPPORT + if (client_active) { + start_server (); + ign_setup (); + if (local) + send_arg("-l"); + send_file_names (argc, argv, SEND_EXPAND_WILD); + send_files (argc, argv, local, 0); + send_to_server ("remove\012", 0); + return get_responses_and_close (); + } +#endif + + /* start the recursion processor */ + err = start_recursion (remove_fileproc, (FILESDONEPROC) NULL, + remove_dirproc, (DIRLEAVEPROC) NULL, argc, argv, + local, W_LOCAL, 0, 1, (char *) NULL, 1, 0); + + if (removed_files) + error (0, 0, "use '%s commit' to remove %s permanently", program_name, + (removed_files == 1) ? "this file" : "these files"); + + if (existing_files) + error (0, 0, + ((existing_files == 1) ? + "%d file exists; remove it first" : + "%d files exist; remove them first"), + existing_files); + + return (err); +} + +/* + * remove the file, only if it has already been physically removed + */ +/* ARGSUSED */ +static int +remove_fileproc (finfo) + struct file_info *finfo; +{ + char fname[PATH_MAX]; + Vers_TS *vers; + + if (force) + { + if (!noexec) + { + if (unlink (finfo->file) < 0 && ! existence_error (errno)) + { + error (0, errno, "unable to remove %s", finfo->fullname); + } + } + /* else FIXME should probably act as if the file doesn't exist + in doing the following checks. */ + } + + vers = Version_TS (finfo->repository, (char *) NULL, (char *) NULL, (char *) NULL, + finfo->file, 0, 0, finfo->entries, finfo->rcs); + + if (vers->ts_user != NULL) + { + existing_files++; + if (!quiet) + error (0, 0, "file `%s' still in working directory", + finfo->fullname); + } + else if (vers->vn_user == NULL) + { + if (!quiet) + error (0, 0, "nothing known about `%s'", finfo->fullname); + } + else if (vers->vn_user[0] == '0' && vers->vn_user[1] == '\0') + { + /* + * It's a file that has been added, but not commited yet. So, + * remove the ,t file for it and scratch it from the + * entries file. */ + Scratch_Entry (finfo->entries, finfo->file); + (void) sprintf (fname, "%s/%s%s", CVSADM, finfo->file, CVSEXT_LOG); + (void) unlink_file (fname); + if (!quiet) + error (0, 0, "removed `%s'", finfo->fullname); + +#ifdef SERVER_SUPPORT + if (server_active) + server_checked_in (finfo->file, finfo->update_dir, finfo->repository); +#endif + } + else if (vers->vn_user[0] == '-') + { + if (!quiet) + error (0, 0, "file `%s' already scheduled for removal", + finfo->fullname); + } + else + { + /* Re-register it with a negative version number. */ + (void) strcpy (fname, "-"); + (void) strcat (fname, vers->vn_user); + Register (finfo->entries, finfo->file, fname, vers->ts_rcs, vers->options, + vers->tag, vers->date, vers->ts_conflict); + if (!quiet) + error (0, 0, "scheduling `%s' for removal", finfo->fullname); + removed_files++; + +#ifdef SERVER_SUPPORT + if (server_active) + server_checked_in (finfo->file, finfo->update_dir, finfo->repository); +#endif + } + + freevers_ts (&vers); + return (0); +} + +/* + * Print a warm fuzzy message + */ +/* ARGSUSED */ +static Dtype +remove_dirproc (dir, repos, update_dir) + char *dir; + char *repos; + char *update_dir; +{ + if (!quiet) + error (0, 0, "Removing %s", update_dir); + return (R_PROCESS); +} diff --git a/contrib/cvs/src/repos.c b/contrib/cvs/src/repos.c new file mode 100644 index 0000000..7beaaba --- /dev/null +++ b/contrib/cvs/src/repos.c @@ -0,0 +1,147 @@ +/* + * Copyright (c) 1992, Brian Berliner and Jeff Polk + * Copyright (c) 1989-1992, Brian Berliner + * + * You may distribute under the terms of the GNU General Public License as + * specified in the README file that comes with the CVS 1.4 kit. + */ + +#include "cvs.h" + +/* Determine the name of the RCS repository for directory DIR in the + current working directory, or for the current working directory + itself if DIR is NULL. Returns the name in a newly-malloc'd + string. On error, gives a fatal error and does not return. + UPDATE_DIR is the path from where cvs was invoked (for use in error + messages), and should contain DIR as its last component. + UPDATE_DIR can be NULL to signify the directory in which cvs was + invoked. */ + +char * +Name_Repository (dir, update_dir) + char *dir; + char *update_dir; +{ + FILE *fpin; + char *ret, *xupdate_dir; + char repos[PATH_MAX]; + char path[PATH_MAX]; + char tmp[PATH_MAX]; + char cvsadm[PATH_MAX]; + char *cp; + + if (update_dir && *update_dir) + xupdate_dir = update_dir; + else + xupdate_dir = "."; + + if (dir != NULL) + (void) sprintf (cvsadm, "%s/%s", dir, CVSADM); + else + (void) strcpy (cvsadm, CVSADM); + + /* sanity checks */ + if (!isdir (cvsadm)) + { + error (0, 0, "in directory %s:", xupdate_dir); + error (1, 0, "there is no version here; do '%s checkout' first", + program_name); + } + + if (dir != NULL) + (void) sprintf (tmp, "%s/%s", dir, CVSADM_ENT); + else + (void) strcpy (tmp, CVSADM_ENT); + + if (!isreadable (tmp)) + { + error (0, 0, "in directory %s:", xupdate_dir); + error (1, 0, "*PANIC* administration files missing"); + } + + if (dir != NULL) + (void) sprintf (tmp, "%s/%s", dir, CVSADM_REP); + else + (void) strcpy (tmp, CVSADM_REP); + + if (!isreadable (tmp)) + { + error (0, 0, "in directory %s:", xupdate_dir); + error (1, 0, "*PANIC* administration files missing"); + } + + /* + * The assumption here is that the repository is always contained in the + * first line of the "Repository" file. + */ + fpin = open_file (tmp, "r"); + + if (fgets (repos, PATH_MAX, fpin) == NULL) + { + error (0, 0, "in directory %s:", xupdate_dir); + error (1, errno, "cannot read %s", CVSADM_REP); + } + (void) fclose (fpin); + if ((cp = strrchr (repos, '\n')) != NULL) + *cp = '\0'; /* strip the newline */ + + /* + * If this is a relative repository pathname, turn it into an absolute + * one by tacking on the CVSROOT environment variable. If the CVSROOT + * environment variable is not set, die now. + */ + if (strcmp (repos, "..") == 0 || strncmp (repos, "../", 3) == 0) + { + error (0, 0, "in directory %s:", xupdate_dir); + error (0, 0, "`..'-relative repositories are not supported."); + error (1, 0, "illegal source repository"); + } + if (! isabsolute(repos)) + { + if (CVSroot == NULL) + { + error (0, 0, "in directory %s:", xupdate_dir); + error (0, 0, "must set the CVSROOT environment variable\n"); + error (0, 0, "or specify the '-d' option to %s.", program_name); + error (1, 0, "illegal repository setting"); + } + (void) strcpy (path, repos); + (void) sprintf (repos, "%s/%s", CVSroot, path); + } +#ifdef CLIENT_SUPPORT + if (!client_active && !isdir (repos)) +#else + if (!isdir (repos)) +#endif + { + error (0, 0, "in directory %s:", xupdate_dir); + error (1, 0, "there is no repository %s", repos); + } + + /* allocate space to return and fill it in */ + strip_path (repos); + ret = xstrdup (repos); + return (ret); +} + +/* + * Return a pointer to the repository name relative to CVSROOT from a + * possibly fully qualified repository + */ +char * +Short_Repository (repository) + char *repository; +{ + if (repository == NULL) + return (NULL); + + /* If repository matches CVSroot at the beginning, strip off CVSroot */ + /* And skip leading '/' in rep, in case CVSroot ended with '/'. */ + if (strncmp (CVSroot, repository, strlen (CVSroot)) == 0) + { + char *rep = repository + strlen (CVSroot); + return (*rep == '/') ? rep+1 : rep; + } + else + return (repository); +} diff --git a/contrib/cvs/src/root.c b/contrib/cvs/src/root.c new file mode 100644 index 0000000..0742cf0 --- /dev/null +++ b/contrib/cvs/src/root.c @@ -0,0 +1,177 @@ +/* + * Copyright (c) 1992, Mark D. Baushke + * + * You may distribute under the terms of the GNU General Public License as + * specified in the README file that comes with the CVS 1.4 kit. + * + * Name of Root + * + * Determine the path to the CVSROOT and set "Root" accordingly. + * If this looks like of modified clone of Name_Repository() in + * repos.c, it is... + */ + +#include "cvs.h" + +char * +Name_Root(dir, update_dir) + char *dir; + char *update_dir; +{ + FILE *fpin; + char *ret, *xupdate_dir; + char root[PATH_MAX]; + char tmp[PATH_MAX]; + char cvsadm[PATH_MAX]; + char *cp; + + if (update_dir && *update_dir) + xupdate_dir = update_dir; + else + xupdate_dir = "."; + + if (dir != NULL) + { + (void) sprintf (cvsadm, "%s/%s", dir, CVSADM); + (void) sprintf (tmp, "%s/%s", dir, CVSADM_ROOT); + } + else + { + (void) strcpy (cvsadm, CVSADM); + (void) strcpy (tmp, CVSADM_ROOT); + } + + /* + * Do not bother looking for a readable file if there is no cvsadm + * directory present. + * + * It is possible that not all repositories will have a CVS/Root + * file. This is ok, but the user will need to specify -d + * /path/name or have the environment variable CVSROOT set in + * order to continue. + */ + if ((!isdir (cvsadm)) || (!isreadable (tmp))) + { + if (CVSroot == NULL) + { + error (0, 0, "in directory %s:", xupdate_dir); + error (0, 0, "must set the CVSROOT environment variable"); + error (0, 0, "or specify the '-d' option to %s.", program_name); + } + return (NULL); + } + + /* + * The assumption here is that the CVS Root is always contained in the + * first line of the "Root" file. + */ + fpin = open_file (tmp, "r"); + + if (fgets (root, PATH_MAX, fpin) == NULL) + { + error (0, 0, "in directory %s:", xupdate_dir); + error (0, errno, "cannot read %s", CVSADM_ROOT); + error (0, 0, "please correct this problem"); + return (NULL); + } + (void) fclose (fpin); + if ((cp = strrchr (root, '\n')) != NULL) + *cp = '\0'; /* strip the newline */ + + /* + * root now contains a candidate for CVSroot. It must be an + * absolute pathname + */ + +#ifdef CLIENT_SUPPORT + /* It must specify a server via remote CVS or be an absolute pathname. */ + if ((strchr (root, ':') == NULL) + && ! isabsolute (root)) +#else /* ! CLIENT_SUPPORT */ + if (root[0] != '/') +#endif /* CLIENT_SUPPORT */ + { + error (0, 0, "in directory %s:", xupdate_dir); + error (0, 0, + "ignoring %s because it does not contain an absolute pathname.", + CVSADM_ROOT); + return (NULL); + } + +#ifdef CLIENT_SUPPORT + if ((strchr (root, ':') == NULL) && !isdir (root)) +#else /* ! CLIENT_SUPPORT */ + if (!isdir (root)) +#endif /* CLIENT_SUPPORT */ + { + error (0, 0, "in directory %s:", xupdate_dir); + error (0, 0, + "ignoring %s because it specifies a non-existent repository %s", + CVSADM_ROOT, root); + return (NULL); + } + + /* allocate space to return and fill it in */ + strip_path (root); + ret = xstrdup (root); + return (ret); +} + +/* + * Returns non-zero if the two directories have the same stat values + * which indicates that they are really the same directories. + */ +int +same_directories (dir1, dir2) + char *dir1; + char *dir2; +{ + struct stat sb1; + struct stat sb2; + int ret; + + if (stat (dir1, &sb1) < 0) + return (0); + if (stat (dir2, &sb2) < 0) + return (0); + + ret = 0; + if ( (memcmp( &sb1.st_dev, &sb2.st_dev, sizeof(dev_t) ) == 0) && + (memcmp( &sb1.st_ino, &sb2.st_ino, sizeof(ino_t) ) == 0)) + ret = 1; + + return (ret); +} + + +/* + * Write the CVS/Root file so that the environment variable CVSROOT + * and/or the -d option to cvs will be validated or not necessary for + * future work. + */ +void +Create_Root (dir, rootdir) + char *dir; + char *rootdir; +{ + FILE *fout; + char tmp[PATH_MAX]; + + if (noexec) + return; + + /* record the current cvs root */ + + if (rootdir != NULL) + { + if (dir != NULL) + (void) sprintf (tmp, "%s/%s", dir, CVSADM_ROOT); + else + (void) strcpy (tmp, CVSADM_ROOT); + fout = open_file (tmp, "w+"); + if (fprintf (fout, "%s\n", rootdir) < 0) + error (1, errno, "write to %s failed", tmp); + if (fclose (fout) == EOF) + error (1, errno, "cannot close %s", tmp); + } +} diff --git a/contrib/cvs/src/rtag.c b/contrib/cvs/src/rtag.c new file mode 100644 index 0000000..8609647 --- /dev/null +++ b/contrib/cvs/src/rtag.c @@ -0,0 +1,682 @@ +/* + * Copyright (c) 1992, Brian Berliner and Jeff Polk + * Copyright (c) 1989-1992, Brian Berliner + * + * You may distribute under the terms of the GNU General Public License as + * specified in the README file that comes with the CVS 1.4 kit. + * + * Rtag + * + * Add or delete a symbolic name to an RCS file, or a collection of RCS files. + * Uses the modules database, if necessary. + */ + +#include "cvs.h" + +static int check_fileproc PROTO((struct file_info *finfo)); +static int check_filesdoneproc PROTO((int err, char *repos, char *update_dir)); +static int pretag_proc PROTO((char *repository, char *filter)); +static void masterlist_delproc PROTO((Node *p)); +static void tag_delproc PROTO((Node *p)); +static int pretag_list_proc PROTO((Node *p, void *closure)); + +static Dtype rtag_dirproc PROTO((char *dir, char *repos, char *update_dir)); +static int rtag_fileproc PROTO((struct file_info *finfo)); +static int rtag_proc PROTO((int *pargc, char **argv, char *xwhere, + char *mwhere, char *mfile, int shorten, + int local_specified, char *mname, char *msg)); +static int rtag_delete PROTO((RCSNode *rcsfile)); + + +struct tag_info +{ + Ctype status; + char *rev; + char *tag; + char *options; +}; + +struct master_lists +{ + List *tlist; +}; + +static List *mtlist; +static List *tlist; + +static char *symtag; +static char *numtag; +static int numtag_validated = 0; +static int delete_flag; /* adding a tag by default */ +static int attic_too; /* remove tag from Attic files */ +static int branch_mode; /* make an automagic "branch" tag */ +static char *date; +static int local; /* recursive by default */ +static int force_tag_match = 1; /* force by default */ +static int force_tag_move; /* don't move existing tags by default */ + +static const char *const rtag_usage[] = +{ + "Usage: %s %s [-aflRnF] [-b] [-d] [-r tag|-D date] tag modules...\n", + "\t-a\tClear tag from removed files that would not otherwise be tagged.\n", + "\t-f\tForce a head revision match if tag/date not found.\n", + "\t-l\tLocal directory only, not recursive\n", + "\t-R\tProcess directories recursively.\n", + "\t-n\tNo execution of 'tag program'\n", + "\t-d\tDelete the given Tag.\n", + "\t-b\tMake the tag a \"branch\" tag, allowing concurrent development.\n", + "\t-[rD]\tExisting tag or Date.\n", + "\t-F\tMove tag if it already exists\n", + NULL +}; + +int +rtag (argc, argv) + int argc; + char **argv; +{ + register int i; + int c; + DBM *db; + int run_module_prog = 1; + int err = 0; + + if (argc == -1) + usage (rtag_usage); + + optind = 1; + while ((c = getopt (argc, argv, "FanfQqlRdbr:D:")) != -1) + { + switch (c) + { + case 'a': + attic_too = 1; + break; + case 'n': + run_module_prog = 0; + break; + case 'Q': + case 'q': +#ifdef SERVER_SUPPORT + /* The CVS 1.5 client sends these options (in addition to + Global_option requests), so we must ignore them. */ + if (!server_active) +#endif + error (1, 0, + "-q or -Q must be specified before \"%s\"", + command_name); + break; + case 'l': + local = 1; + break; + case 'R': + local = 0; + break; + case 'd': + delete_flag = 1; + break; + case 'f': + force_tag_match = 0; + break; + case 'b': + branch_mode = 1; + break; + case 'r': + numtag = optarg; + break; + case 'D': + if (date) + free (date); + date = Make_Date (optarg); + break; + case 'F': + force_tag_move = 1; + break; + case '?': + default: + usage (rtag_usage); + break; + } + } + argc -= optind; + argv += optind; + if (argc < 2) + usage (rtag_usage); + symtag = argv[0]; + argc--; + argv++; + + if (date && numtag) + error (1, 0, "-r and -D options are mutually exclusive"); + if (delete_flag && branch_mode) + error (0, 0, "warning: -b ignored with -d options"); + RCS_check_tag (symtag); + +#ifdef CLIENT_SUPPORT + if (client_active) + { + /* We're the client side. Fire up the remote server. */ + start_server (); + + ign_setup (); + + if (local) + send_arg("-l"); + if (delete_flag) + send_arg("-d"); + if (branch_mode) + send_arg("-b"); + if (force_tag_move) + send_arg("-F"); + if (run_module_prog) + send_arg("-n"); + if (attic_too) + send_arg("-a"); + + if (numtag) + option_with_arg ("-r", numtag); + if (date) + client_senddate (date); + + send_arg (symtag); + + { + int i; + for (i = 0; i < argc; ++i) + send_arg (argv[i]); + } + + send_to_server ("rtag\012", 0); + return get_responses_and_close (); + } +#endif + + db = open_module (); + for (i = 0; i < argc; i++) + { + /* XXX last arg should be repository, but doesn't make sense here */ + history_write ('T', (delete_flag ? "D" : (numtag ? numtag : + (date ? date : "A"))), symtag, argv[i], ""); + err += do_module (db, argv[i], TAG, delete_flag ? "Untagging" : "Tagging", + rtag_proc, (char *) NULL, 0, 0, run_module_prog, + symtag); + } + close_module (db); + return (err); +} + +/* + * callback proc for doing the real work of tagging + */ +/* ARGSUSED */ +static int +rtag_proc (pargc, argv, xwhere, mwhere, mfile, shorten, local_specified, + mname, msg) + int *pargc; + char **argv; + char *xwhere; + char *mwhere; + char *mfile; + int shorten; + int local_specified; + char *mname; + char *msg; +{ + int err = 0; + int which; + char repository[PATH_MAX]; + char where[PATH_MAX]; + + (void) sprintf (repository, "%s/%s", CVSroot, argv[0]); + (void) strcpy (where, argv[0]); + + /* if mfile isn't null, we need to set up to do only part of the module */ + if (mfile != NULL) + { + char *cp; + char path[PATH_MAX]; + + /* if the portion of the module is a path, put the dir part on repos */ + if ((cp = strrchr (mfile, '/')) != NULL) + { + *cp = '\0'; + (void) strcat (repository, "/"); + (void) strcat (repository, mfile); + (void) strcat (where, "/"); + (void) strcat (where, mfile); + mfile = cp + 1; + } + + /* take care of the rest */ + (void) sprintf (path, "%s/%s", repository, mfile); + if (isdir (path)) + { + /* directory means repository gets the dir tacked on */ + (void) strcpy (repository, path); + (void) strcat (where, "/"); + (void) strcat (where, mfile); + } + else + { + int i; + + /* a file means muck argv */ + for (i = 1; i < *pargc; i++) + free (argv[i]); + argv[1] = xstrdup (mfile); + (*pargc) = 2; + } + } + + /* chdir to the starting directory */ + if (chdir (repository) < 0) + { + error (0, errno, "cannot chdir to %s", repository); + return (1); + } + + if (delete_flag || attic_too || (force_tag_match && numtag)) + which = W_REPOS | W_ATTIC; + else + which = W_REPOS; + + if (numtag != NULL && !numtag_validated) + { + tag_check_valid (numtag, *pargc - 1, argv + 1, local, 0, NULL); + numtag_validated = 1; + } + + /* check to make sure they are authorized to tag all the + specified files in the repository */ + + mtlist = getlist(); + err = start_recursion (check_fileproc, check_filesdoneproc, + (DIRENTPROC) NULL, (DIRLEAVEPROC) NULL, + *pargc - 1, argv + 1, local, which, 0, 1, + where, 1, 1); + + if (err) + { + error (1, 0, "correct the above errors first!"); + } + + /* start the recursion processor */ + err = start_recursion (rtag_fileproc, (FILESDONEPROC) NULL, rtag_dirproc, + (DIRLEAVEPROC) NULL, *pargc - 1, argv + 1, local, + which, 0, 1, where, 1, 1); + + dellist(&mtlist); + + return (err); +} + +/* check file that is to be tagged */ +/* All we do here is add it to our list */ + +static int +check_fileproc (finfo) + struct file_info *finfo; +{ + char *xdir; + Node *p; + Vers_TS *vers; + + if (finfo->update_dir[0] == '\0') + xdir = "."; + else + xdir = finfo->update_dir; + if ((p = findnode (mtlist, xdir)) != NULL) + { + tlist = ((struct master_lists *) p->data)->tlist; + } + else + { + struct master_lists *ml; + + tlist = getlist (); + p = getnode (); + p->key = xstrdup (xdir); + p->type = UPDATE; + ml = (struct master_lists *) + xmalloc (sizeof (struct master_lists)); + ml->tlist = tlist; + p->data = (char *) ml; + p->delproc = masterlist_delproc; + (void) addnode (mtlist, p); + } + /* do tlist */ + p = getnode (); + p->key = xstrdup (finfo->file); + p->type = UPDATE; + p->delproc = tag_delproc; + vers = Version_TS (finfo->repository, (char *) NULL, (char *) NULL, + (char *) NULL, finfo->file, 0, 0, finfo->entries, finfo->rcs); + p->data = RCS_getversion(vers->srcfile, numtag, date, force_tag_match, 0); + if (p->data != NULL) + { + int addit = 1; + char *oversion; + + oversion = RCS_getversion (vers->srcfile, symtag, (char *) NULL, 1, 0); + if (oversion == NULL) + { + if (delete_flag) + { + addit = 0; + } + } + else if (strcmp(oversion, p->data) == 0) + { + addit = 0; + } + else if (!force_tag_move) + { + addit = 0; + } + if (oversion != NULL) + { + free(oversion); + } + if (!addit) + { + free(p->data); + p->data = NULL; + } + } + freevers_ts (&vers); + (void) addnode (tlist, p); + return (0); +} + +static int +check_filesdoneproc(err, repos, update_dir) + int err; + char *repos; + char *update_dir; +{ + int n; + Node *p; + + p = findnode(mtlist, update_dir); + if (p != NULL) + { + tlist = ((struct master_lists *) p->data)->tlist; + } + else + { + tlist = (List *) NULL; + } + if ((tlist == NULL) || (tlist->list->next == tlist->list)) + { + return (err); + } + if ((n = Parse_Info(CVSROOTADM_TAGINFO, repos, pretag_proc, 1)) > 0) + { + error (0, 0, "Pre-tag check failed"); + err += n; + } + return (err); +} + +static int +pretag_proc(repository, filter) + char *repository; + char *filter; +{ + if (filter[0] == '/') + { + char *s, *cp; + + s = xstrdup(filter); + for (cp=s; *cp; cp++) + { + if (isspace(*cp)) + { + *cp = '\0'; + break; + } + } + if (!isfile(s)) + { + error (0, errno, "cannot find pre-tag filter '%s'", s); + free(s); + return (1); + } + free(s); + } + run_setup("%s %s %s %s", + filter, + symtag, + delete_flag ? "del" : force_tag_move ? "mov" : "add", + repository); + walklist(tlist, pretag_list_proc, NULL); + return (run_exec(RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL|RUN_REALLY)); +} + +static void +masterlist_delproc(p) + Node *p; +{ + struct master_lists *ml; + + ml = (struct master_lists *)p->data; + dellist(&ml->tlist); + free(ml); + return; +} + +static void +tag_delproc(p) + Node *p; +{ + if (p->data != NULL) + { + free(p->data); + p->data = NULL; + } + return; +} + +static int +pretag_list_proc(p, closure) + Node *p; + void *closure; +{ + if (p->data != NULL) + { + run_arg(p->key); + run_arg(p->data); + } + return (0); +} + +/* + * Called to tag a particular file, as appropriate with the options that were + * set above. + */ +/* ARGSUSED */ +static int +rtag_fileproc (finfo) + struct file_info *finfo; +{ + RCSNode *rcsfile; + char *version, *rev; + int retcode = 0; + + /* find the parsed RCS data */ + if ((rcsfile = finfo->rcs) == NULL) + return (1); + + /* + * For tagging an RCS file which is a symbolic link, you'd best be + * running with RCS 5.6, since it knows how to handle symbolic links + * correctly without breaking your link! + */ + + if (delete_flag) + return (rtag_delete (rcsfile)); + + /* + * If we get here, we are adding a tag. But, if -a was specified, we + * need to check to see if a -r or -D option was specified. If neither + * was specified and the file is in the Attic, remove the tag. + */ + if (attic_too && (!numtag && !date)) + { + if ((rcsfile->flags & VALID) && (rcsfile->flags & INATTIC)) + return (rtag_delete (rcsfile)); + } + + version = RCS_getversion (rcsfile, numtag, date, force_tag_match, 0); + if (version == NULL) + { + /* If -a specified, clean up any old tags */ + if (attic_too) + (void) rtag_delete (rcsfile); + + if (!quiet && !force_tag_match) + { + error (0, 0, "cannot find tag `%s' in `%s'", + numtag ? numtag : "head", rcsfile->path); + return (1); + } + return (0); + } + if (numtag && isdigit (*numtag) && strcmp (numtag, version) != 0) + { + + /* + * We didn't find a match for the numeric tag that was specified, but + * that's OK. just pass the numeric tag on to rcs, to be tagged as + * specified. Could get here if one tried to tag "1.1.1" and there + * was a 1.1.1 branch with some head revision. In this case, we want + * the tag to reference "1.1.1" and not the revision at the head of + * the branch. Use a symbolic tag for that. + */ + rev = branch_mode ? RCS_magicrev (rcsfile, version) : numtag; + retcode = RCS_settag(rcsfile->path, symtag, numtag); + } + else + { + char *oversion; + + /* + * As an enhancement for the case where a tag is being re-applied to + * a large body of a module, make one extra call to RCS_getversion to + * see if the tag is already set in the RCS file. If so, check to + * see if it needs to be moved. If not, do nothing. This will + * likely save a lot of time when simply moving the tag to the + * "current" head revisions of a module -- which I have found to be a + * typical tagging operation. + */ + rev = branch_mode ? RCS_magicrev (rcsfile, version) : version; + oversion = RCS_getversion (rcsfile, symtag, (char *) NULL, 1, 0); + if (oversion != NULL) + { + int isbranch = RCS_isbranch (finfo->rcs, symtag); + + /* + * if versions the same and neither old or new are branches don't + * have to do anything + */ + if (strcmp (version, oversion) == 0 && !branch_mode && !isbranch) + { + free (oversion); + free (version); + return (0); + } + + if (!force_tag_move) + { + /* we're NOT going to move the tag */ + (void) printf ("W %s", finfo->fullname); + + (void) printf (" : %s already exists on %s %s", + symtag, isbranch ? "branch" : "version", + oversion); + (void) printf (" : NOT MOVING tag to %s %s\n", + branch_mode ? "branch" : "version", rev); + free (oversion); + free (version); + return (0); + } + free (oversion); + } + retcode = RCS_settag(rcsfile->path, symtag, rev); + } + + if (retcode != 0) + { + error (1, retcode == -1 ? errno : 0, + "failed to set tag `%s' to revision `%s' in `%s'", + symtag, rev, rcsfile->path); + if (branch_mode) + free (rev); + free (version); + return (1); + } + if (branch_mode) + free (rev); + free (version); + return (0); +} + +/* + * If -d is specified, "force_tag_match" is set, so that this call to + * RCS_getversion() will return a NULL version string if the symbolic + * tag does not exist in the RCS file. + * + * If the -r flag was used, numtag is set, and we only delete the + * symtag from files that have numtag. + * + * This is done here because it's MUCH faster than just blindly calling + * "rcs" to remove the tag... trust me. + */ +static int +rtag_delete (rcsfile) + RCSNode *rcsfile; +{ + char *version; + int retcode; + + if (numtag) + { + version = RCS_getversion (rcsfile, numtag, (char *) NULL, 1, 0); + if (version == NULL) + return (0); + free (version); + } + + version = RCS_getversion (rcsfile, symtag, (char *) NULL, 1, 0); + if (version == NULL) + return (0); + free (version); + + if ((retcode = RCS_deltag(rcsfile->path, symtag, 1)) != 0) + { + if (!quiet) + error (0, retcode == -1 ? errno : 0, + "failed to remove tag `%s' from `%s'", symtag, + rcsfile->path); + return (1); + } + return (0); +} + +/* + * Print a warm fuzzy message + */ +/* ARGSUSED */ +static Dtype +rtag_dirproc (dir, repos, update_dir) + char *dir; + char *repos; + char *update_dir; +{ + if (!quiet) + error (0, 0, "%s %s", delete_flag ? "Untagging" : "Tagging", update_dir); + return (R_PROCESS); +} + + + diff --git a/contrib/cvs/src/run.c b/contrib/cvs/src/run.c new file mode 100644 index 0000000..036821e --- /dev/null +++ b/contrib/cvs/src/run.c @@ -0,0 +1,541 @@ +/* run.c --- routines for executing subprocesses. + + This file is part of GNU CVS. + + GNU CVS 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. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. */ + +#include "cvs.h" + +#ifdef HAVE_VPRINTF +#if defined (USE_PROTOTYPES) ? USE_PROTOTYPES : defined (__STDC__) +#include <stdarg.h> +#define VA_START(args, lastarg) va_start(args, lastarg) +#else +#include <varargs.h> +#define VA_START(args, lastarg) va_start(args) +#endif +#else +#define va_alist a1, a2, a3, a4, a5, a6, a7, a8 +#define va_dcl char *a1, *a2, *a3, *a4, *a5, *a6, *a7, *a8; +#endif + +static void run_add_arg PROTO((const char *s)); +static void run_init_prog PROTO((void)); + +extern char *strtok (); + +/* + * To exec a program under CVS, first call run_setup() to setup any initial + * arguments. The options to run_setup are essentially like printf(). The + * arguments will be parsed into whitespace separated words and added to the + * global run_argv list. + * + * Then, optionally call run_arg() for each additional argument that you'd like + * to pass to the executed program. + * + * Finally, call run_exec() to execute the program with the specified arguments. + * The execvp() syscall will be used, so that the PATH is searched correctly. + * File redirections can be performed in the call to run_exec(). + */ +static char *run_prog; +static char **run_argv; +static int run_argc; +static int run_argc_allocated; + +/* VARARGS */ +#if defined (HAVE_VPRINTF) && (defined (USE_PROTOTYPES) ? USE_PROTOTYPES : defined (__STDC__)) +void +run_setup (const char *fmt,...) +#else +void +run_setup (fmt, va_alist) + char *fmt; + va_dcl +#endif +{ +#ifdef HAVE_VPRINTF + va_list args; +#endif + char *cp; + int i; + + run_init_prog (); + + /* clean out any malloc'ed values from run_argv */ + for (i = 0; i < run_argc; i++) + { + if (run_argv[i]) + { + free (run_argv[i]); + run_argv[i] = (char *) 0; + } + } + run_argc = 0; + + /* process the varargs into run_prog */ +#ifdef HAVE_VPRINTF + VA_START (args, fmt); + (void) vsprintf (run_prog, fmt, args); + va_end (args); +#else + (void) sprintf (run_prog, fmt, a1, a2, a3, a4, a5, a6, a7, a8); +#endif + + /* put each word into run_argv, allocating it as we go */ + for (cp = strtok (run_prog, " \t"); cp; cp = strtok ((char *) NULL, " \t")) + run_add_arg (cp); +} + +void +run_arg (s) + const char *s; +{ + run_add_arg (s); +} + +/* VARARGS */ +#if defined (HAVE_VPRINTF) && (defined (USE_PROTOTYPES) ? USE_PROTOTYPES : defined (__STDC__)) +void +run_args (const char *fmt,...) +#else +void +run_args (fmt, va_alist) + char *fmt; + va_dcl +#endif +{ +#ifdef HAVE_VPRINTF + va_list args; +#endif + + run_init_prog (); + + /* process the varargs into run_prog */ +#ifdef HAVE_VPRINTF + VA_START (args, fmt); + (void) vsprintf (run_prog, fmt, args); + va_end (args); +#else + (void) sprintf (run_prog, fmt, a1, a2, a3, a4, a5, a6, a7, a8); +#endif + + /* and add the (single) argument to the run_argv list */ + run_add_arg (run_prog); +} + +static void +run_add_arg (s) + const char *s; +{ + /* allocate more argv entries if we've run out */ + if (run_argc >= run_argc_allocated) + { + run_argc_allocated += 50; + run_argv = (char **) xrealloc ((char *) run_argv, + run_argc_allocated * sizeof (char **)); + } + + if (s) + run_argv[run_argc++] = xstrdup (s); + else + run_argv[run_argc] = (char *) 0; /* not post-incremented on purpose! */ +} + +static void +run_init_prog () +{ + /* make sure that run_prog is allocated once */ + if (run_prog == (char *) 0) + run_prog = xmalloc (10 * 1024); /* 10K of args for _setup and _arg */ +} + +int +run_exec (stin, stout, sterr, flags) + char *stin; + char *stout; + char *sterr; + int flags; +{ + int shin, shout, sherr; + int mode_out, mode_err; + int status; + int rc = -1; + int rerrno = 0; + int pid, w; + +#ifdef POSIX_SIGNALS + sigset_t sigset_mask, sigset_omask; + struct sigaction act, iact, qact; + +#else +#ifdef BSD_SIGNALS + int mask; + struct sigvec vec, ivec, qvec; + +#else + RETSIGTYPE (*istat) (), (*qstat) (); +#endif +#endif + + if (trace) + { +#ifdef SERVER_SUPPORT + (void) fprintf (stderr, "%c-> system(", (server_active) ? 'S' : ' '); +#else + (void) fprintf (stderr, "-> system("); +#endif + run_print (stderr); + (void) fprintf (stderr, ")\n"); + } + if (noexec && (flags & RUN_REALLY) == 0) + return (0); + + /* make sure that we are null terminated, since we didn't calloc */ + run_add_arg ((char *) 0); + + /* setup default file descriptor numbers */ + shin = 0; + shout = 1; + sherr = 2; + + /* set the file modes for stdout and stderr */ + mode_out = mode_err = O_WRONLY | O_CREAT; + mode_out |= ((flags & RUN_STDOUT_APPEND) ? O_APPEND : O_TRUNC); + mode_err |= ((flags & RUN_STDERR_APPEND) ? O_APPEND : O_TRUNC); + + if (stin && (shin = open (stin, O_RDONLY)) == -1) + { + rerrno = errno; + error (0, errno, "cannot open %s for reading (prog %s)", + stin, run_argv[0]); + goto out0; + } + if (stout && (shout = open (stout, mode_out, 0666)) == -1) + { + rerrno = errno; + error (0, errno, "cannot open %s for writing (prog %s)", + stout, run_argv[0]); + goto out1; + } + if (sterr && (flags & RUN_COMBINED) == 0) + { + if ((sherr = open (sterr, mode_err, 0666)) == -1) + { + rerrno = errno; + error (0, errno, "cannot open %s for writing (prog %s)", + sterr, run_argv[0]); + goto out2; + } + } + + /* Make sure we don't flush this twice, once in the subprocess. */ + fflush (stdout); + fflush (stderr); + + /* The output files, if any, are now created. Do the fork and dups */ +#ifdef HAVE_VFORK + pid = vfork (); +#else + pid = fork (); +#endif + if (pid == 0) + { + if (shin != 0) + { + (void) dup2 (shin, 0); + (void) close (shin); + } + if (shout != 1) + { + (void) dup2 (shout, 1); + (void) close (shout); + } + if (flags & RUN_COMBINED) + (void) dup2 (1, 2); + else if (sherr != 2) + { + (void) dup2 (sherr, 2); + (void) close (sherr); + } + + /* dup'ing is done. try to run it now */ + (void) execvp (run_argv[0], run_argv); + error (0, errno, "cannot exec %s", run_argv[0]); + _exit (127); + } + else if (pid == -1) + { + rerrno = errno; + goto out; + } + + /* the parent. Ignore some signals for now */ +#ifdef POSIX_SIGNALS + if (flags & RUN_SIGIGNORE) + { + act.sa_handler = SIG_IGN; + (void) sigemptyset (&act.sa_mask); + act.sa_flags = 0; + (void) sigaction (SIGINT, &act, &iact); + (void) sigaction (SIGQUIT, &act, &qact); + } + else + { + (void) sigemptyset (&sigset_mask); + (void) sigaddset (&sigset_mask, SIGINT); + (void) sigaddset (&sigset_mask, SIGQUIT); + (void) sigprocmask (SIG_SETMASK, &sigset_mask, &sigset_omask); + } +#else +#ifdef BSD_SIGNALS + if (flags & RUN_SIGIGNORE) + { + memset ((char *) &vec, 0, sizeof (vec)); + vec.sv_handler = SIG_IGN; + (void) sigvec (SIGINT, &vec, &ivec); + (void) sigvec (SIGQUIT, &vec, &qvec); + } + else + mask = sigblock (sigmask (SIGINT) | sigmask (SIGQUIT)); +#else + istat = signal (SIGINT, SIG_IGN); + qstat = signal (SIGQUIT, SIG_IGN); +#endif +#endif + + /* wait for our process to die and munge return status */ +#ifdef POSIX_SIGNALS + while ((w = waitpid (pid, &status, 0)) == -1 && errno == EINTR) + ; +#else + while ((w = wait (&status)) != pid) + { + if (w == -1 && errno != EINTR) + break; + } +#endif + + if (w == -1) + { + rc = -1; + rerrno = errno; + } +#ifndef VMS /* status is return status */ + else if (WIFEXITED (status)) + rc = WEXITSTATUS (status); + else if (WIFSIGNALED (status)) + { + if (WTERMSIG (status) == SIGPIPE) + error (1, 0, "broken pipe"); + rc = 2; + } + else + rc = 1; +#else /* VMS */ + rc = WEXITSTATUS (status); +#endif /* VMS */ + + /* restore the signals */ +#ifdef POSIX_SIGNALS + if (flags & RUN_SIGIGNORE) + { + (void) sigaction (SIGINT, &iact, (struct sigaction *) NULL); + (void) sigaction (SIGQUIT, &qact, (struct sigaction *) NULL); + } + else + (void) sigprocmask (SIG_SETMASK, &sigset_omask, (sigset_t *) NULL); +#else +#ifdef BSD_SIGNALS + if (flags & RUN_SIGIGNORE) + { + (void) sigvec (SIGINT, &ivec, (struct sigvec *) NULL); + (void) sigvec (SIGQUIT, &qvec, (struct sigvec *) NULL); + } + else + (void) sigsetmask (mask); +#else + (void) signal (SIGINT, istat); + (void) signal (SIGQUIT, qstat); +#endif +#endif + + /* cleanup the open file descriptors */ + out: + if (sterr) + (void) close (sherr); + out2: + if (stout) + (void) close (shout); + out1: + if (stin) + (void) close (shin); + + out0: + if (rerrno) + errno = rerrno; + return (rc); +} + +void +run_print (fp) + FILE *fp; +{ + int i; + + for (i = 0; i < run_argc; i++) + { + (void) fprintf (fp, "'%s'", run_argv[i]); + if (i != run_argc - 1) + (void) fprintf (fp, " "); + } +} + +FILE * +run_popen (cmd, mode) + const char *cmd; + const char *mode; +{ + if (trace) +#ifdef SERVER_SUPPORT + (void) fprintf (stderr, "%c-> run_popen(%s,%s)\n", + (server_active) ? 'S' : ' ', cmd, mode); +#else + (void) fprintf (stderr, "-> run_popen(%s,%s)\n", cmd, mode); +#endif + if (noexec) + return (NULL); + + return (popen (cmd, mode)); +} + +extern int evecvp PROTO((char *file, char **argv)); + +int +piped_child (command, tofdp, fromfdp) + char **command; + int *tofdp; + int *fromfdp; +{ + int pid; + int to_child_pipe[2]; + int from_child_pipe[2]; + + if (pipe (to_child_pipe) < 0) + error (1, errno, "cannot create pipe"); + if (pipe (from_child_pipe) < 0) + error (1, errno, "cannot create pipe"); + + pid = fork (); + if (pid < 0) + error (1, errno, "cannot fork"); + if (pid == 0) + { + if (dup2 (to_child_pipe[0], STDIN_FILENO) < 0) + error (1, errno, "cannot dup2"); + if (close (to_child_pipe[1]) < 0) + error (1, errno, "cannot close"); + if (close (from_child_pipe[0]) < 0) + error (1, errno, "cannot close"); + if (dup2 (from_child_pipe[1], STDOUT_FILENO) < 0) + error (1, errno, "cannot dup2"); + + execvp (command[0], command); + error (1, errno, "cannot exec"); + } + if (close (to_child_pipe[0]) < 0) + error (1, errno, "cannot close"); + if (close (from_child_pipe[1]) < 0) + error (1, errno, "cannot close"); + + *tofdp = to_child_pipe[1]; + *fromfdp = from_child_pipe[0]; + return pid; +} + + +void +close_on_exec (fd) + int fd; +{ +#if defined (FD_CLOEXEC) && defined (F_SETFD) + if (fcntl (fd, F_SETFD, 1)) + error (1, errno, "can't set close-on-exec flag on %d", fd); +#endif +} + +/* + * dir = 0 : main proc writes to new proc, which writes to oldfd + * dir = 1 : main proc reads from new proc, which reads from oldfd + * + * Returns: a file descriptor. On failure (i.e., the exec fails), + * then filter_stream_through_program() complains and dies. + */ + +int +filter_stream_through_program (oldfd, dir, prog, pidp) + int oldfd, dir; + char **prog; + pid_t *pidp; +{ + int p[2], newfd; + pid_t newpid; + + if (pipe (p)) + error (1, errno, "cannot create pipe"); + newpid = fork (); + if (pidp) + *pidp = newpid; + switch (newpid) + { + case -1: + error (1, errno, "cannot fork"); + case 0: + /* child */ + if (dir) + { + /* write to new pipe */ + close (p[0]); + dup2 (oldfd, 0); + dup2 (p[1], 1); + } + else + { + /* read from new pipe */ + close (p[1]); + dup2 (p[0], 0); + dup2 (oldfd, 1); + } + /* Should I be blocking some signals here? */ + execvp (prog[0], prog); + error (1, errno, "couldn't exec %s", prog[0]); + default: + /* parent */ + close (oldfd); + if (dir) + { + /* read from new pipe */ + close (p[1]); + newfd = p[0]; + } + else + { + /* write to new pipe */ + close (p[0]); + newfd = p[1]; + } + close_on_exec (newfd); + return newfd; + } +} diff --git a/contrib/cvs/src/sanity.sh b/contrib/cvs/src/sanity.sh new file mode 100755 index 0000000..4f80f90 --- /dev/null +++ b/contrib/cvs/src/sanity.sh @@ -0,0 +1,2869 @@ +#! /bin/sh +: +# sanity.sh -- a growing testsuite for cvs. +# +# Copyright (C) 1992, 1993 Cygnus Support +# +# Original Author: K. Richard Pixley + +# usage: sanity.sh [-r] @var{cvs-to-test} @var{tests-to-run} +# -r means to test remote instead of local cvs. +# @var{tests-to-run} are the names of the tests to run; if omitted run all +# tests. + +# See TODO list at end of file. + +# required to make this script work properly. +unset CVSREAD + +TESTDIR=/tmp/cvs-sanity + +# "debugger" +#set -x + +echo 'This test should produce no other output than this line, and a final "OK".' + +if test x"$1" = x"-r"; then + shift + remote=yes +else + remote=no +fi + +# The --keep option will eventually cause all the tests to leave around the +# contents of the /tmp directory; right now only some implement it. Not +# useful if you are running more than one test. +# FIXME: need some real option parsing so this doesn't depend on the order +# in which they are specified. +if test x"$1" = x"--keep"; then + shift + keep=yes +else + keep=no +fi + +# Use full path for CVS executable, so that CVS_SERVER gets set properly +# for remote. +case $1 in +/*) + testcvs=$1 + ;; +*) + testcvs=`pwd`/$1 + ;; +esac + +shift + +# Regexp to match what CVS will call itself in output that it prints. +# FIXME: we don't properly quote this--if the name contains . we'll +# just spuriously match a few things; if the name contains other regexp +# special characters we are probably in big trouble. +PROG=`basename ${testcvs}` + +# FIXME: try things (what things? checkins?) without -m. +# +# Some of these tests are written to expect -Q. But testing with +# -Q is kind of bogus, it is not the way users actually use CVS (usually). +# So new tests probably should invoke ${testcvs} directly, rather than ${CVS}. +# and then they've obviously got to do something with the output.... +# +CVS="${testcvs} -Q" + +LOGFILE=`pwd`/check.log + +# Save the previous log in case the person running the tests decides +# they want to look at it. The extension ".plog" is chosen for consistency +# with dejagnu. +if test -f check.log; then + mv check.log check.plog +fi + +# That we should have to do this is total bogosity, but GNU expr +# version 1.9.4 uses the emacs definition of "$" instead of the unix +# (e.g. SunOS 4.1.3 expr) one. Rumor has it this will be fixed in the +# next release of GNU expr after 1.12 (but we still have to cater to the old +# ones for some time because they are in many linux distributions). +ENDANCHOR="$" +if expr 'abc +def' : 'abc$' >/dev/null; then + ENDANCHOR='\'\' +fi + +# Work around another GNU expr (version 1.10) bug/incompatibility. +# "." doesn't appear to match a newline (it does with SunOS 4.1.3 expr). +# Note that the workaround is not a complete equivalent of .* because +# the first parenthesized expression in the regexp must match something +# in order for expr to return a successful exit status. +# Rumor has it this will be fixed in the +# next release of GNU expr after 1.12 (but we still have to cater to the old +# ones for some time because they are in many linux distributions). +DOTSTAR='.*' +if expr 'abc +def' : "a${DOTSTAR}f" >/dev/null; then + : good, it works +else + DOTSTAR='\(.\| +\)*' +fi + +# Work around yet another GNU expr (version 1.10) bug/incompatibility. +# "+" is a special character, yet for unix expr (e.g. SunOS 4.1.3) +# it is not. I doubt that POSIX allows us to use \+ and assume it means +# (non-special) +, so here is another workaround +# Rumor has it this will be fixed in the +# next release of GNU expr after 1.12 (but we still have to cater to the old +# ones for some time because they are in many linux distributions). +PLUS='+' +if expr 'a +b' : "a ${PLUS}b" >/dev/null; then + : good, it works +else + PLUS='\+' +fi + +# Likewise, for ? +QUESTION='?' +if expr 'a?b' : "a${QUESTION}b" >/dev/null; then + : good, it works +else + QUESTION='\?' +fi + +# Cause NextStep 3.3 users to lose in a more graceful fashion. +if expr 'abc +def' : 'abc +def' >/dev/null; then + : good, it works +else + echo 'Running these tests requires an "expr" program that can handle' + echo 'multi-line patterns. Make sure that such an expr (GNU, or many but' + echo 'not all vendor-supplied versions) is in your path.' + exit 1 +fi + +# Warn SunOS, SysVr3.2, etc., users that they may be partially losing +if expr 'a +b' : 'a +c' >/dev/null; then + echo 'Warning: you are using a version of expr which does not correctly' + echo 'match multi-line patterns. Some tests may spuriously pass.' + echo 'You may wish to make sure GNU expr is in your path.' +else + : good, it works +fi + +pass () +{ + echo "PASS: $1" >>${LOGFILE} +} + +fail () +{ + echo "FAIL: $1" | tee -a ${LOGFILE} + # This way the tester can go and see what remnants were left + exit 1 +} + +# See dotest and dotest_fail for explanation (this is the parts +# of the implementation common to the two). +dotest_internal () +{ + # expr can't distinguish between "zero characters matched" and "no match", + # so special-case it. + if test -z "$3"; then + if test -s ${TESTDIR}/dotest.tmp; then + echo "** expected: " >>${LOGFILE} + echo "$3" >>${LOGFILE} + echo "** got: " >>${LOGFILE} + cat ${TESTDIR}/dotest.tmp >>${LOGFILE} + fail "$1" + else + cat ${TESTDIR}/dotest.tmp >>${LOGFILE} + pass "$1" + fi + else + if expr "`cat ${TESTDIR}/dotest.tmp`" : \ + "$3"${ENDANCHOR} >/dev/null; then + cat ${TESTDIR}/dotest.tmp >>${LOGFILE} + pass "$1" + else + if test x"$4" != x; then + if expr "`cat ${TESTDIR}/dotest.tmp`" : \ + "$4"${ENDANCHOR} >/dev/null; then + cat ${TESTDIR}/dotest.tmp >>${LOGFILE} + pass "$1" + else + echo "** expected: " >>${LOGFILE} + echo "$3" >>${LOGFILE} + echo "** or: " >>${LOGFILE} + echo "$4" >>${LOGFILE} + echo "** got: " >>${LOGFILE} + cat ${TESTDIR}/dotest.tmp >>${LOGFILE} + fail "$1" + fi + else + echo "** expected: " >>${LOGFILE} + echo "$3" >>${LOGFILE} + echo "** got: " >>${LOGFILE} + cat ${TESTDIR}/dotest.tmp >>${LOGFILE} + fail "$1" + fi + fi + fi +} + +# Usage: +# dotest TESTNAME COMMAND OUTPUT [OUTPUT2] +# TESTNAME is the name used in the log to identify the test. +# COMMAND is the command to run; for the test to pass, it exits with +# exitstatus zero. +# OUTPUT is a regexp which is compared against the output (stdout and +# stderr combined) from the test. It is anchored to the start and end +# of the output, so should start or end with ".*" if that is what is desired. +# Trailing newlines are stripped from the command's actual output before +# matching against OUTPUT. +# If OUTPUT2 is specified and the output matches it, then it is also +# a pass (partial workaround for the fact that some versions of expr +# lack \|). +dotest () +{ + if $2 >${TESTDIR}/dotest.tmp 2>&1; then + : so far so good + else + status=$? + cat ${TESTDIR}/dotest.tmp >>${LOGFILE} + echo "exit status was $status" >>${LOGFILE} + fail "$1" + fi + dotest_internal "$@" +} + +# Like dotest except exitstatus should be nonzero. +dotest_fail () +{ + if $2 >${TESTDIR}/dotest.tmp 2>&1; then + status=$? + cat ${TESTDIR}/dotest.tmp >>${LOGFILE} + echo "exit status was $status" >>${LOGFILE} + fail "$1" + else + : so far so good + fi + dotest_internal "$@" +} + +# Like dotest except second argument is the required exitstatus. +dotest_status () +{ + $3 >${TESTDIR}/dotest.tmp 2>&1 + status=$? + if test "$status" = "$2"; then + : so far so good + else + cat ${TESTDIR}/dotest.tmp >>${LOGFILE} + echo "exit status was $status; expected $2" >>${LOGFILE} + fail "$1" + fi + dotest_internal "$1" "$3" "$4" "$5" +} + +# clean any old remnants +rm -rf ${TESTDIR} +mkdir ${TESTDIR} +cd ${TESTDIR} + +# Avoid picking up any stray .cvsrc, etc., from the user running the tests +mkdir home +HOME=${TESTDIR}/home; export HOME + +# Remaining arguments are the names of tests to run. +# +# The testsuite is broken up into (hopefully manageably-sized) +# independently runnable tests, so that one can quickly get a result +# from a cvs or testsuite change, and to facilitate understanding the +# tests. + +if test x"$*" = x; then + tests="basica basic1 deep basic2 death branches import new conflicts modules mflag errmsg1 devcom ignore binfiles info" +else + tests="$*" +fi + +# this should die +if ${CVS} -d `pwd`/cvsroot co cvs-sanity 2>> ${LOGFILE} ; then + echo "FAIL: test 1" | tee -a ${LOGFILE} + exit 1 +else + echo "PASS: test 1" >>${LOGFILE} +fi + +# this should still die +mkdir cvsroot +if ${CVS} -d `pwd`/cvsroot co cvs-sanity 2>> ${LOGFILE} ; then + echo "FAIL: test 2" | tee -a ${LOGFILE} + exit 1 +else + echo "PASS: test 2" >>${LOGFILE} +fi + +# this should still die +mkdir cvsroot/CVSROOT +if ${CVS} -d `pwd`/cvsroot co cvs-sanity 2>> ${LOGFILE} ; then + echo "FAIL: test 3" | tee -a ${LOGFILE} + exit 1 +else + echo "PASS: test 3" >>${LOGFILE} +fi + +# This one should work, although it should spit a warning. +mkdir tmp ; cd tmp +${CVS} -d `pwd`/../cvsroot co CVSROOT 2>> ${LOGFILE} +cd .. ; rm -rf tmp + +# set up a minimal modules file... +# (now that mkmodules is gone, this doesn't test -i the way it +# used to. In fact, it looks like a noop to me). +echo "CVSROOT CVSROOT" > cvsroot/CVSROOT/modules +# The following line stolen from cvsinit.sh. FIXME: create our +# repository via cvsinit.sh; that way we test it too. +(cd cvsroot/CVSROOT; ci -q -u -t/dev/null \ + -m'initial checkin of modules' modules) + +# This one should succeed. No warnings. +mkdir tmp ; cd tmp +if ${CVS} -d `pwd`/../cvsroot co CVSROOT ; then + echo "PASS: test 4" >>${LOGFILE} +else + echo "FAIL: test 4" | tee -a ${LOGFILE} + exit 1 +fi + +if echo "yes" | ${CVS} -d `pwd`/../cvsroot release -d CVSROOT ; then + echo "PASS: test 4.5" >>${LOGFILE} +else + echo "FAIL: test 4.5" | tee -a ${LOGFILE} + exit 1 +fi +# this had better be empty +cd ..; rmdir tmp +dotest_fail 4.75 "test -d tmp" '' + +# a simple function to compare directory contents +# +# BTW, I don't care any more -- if you don't have a /bin/sh that handles +# shell functions, well get one. +# +# Returns: ISDIFF := true|false +# +directory_cmp () +{ + OLDPWD=`pwd` + DIR_1=$1 + DIR_2=$2 + ISDIFF=false + + cd $DIR_1 + find . -print | fgrep -v /CVS | sort > /tmp/dc$$d1 + + # go back where we were to avoid symlink hell... + cd $OLDPWD + cd $DIR_2 + find . -print | fgrep -v /CVS | sort > /tmp/dc$$d2 + + if diff /tmp/dc$$d1 /tmp/dc$$d2 >/dev/null 2>&1 + then + : + else + ISDIFF=true + return + fi + cd $OLDPWD + while read a + do + if [ -f $DIR_1/"$a" ] ; then + cmp -s $DIR_1/"$a" $DIR_2/"$a" + if [ $? -ne 0 ] ; then + ISDIFF=true + fi + fi + done < /tmp/dc$$d1 +### FIXME: +### rm -f /tmp/dc$$* +} + +# so much for the setup. Let's try something harder. + +# Try setting CVSROOT so we don't have to worry about it anymore. (now that +# we've tested -d cvsroot.) +CVSROOT_DIRNAME=${TESTDIR}/cvsroot +CVSROOT=${CVSROOT_DIRNAME} ; export CVSROOT +if test "x$remote" = xyes; then + CVSROOT=`hostname`:${CVSROOT_DIRNAME} ; export CVSROOT + # Use rsh so we can test it without having to muck with inetd or anything + # like that. Also needed to get CVS_SERVER to work. + CVS_CLIENT_PORT=-1; export CVS_CLIENT_PORT + CVS_SERVER=${testcvs}; export CVS_SERVER +fi + +# start keeping history +touch ${CVSROOT_DIRNAME}/CVSROOT/history + +### The big loop +for what in $tests; do + case $what in + basica) + # Similar in spirit to some of the basic1, and basic2 + # tests, but hopefully a lot faster. Also tests operating on + # files two directories down *without* operating on the parent dirs. + + # Using mkdir in the repository is used throughout these + # tests to create a top-level directory. I think instead it + # should be: + # cvs co -l . + # mkdir first-dir + # cvs add first-dir + # but currently that works only for local CVS, not remote. + mkdir ${CVSROOT_DIRNAME}/first-dir + dotest basica-1 "${testcvs} -q co first-dir" '' + cd first-dir + + # Test a few operations, to ensure they gracefully do + # nothing in an empty directory. + dotest basica-1a0 "${testcvs} -q update" '' + dotest basica-1a1 "${testcvs} -q diff -c" '' + dotest basica-1a2 "${testcvs} -q status" '' + + mkdir sdir + dotest basica-2 "${testcvs} add sdir" \ +'Directory /tmp/cvs-sanity/cvsroot/first-dir/sdir added to the repository' + cd sdir + mkdir ssdir + dotest basica-3 "${testcvs} add ssdir" \ +'Directory /tmp/cvs-sanity/cvsroot/first-dir/sdir/ssdir added to the repository' + cd ssdir + echo ssfile >ssfile + + # Trying to commit it without a "cvs add" should be an error. + # The "use `cvs add' to create an entry" message is the one + # that I consider to be more correct, but local cvs prints the + # "nothing known" message and noone has gotten around to fixing it. + dotest_fail basica-notadded "${testcvs} -q ci ssfile" \ +"${PROG} [a-z]*: use "'`cvs add'\'' to create an entry for ssfile +'"${PROG}"' \[[a-z]* aborted\]: correct above errors first!' \ +"${PROG}"' [a-z]*: nothing known about `ssfile'\'' +'"${PROG}"' \[[a-z]* aborted\]: correct above errors first!' + + dotest basica-4 "${testcvs} add ssfile" \ +"${PROG}"' [a-z]*: scheduling file `ssfile'\'' for addition +'"${PROG}"' [a-z]*: use '\''cvs commit'\'' to add this file permanently' + dotest_fail basica-4a "${testcvs} tag tag0 ssfile" \ +"${PROG} [a-z]*: nothing known about ssfile +${PROG} "'\[[a-z]* aborted\]: correct the above errors first!' + cd ../.. + dotest basica-5 "${testcvs} -q ci -m add-it" \ +'RCS file: /tmp/cvs-sanity/cvsroot/first-dir/sdir/ssdir/ssfile,v +done +Checking in sdir/ssdir/ssfile; +/tmp/cvs-sanity/cvsroot/first-dir/sdir/ssdir/ssfile,v <-- ssfile +initial revision: 1.1 +done' + dotest_fail basica-5a \ + "${testcvs} -q tag BASE sdir/ssdir/ssfile" \ +"${PROG} [a-z]*: Attempt to add reserved tag name BASE +${PROG} \[[a-z]* aborted\]: failed to set tag BASE to revision 1.1 in /tmp/cvs-sanity/cvsroot/first-dir/sdir/ssdir/ssfile,v" + dotest basica-5b "${testcvs} -q tag NOT_RESERVED" \ +'T sdir/ssdir/ssfile' + + dotest basica-6 "${testcvs} -q update" '' + echo "ssfile line 2" >>sdir/ssdir/ssfile + dotest_status basica-6.2 1 "${testcvs} -q diff -c" \ +'Index: sdir/ssdir/ssfile +=================================================================== +RCS file: /tmp/cvs-sanity/cvsroot/first-dir/sdir/ssdir/ssfile,v +retrieving revision 1\.1 +diff -c -r1\.1 ssfile +\*\*\* ssfile [0-9/]* [0-9:]* 1\.1 +--- ssfile [0-9/]* [0-9:]* +\*\*\*\*\*\*\*\*\*\*\*\*\*\*\* +\*\*\* 1 \*\*\*\* +--- 1,2 ---- + ssfile +'"${PLUS} ssfile line 2" + dotest basica-7 "${testcvs} -q ci -m modify-it" \ +'Checking in sdir/ssdir/ssfile; +/tmp/cvs-sanity/cvsroot/first-dir/sdir/ssdir/ssfile,v <-- ssfile +new revision: 1.2; previous revision: 1.1 +done' + dotest_fail basica-nonexist "${testcvs} -q ci nonexist" \ +"${PROG}"' [a-z]*: nothing known about `nonexist'\'' +'"${PROG}"' \[[a-z]* aborted\]: correct above errors first!' + dotest basica-8 "${testcvs} -q update" '' + dotest_fail basica-9 \ + "${testcvs} -q -d /tmp/cvs-sanity/nonexist update" \ +"${PROG}: .*/tmp/cvs-sanity/cvsroot value for CVS Root found in CVS/Root +${PROG}"': does not match command line -d /tmp/cvs-sanity/nonexist setting +'"${PROG}"': you may wish to try the cvs command again without the -d option ' + + dotest basica-10 "${testcvs} annotate" \ +'Annotations for sdir/ssdir/ssfile +\*\*\*\*\*\*\*\*\*\*\*\*\*\*\* +1.1 .[a-z@][a-z@ ]* [0-9a-zA-Z-]*.: ssfile +1.2 .[a-z@][a-z@ ]* [0-9a-zA-Z-]*.: ssfile line 2' + cd .. + + rm -rf ${CVSROOT_DIRNAME}/first-dir + rm -r first-dir + ;; + + basic1) # first dive - add a files, first singly, then in a group. + mkdir ${CVSROOT_DIRNAME}/first-dir + # check out an empty directory + if ${CVS} co first-dir ; then + echo "PASS: test 13a" >>${LOGFILE} + else + echo "FAIL: test 13a" | tee -a ${LOGFILE}; exit 1 + fi + + cd first-dir + files=first-file + for i in a b ; do + for j in ${files} ; do + echo $j > $j + done + + for do in add rm ; do + for j in ${do} "commit -m test" ; do + # ${do} + if ${CVS} $j ${files} >> ${LOGFILE} 2>&1; then + echo "PASS: test 14-${do}-$j" >>${LOGFILE} + else + echo "FAIL: test 14-${do}-$j" | tee -a ${LOGFILE}; exit 1 + fi + + # update it. + if [ "${do}" = "rm" -a "$j" != "commit -m test" ] || ${CVS} update ${files} ; then + echo "PASS: test 15-${do}-$j" >>${LOGFILE} + else + echo "FAIL: test 15-${do}-$j" | tee -a ${LOGFILE}; exit 1 + fi + + # update all. + if ${CVS} update ; then + echo "PASS: test 16-${do}-$j" >>${LOGFILE} + else + echo "FAIL: test 16-${do}-$j" | tee -a ${LOGFILE}; exit 1 + fi + + # status all. + if ${CVS} status >> ${LOGFILE}; then + echo "PASS: test 17-${do}-$j" >>${LOGFILE} + else + echo "FAIL: test 17-${do}-$j" | tee -a ${LOGFILE}; exit 1 + fi + + # FIXME: this one doesn't work yet for added files. + # log all. + if ${CVS} log >> ${LOGFILE}; then + echo "PASS: test 18-${do}-$j" >>${LOGFILE} + else + echo "FAIL: test 18-${do}-$j" | tee -a ${LOGFILE} + fi + + cd .. + # update all. + if ${CVS} update ; then + echo "PASS: test 21-${do}-$j" >>${LOGFILE} + else + echo "FAIL: test 21-${do}-$j" | tee -a ${LOGFILE}; exit 1 + fi + + # log all. + # FIXME: doesn't work right for added files. + if ${CVS} log first-dir >> ${LOGFILE}; then + echo "PASS: test 22-${do}-$j" >>${LOGFILE} + else + echo "FAIL: test 22-${do}-$j" | tee -a ${LOGFILE} + fi + + # status all. + if ${CVS} status first-dir >> ${LOGFILE}; then + echo "PASS: test 23-${do}-$j" >>${LOGFILE} + else + echo "FAIL: test 23-${do}-$j" | tee -a ${LOGFILE}; exit 1 + fi + + # update all. + if ${CVS} update first-dir ; then + echo "PASS: test 24-${do}-$j" >>${LOGFILE} + else + echo "FAIL: test 24-${do}-$j" | tee -a ${LOGFILE} ; exit 1 + fi + + # update all. + if ${CVS} co first-dir ; then + echo "PASS: test 27-${do}-$j" >>${LOGFILE} + else + echo "FAIL: test 27-${do}-$j" | tee -a ${LOGFILE} ; exit 1 + fi + + cd first-dir + done # j + rm -f ${files} + done # do + + files="file2 file3 file4 file5" + done + if ${CVS} tag first-dive ; then + echo "PASS: test 28" >>${LOGFILE} + else + echo "FAIL: test 28" | tee -a ${LOGFILE} ; exit 1 + fi + cd .. + rm -rf ${CVSROOT_DIRNAME}/first-dir + rm -rf first-dir + ;; + + deep) + # Test the ability to operate on directories nested rather deeply. + mkdir ${CVSROOT_DIRNAME}/first-dir + dotest deep-1 "${testcvs} -q co first-dir" '' + cd first-dir + for i in dir1 dir2 dir3 dir4 dir5 dir6 dir7 dir8; do + mkdir $i + dotest deep-2-$i "${testcvs} add $i" \ +'Directory /tmp/cvs-sanity/cvsroot/first-dir/dir1[/dir0-9]* added to the repository' + cd $i + echo file1 >file1 + dotest deep-3-$i "${testcvs} add file1" \ +"${PROG}"' [a-z]*: scheduling file `file1'\'' for addition +'"${PROG}"' [a-z]*: use '\''cvs commit'\'' to add this file permanently' + done + cd ../../../../../../../../.. + dotest deep-4 "${testcvs} -q ci -m add-them first-dir" \ +'RCS file: /tmp/cvs-sanity/cvsroot/first-dir/dir1/file1,v +done +Checking in first-dir/dir1/file1; +/tmp/cvs-sanity/cvsroot/first-dir/dir1/file1,v <-- file1 +initial revision: 1.1 +done +RCS file: /tmp/cvs-sanity/cvsroot/first-dir/dir1/dir2/file1,v +done +Checking in first-dir/dir1/dir2/file1; +/tmp/cvs-sanity/cvsroot/first-dir/dir1/dir2/file1,v <-- file1 +initial revision: 1.1 +done +RCS file: /tmp/cvs-sanity/cvsroot/first-dir/dir1/dir2/dir3/file1,v +done +Checking in first-dir/dir1/dir2/dir3/file1; +/tmp/cvs-sanity/cvsroot/first-dir/dir1/dir2/dir3/file1,v <-- file1 +initial revision: 1.1 +done +RCS file: /tmp/cvs-sanity/cvsroot/first-dir/dir1/dir2/dir3/dir4/file1,v +done +Checking in first-dir/dir1/dir2/dir3/dir4/file1; +/tmp/cvs-sanity/cvsroot/first-dir/dir1/dir2/dir3/dir4/file1,v <-- file1 +initial revision: 1.1 +done +RCS file: /tmp/cvs-sanity/cvsroot/first-dir/dir1/dir2/dir3/dir4/dir5/file1,v +done +Checking in first-dir/dir1/dir2/dir3/dir4/dir5/file1; +/tmp/cvs-sanity/cvsroot/first-dir/dir1/dir2/dir3/dir4/dir5/file1,v <-- file1 +initial revision: 1.1 +done +RCS file: /tmp/cvs-sanity/cvsroot/first-dir/dir1/dir2/dir3/dir4/dir5/dir6/file1,v +done +Checking in first-dir/dir1/dir2/dir3/dir4/dir5/dir6/file1; +/tmp/cvs-sanity/cvsroot/first-dir/dir1/dir2/dir3/dir4/dir5/dir6/file1,v <-- file1 +initial revision: 1.1 +done +RCS file: /tmp/cvs-sanity/cvsroot/first-dir/dir1/dir2/dir3/dir4/dir5/dir6/dir7/file1,v +done +Checking in first-dir/dir1/dir2/dir3/dir4/dir5/dir6/dir7/file1; +/tmp/cvs-sanity/cvsroot/first-dir/dir1/dir2/dir3/dir4/dir5/dir6/dir7/file1,v <-- file1 +initial revision: 1.1 +done +RCS file: /tmp/cvs-sanity/cvsroot/first-dir/dir1/dir2/dir3/dir4/dir5/dir6/dir7/dir8/file1,v +done +Checking in first-dir/dir1/dir2/dir3/dir4/dir5/dir6/dir7/dir8/file1; +/tmp/cvs-sanity/cvsroot/first-dir/dir1/dir2/dir3/dir4/dir5/dir6/dir7/dir8/file1,v <-- file1 +initial revision: 1.1 +done' + + if echo "yes" | ${testcvs} release -d first-dir >>${LOGFILE}; then + pass deep-5 + else + fail deep-5 + fi + rm -rf ${CVSROOT_DIRNAME}/first-dir + ;; + + basic2) + # Test rtag, import, history, various miscellaneous operations + + # First empty the history file + rm ${CVSROOT_DIRNAME}/CVSROOT/history + touch ${CVSROOT_DIRNAME}/CVSROOT/history + + mkdir ${CVSROOT_DIRNAME}/first-dir + dotest basic2-1 "${testcvs} -q co first-dir" '' + for i in first-dir dir1 dir2 ; do + if [ ! -d $i ] ; then + mkdir $i + if ${CVS} add $i >> ${LOGFILE}; then + echo "PASS: test 29-$i" >>${LOGFILE} + else + echo "FAIL: test 29-$i" | tee -a ${LOGFILE} ; exit 1 + fi + fi + + cd $i + + for j in file6 file7; do + echo $j > $j + done + + if ${CVS} add file6 file7 2>> ${LOGFILE}; then + echo "PASS: test 30-$i-$j" >>${LOGFILE} + else + echo "FAIL: test 30-$i-$j" | tee -a ${LOGFILE} ; exit 1 + fi + done + cd ../../.. + if ${CVS} update first-dir ; then + echo "PASS: test 31" >>${LOGFILE} + else + echo "FAIL: test 31" | tee -a ${LOGFILE} ; exit 1 + fi + + # fixme: doesn't work right for added files. + if ${CVS} log first-dir >> ${LOGFILE}; then + echo "PASS: test 32" >>${LOGFILE} + else + echo "FAIL: test 32" | tee -a ${LOGFILE} # ; exit 1 + fi + + if ${CVS} status first-dir >> ${LOGFILE}; then + echo "PASS: test 33" >>${LOGFILE} + else + echo "FAIL: test 33" | tee -a ${LOGFILE} ; exit 1 + fi + +# if ${CVS} diff -u first-dir >> ${LOGFILE} || [ $? = 1 ] ; then +# echo "PASS: test 34" >>${LOGFILE} +# else +# echo "FAIL: test 34" | tee -a ${LOGFILE} # ; exit 1 +# fi + + if ${CVS} ci -m "second dive" first-dir >> ${LOGFILE} 2>&1; then + echo "PASS: test 35" >>${LOGFILE} + else + echo "FAIL: test 35" | tee -a ${LOGFILE} ; exit 1 + fi + + if ${CVS} tag second-dive first-dir ; then + echo "PASS: test 36" >>${LOGFILE} + else + echo "FAIL: test 36" | tee -a ${LOGFILE} ; exit 1 + fi + + # third dive - in bunch o' directories, add bunch o' files, + # delete some, change some. + + for i in first-dir dir1 dir2 ; do + cd $i + + # modify a file + echo file6 >>file6 + + # delete a file + rm file7 + + if ${CVS} rm file7 2>> ${LOGFILE}; then + echo "PASS: test 37-$i" >>${LOGFILE} + else + echo "FAIL: test 37-$i" | tee -a ${LOGFILE} ; exit 1 + fi + + # and add a new file + echo file14 >file14 + + if ${CVS} add file14 2>> ${LOGFILE}; then + echo "PASS: test 38-$i" >>${LOGFILE} + else + echo "FAIL: test 38-$i" | tee -a ${LOGFILE} ; exit 1 + fi + done + cd ../../.. + if ${CVS} update first-dir ; then + echo "PASS: test 39" >>${LOGFILE} + else + echo "FAIL: test 39" | tee -a ${LOGFILE} ; exit 1 + fi + + # fixme: doesn't work right for added files + if ${CVS} log first-dir >> ${LOGFILE}; then + echo "PASS: test 40" >>${LOGFILE} + else + echo "FAIL: test 40" | tee -a ${LOGFILE} # ; exit 1 + fi + + if ${CVS} status first-dir >> ${LOGFILE}; then + echo "PASS: test 41" >>${LOGFILE} + else + echo "FAIL: test 41" | tee -a ${LOGFILE} ; exit 1 + fi + +# if ${CVS} diff -u first-dir >> ${LOGFILE} || [ $? = 1 ] ; then +# echo "PASS: test 42" >>${LOGFILE} +# else +# echo "FAIL: test 42" | tee -a ${LOGFILE} # ; exit 1 +# fi + + if ${CVS} ci -m "third dive" first-dir >>${LOGFILE} 2>&1; then + echo "PASS: test 43" >>${LOGFILE} + else + echo "FAIL: test 43" | tee -a ${LOGFILE} ; exit 1 + fi + dotest 43.5 "${testcvs} -q update first-dir" '' + + if ${CVS} tag third-dive first-dir ; then + echo "PASS: test 44" >>${LOGFILE} + else + echo "FAIL: test 44" | tee -a ${LOGFILE} ; exit 1 + fi + + if echo "yes" | ${CVS} release -d first-dir ; then + echo "PASS: test 45" >>${LOGFILE} + else + echo "FAIL: test 45" | tee -a ${LOGFILE} ; exit 1 + fi + + # end of third dive + if [ -d first-dir ] ; then + echo "FAIL: test 45.5" | tee -a ${LOGFILE} ; exit 1 + else + echo "PASS: test 45.5" >>${LOGFILE} + fi + + # now try some rtags + + # rtag HEADS + if ${CVS} rtag rtagged-by-head first-dir ; then + echo "PASS: test 46" >>${LOGFILE} + else + echo "FAIL: test 46" | tee -a ${LOGFILE} ; exit 1 + fi + + # tag by tag + if ${CVS} rtag -r rtagged-by-head rtagged-by-tag first-dir ; then + echo "PASS: test 47" >>${LOGFILE} + else + echo "FAIL: test 47" | tee -a ${LOGFILE} ; exit 1 + fi + + # tag by revision + if ${CVS} rtag -r1.1 rtagged-by-revision first-dir ; then + echo "PASS: test 48" >>${LOGFILE} + else + echo "FAIL: test 48" | tee -a ${LOGFILE} ; exit 1 + fi + + # rdiff by revision + if ${CVS} rdiff -r1.1 -rrtagged-by-head first-dir >> ${LOGFILE} || [ $? = 1 ] ; then + echo "PASS: test 49" >>${LOGFILE} + else + echo "FAIL: test 49" | tee -a ${LOGFILE} ; exit 1 + fi + + # now export by rtagged-by-head and rtagged-by-tag and compare. + rm -rf first-dir + if ${CVS} export -r rtagged-by-head first-dir ; then + echo "PASS: test 50" >>${LOGFILE} + else + echo "FAIL: test 50" | tee -a ${LOGFILE} ; exit 1 + fi + + mv first-dir 1dir + if ${CVS} export -r rtagged-by-tag first-dir ; then + echo "PASS: test 51" >>${LOGFILE} + else + echo "FAIL: test 51" | tee -a ${LOGFILE} ; exit 1 + fi + + directory_cmp 1dir first-dir + + if $ISDIFF ; then + echo "FAIL: test 52" | tee -a ${LOGFILE} ; exit 1 + else + echo "PASS: test 52" >>${LOGFILE} + fi + rm -rf 1dir first-dir + + # checkout by revision vs export by rtagged-by-revision and compare. + if ${CVS} export -rrtagged-by-revision -d export-dir first-dir ; then + echo "PASS: test 53" >>${LOGFILE} + else + echo "FAIL: test 53" | tee -a ${LOGFILE} ; exit 1 + fi + + if ${CVS} co -r1.1 first-dir ; then + echo "PASS: test 54" >>${LOGFILE} + else + echo "FAIL: test 54" | tee -a ${LOGFILE} ; exit 1 + fi + + # directory copies are done in an oblique way in order to avoid a bug in sun's tmp filesystem. + mkdir first-dir.cpy ; (cd first-dir ; tar cf - * | (cd ../first-dir.cpy ; tar xf -)) + + directory_cmp first-dir export-dir + + if $ISDIFF ; then + echo "FAIL: test 55" | tee -a ${LOGFILE} ; exit 1 + else + echo "PASS: test 55" >>${LOGFILE} + fi + + # interrupt, while we've got a clean 1.1 here, let's import it into another tree. + 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 /tmp/cvs-sanity/cvsroot/second-dir/dir1 +N second-dir/dir1/file14 +N second-dir/dir1/file6 +N second-dir/dir1/file7 +'"${PROG}"' [a-z]*: Importing /tmp/cvs-sanity/cvsroot/second-dir/dir1/dir2 +N second-dir/dir1/dir2/file14 +N second-dir/dir1/dir2/file6 +N second-dir/dir1/dir2/file7 + +No conflicts created by this import' + + cd .. + + if ${CVS} export -r HEAD second-dir ; then + echo "PASS: test 57" >>${LOGFILE} + else + echo "FAIL: test 57" | tee -a ${LOGFILE} ; exit 1 + fi + + directory_cmp first-dir second-dir + + if $ISDIFF ; then + echo "FAIL: test 58" | tee -a ${LOGFILE} ; exit 1 + else + echo "PASS: test 58" >>${LOGFILE} + fi + + rm -rf second-dir + rm -rf export-dir first-dir + mkdir first-dir + (cd first-dir.cpy ; tar cf - * | (cd ../first-dir ; tar xf -)) + + # update the top, cancelling sticky tags, retag, update other copy, compare. + cd first-dir + if ${CVS} update -A -l *file* 2>> ${LOGFILE}; then + echo "PASS: test 59" >>${LOGFILE} + else + echo "FAIL: test 59" | tee -a ${LOGFILE} ; exit 1 + fi + + # If we don't delete the tag first, cvs won't retag it. + # This would appear to be a feature. + if ${CVS} tag -l -d rtagged-by-revision ; then + echo "PASS: test 60a" >>${LOGFILE} + else + echo "FAIL: test 60a" | tee -a ${LOGFILE} ; exit 1 + fi + if ${CVS} tag -l rtagged-by-revision ; then + echo "PASS: test 60b" >>${LOGFILE} + else + echo "FAIL: test 60b" | tee -a ${LOGFILE} ; exit 1 + fi + + cd .. + mv first-dir 1dir + mv first-dir.cpy first-dir + cd first-dir + + dotest 61 "${testcvs} -q diff -u" '' + + if ${CVS} update ; then + echo "PASS: test 62" >>${LOGFILE} + else + echo "FAIL: test 62" | tee -a ${LOGFILE} ; exit 1 + fi + + cd .. + + #### FIXME: is this expected to work??? Need to investigate + #### and fix or remove the test. +# directory_cmp 1dir first-dir +# +# if $ISDIFF ; then +# echo "FAIL: test 63" | tee -a ${LOGFILE} # ; exit 1 +# else +# echo "PASS: test 63" >>${LOGFILE} +# fi + rm -rf 1dir first-dir + + # Test the cvs history command. + + # The reason that there are two patterns rather than using + # \(/tmp/cvs-sanity\|<remote>\) is that we are trying to + # make this portable. Perhaps at some point we should + # ditch that notion and require GNU expr (or dejagnu or....) + # since it seems to be so painful. + + # why are there two lines at the end of the local output + # which don't exist in the remote output? would seem to be + # a CVS bug. + dotest basic2-64 "${testcvs} his -e -a" \ +'O [0-9/]* [0-9:]* '"${PLUS}"'0000 [a-z@][a-z@]* first-dir =first-dir= /tmp/cvs-sanity/\* +A [0-9/]* [0-9:]* '"${PLUS}"'0000 [a-z@][a-z@]* 1.1 file6 first-dir == /tmp/cvs-sanity +A [0-9/]* [0-9:]* '"${PLUS}"'0000 [a-z@][a-z@]* 1.1 file7 first-dir == /tmp/cvs-sanity +A [0-9/]* [0-9:]* '"${PLUS}"'0000 [a-z@][a-z@]* 1.1 file6 first-dir/dir1 == /tmp/cvs-sanity +A [0-9/]* [0-9:]* '"${PLUS}"'0000 [a-z@][a-z@]* 1.1 file7 first-dir/dir1 == /tmp/cvs-sanity +A [0-9/]* [0-9:]* '"${PLUS}"'0000 [a-z@][a-z@]* 1.1 file6 first-dir/dir1/dir2 == /tmp/cvs-sanity +A [0-9/]* [0-9:]* '"${PLUS}"'0000 [a-z@][a-z@]* 1.1 file7 first-dir/dir1/dir2 == /tmp/cvs-sanity +A [0-9/]* [0-9:]* '"${PLUS}"'0000 [a-z@][a-z@]* 1.1 file14 first-dir == /tmp/cvs-sanity +M [0-9/]* [0-9:]* '"${PLUS}"'0000 [a-z@][a-z@]* 1.2 file6 first-dir == /tmp/cvs-sanity +A [0-9/]* [0-9:]* '"${PLUS}"'0000 [a-z@][a-z@]* 1.1 file14 first-dir/dir1 == /tmp/cvs-sanity +M [0-9/]* [0-9:]* '"${PLUS}"'0000 [a-z@][a-z@]* 1.2 file6 first-dir/dir1 == /tmp/cvs-sanity +A [0-9/]* [0-9:]* '"${PLUS}"'0000 [a-z@][a-z@]* 1.1 file14 first-dir/dir1/dir2 == /tmp/cvs-sanity +M [0-9/]* [0-9:]* '"${PLUS}"'0000 [a-z@][a-z@]* 1.2 file6 first-dir/dir1/dir2 == /tmp/cvs-sanity +F [0-9/]* [0-9:]* '"${PLUS}"'0000 [a-z@][a-z@]* =first-dir= /tmp/cvs-sanity/\* +T [0-9/]* [0-9:]* '"${PLUS}"'0000 [a-z@][a-z@]* first-dir \[rtagged-by-head:A\] +T [0-9/]* [0-9:]* '"${PLUS}"'0000 [a-z@][a-z@]* first-dir \[rtagged-by-tag:rtagged-by-head\] +T [0-9/]* [0-9:]* '"${PLUS}"'0000 [a-z@][a-z@]* first-dir \[rtagged-by-revision:1.1\] +O [0-9/]* [0-9:]* '"${PLUS}"'0000 [a-z@][a-z@]* \[1.1\] first-dir =first-dir= /tmp/cvs-sanity/\* +U [0-9/]* [0-9:]* '"${PLUS}"'0000 [a-z@][a-z@]* 1.2 file6 first-dir == /tmp/cvs-sanity/first-dir +U [0-9/]* [0-9:]* '"${PLUS}"'0000 [a-z@][a-z@]* 1.2 file7 first-dir == /tmp/cvs-sanity/first-dir' \ +'O [0-9/]* [0-9:]* '"${PLUS}"'0000 [a-z@][a-z@]* first-dir =first-dir= <remote>/\* +A [0-9/]* [0-9:]* '"${PLUS}"'0000 [a-z@][a-z@]* 1.1 file6 first-dir == <remote> +A [0-9/]* [0-9:]* '"${PLUS}"'0000 [a-z@][a-z@]* 1.1 file7 first-dir == <remote> +A [0-9/]* [0-9:]* '"${PLUS}"'0000 [a-z@][a-z@]* 1.1 file6 first-dir/dir1 == <remote> +A [0-9/]* [0-9:]* '"${PLUS}"'0000 [a-z@][a-z@]* 1.1 file7 first-dir/dir1 == <remote> +A [0-9/]* [0-9:]* '"${PLUS}"'0000 [a-z@][a-z@]* 1.1 file6 first-dir/dir1/dir2 == <remote> +A [0-9/]* [0-9:]* '"${PLUS}"'0000 [a-z@][a-z@]* 1.1 file7 first-dir/dir1/dir2 == <remote> +A [0-9/]* [0-9:]* '"${PLUS}"'0000 [a-z@][a-z@]* 1.1 file14 first-dir == <remote> +M [0-9/]* [0-9:]* '"${PLUS}"'0000 [a-z@][a-z@]* 1.2 file6 first-dir == <remote> +A [0-9/]* [0-9:]* '"${PLUS}"'0000 [a-z@][a-z@]* 1.1 file14 first-dir/dir1 == <remote> +M [0-9/]* [0-9:]* '"${PLUS}"'0000 [a-z@][a-z@]* 1.2 file6 first-dir/dir1 == <remote> +A [0-9/]* [0-9:]* '"${PLUS}"'0000 [a-z@][a-z@]* 1.1 file14 first-dir/dir1/dir2 == <remote> +M [0-9/]* [0-9:]* '"${PLUS}"'0000 [a-z@][a-z@]* 1.2 file6 first-dir/dir1/dir2 == <remote> +F [0-9/]* [0-9:]* '"${PLUS}"'0000 [a-z@][a-z@]* =first-dir= <remote>/\* +T [0-9/]* [0-9:]* '"${PLUS}"'0000 [a-z@][a-z@]* first-dir \[rtagged-by-head:A\] +T [0-9/]* [0-9:]* '"${PLUS}"'0000 [a-z@][a-z@]* first-dir \[rtagged-by-tag:rtagged-by-head\] +T [0-9/]* [0-9:]* '"${PLUS}"'0000 [a-z@][a-z@]* first-dir \[rtagged-by-revision:1.1\] +O [0-9/]* [0-9:]* '"${PLUS}"'0000 [a-z@][a-z@]* \[1.1\] first-dir =first-dir= <remote>/\*' + + rm -rf ${CVSROOT_DIRNAME}/first-dir + rm -rf ${CVSROOT_DIRNAME}/second-dir + ;; + + death) # next dive. test death support. + mkdir ${CVSROOT_DIRNAME}/first-dir + if ${CVS} co first-dir ; then + echo "PASS: test 65" >>${LOGFILE} + else + echo "FAIL: test 65" | tee -a ${LOGFILE} ; exit 1 + fi + + cd first-dir + + # Create a directory with only dead files, to make sure CVS + # doesn't get confused by it. + mkdir subdir + dotest 65a0 "${testcvs} add subdir" \ +'Directory /tmp/cvs-sanity/cvsroot/first-dir/subdir added to the repository' + cd subdir + echo file in subdir >sfile + dotest 65a1 "${testcvs} add sfile" \ +"${PROG}"' [a-z]*: scheduling file `sfile'\'' for addition +'"${PROG}"' [a-z]*: use '\''cvs commit'\'' to add this file permanently' + dotest 65a2 "${testcvs} -q ci -m add-it" \ +'RCS file: /tmp/cvs-sanity/cvsroot/first-dir/subdir/sfile,v +done +Checking in sfile; +/tmp/cvs-sanity/cvsroot/first-dir/subdir/sfile,v <-- sfile +initial revision: 1.1 +done' + rm sfile + dotest 65a3 "${testcvs} rm sfile" \ +"${PROG}"' [a-z]*: scheduling `sfile'\'' for removal +'"${PROG}"' [a-z]*: use '\'"${PROG}"' commit'\'' to remove this file permanently' + dotest 65a4 "${testcvs} -q ci -m remove-it" \ +'Removing sfile; +/tmp/cvs-sanity/cvsroot/first-dir/subdir/sfile,v <-- sfile +new revision: delete; previous revision: 1.1 +done' + cd .. + dotest 65a5 "${testcvs} -q update -P" '' + dotest_fail 65a6 "test -d subdir" '' + + # add a file. + touch file1 + if ${CVS} add file1 2>> ${LOGFILE}; then + echo "PASS: test 66" >>${LOGFILE} + else + echo "FAIL: test 66" | tee -a ${LOGFILE} ; exit 1 + fi + + # commit + if ${CVS} ci -m test >> ${LOGFILE} 2>&1; then + echo "PASS: test 67" >>${LOGFILE} + else + echo "FAIL: test 67" | tee -a ${LOGFILE} ; exit 1 + fi + + # remove + rm file1 + if ${CVS} rm file1 2>> ${LOGFILE}; then + echo "PASS: test 68" >>${LOGFILE} + else + echo "FAIL: test 68" | tee -a ${LOGFILE} ; exit 1 + fi + + # commit + if ${CVS} ci -m test >>${LOGFILE} ; then + echo "PASS: test 69" >>${LOGFILE} + else + echo "FAIL: test 69" | tee -a ${LOGFILE} ; exit 1 + fi + + dotest_fail 69a0 "test -f file1" '' + # get the old contents of file1 back + if ${testcvs} update -p -r 1.1 file1 >file1 2>>${LOGFILE}; then + pass 69a1 + else + fail 69a1 + fi + dotest 69a2 "cat file1" '' + + # create second file + touch file2 + if ${CVS} add file1 file2 2>> ${LOGFILE}; then + echo "PASS: test 70" >>${LOGFILE} + else + echo "FAIL: test 70" | tee -a ${LOGFILE} ; exit 1 + fi + + # commit + if ${CVS} ci -m test >> ${LOGFILE} 2>&1; then + echo "PASS: test 71" >>${LOGFILE} + else + echo "FAIL: test 71" | tee -a ${LOGFILE} ; exit 1 + fi + + # log + if ${CVS} log file1 >> ${LOGFILE}; then + echo "PASS: test 72" >>${LOGFILE} + else + echo "FAIL: test 72" | tee -a ${LOGFILE} ; exit 1 + fi + + # file4 will be dead at the time of branching and stay dead. + echo file4 > file4 + dotest death-file4-add "${testcvs} add file4" \ +"${PROG}"' [a-z]*: scheduling file `file4'\'' for addition +'"${PROG}"' [a-z]*: use '\''cvs commit'\'' to add this file permanently' + dotest death-file4-ciadd "${testcvs} -q ci -m add file4" \ +'RCS file: /tmp/cvs-sanity/cvsroot/first-dir/file4,v +done +Checking in file4; +/tmp/cvs-sanity/cvsroot/first-dir/file4,v <-- file4 +initial revision: 1.1 +done' + rm file4 + dotest death-file4-rm "${testcvs} remove file4" \ +"${PROG}"' [a-z]*: scheduling `file4'\'' for removal +'"${PROG}"' [a-z]*: use '\'"${PROG}"' commit'\'' to remove this file permanently' + dotest death-file4-cirm "${testcvs} -q ci -m remove file4" \ +'Removing file4; +/tmp/cvs-sanity/cvsroot/first-dir/file4,v <-- file4 +new revision: delete; previous revision: 1.1 +done' + + # branch1 + if ${CVS} tag -b branch1 ; then + echo "PASS: test 73" >>${LOGFILE} + else + echo "FAIL: test 73" | tee -a ${LOGFILE} ; exit 1 + fi + + # and move to the branch. + if ${CVS} update -r branch1 ; then + echo "PASS: test 74" >>${LOGFILE} + else + echo "FAIL: test 74" | tee -a ${LOGFILE} ; exit 1 + fi + + dotest_fail death-file4-3 "test -f file4" '' + + # add a file in the branch + echo line1 from branch1 >> file3 + if ${CVS} add file3 2>> ${LOGFILE}; then + echo "PASS: test 75" >>${LOGFILE} + else + echo "FAIL: test 75" | tee -a ${LOGFILE} ; exit 1 + fi + + # commit + if ${CVS} ci -m test >> ${LOGFILE} 2>&1; then + echo "PASS: test 76" >>${LOGFILE} + else + echo "FAIL: test 76" | tee -a ${LOGFILE} ; exit 1 + fi + + # remove + rm file3 + if ${CVS} rm file3 2>> ${LOGFILE}; then + echo "PASS: test 77" >>${LOGFILE} + else + echo "FAIL: test 77" | tee -a ${LOGFILE} ; exit 1 + fi + + # commit + if ${CVS} ci -m test >>${LOGFILE} ; then + echo "PASS: test 78" >>${LOGFILE} + else + echo "FAIL: test 78" | tee -a ${LOGFILE} ; exit 1 + fi + + # add again + echo line1 from branch1 >> file3 + if ${CVS} add file3 2>> ${LOGFILE}; then + echo "PASS: test 79" >>${LOGFILE} + else + echo "FAIL: test 79" | tee -a ${LOGFILE} ; exit 1 + fi + + # commit + if ${CVS} ci -m test >> ${LOGFILE} 2>&1; then + echo "PASS: test 80" >>${LOGFILE} + else + echo "FAIL: test 80" | tee -a ${LOGFILE} ; exit 1 + fi + + # change the first file + echo line2 from branch1 >> file1 + + # commit + if ${CVS} ci -m test >> ${LOGFILE} 2>&1; then + echo "PASS: test 81" >>${LOGFILE} + else + echo "FAIL: test 81" | tee -a ${LOGFILE} ; exit 1 + fi + + # remove the second + rm file2 + if ${CVS} rm file2 2>> ${LOGFILE}; then + echo "PASS: test 82" >>${LOGFILE} + else + echo "FAIL: test 82" | tee -a ${LOGFILE} ; exit 1 + fi + + # commit + if ${CVS} ci -m test >>${LOGFILE}; then + echo "PASS: test 83" >>${LOGFILE} + else + echo "FAIL: test 83" | tee -a ${LOGFILE} ; exit 1 + fi + + # back to the trunk. + if ${CVS} update -A 2>> ${LOGFILE}; then + echo "PASS: test 84" >>${LOGFILE} + else + echo "FAIL: test 84" | tee -a ${LOGFILE} ; exit 1 + fi + + dotest_fail death-file4-4 "test -f file4" '' + + if [ -f file3 ] ; then + echo "FAIL: test 85" | tee -a ${LOGFILE} ; exit 1 + else + echo "PASS: test 85" >>${LOGFILE} + fi + + # join + if ${CVS} update -j branch1 >> ${LOGFILE} 2>&1; then + echo "PASS: test 86" >>${LOGFILE} + else + echo "FAIL: test 86" | tee -a ${LOGFILE} ; exit 1 + fi + + dotest_fail death-file4-5 "test -f file4" '' + + if [ -f file3 ] ; then + echo "PASS: test 87" >>${LOGFILE} + else + echo "FAIL: test 87" | tee -a ${LOGFILE} ; exit 1 + fi + + # Make sure that we joined the correct change to file1 + if echo line2 from branch1 | cmp - file1 >/dev/null; then + echo 'PASS: test 87a' >>${LOGFILE} + else + echo 'FAIL: test 87a' | tee -a ${LOGFILE} + exit 1 + fi + + # update + if ${CVS} update ; then + echo "PASS: test 88" >>${LOGFILE} + else + echo "FAIL: test 88" | tee -a ${LOGFILE} ; exit 1 + fi + + # commit + if ${CVS} ci -m test >>${LOGFILE} 2>&1; then + echo "PASS: test 89" >>${LOGFILE} + else + echo "FAIL: test 89" | tee -a ${LOGFILE} ; exit 1 + fi + + # remove first file. + rm file1 + if ${CVS} rm file1 2>> ${LOGFILE}; then + echo "PASS: test 90" >>${LOGFILE} + else + echo "FAIL: test 90" | tee -a ${LOGFILE} ; exit 1 + fi + + # commit + if ${CVS} ci -m test >>${LOGFILE}; then + echo "PASS: test 91" >>${LOGFILE} + else + echo "FAIL: test 91" | tee -a ${LOGFILE} ; exit 1 + fi + + if [ -f file1 ] ; then + echo "FAIL: test 92" | tee -a ${LOGFILE} ; exit 1 + else + echo "PASS: test 92" >>${LOGFILE} + fi + + # typo; try to get to the branch and fail + dotest_fail 92.1a "${testcvs} update -r brnach1" \ + "${PROG}"' \[[a-z]* aborted\]: no such tag brnach1' + # Make sure we are still on the trunk + if test -f file1 ; then + echo "FAIL: 92.1b" | tee -a ${LOGFILE} ; exit 1 + else + echo "PASS: 92.1b" >>${LOGFILE} + fi + if test -f file2 ; then + echo "PASS: 92.1c" >>${LOGFILE} + else + echo "FAIL: 92.1c" | tee -a ${LOGFILE} ; exit 1 + fi + + # back to branch1 + if ${CVS} update -r branch1 2>> ${LOGFILE}; then + echo "PASS: test 93" >>${LOGFILE} + else + echo "FAIL: test 93" | tee -a ${LOGFILE} ; exit 1 + fi + + dotest_fail death-file4-6 "test -f file4" '' + + if [ -f file1 ] ; then + echo "PASS: test 94" >>${LOGFILE} + else + echo "FAIL: test 94" | tee -a ${LOGFILE} ; exit 1 + fi + + # and join + if ${CVS} update -j HEAD >> ${LOGFILE} 2>&1; then + echo "PASS: test 95" >>${LOGFILE} + else + echo "FAIL: test 95" | tee -a ${LOGFILE} ; exit 1 + fi + + dotest_fail death-file4-7 "test -f file4" '' + + cd .. ; rm -rf first-dir ${CVSROOT_DIRNAME}/first-dir + ;; + branches) + # More branch tests, including branches off of branches + mkdir ${CVSROOT_DIRNAME}/first-dir + dotest branches-1 "${testcvs} -q co first-dir" '' + cd first-dir + echo 1:ancest >file1 + echo 2:ancest >file2 + echo 3:ancest >file3 + echo 4:trunk-1 >file4 + dotest branches-2 "${testcvs} add file1 file2 file3 file4" \ +"${PROG}"' [a-z]*: scheduling file `file1'\'' for addition +'"${PROG}"' [a-z]*: scheduling file `file2'\'' for addition +'"${PROG}"' [a-z]*: scheduling file `file3'\'' for addition +'"${PROG}"' [a-z]*: scheduling file `file4'\'' for addition +'"${PROG}"' [a-z]*: use '\''cvs commit'\'' to add these files permanently' + dotest branches-3 "${testcvs} -q ci -m add-it" \ +'RCS file: /tmp/cvs-sanity/cvsroot/first-dir/file1,v +done +Checking in file1; +/tmp/cvs-sanity/cvsroot/first-dir/file1,v <-- file1 +initial revision: 1.1 +done +RCS file: /tmp/cvs-sanity/cvsroot/first-dir/file2,v +done +Checking in file2; +/tmp/cvs-sanity/cvsroot/first-dir/file2,v <-- file2 +initial revision: 1.1 +done +RCS file: /tmp/cvs-sanity/cvsroot/first-dir/file3,v +done +Checking in file3; +/tmp/cvs-sanity/cvsroot/first-dir/file3,v <-- file3 +initial revision: 1.1 +done +RCS file: /tmp/cvs-sanity/cvsroot/first-dir/file4,v +done +Checking in file4; +/tmp/cvs-sanity/cvsroot/first-dir/file4,v <-- file4 +initial revision: 1.1 +done' + echo 4:trunk-2 >file4 + dotest branches-3.2 "${testcvs} -q ci -m trunk-before-branch" \ +'Checking in file4; +/tmp/cvs-sanity/cvsroot/first-dir/file4,v <-- file4 +new revision: 1.2; previous revision: 1.1 +done' + dotest branches-4 "${testcvs} tag -b br1" "${PROG}"' [a-z]*: Tagging \. +T file1 +T file2 +T file3 +T file4' + dotest branches-5 "${testcvs} update -r br1" \ +"${PROG}"' [a-z]*: Updating \.' + echo 1:br1 >file1 + echo 2:br1 >file2 + echo 4:br1 >file4 + dotest branches-6 "${testcvs} -q ci -m modify" \ +'Checking in file1; +/tmp/cvs-sanity/cvsroot/first-dir/file1,v <-- file1 +new revision: 1.1.2.1; previous revision: 1.1 +done +Checking in file2; +/tmp/cvs-sanity/cvsroot/first-dir/file2,v <-- file2 +new revision: 1.1.2.1; previous revision: 1.1 +done +Checking in file4; +/tmp/cvs-sanity/cvsroot/first-dir/file4,v <-- file4 +new revision: 1.2.2.1; previous revision: 1.2 +done' + dotest branches-7 "${testcvs} -q tag -b brbr" 'T file1 +T file2 +T file3 +T file4' + dotest branches-8 "${testcvs} -q update -r brbr" '' + echo 1:brbr >file1 + echo 4:brbr >file4 + dotest branches-9 "${testcvs} -q ci -m modify" \ +'Checking in file1; +/tmp/cvs-sanity/cvsroot/first-dir/file1,v <-- file1 +new revision: 1.1.2.1.2.1; previous revision: 1.1.2.1 +done +Checking in file4; +/tmp/cvs-sanity/cvsroot/first-dir/file4,v <-- file4 +new revision: 1.2.2.1.2.1; previous revision: 1.2.2.1 +done' + dotest branches-10 "cat file1 file2 file3 file4" '1:brbr +2:br1 +3:ancest +4:brbr' + dotest branches-11 "${testcvs} -q update -r br1" \ +'[UP] file1 +[UP] file4' + dotest branches-12 "cat file1 file2 file3 file4" '1:br1 +2:br1 +3:ancest +4:br1' + echo 4:br1-2 >file4 + dotest branches-12.2 "${testcvs} -q ci -m change-on-br1" \ +'Checking in file4; +/tmp/cvs-sanity/cvsroot/first-dir/file4,v <-- file4 +new revision: 1.2.2.2; previous revision: 1.2.2.1 +done' + dotest branches-13 "${testcvs} -q update -A" '[UP] file1 +[UP] file2 +[UP] file4' + dotest branches-14 "cat file1 file2 file3 file4" '1:ancest +2:ancest +3:ancest +4:trunk-2' + echo 4:trunk-3 >file4 + dotest branches-14.2 \ + "${testcvs} -q ci -m trunk-change-after-branch" \ +'Checking in file4; +/tmp/cvs-sanity/cvsroot/first-dir/file4,v <-- file4 +new revision: 1.3; previous revision: 1.2 +done' + dotest branches-14.3 "${testcvs} log file4" \ +' +RCS file: /tmp/cvs-sanity/cvsroot/first-dir/file4,v +Working file: file4 +head: 1\.3 +branch: +locks: strict +access list: +symbolic names: + brbr: 1\.2\.2\.1\.0\.2 + br1: 1\.2\.0\.2 +keyword substitution: kv +total revisions: 6; selected revisions: 6 +description: +---------------------------- +revision 1\.3 +date: [0-9/: ]*; author: [a-z@][a-z@]*; state: Exp; lines: '"${PLUS}"'1 -1 +trunk-change-after-branch +---------------------------- +revision 1\.2 +date: [0-9/: ]*; author: [a-z@][a-z@]*; state: Exp; lines: '"${PLUS}"'1 -1 +branches: 1\.2\.2; +trunk-before-branch +---------------------------- +revision 1\.1 +date: [0-9/: ]*; author: [a-z@][a-z@]*; state: Exp; +add-it +---------------------------- +revision 1\.2\.2\.2 +date: [0-9/: ]*; author: [a-z@][a-z@]*; state: Exp; lines: '"${PLUS}"'1 -1 +change-on-br1 +---------------------------- +revision 1\.2\.2\.1 +date: [0-9/: ]*; author: [a-z@][a-z@]*; state: Exp; lines: '"${PLUS}"'1 -1 +branches: 1\.2\.2\.1\.2; +modify +---------------------------- +revision 1\.2\.2\.1\.2\.1 +date: [0-9/: ]*; author: [a-z@][a-z@]*; state: Exp; lines: '"${PLUS}"'1 -1 +modify +=============================================================================' + dotest_status branches-14.4 1 \ + "${testcvs} diff -c -r 1.1 -r 1.3 file4" \ +'Index: file4 +=================================================================== +RCS file: /tmp/cvs-sanity/cvsroot/first-dir/file4,v +retrieving revision 1\.1 +retrieving revision 1\.3 +diff -c -r1\.1 -r1\.3 +\*\*\* file4 [0-9/]* [0-9:]* 1\.1 +--- file4 [0-9/]* [0-9:]* 1\.3 +\*\*\*\*\*\*\*\*\*\*\*\*\*\*\* +\*\*\* 1 \*\*\*\* +! 4:trunk-1 +--- 1 ---- +! 4:trunk-3' + dotest_status branches-14.5 1 \ + "${testcvs} diff -c -r 1.1 -r 1.2.2.1 file4" \ +'Index: file4 +=================================================================== +RCS file: /tmp/cvs-sanity/cvsroot/first-dir/file4,v +retrieving revision 1\.1 +retrieving revision 1\.2\.2\.1 +diff -c -r1\.1 -r1\.2\.2\.1 +\*\*\* file4 [0-9/]* [0-9:]* 1\.1 +--- file4 [0-9/]* [0-9:]* 1\.2\.2\.1 +\*\*\*\*\*\*\*\*\*\*\*\*\*\*\* +\*\*\* 1 \*\*\*\* +! 4:trunk-1 +--- 1 ---- +! 4:br1' + dotest branches-15 \ + "${testcvs} update -j 1.1.2.1 -j 1.1.2.1.2.1 file1" \ + 'RCS file: /tmp/cvs-sanity/cvsroot/first-dir/file1,v +retrieving revision 1.1.2.1 +retrieving revision 1.1.2.1.2.1 +Merging differences between 1.1.2.1 and 1.1.2.1.2.1 into file1 +rcsmerge: warning: conflicts during merge' + dotest branches-16 "cat file1" '<<<<<<< file1 +1:ancest +======= +1:brbr +>>>>>>> 1.1.2.1.2.1' + cd .. + + if test "$keep" = yes; then + echo Keeping /tmp/cvs-sanity and exiting due to --keep + exit 0 + fi + + rm -rf ${CVSROOT_DIRNAME}/first-dir + rm -r first-dir + ;; + + import) # test death after import + # import + mkdir import-dir ; cd import-dir + + for i in 1 2 3 4 ; do + echo imported file"$i" > imported-file"$i" + done + + # This directory should be on the default ignore list, + # so it shouldn't get imported. + mkdir RCS + echo ignore.me >RCS/ignore.me + + echo 'import should not expand $''Id$' >>imported-file2 + cp imported-file2 ../imported-file2-orig.tmp + + if ${CVS} import -m first-import first-dir vendor-branch junk-1_0 ; then + echo "PASS: test 96" >>${LOGFILE} + else + echo "FAIL: test 96" | tee -a ${LOGFILE} ; exit 1 + fi + + if cmp ../imported-file2-orig.tmp imported-file2; then + pass 96.5 + else + fail 96.5 + fi + cd .. + + # co + if ${CVS} co first-dir ; then + echo "PASS: test 97" >>${LOGFILE} + else + echo "FAIL: test 97" | tee -a ${LOGFILE} ; exit 1 + fi + + cd first-dir + for i in 1 2 3 4 ; do + if [ -f imported-file"$i" ] ; then + echo "PASS: test 98-$i" >>${LOGFILE} + else + echo "FAIL: test 98-$i" | tee -a ${LOGFILE} ; exit 1 + fi + done + if test -d RCS; then + echo "FAIL: test 98.5" | tee -a ${LOGFILE} ; exit 1 + else + echo "PASS: test 98.5" >>${LOGFILE} + fi + + # remove + rm imported-file1 + if ${CVS} rm imported-file1 2>> ${LOGFILE}; then + echo "PASS: test 99" >>${LOGFILE} + else + echo "FAIL: test 99" | tee -a ${LOGFILE} ; exit 1 + fi + + # change + # this sleep is significant. Otherwise, on some machines, things happen so + # fast that the file mod times do not differ. + sleep 1 + echo local-change >> imported-file2 + + # commit + if ${CVS} ci -m local-changes >> ${LOGFILE} 2>&1; then + echo "PASS: test 100" >>${LOGFILE} + else + echo "FAIL: test 100" | tee -a ${LOGFILE} ; exit 1 + fi + + # log + if ${CVS} log imported-file1 | grep '1.1.1.2 (dead)' ; then + echo "FAIL: test 101" | tee -a ${LOGFILE} ; exit 1 + else + echo "PASS: test 101" >>${LOGFILE} + fi + + # update into the vendor branch. + if ${CVS} update -rvendor-branch ; then + echo "PASS: test 102" >>${LOGFILE} + else + echo "FAIL: test 102" | tee -a ${LOGFILE} ; exit 1 + fi + + # remove file4 on the vendor branch + rm imported-file4 + + if ${CVS} rm imported-file4 2>> ${LOGFILE}; then + echo "PASS: test 103" >>${LOGFILE} + else + echo "FAIL: test 103" | tee -a ${LOGFILE} ; exit 1 + fi + + # commit + if ${CVS} ci -m vendor-removed imported-file4 >>${LOGFILE}; then + echo "PASS: test 104" >>${LOGFILE} + else + echo "FAIL: test 104" | tee -a ${LOGFILE} ; exit 1 + fi + + # update to main line + if ${CVS} update -A 2>> ${LOGFILE}; then + echo "PASS: test 105" >>${LOGFILE} + else + echo "FAIL: test 105" | tee -a ${LOGFILE} ; exit 1 + fi + + # second import - file4 deliberately unchanged + cd ../import-dir + for i in 1 2 3 ; do + echo rev 2 of file $i >> imported-file"$i" + done + cp imported-file2 ../imported-file2-orig.tmp + + if ${CVS} import -m second-import first-dir vendor-branch junk-2_0 ; then + echo "PASS: test 106" >>${LOGFILE} + else + echo "FAIL: test 106" | tee -a ${LOGFILE} ; exit 1 + fi + if cmp ../imported-file2-orig.tmp imported-file2; then + pass 106.5 + else + fail 106.5 + fi + cd .. + + # co + if ${CVS} co first-dir ; then + echo "PASS: test 107" >>${LOGFILE} + else + echo "FAIL: test 107" | tee -a ${LOGFILE} ; exit 1 + fi + + cd first-dir + + if [ -f imported-file1 ] ; then + echo "FAIL: test 108" | tee -a ${LOGFILE} ; exit 1 + else + echo "PASS: test 108" >>${LOGFILE} + fi + + for i in 2 3 ; do + if [ -f imported-file"$i" ] ; then + echo "PASS: test 109-$i" >>${LOGFILE} + else + echo "FAIL: test 109-$i" | tee -a ${LOGFILE} ; exit 1 + fi + done + + # check vendor branch for file4 + if ${CVS} update -rvendor-branch ; then + echo "PASS: test 110" >>${LOGFILE} + else + echo "FAIL: test 110" | tee -a ${LOGFILE} ; exit 1 + fi + + if [ -f imported-file4 ] ; then + echo "PASS: test 111" >>${LOGFILE} + else + echo "FAIL: test 111" | tee -a ${LOGFILE} ; exit 1 + fi + + # update to main line + if ${CVS} update -A 2>> ${LOGFILE}; then + echo "PASS: test 112" >>${LOGFILE} + else + echo "FAIL: test 112" | tee -a ${LOGFILE} ; exit 1 + fi + + cd .. + + if ${CVS} co -jjunk-1_0 -jjunk-2_0 first-dir >>${LOGFILE} 2>&1; then + echo "PASS: test 113" >>${LOGFILE} + else + echo "FAIL: test 113" | tee -a ${LOGFILE} ; exit 1 + fi + + cd first-dir + + if [ -f imported-file1 ] ; then + echo "FAIL: test 114" | tee -a ${LOGFILE} ; exit 1 + else + echo "PASS: test 114" >>${LOGFILE} + fi + + for i in 2 3 ; do + if [ -f imported-file"$i" ] ; then + echo "PASS: test 115-$i" >>${LOGFILE} + else + echo "FAIL: test 115-$i" | tee -a ${LOGFILE} ; exit 1 + fi + done + + if cat imported-file2 | grep '====' >> ${LOGFILE}; then + echo "PASS: test 116" >>${LOGFILE} + else + echo "FAIL: test 116" | tee -a ${LOGFILE} ; exit 1 + fi + cd .. ; rm -rf first-dir ${CVSROOT_DIRNAME}/first-dir + rm -rf import-dir + ;; + + new) # look for stray "no longer pertinent" messages. + mkdir ${CVSROOT_DIRNAME}/first-dir + + if ${CVS} co first-dir ; then + echo "PASS: test 117" >>${LOGFILE} + else + echo "FAIL: test 117" | tee -a ${LOGFILE} ; exit 1 + fi + + cd first-dir + touch a + + if ${CVS} add a 2>>${LOGFILE}; then + echo "PASS: test 118" >>${LOGFILE} + else + echo "FAIL: test 118" | tee -a ${LOGFILE} ; exit 1 + fi + + if ${CVS} ci -m added >>${LOGFILE} 2>&1; then + echo "PASS: test 119" >>${LOGFILE} + else + echo "FAIL: test 119" | tee -a ${LOGFILE} ; exit 1 + fi + + rm a + + if ${CVS} rm a 2>>${LOGFILE}; then + echo "PASS: test 120" >>${LOGFILE} + else + echo "FAIL: test 120" | tee -a ${LOGFILE} ; exit 1 + fi + + if ${CVS} ci -m removed >>${LOGFILE} ; then + echo "PASS: test 121" >>${LOGFILE} + else + echo "FAIL: test 121" | tee -a ${LOGFILE} ; exit 1 + fi + + if ${CVS} update -A 2>&1 | grep longer ; then + echo "FAIL: test 122" | tee -a ${LOGFILE} ; exit 1 + else + echo "PASS: test 122" >>${LOGFILE} + fi + + if ${CVS} update -rHEAD 2>&1 | grep longer ; then + echo "FAIL: test 123" | tee -a ${LOGFILE} ; exit 1 + else + echo "PASS: test 123" >>${LOGFILE} + fi + + cd .. ; rm -rf first-dir ; rm -rf ${CVSROOT_DIRNAME}/first-dir + ;; + + conflicts) + rm -rf first-dir ${CVSROOT_DIRNAME}/first-dir + mkdir ${CVSROOT_DIRNAME}/first-dir + + mkdir 1 + cd 1 + + if ${CVS} co first-dir ; then + echo 'PASS: test 124' >>${LOGFILE} + else + echo 'FAIL: test 124' | tee -a ${LOGFILE} + fi + + cd first-dir + touch a + + if ${CVS} add a 2>>${LOGFILE} ; then + echo 'PASS: test 125' >>${LOGFILE} + else + echo 'FAIL: test 125' | tee -a ${LOGFILE} + fi + + if ${CVS} ci -m added >>${LOGFILE} 2>&1; then + echo 'PASS: test 126' >>${LOGFILE} + else + echo 'FAIL: test 126' | tee -a ${LOGFILE} + fi + + cd ../.. + mkdir 2 + cd 2 + + if ${CVS} co first-dir ; then + echo 'PASS: test 127' >>${LOGFILE} + else + echo 'FAIL: test 127' | tee -a ${LOGFILE} + fi + cd first-dir + if test -f a; then + echo 'PASS: test 127a' >>${LOGFILE} + else + echo 'FAIL: test 127a' | tee -a ${LOGFILE} + fi + + cd ../../1/first-dir + echo add a line >>a + mkdir dir1 + dotest conflicts-127b "${testcvs} add dir1" \ +'Directory /tmp/cvs-sanity/cvsroot/first-dir/dir1 added to the repository' + dotest conflicts-128 "${testcvs} -q ci -m changed" \ +'Checking in a; +/tmp/cvs-sanity/cvsroot/first-dir/a,v <-- a +new revision: 1.2; previous revision: 1.1 +done' + cd ../../2/first-dir + echo add a conflicting line >>a + dotest_fail conflicts-129 "${testcvs} -q ci -m changed" \ +"${PROG}"' [a-z]*: Up-to-date check failed for `a'\'' +'"${PROG}"' \[[a-z]* aborted\]: correct above errors first!' + mkdir dir1 + mkdir sdir + dotest conflicts-130 "${testcvs} -q update" \ +'RCS file: /tmp/cvs-sanity/cvsroot/first-dir/a,v +retrieving revision 1.1 +retrieving revision 1.2 +Merging differences between 1.1 and 1.2 into a +rcsmerge: warning: conflicts during merge +'"${PROG}"' [a-z]*: conflicts found in a +C a +'"${QUESTION}"' dir1 +'"${QUESTION}"' sdir' \ +''"${QUESTION}"' dir1 +'"${QUESTION}"' sdir +RCS file: /tmp/cvs-sanity/cvsroot/first-dir/a,v +retrieving revision 1.1 +retrieving revision 1.2 +Merging differences between 1.1 and 1.2 into a +rcsmerge: warning: conflicts during merge +'"${PROG}"' [a-z]*: conflicts found in a +C a' + + # Try to check in the file with the conflict markers in it. + if ${CVS} ci -m try 2>>${LOGFILE}; then + echo 'FAIL: test 131' | tee -a ${LOGFILE} + else + # Should tell us to resolve conflict first + echo 'PASS: test 131' >>${LOGFILE} + fi + + echo lame attempt at resolving it >>a + # Try to check in the file with the conflict markers in it. + if ${CVS} ci -m try >>${LOGFILE} 2>&1; then + echo 'FAIL: test 132' | tee -a ${LOGFILE} + else + # Should tell us to resolve conflict first + echo 'PASS: test 132' >>${LOGFILE} + fi + + echo resolve conflict >a + if ${CVS} ci -m resolved >>${LOGFILE} 2>&1; then + echo 'PASS: test 133' >>${LOGFILE} + else + echo 'FAIL: test 133' | tee -a ${LOGFILE} + fi + + # Now test that we can add a file in one working directory + # and have an update in another get it. + cd ../../1/first-dir + echo abc >abc + if ${testcvs} add abc >>${LOGFILE} 2>&1; then + echo 'PASS: test 134' >>${LOGFILE} + else + echo 'FAIL: test 134' | tee -a ${LOGFILE} + fi + if ${testcvs} ci -m 'add abc' abc >>${LOGFILE} 2>&1; then + echo 'PASS: test 135' >>${LOGFILE} + else + echo 'FAIL: test 135' | tee -a ${LOGFILE} + fi + cd ../../2 + dotest conflicts-136 "${testcvs} -q update" \ +'[UP] first-dir/abc +'"${QUESTION}"' first-dir/dir1 +'"${QUESTION}"' first-dir/sdir' \ +''"${QUESTION}"' first-dir/dir1 +'"${QUESTION}"' first-dir/sdir +[UP] first-dir/abc' + dotest conflicts-137 'test -f first-dir/abc' '' + rmdir first-dir/dir1 first-dir/sdir + + # Now test something similar, but in which the parent directory + # (not the directory in question) has the Entries.Static flag + # set. + cd ../1/first-dir + mkdir subdir + if ${testcvs} add subdir >>${LOGFILE}; then + echo 'PASS: test 138' >>${LOGFILE} + else + echo 'FAIL: test 138' | tee -a ${LOGFILE} + fi + cd ../.. + mkdir 3 + cd 3 + if ${testcvs} -q co first-dir/abc first-dir/subdir \ + >>${LOGFILE}; then + echo 'PASS: test 139' >>${LOGFILE} + else + echo 'FAIL: test 139' | tee -a ${LOGFILE} + fi + cd ../1/first-dir/subdir + echo sss >sss + if ${testcvs} add sss >>${LOGFILE} 2>&1; then + echo 'PASS: test 140' >>${LOGFILE} + else + echo 'FAIL: test 140' | tee -a ${LOGFILE} + fi + if ${testcvs} ci -m adding sss >>${LOGFILE} 2>&1; then + echo 'PASS: test 140' >>${LOGFILE} + else + echo 'FAIL: test 140' | tee -a ${LOGFILE} + fi + cd ../../../3/first-dir + if ${testcvs} -q update >>${LOGFILE}; then + echo 'PASS: test 141' >>${LOGFILE} + else + echo 'FAIL: test 141' | tee -a ${LOGFILE} + fi + if test -f subdir/sss; then + echo 'PASS: test 142' >>${LOGFILE} + else + echo 'FAIL: test 142' | tee -a ${LOGFILE} + fi + + cd ../.. + rm -rf 1 2 3 ; rm -rf ${CVSROOT_DIRNAME}/first-dir + ;; + modules) + rm -rf first-dir ${CVSROOT_DIRNAME}/first-dir + mkdir ${CVSROOT_DIRNAME}/first-dir + + mkdir 1 + cd 1 + + if ${testcvs} -q co first-dir; then + echo 'PASS: test 143' >>${LOGFILE} + else + echo 'FAIL: test 143' | tee -a ${LOGFILE} + exit 1 + fi + + cd first-dir + mkdir subdir + ${testcvs} add subdir >>${LOGFILE} + cd subdir + + mkdir ssdir + ${testcvs} add ssdir >>${LOGFILE} + + touch a b + + if ${testcvs} add a b 2>>${LOGFILE} ; then + echo 'PASS: test 144' >>${LOGFILE} + else + echo 'FAIL: test 144' | tee -a ${LOGFILE} + exit 1 + fi + + if ${testcvs} ci -m added >>${LOGFILE} 2>&1; then + echo 'PASS: test 145' >>${LOGFILE} + else + echo 'FAIL: test 145' | tee -a ${LOGFILE} + exit 1 + fi + + cd .. + if ${testcvs} -q co CVSROOT >>${LOGFILE}; then + echo 'PASS: test 146' >>${LOGFILE} + else + echo 'FAIL: test 146' | tee -a ${LOGFILE} + exit 1 + fi + + # Here we test that CVS can deal with CVSROOT (whose repository + # is at top level) in the same directory as subdir (whose repository + # is a subdirectory of first-dir). TODO: Might want to check that + # files can actually get updated in this state. + if ${testcvs} -q update; then + echo 'PASS: test 147' >>${LOGFILE} + else + echo 'FAIL: test 147' | tee -a ${LOGFILE} + exit 1 + fi + + echo realmodule first-dir/subdir a >>CVSROOT/modules + echo dirmodule first-dir/subdir >>CVSROOT/modules + echo namedmodule -d nameddir first-dir/subdir >>CVSROOT/modules + echo aliasmodule -a first-dir/subdir/a >>CVSROOT/modules + echo aliasnested -a first-dir/subdir/ssdir >>CVSROOT/modules + + # Options must come before arguments. It is possible this should + # be relaxed at some point (though the result would be bizarre for + # -a); for now test the current behavior. + echo bogusalias first-dir/subdir/a -a >>CVSROOT/modules + if ${testcvs} ci -m 'add modules' CVSROOT/modules \ + >>${LOGFILE} 2>&1; then + echo 'PASS: test 148' >>${LOGFILE} + else + echo 'FAIL: test 148' | tee -a ${LOGFILE} + exit 1 + fi + cd .. + dotest 148a0 "${testcvs} co -c" 'CVSROOT CVSROOT +aliasmodule -a first-dir/subdir/a +aliasnested -a first-dir/subdir/ssdir +bogusalias first-dir/subdir/a -a +dirmodule first-dir/subdir +namedmodule -d nameddir first-dir/subdir +realmodule first-dir/subdir a' + # I don't know why aliasmodule isn't printed (I would have thought + # that it gets printed without the -a; although I'm not sure that + # printing expansions without options is useful). + dotest 148a1 "${testcvs} co -s" 'CVSROOT NONE CVSROOT +bogusalias NONE first-dir/subdir/a -a +dirmodule NONE first-dir/subdir +namedmodule NONE first-dir/subdir +realmodule NONE first-dir/subdir a' + + # Test that real modules check out to realmodule/a, not subdir/a. + if ${testcvs} co realmodule >>${LOGFILE}; then + echo 'PASS: test 149a1' >>${LOGFILE} + else + echo 'FAIL: test 149a1' | tee -a ${LOGFILE} + exit 1 + fi + if test -d realmodule && test -f realmodule/a; then + echo 'PASS: test 149a2' >>${LOGFILE} + else + echo 'FAIL: test 149a2' | tee -a ${LOGFILE} + exit 1 + fi + if test -f realmodule/b; then + echo 'FAIL: test 149a3' | tee -a ${LOGFILE} + exit 1 + else + echo 'PASS: test 149a3' >>${LOGFILE} + fi + if ${testcvs} -q co realmodule; then + echo 'PASS: test 149a4' >>${LOGFILE} + else + echo 'FAIL: test 149a4' | tee -a ${LOGFILE} + exit 1 + fi + if echo "yes" | ${testcvs} release -d realmodule >>${LOGFILE} ; then + echo 'PASS: test 149a5' >>${LOGFILE} + else + echo 'FAIL: test 149a5' | tee -a ${LOGFILE} + exit 1 + fi + + # Now test the ability to check out a single file from a directory + if ${testcvs} co dirmodule/a >>${LOGFILE}; then + echo 'PASS: test 150c' >>${LOGFILE} + else + echo 'FAIL: test 150c' | tee -a ${LOGFILE} + exit 1 + fi + if test -d dirmodule && test -f dirmodule/a; then + echo 'PASS: test 150d' >>${LOGFILE} + else + echo 'FAIL: test 150d' | tee -a ${LOGFILE} + exit 1 + fi + if test -f dirmodule/b; then + echo 'FAIL: test 150e' | tee -a ${LOGFILE} + exit 1 + else + echo 'PASS: test 150e' >>${LOGFILE} + fi + if echo "yes" | ${testcvs} release -d dirmodule >>${LOGFILE} ; then + echo 'PASS: test 150f' >>${LOGFILE} + else + echo 'FAIL: test 150f' | tee -a ${LOGFILE} + exit 1 + fi + # Now test the ability to correctly reject a non-existent filename. + # For maximum studliness we would check that an error message is + # being output. + if ${testcvs} co dirmodule/nonexist >>${LOGFILE} 2>&1; then + # We accept a zero exit status because it is what CVS does + # (Dec 95). Probably the exit status should be nonzero, + # however. + echo 'PASS: test 150g1' >>${LOGFILE} + else + echo 'PASS: test 150g1' >>${LOGFILE} + fi + # We tolerate the creation of the dirmodule directory, since that + # is what CVS does, not because we view that as preferable to not + # creating it. + if test -f dirmodule/a || test -f dirmodule/b; then + echo 'FAIL: test 150g2' | tee -a ${LOGFILE} + exit 1 + else + echo 'PASS: test 150g2' >>${LOGFILE} + fi + rm -rf dirmodule + + # Now test that a module using -d checks out to the specified + # directory. + dotest 150h1 "${testcvs} -q co namedmodule" 'U nameddir/a +U nameddir/b' + if test -f nameddir/a && test -f nameddir/b; then + pass 150h2 + else + fail 150h2 + fi + echo add line >>nameddir/a + dotest 150h3 "${testcvs} -q co namedmodule" 'M nameddir/a' + rm nameddir/a + dotest 150h4 "${testcvs} -q co namedmodule" 'U nameddir/a' + if echo "yes" | ${testcvs} release -d nameddir >>${LOGFILE} ; then + pass 150h99 + else + fail 150h99 + fi + + # Now test that alias modules check out to subdir/a, not + # aliasmodule/a. + if ${testcvs} co aliasmodule >>${LOGFILE}; then + echo 'PASS: test 151' >>${LOGFILE} + else + echo 'FAIL: test 151' | tee -a ${LOGFILE} + exit 1 + fi + if test -d aliasmodule; then + echo 'FAIL: test 152' | tee -a ${LOGFILE} + exit 1 + else + echo 'PASS: test 152' >>${LOGFILE} + fi + echo abc >>first-dir/subdir/a + if (${testcvs} -q co aliasmodule | tee test153.tmp) \ + >>${LOGFILE}; then + echo 'PASS: test 153' >>${LOGFILE} + else + echo 'FAIL: test 153' | tee -a ${LOGFILE} + exit 1 + fi + echo 'M first-dir/subdir/a' >ans153.tmp + if cmp test153.tmp ans153.tmp; then + echo 'PASS: test 154' >>${LOGFILE} + else + echo 'FAIL: test 154' | tee -a ${LOGFILE} + exit 1 + fi + + cd .. + rm -rf 1 + + mkdir 2 + cd 2 + dotest modules-155a0 "${testcvs} co aliasnested" \ +"${PROG} [a-z]*: Updating first-dir/subdir/ssdir" + dotest modules-155a1 "test -d first-dir" '' + dotest modules-155a2 "test -d first-dir/subdir" '' + dotest modules-155a3 "test -d first-dir/subdir/ssdir" '' + # Test that nothing extraneous got created. + dotest modules-155a4 "ls -1" "first-dir" + cd .. + rm -rf 2 + + rm -rf ${CVSROOT_DIRNAME}/first-dir + ;; + mflag) + for message in '' ' ' ' + ' ' test' ; do + # Set up + mkdir a-dir; cd a-dir + # Test handling of -m during import + echo testa >>test + if ${testcvs} import -m "$message" a-dir A A1 >>${LOGFILE} 2>&1;then + echo 'PASS: test 156' >>${LOGFILE} + else + echo 'FAIL: test 156' | tee -a ${LOGFILE} + exit 1 + fi + # Must import twice since the first time uses inline code that + # avoids RCS call. + echo testb >>test + if ${testcvs} import -m "$message" a-dir A A2 >>${LOGFILE} 2>&1;then + echo 'PASS: test 157' >>${LOGFILE} + else + echo 'FAIL: test 157' | tee -a ${LOGFILE} + exit 1 + fi + # Test handling of -m during ci + cd ..; rm -rf a-dir; + if ${testcvs} co a-dir >>${LOGFILE} 2>&1; then + echo 'PASS: test 158' >>${LOGFILE} + else + echo 'FAIL: test 158' | tee -a ${LOGFILE} + exit 1 + fi + cd a-dir + echo testc >>test + if ${testcvs} ci -m "$message" >>${LOGFILE} 2>&1; then + echo 'PASS: test 159' >>${LOGFILE} + else + echo 'FAIL: test 159' | tee -a ${LOGFILE} + exit 1 + fi + # Test handling of -m during rm/ci + rm test; + if ${testcvs} rm test >>${LOGFILE} 2>&1; then + echo 'PASS: test 160' >>${LOGFILE} + else + echo 'FAIL: test 160' | tee -a ${LOGFILE} + exit 1 + fi + if ${testcvs} ci -m "$message" >>${LOGFILE} 2>&1; then + echo 'PASS: test 161' >>${LOGFILE} + else + echo 'FAIL: test 161' | tee -a ${LOGFILE} + exit 1 + fi + # Clean up + cd ..; rm -rf a-dir ${CVSROOT_DIRNAME}/a-dir + done + ;; + errmsg1) + mkdir ${CVSROOT_DIRNAME}/1dir + mkdir 1 + cd 1 + if ${testcvs} -q co 1dir; then + echo 'PASS: test 162' >>${LOGFILE} + else + echo 'FAIL: test 162' | tee -a ${LOGFILE} + exit 1 + fi + cd 1dir + touch foo + if ${testcvs} add foo 2>>${LOGFILE}; then + echo 'PASS: test 163' >>${LOGFILE} + else + echo 'FAIL: test 163' | tee -a ${LOGFILE} + exit 1 + fi + if ${testcvs} ci -m added >>${LOGFILE} 2>&1; then + echo 'PASS: test 164' >>${LOGFILE} + else + echo 'FAIL: test 164' | tee -a ${LOGFILE} + exit 1 + fi + cd ../.. + mkdir 2 + cd 2 + if ${testcvs} -q co 1dir >>${LOGFILE}; then + echo 'PASS: test 165' >>${LOGFILE} + else + echo 'FAIL: test 165' | tee -a ${LOGFILE} + exit 1 + fi + chmod a-w 1dir + cd ../1/1dir + rm foo; + if ${testcvs} rm foo >>${LOGFILE} 2>&1; then + echo 'PASS: test 166' >>${LOGFILE} + else + echo 'FAIL: test 166' | tee -a ${LOGFILE} + exit 1 + fi + if ${testcvs} ci -m removed >>${LOGFILE} 2>&1; then + echo 'PASS: test 167' >>${LOGFILE} + else + echo 'FAIL: test 167' | tee -a ${LOGFILE} + exit 1 + fi + + cd ../../2/1dir + # FIXME: should be using dotest and PROG. + ${testcvs} -q update 2>../tst167.err + CVSBASE=`basename $testcvs` # Get basename of CVS executable. + cat <<EOF >../tst167.ans +$CVSBASE server: warning: foo is not (any longer) pertinent +$CVSBASE update: unable to remove ./foo: Permission denied +EOF + if cmp ../tst167.ans ../tst167.err >/dev/null || + ( echo "$CVSBASE [update aborted]: cannot rename file foo to CVS/,,foo: Permission denied" | cmp - ../tst167.err >/dev/null ) + then + echo 'PASS: test 168' >>${LOGFILE} + else + echo 'FAIL: test 168' | tee -a ${LOGFILE} + exit 1 + fi + + cd .. + chmod u+w 1dir + cd .. + rm -rf 1 2 ${CVSROOT_DIRNAME}/1dir + ;; + + devcom) + mkdir ${CVSROOT_DIRNAME}/first-dir + mkdir 1 + cd 1 + if ${testcvs} -q co first-dir >>${LOGFILE} ; then + echo 'PASS: test 169' >>${LOGFILE} + else + echo 'FAIL: test 169' | tee -a ${LOGFILE} + exit 1 + fi + + cd first-dir + echo abb >abb + if ${testcvs} add abb 2>>${LOGFILE}; then + echo 'PASS: test 170' >>${LOGFILE} + else + echo 'FAIL: test 170' | tee -a ${LOGFILE} + exit 1 + fi + if ${testcvs} ci -m added >>${LOGFILE} 2>&1; then + echo 'PASS: test 171' >>${LOGFILE} + else + echo 'FAIL: test 171' | tee -a ${LOGFILE} + exit 1 + fi + dotest_fail 171a0 "${testcvs} watch" "Usage${DOTSTAR}" + if ${testcvs} watch on; then + echo 'PASS: test 172' >>${LOGFILE} + else + echo 'FAIL: test 172' | tee -a ${LOGFILE} + fi + echo abc >abc + if ${testcvs} add abc 2>>${LOGFILE}; then + echo 'PASS: test 173' >>${LOGFILE} + else + echo 'FAIL: test 173' | tee -a ${LOGFILE} + fi + if ${testcvs} ci -m added >>${LOGFILE} 2>&1; then + echo 'PASS: test 174' >>${LOGFILE} + else + echo 'FAIL: test 174' | tee -a ${LOGFILE} + fi + + cd ../.. + mkdir 2 + cd 2 + + if ${testcvs} -q co first-dir >>${LOGFILE}; then + echo 'PASS: test 175' >>${LOGFILE} + else + echo 'FAIL: test 175' | tee -a ${LOGFILE} + fi + cd first-dir + if test -w abb; then + echo 'FAIL: test 176' | tee -a ${LOGFILE} + else + echo 'PASS: test 176' >>${LOGFILE} + fi + if test -w abc; then + echo 'FAIL: test 177' | tee -a ${LOGFILE} + else + echo 'PASS: test 177' >>${LOGFILE} + fi + + if ${testcvs} editors >../ans178.tmp; then + echo 'PASS: test 178' >>${LOGFILE} + else + echo 'FAIL: test 178' | tee -a ${LOGFILE} + fi + cat ../ans178.tmp >>${LOGFILE} + if test -s ../ans178.tmp; then + echo 'FAIL: test 178a' | tee -a ${LOGFILE} + else + echo 'PASS: test 178a' >>${LOGFILE} + fi + + if ${testcvs} edit abb; then + echo 'PASS: test 179' >>${LOGFILE} + else + echo 'FAIL: test 179' | tee -a ${LOGFILE} + exit 1 + fi + + if ${testcvs} editors >../ans180.tmp; then + echo 'PASS: test 180' >>${LOGFILE} + else + echo 'FAIL: test 180' | tee -a ${LOGFILE} + exit 1 + fi + cat ../ans180.tmp >>${LOGFILE} + if test -s ../ans180.tmp; then + echo 'PASS: test 181' >>${LOGFILE} + else + echo 'FAIL: test 181' | tee -a ${LOGFILE} + fi + + echo aaaa >>abb + if ${testcvs} ci -m modify abb >>${LOGFILE} 2>&1; then + echo 'PASS: test 182' >>${LOGFILE} + else + echo 'FAIL: test 182' | tee -a ${LOGFILE} + fi + # Unedit of a file not being edited should be a noop. + dotest 182.5 "${testcvs} unedit abb" '' + + if ${testcvs} editors >../ans183.tmp; then + echo 'PASS: test 183' >>${LOGFILE} + else + echo 'FAIL: test 183' | tee -a ${LOGFILE} + fi + cat ../ans183.tmp >>${LOGFILE} + if test -s ../ans183.tmp; then + echo 'FAIL: test 184' | tee -a ${LOGFILE} + else + echo 'PASS: test 184' >>${LOGFILE} + fi + + if test -w abb; then + echo 'FAIL: test 185' | tee -a ${LOGFILE} + else + echo 'PASS: test 185' >>${LOGFILE} + fi + + if ${testcvs} edit abc; then + echo 'PASS: test 186a1' >>${LOGFILE} + else + echo 'FAIL: test 186a1' | tee -a ${LOGFILE} + fi + # Unedit of an unmodified file. + if ${testcvs} unedit abc; then + echo 'PASS: test 186a2' >>${LOGFILE} + else + echo 'FAIL: test 186a2' | tee -a ${LOGFILE} + fi + if ${testcvs} edit abc; then + echo 'PASS: test 186a3' >>${LOGFILE} + else + echo 'FAIL: test 186a3' | tee -a ${LOGFILE} + fi + echo changedabc >abc + # Try to unedit a modified file; cvs should ask for confirmation + if (echo no | ${testcvs} unedit abc) >>${LOGFILE}; then + echo 'PASS: test 186a4' >>${LOGFILE} + else + echo 'FAIL: test 186a4' | tee -a ${LOGFILE} + fi + if echo changedabc | cmp - abc; then + echo 'PASS: test 186a5' >>${LOGFILE} + else + echo 'FAIL: test 186a5' | tee -a ${LOGFILE} + fi + # OK, now confirm the unedit + if (echo yes | ${testcvs} unedit abc) >>${LOGFILE}; then + echo 'PASS: test 186a6' >>${LOGFILE} + else + echo 'FAIL: test 186a6' | tee -a ${LOGFILE} + fi + if echo abc | cmp - abc; then + echo 'PASS: test 186a7' >>${LOGFILE} + else + echo 'FAIL: test 186a7' | tee -a ${LOGFILE} + fi + + dotest devcom-a0 "${testcvs} watchers" '' + dotest devcom-a1 "${testcvs} watch add" '' + dotest devcom-a2 "${testcvs} watchers" \ +'abb [a-z0-9]* edit unedit commit +abc [a-z0-9]* edit unedit commit' + dotest devcom-a3 "${testcvs} watch remove -a unedit abb" '' + dotest devcom-a4 "${testcvs} watchers abb" \ +'abb [a-z0-9]* edit commit' + + cd ../.. + rm -rf 1 2 ${CVSROOT_DIRNAME}/first-dir + ;; + + ignore) + dotest 187a1 "${testcvs} -q co CVSROOT" 'U CVSROOT/modules' + cd CVSROOT + echo rootig.c >cvsignore + dotest 187a2 "${testcvs} add cvsignore" "${PROG}"' [a-z]*: scheduling file `cvsignore'"'"' for addition +'"${PROG}"' [a-z]*: use '"'"'cvs commit'"'"' to add this file permanently' + + # As of Jan 96, local CVS prints "Examining ." and remote doesn't. + # Accept either. + dotest 187a3 " ${testcvs} ci -m added" \ +"${DOTSTAR}"'CS file: /tmp/cvs-sanity/cvsroot/CVSROOT/cvsignore,v +done +Checking in cvsignore; +/tmp/cvs-sanity/cvsroot/CVSROOT/cvsignore,v <-- cvsignore +initial revision: 1.1 +done +'"${PROG}"' [a-z]*: Rebuilding administrative file database' + + cd .. + if echo "yes" | ${testcvs} release -d CVSROOT >>${LOGFILE} ; then + echo 'PASS: test 187a4' >>${LOGFILE} + else + echo 'FAIL: test 187a4' | tee -a ${LOGFILE} + exit 1 + fi + + # CVS looks at the home dir from getpwuid, not HOME (is that correct + # behavior?), so this is hard to test and we won't try. + # echo foobar.c >${HOME}/.cvsignore + CVSIGNORE=envig.c; export CVSIGNORE + mkdir dir-to-import + cd dir-to-import + touch foobar.c bar.c rootig.c defig.o envig.c optig.c + # We really should allow the files to be listed in any order. + # But we (kludgily) just list the orders which have been observed. + dotest 188a "${testcvs} import -m m -I optig.c first-dir tag1 tag2" \ + 'N first-dir/foobar.c +N first-dir/bar.c +I first-dir/rootig.c +I first-dir/defig.o +I first-dir/envig.c +I first-dir/optig.c + +No conflicts created by this import' 'I first-dir/defig.o +I first-dir/envig.c +I first-dir/optig.c +N first-dir/foobar.c +N first-dir/bar.c +I first-dir/rootig.c + +No conflicts created by this import' + dotest 188b "${testcvs} import -m m -I ! second-dir tag3 tag4" \ + 'N second-dir/foobar.c +N second-dir/bar.c +N second-dir/rootig.c +N second-dir/defig.o +N second-dir/envig.c +N second-dir/optig.c + +No conflicts created by this import' + cd .. + rm -rf dir-to-import + + dotest 189a "${testcvs} -q co second-dir" \ +'U second-dir/bar.c +U second-dir/defig.o +U second-dir/envig.c +U second-dir/foobar.c +U second-dir/optig.c +U second-dir/rootig.c' + rm -rf second-dir + dotest 189b "${testcvs} -q co first-dir" 'U first-dir/bar.c +U first-dir/foobar.c' + cd first-dir + touch rootig.c defig.o envig.c optig.c notig.c + dotest 189c "${testcvs} -q update -I optig.c" "${QUESTION} notig.c" + # The fact that CVS requires us to specify -I CVS here strikes me + # as a bug. + dotest 189d "${testcvs} -q update -I ! -I CVS" "${QUESTION} rootig.c +${QUESTION} defig.o +${QUESTION} envig.c +${QUESTION} optig.c +${QUESTION} notig.c" + cd .. + rm -rf first-dir + + rm -rf ${CVSROOT_DIRNAME}/first-dir ${CVSROOT_DIRNAME}/second-dir + ;; + + binfiles) + # Test cvs's ability to handle binary files. + mkdir ${CVSROOT_DIRNAME}/first-dir + mkdir 1; cd 1 + dotest binfiles-1 "${testcvs} -q co first-dir" '' + awk 'BEGIN { printf "%c%c%c%c%c%c", 2, 10, 137, 0, 13, 10 }' \ + </dev/null >binfile.dat + cat binfile.dat binfile.dat >binfile2.dat + cd first-dir + cp ../binfile.dat binfile + dotest binfiles-2 "${testcvs} add -kb binfile" \ +"${PROG}"' [a-z]*: scheduling file `binfile'\'' for addition +'"${PROG}"' [a-z]*: use '\''cvs commit'\'' to add this file permanently' + dotest binfiles-3 "${testcvs} -q ci -m add-it" \ +'RCS file: /tmp/cvs-sanity/cvsroot/first-dir/binfile,v +done +Checking in binfile; +/tmp/cvs-sanity/cvsroot/first-dir/binfile,v <-- binfile +initial revision: 1.1 +done' + cd ../.. + mkdir 2; cd 2 + dotest binfiles-4 "${testcvs} -q co first-dir" 'U first-dir/binfile' + cd first-dir + dotest binfiles-5 "cmp ../../1/binfile.dat binfile" '' + # Testing that sticky options is -kb is the closest thing we have + # to testing that binary files work right on non-unix machines + # (until there is automated testing for such machines, of course). + dotest binfiles-5.5 "${testcvs} status binfile" \ +'=================================================================== +File: binfile Status: Up-to-date + + Working revision: 1\.1.* + Repository revision: 1\.1 /tmp/cvs-sanity/cvsroot/first-dir/binfile,v + Sticky Tag: (none) + Sticky Date: (none) + Sticky Options: -kb' + cp ../../1/binfile2.dat binfile + dotest binfiles-6 "${testcvs} -q ci -m modify-it" \ +'Checking in binfile; +/tmp/cvs-sanity/cvsroot/first-dir/binfile,v <-- binfile +new revision: 1.2; previous revision: 1.1 +done' + cd ../../1/first-dir + dotest binfiles-7 "${testcvs} -q update" '[UP] binfile' + dotest binfiles-8 "cmp ../binfile2.dat binfile" '' + + # The bugs which these test for are apparently not fixed for remote. + if test "$remote" = no; then + dotest binfiles-9 "${testcvs} -q update -A" '' + dotest binfiles-10 "${testcvs} -q update -kk" '[UP] binfile' + dotest binfiles-11 "${testcvs} -q update" '' + dotest binfiles-12 "${testcvs} -q update -A" '[UP] binfile' + dotest binfiles-13 "${testcvs} -q update -A" '' + fi + + cd ../../2/first-dir + echo 'this file is $''RCSfile$' >binfile + dotest binfiles-14a "${testcvs} -q ci -m modify-it" \ +'Checking in binfile; +/tmp/cvs-sanity/cvsroot/first-dir/binfile,v <-- binfile +new revision: 1.3; previous revision: 1.2 +done' + dotest binfiles-14b "cat binfile" 'this file is $''RCSfile$' + # See binfiles-5.5 for discussion of -kb. + dotest binfiles-14c "${testcvs} status binfile" \ +'=================================================================== +File: binfile Status: Up-to-date + + Working revision: 1\.3.* + Repository revision: 1\.3 /tmp/cvs-sanity/cvsroot/first-dir/binfile,v + Sticky Tag: (none) + Sticky Date: (none) + Sticky Options: -kb' + dotest binfiles-14d "${testcvs} admin -kv binfile" \ +'RCS file: /tmp/cvs-sanity/cvsroot/first-dir/binfile,v +done' + # cvs admin doesn't change the checked-out file or its sticky + # kopts. There probably should be a way which does (but + # what if the file is modified? And do we try to version + # control the kopt setting?) + dotest binfiles-14e "cat binfile" 'this file is $''RCSfile$' + dotest binfiles-14f "${testcvs} status binfile" \ +'=================================================================== +File: binfile Status: Up-to-date + + Working revision: 1\.3.* + Repository revision: 1\.3 /tmp/cvs-sanity/cvsroot/first-dir/binfile,v + Sticky Tag: (none) + Sticky Date: (none) + Sticky Options: -kb' + dotest binfiles-14g "${testcvs} -q update -A" '[UP] binfile' + dotest binfiles-14h "cat binfile" 'this file is binfile,v' + dotest binfiles-14i "${testcvs} status binfile" \ +'=================================================================== +File: binfile Status: Up-to-date + + Working revision: 1\.3.* + Repository revision: 1\.3 /tmp/cvs-sanity/cvsroot/first-dir/binfile,v + Sticky Tag: (none) + Sticky Date: (none) + Sticky Options: -kv' + + cd ../.. + rm -rf ${CVSROOT_DIRNAME}/first-dir + rm -r 1 2 + ;; + info) + # Test CVS's ability to handle *info files. + dotest info-1 "${testcvs} -q co CVSROOT" "[UP] CVSROOT${DOTSTAR}" + cd CVSROOT + echo "ALL sh -c \"echo x\${=MYENV}\${=OTHER}y\${=ZEE}=\$USER=\$CVSROOT= >>$TESTDIR/testlog; cat >/dev/null\"" > loginfo + dotest info-2 "${testcvs} add loginfo" \ +"${PROG}"' [a-z]*: scheduling file `loginfo'"'"' for addition +'"${PROG}"' [a-z]*: use '"'"'cvs commit'"'"' to add this file permanently' + dotest info-3 "${testcvs} -q ci -m new-loginfo" \ +'RCS file: /tmp/cvs-sanity/cvsroot/CVSROOT/loginfo,v +done +Checking in loginfo; +/tmp/cvs-sanity/cvsroot/CVSROOT/loginfo,v <-- loginfo +initial revision: 1.1 +done +'"${PROG}"' [a-z]*: Rebuilding administrative file database' + cd .. + if echo "yes" | ${testcvs} release -d CVSROOT >>${LOGFILE} ; then + pass info-4 + else + fail info-4 + fi + + mkdir ${CVSROOT_DIRNAME}/first-dir + dotest info-5 "${testcvs} -q co first-dir" '' + cd first-dir + touch file1 + dotest info-6 "${testcvs} add file1" \ +"${PROG}"' [a-z]*: scheduling file `file1'\'' for addition +'"${PROG}"' [a-z]*: use '\''cvs commit'\'' to add this file permanently' + echo "cvs -s OTHER=not-this -s MYENV=env-" >>$HOME/.cvsrc + dotest info-6a "${testcvs} -q -s OTHER=value ci -m add-it" \ +'RCS file: /tmp/cvs-sanity/cvsroot/first-dir/file1,v +done +Checking in file1; +/tmp/cvs-sanity/cvsroot/first-dir/file1,v <-- file1 +initial revision: 1.1 +done +'"${PROG}"' [a-z]*: loginfo:1: no such user variable ${=ZEE}' + echo line1 >>file1 + dotest info-7 "${testcvs} -q -s OTHER=value -s ZEE=z ci -m mod-it" \ +'Checking in file1; +/tmp/cvs-sanity/cvsroot/first-dir/file1,v <-- file1 +new revision: 1.2; previous revision: 1.1 +done' + cd .. + if echo "yes" | ${testcvs} release -d first-dir >>${LOGFILE} ; then + pass info-8 + else + fail info-8 + fi + dotest info-9 "cat $TESTDIR/testlog" 'xenv-valueyz=[a-z@][a-z@]*=/tmp/cvs-sanity/cvsroot=' + + # I think this might be doable with cvs remove, or at least + # checking in a version with only comments, but I'm too lazy + # at the moment. Blow it away. + rm -f ${CVSROOT_DIRNAME}/CVSROOT/loginfo* + + rm -rf ${CVSROOT_DIRNAME}/first-dir + ;; + *) + echo $what is not the name of a test -- ignored + ;; + esac +done + +echo "OK, all tests completed." + +# TODO: +# * Test `cvs admin'. +# * Test `cvs update -d foo' (where foo does not exist). +# * Test `cvs update foo bar' (where foo and bar are both from the same +# repository). Suppose one is a branch--make sure that both directories +# get updated with the respective correct thing. +# * `cvs update ../foo'. Also ../../foo ./../foo foo/../../bar /foo/bar +# foo/.././../bar foo/../bar etc. +# * Test all flags in modules file. +# Test that ciprog gets run both on checkin in that directory, or a +# higher-level checkin which recurses into it. +# * Test that $ followed by "Header" followed by $ gets expanded on checkin. +# * Test operations on a directory that contains other directories but has +# no files of its own. +# * -t global option +# * cvs rm followed by cvs add or vice versa (with no checkin in between). +# * cvs rm twice (should be a nice error message). +# * -P option to checkout--(a) refrains from checking out new empty dirs, +# (b) prunes empty dirs already there. +# * Test that cvs -d `hostname`:/tmp/cvs-sanity/non/existent co foo +# gives an appropriate error (e.g. +# Cannot access /tmp/cvs-sanity/non-existent/CVSROOT +# No such file or directory). +# * Test ability to send notifications in response to watches. (currently +# hard to test because CVS doesn't send notifications if username is the +# same). +# * Test that remote edit and/or unedit works when disconnected from +# server (e.g. set CVS_SERVER to "foobar"). +# End of TODO list. + +# Remove the test directory, but first change out of it. +cd /tmp +rm -rf ${TESTDIR} + +# end of sanity.sh diff --git a/contrib/cvs/src/scramble.c b/contrib/cvs/src/scramble.c new file mode 100644 index 0000000..07094a6 --- /dev/null +++ b/contrib/cvs/src/scramble.c @@ -0,0 +1,246 @@ +/* + * Trivially encode strings to protect them from innocent eyes (i.e., + * inadvertent password compromises, like a network administrator + * who's watching packets for legitimate reasons and accidentally sees + * the password protocol go by). + * + * This is NOT secure encryption. + * + * It would be tempting to encode the password according to username + * and repository, so that the same password would encode to a + * different string when used with different usernames and/or + * repositories. However, then users would not be able to cut and + * paste passwords around. They're not supposed to anyway, but we all + * know they will, and there's no reason to make it harder for them if + * we're not trying to provide real security anyway. + */ + +/* Set this to test as a standalone program. */ +/* #define DIAGNOSTIC */ + +#ifndef DIAGNOSTIC +#include "cvs.h" +#else /* ! DIAGNOSTIC */ +/* cvs.h won't define this for us */ +#define AUTH_CLIENT_SUPPORT +#define xmalloc malloc +/* Use "gcc -fwritable-strings". */ +#include <stdio.h> +#include <stdio.h> +#include <string.h> +#endif /* ! DIAGNOSTIC */ + +#if defined(AUTH_CLIENT_SUPPORT) || defined(AUTH_SERVER_SUPPORT) + +/* Map characters to each other randomly and symmetrically, A <--> B. + * + * We divide the ASCII character set into 3 domains: control chars (0 + * thru 31), printing chars (32 through 126), and "meta"-chars (127 + * through 255). The control chars map _to_ themselves, the printing + * chars map _among_ themselves, and the meta chars map _among_ + * themselves. Why is this thus? + * + * No character in any of these domains maps to a character in another + * domain, because I'm not sure what characters are legal in + * passwords, or what tools people are likely to use to cut and paste + * them. It seems prudent not to introduce control or meta chars, + * unless the user introduced them first. And having the control + * chars all map to themselves insures that newline and + * carriage-return are safely handled. + */ + +static unsigned char +shifts[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, +17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 114, 120, +53, 79, 96, 109, 72, 108, 70, 64, 76, 67, 116, 74, 68, 87, 111, 52, +75, 119, 49, 34, 82, 81, 95, 65, 112, 86, 118, 110, 122, 105, 41, 57, +83, 43, 46, 102, 40, 89, 38, 103, 45, 50, 42, 123, 91, 35, 125, 55, +54, 66, 124, 126, 59, 47, 92, 71, 115, 78, 88, 107, 106, 56, 36, 121, +117, 104, 101, 100, 69, 73, 99, 63, 94, 93, 39, 37, 61, 48, 58, 113, +32, 90, 44, 98, 60, 51, 33, 97, 62, 77, 84, 80, 85, 223, 225, 216, +187, 166, 229, 189, 222, 188, 141, 249, 148, 200, 184, 136, 248, 190, +199, 170, 181, 204, 138, 232, 218, 183, 255, 234, 220, 247, 213, 203, +226, 193, 174, 172, 228, 252, 217, 201, 131, 230, 197, 211, 145, 238, +161, 179, 160, 212, 207, 221, 254, 173, 202, 146, 224, 151, 140, 196, +205, 130, 135, 133, 143, 246, 192, 159, 244, 239, 185, 168, 215, 144, +139, 165, 180, 157, 147, 186, 214, 176, 227, 231, 219, 169, 175, 156, +206, 198, 129, 164, 150, 210, 154, 177, 134, 127, 182, 128, 158, 208, +162, 132, 167, 209, 149, 241, 153, 251, 237, 236, 171, 195, 243, 233, +253, 240, 194, 250, 191, 155, 142, 137, 245, 235, 163, 242, 178, 152 }; + + +/* SCRAMBLE and DESCRAMBLE work like this: + * + * scramble(STR) returns SCRM, a scrambled copy of STR. SCRM[0] is a + * single letter indicating the scrambling method. As of this + * writing, the only legal method is 'A', but check the code for more + * up-to-date information. The copy will have been allocated with + * malloc(). + * + * descramble(SCRM) returns STR, again in its own malloc'd space. + * descramble() uses SCRM[0] to determine which method of unscrambling + * to use. If it does not recognize the method, it dies with error. + */ + +/* Return a malloc'd, scrambled version of STR. */ +char * +scramble (str) + char *str; +{ + int i; + char *s; + + /* +2 to hold the 'A' prefix that indicates which version of + * scrambling this is (the first, obviously, since we only do one + * kind of scrambling so far), and then the '\0' of course. + */ + s = (char *) xmalloc (strlen (str) + 2); + + s[0] = 'A'; /* Scramble (TM) version prefix. */ + strcpy (s + 1, str); + + for (i = 1; s[i]; i++) + s[i] = shifts[(unsigned char)(s[i])]; + + return s; +} + +/* Decode the string in place. */ +char * +descramble (str) + char *str; +{ + char *s; + int i; + + /* For now we can only handle one kind of scrambling. In the future + * there may be other kinds, and this `if' will become a `switch'. + */ + if (str[0] != 'A') +#ifndef DIAGNOSTIC + error (1, 0, "descramble: unknown scrambling method"); +#else /* DIAGNOSTIC */ + { + fprintf (stderr, "descramble: unknown scrambling method\n", str); + fflush (stderr); + exit (EXIT_FAILURE); + } +#endif /* DIAGNOSTIC */ + + /* Method `A' is symmetrical, so scramble again to decrypt. */ + s = scramble (str + 1); + + /* Shift the whole string one char to the left, pushing the unwanted + 'A' off the left end. Safe, because s is null-terminated. */ + for (i = 0; s[i]; i++) + s[i] = s[i + 1]; + + return s; +} + +#endif /* (AUTH_CLIENT_SUPPORT || AUTH_SERVER_SUPPORT) from top of file */ + +#ifdef DIAGNOSTIC +int +main () +{ + int i; + char *e, *m, biggie[256]; + + char *cleartexts[5]; + cleartexts[0] = "first"; + cleartexts[1] = "the second"; + cleartexts[2] = "this is the third"; + cleartexts[3] = "$#% !!\\3"; + cleartexts[4] = biggie; + + /* Set up the most important test string: */ + /* Can't have a real ASCII zero in the string, because we want to + use printf, so we substitute the character zero. */ + biggie[0] = '0'; + /* The rest of the string gets straight ascending ASCII. */ + for (i = 1; i < 256; i++) + biggie[i] = i; + + /* Test all the strings. */ + for (i = 0; i < 5; i++) + { + printf ("clear%d: %s\n", i, cleartexts[i]); + e = scramble (cleartexts[i]); + printf ("scram%d: %s\n", i, e); + m = descramble (e); + free (e); + printf ("clear%d: %s\n\n", i, m); + free (m); + } + + fflush (stdout); + return 0; +} +#endif /* DIAGNOSTIC */ + +/* + * ;;; The Emacs Lisp that did the dirty work ;;; + * (progn + * + * ;; Helper func. + * (defun random-elt (lst) + * (let* ((len (length lst)) + * (rnd (random len))) + * (nth rnd lst))) + * + * ;; A list of all characters under 127, each appearing once. + * (setq non-meta-chars + * (let ((i 0) + * (l nil)) + * (while (< i 127) + * (setq l (cons i l) + * i (1+ i))) + * l)) + * + * ;; A list of all characters 127 and above, each appearing once. + * (setq meta-chars + * (let ((i 127) + * (l nil)) + * (while (< i 256) + * (setq l (cons i l) + * i (1+ i))) + * l)) + * + * ;; A vector that will hold the chars in a random order. + * (setq scrambled-chars (make-vector 256 0)) + * + * ;; These characters should map to themselves. + * (let ((i 0)) + * (while (< i 32) + * (aset scrambled-chars i i) + * (setq non-meta-chars (delete i non-meta-chars) + * i (1+ i)))) + * + * ;; Assign random (but unique) values, within the non-meta chars. + * (let ((i 32)) + * (while (< i 127) + * (let ((ch (random-elt non-meta-chars))) + * (if (= 0 (aref scrambled-chars i)) + * (progn + * (aset scrambled-chars i ch) + * (aset scrambled-chars ch i) + * (setq non-meta-chars (delete ch non-meta-chars) + * non-meta-chars (delete i non-meta-chars)))) + * (setq i (1+ i))))) + * + * ;; Assign random (but unique) values, within the non-meta chars. + * (let ((i 127)) + * (while (< i 256) + * (let ((ch (random-elt meta-chars))) + * (if (= 0 (aref scrambled-chars i)) + * (progn + * (aset scrambled-chars i ch) + * (aset scrambled-chars ch i) + * (setq meta-chars (delete ch meta-chars) + * meta-chars (delete i meta-chars)))) + * (setq i (1+ i))))) + * + * ;; Now use the `scrambled-chars' vector to get your C array. + * ) + */ diff --git a/contrib/cvs/src/server.c b/contrib/cvs/src/server.c new file mode 100644 index 0000000..e92445b --- /dev/null +++ b/contrib/cvs/src/server.c @@ -0,0 +1,4642 @@ +#include <assert.h> +#include "cvs.h" +#include "watch.h" +#include "edit.h" +#include "fileattr.h" + +#ifdef SERVER_SUPPORT + +/* for select */ +#include <sys/types.h> +#ifdef HAVE_SYS_BSDTYPES_H +#include <sys/bsdtypes.h> +#endif +#include <sys/time.h> + +#if HAVE_SYS_SELECT_H +#include <sys/select.h> +#endif + +#if HAVE_FCNTL_H +#include <fcntl.h> +#endif + +#ifndef O_NONBLOCK +#define O_NONBLOCK O_NDELAY +#endif + +#ifdef AUTH_SERVER_SUPPORT +/* For initgroups(). */ +#if HAVE_INITGROUPS +#include <grp.h> +#endif /* HAVE_INITGROUPS */ +#endif /* AUTH_SERVER_SUPPORT */ + + +/* Functions which the server calls. */ +int add PROTO((int argc, char **argv)); +int admin PROTO((int argc, char **argv)); +int checkout PROTO((int argc, char **argv)); +int commit PROTO((int argc, char **argv)); +int diff PROTO((int argc, char **argv)); +int history PROTO((int argc, char **argv)); +int import PROTO((int argc, char **argv)); +int cvslog PROTO((int argc, char **argv)); +int patch PROTO((int argc, char **argv)); +int release PROTO((int argc, char **argv)); +int cvsremove PROTO((int argc, char **argv)); +int rtag PROTO((int argc, char **argv)); +int status PROTO((int argc, char **argv)); +int tag PROTO((int argc, char **argv)); +int update PROTO((int argc, char **argv)); + + +/* + * This is where we stash stuff we are going to use. Format string + * which expects a single directory within it, starting with a slash. + */ +static char *server_temp_dir; + +/* Nonzero if we should keep the temp directory around after we exit. */ +static int dont_delete_temp; + +static char no_mem_error; +#define NO_MEM_ERROR (&no_mem_error) + +static void server_write_entries PROTO((void)); + +/* + * Read a line from the stream "instream" without command line editing. + * + * Action is compatible with "readline", e.g. space for the result is + * malloc'd and should be freed by the caller. + * + * A NULL return means end of file. A return of NO_MEM_ERROR means + * that we are out of memory. + */ +static char *read_line PROTO((FILE *)); + +static char * +read_line (stream) + FILE *stream; +{ + int c; + char *result; + int input_index = 0; + int result_size = 80; + + fflush (stdout); + result = (char *) malloc (result_size); + if (result == NULL) + return NO_MEM_ERROR; + + while (1) + { + c = fgetc (stream); + + if (c == EOF) + { + free (result); + return NULL; + } + + if (c == '\n') + break; + + result[input_index++] = c; + while (input_index >= result_size) + { + result_size *= 2; + result = (char *) realloc (result, result_size); + if (result == NULL) + return NO_MEM_ERROR; + } + } + + result[input_index++] = '\0'; + return result; +} + +/* + * Make directory DIR, including all intermediate directories if necessary. + * Returns 0 for success or errno code. + */ +static int mkdir_p PROTO((char *)); + +static int +mkdir_p (dir) + char *dir; +{ + char *p; + char *q = malloc (strlen (dir) + 1); + int retval; + + if (q == NULL) + return ENOMEM; + + /* + * Skip over leading slash if present. We won't bother to try to + * make '/'. + */ + p = dir + 1; + while (1) + { + while (*p != '/' && *p != '\0') + ++p; + if (*p == '/') + { + strncpy (q, dir, p - dir); + q[p - dir] = '\0'; + if (CVS_MKDIR (q, 0777) < 0) + { + if (errno != EEXIST + && (errno != EACCES || !isdir(q))) + { + retval = errno; + goto done; + } + } + ++p; + } + else + { + if (CVS_MKDIR (dir, 0777) < 0) + retval = errno; + else + retval = 0; + goto done; + } + } + done: + free (q); + return retval; +} + +/* + * Print the error response for error code STATUS. The caller is + * reponsible for making sure we get back to the command loop without + * any further output occuring. + */ +static void +print_error (status) + int status; +{ + char *msg; + printf ("error "); + msg = strerror (status); + if (msg) + printf ("%s", msg); + printf ("\n"); +} + +static int pending_error; +/* + * Malloc'd text for pending error. Each line must start with "E ". The + * last line should not end with a newline. + */ +static char *pending_error_text; + +/* If an error is pending, print it and return 1. If not, return 0. */ +static int +print_pending_error () +{ + if (pending_error_text) + { + printf ("%s\n", pending_error_text); + if (pending_error) + print_error (pending_error); + else + printf ("error \n"); + pending_error = 0; + free (pending_error_text); + pending_error_text = NULL; + return 1; + } + else if (pending_error) + { + print_error (pending_error); + pending_error = 0; + return 1; + } + else + return 0; +} + +/* Is an error pending? */ +#define error_pending() (pending_error || pending_error_text) + +int +supported_response (name) + char *name; +{ + struct response *rs; + + for (rs = responses; rs->name != NULL; ++rs) + if (strcmp (rs->name, name) == 0) + return rs->status == rs_supported; + error (1, 0, "internal error: testing support for unknown response?"); + /* NOTREACHED */ + return 0; +} + +static void +serve_valid_responses (arg) + char *arg; +{ + char *p = arg; + char *q; + struct response *rs; + do + { + q = strchr (p, ' '); + if (q != NULL) + *q++ = '\0'; + for (rs = responses; rs->name != NULL; ++rs) + { + if (strcmp (rs->name, p) == 0) + break; + } + if (rs->name == NULL) + /* + * It is a response we have never heard of (and thus never + * will want to use). So don't worry about it. + */ + ; + else + rs->status = rs_supported; + p = q; + } while (q != NULL); + for (rs = responses; rs->name != NULL; ++rs) + { + if (rs->status == rs_essential) + { + printf ("E response `%s' not supported by client\nerror \n", + rs->name); + exit (EXIT_FAILURE); + } + else if (rs->status == rs_optional) + rs->status = rs_not_supported; + } +} + +static int use_dir_and_repos = 0; + +static void +serve_root (arg) + char *arg; +{ + char *env; + extern char *CVSroot; + char path[PATH_MAX]; + int save_errno; + + if (error_pending()) return; + + (void) sprintf (path, "%s/%s", arg, CVSROOTADM); + if (!isaccessible (path, R_OK | X_OK)) + { + save_errno = errno; + pending_error_text = malloc (80 + strlen (path)); + if (pending_error_text != NULL) + sprintf (pending_error_text, "E Cannot access %s", path); + pending_error = save_errno; + } + (void) strcat (path, "/"); + (void) strcat (path, CVSROOTADM_HISTORY); + if (isfile (path) && !isaccessible (path, R_OK | W_OK)) + { + save_errno = errno; + pending_error_text = malloc (80 + strlen (path)); + if (pending_error_text != NULL) + sprintf (pending_error_text, "E \ +Sorry, you don't have read/write access to the history file %s", path); + pending_error = save_errno; + } + + CVSroot = malloc (strlen (arg) + 1); + if (CVSroot == NULL) + { + pending_error = ENOMEM; + return; + } + strcpy (CVSroot, arg); +#ifdef HAVE_PUTENV + env = malloc (strlen (CVSROOT_ENV) + strlen (CVSroot) + 1 + 1); + if (env == NULL) + { + pending_error = ENOMEM; + return; + } + (void) sprintf (env, "%s=%s", CVSROOT_ENV, arg); + (void) putenv (env); + /* do not free env, as putenv has control of it */ +#endif +} + +/* + * Add as many directories to the temp directory as the client tells us it + * will use "..", so we never try to access something outside the temp + * directory via "..". + */ +static void +serve_max_dotdot (arg) + char *arg; +{ + int lim = atoi (arg); + int i; + char *p; + + if (lim < 0) + return; + p = malloc (strlen (server_temp_dir) + 2 * lim + 10); + if (p == NULL) + { + pending_error = ENOMEM; + return; + } + strcpy (p, server_temp_dir); + for (i = 0; i < lim; ++i) + strcat (p, "/d"); + free (server_temp_dir); + server_temp_dir = p; +} + +static char *dir_name; + +static void +dirswitch (dir, repos) + char *dir; + char *repos; +{ + int status; + FILE *f; + + server_write_entries (); + + if (error_pending()) return; + + if (dir_name != NULL) + free (dir_name); + + dir_name = malloc (strlen (server_temp_dir) + strlen (dir) + 40); + if (dir_name == NULL) + { + pending_error = ENOMEM; + return; + } + + strcpy (dir_name, server_temp_dir); + strcat (dir_name, "/"); + strcat (dir_name, dir); + + status = mkdir_p (dir_name); + if (status != 0 + && status != EEXIST) + { + pending_error = status; + pending_error_text = malloc (80 + strlen(dir_name)); + sprintf(pending_error_text, "E cannot mkdir %s", dir_name); + return; + } + if (chdir (dir_name) < 0) + { + pending_error = errno; + pending_error_text = malloc (80 + strlen(dir_name)); + sprintf(pending_error_text, "E cannot change to %s", dir_name); + return; + } + /* + * This is pretty much like calling Create_Admin, but Create_Admin doesn't + * report errors in the right way for us. + */ + if (CVS_MKDIR (CVSADM, 0777) < 0) + { + if (errno == EEXIST) + /* Don't create the files again. */ + return; + pending_error = errno; + return; + } + f = fopen (CVSADM_REP, "w"); + if (f == NULL) + { + pending_error = errno; + return; + } + if (fprintf (f, "%s\n", repos) < 0) + { + pending_error = errno; + fclose (f); + return; + } + if (fclose (f) == EOF) + { + pending_error = errno; + return; + } + f = fopen (CVSADM_ENT, "w+"); + if (f == NULL) + { + pending_error = errno; + pending_error_text = malloc (80 + strlen(CVSADM_ENT)); + sprintf(pending_error_text, "E cannot open %s", CVSADM_ENT); + return; + } + if (fclose (f) == EOF) + { + pending_error = errno; + pending_error_text = malloc (80 + strlen(CVSADM_ENT)); + sprintf(pending_error_text, "E cannot close %s", CVSADM_ENT); + return; + } +} + +static void +serve_repository (arg) + char *arg; +{ + dirswitch (arg + 1, arg); +} + +static void +serve_directory (arg) + char *arg; +{ + char *repos; + use_dir_and_repos = 1; + repos = read_line (stdin); + if (repos == NULL) + { + pending_error_text = malloc (80 + strlen (arg)); + if (pending_error_text) + { + if (feof (stdin)) + sprintf (pending_error_text, + "E end of file reading mode for %s", arg); + else + { + sprintf (pending_error_text, + "E error reading mode for %s", arg); + pending_error = errno; + } + } + else + pending_error = ENOMEM; + } + else if (repos == NO_MEM_ERROR) + { + pending_error = ENOMEM; + } + else + { + dirswitch (arg, repos); + free (repos); + } +} + +static void +serve_static_directory (arg) + char *arg; +{ + FILE *f; + f = fopen (CVSADM_ENTSTAT, "w+"); + if (f == NULL) + { + pending_error = errno; + pending_error_text = malloc (80 + strlen(CVSADM_ENTSTAT)); + sprintf(pending_error_text, "E cannot open %s", CVSADM_ENTSTAT); + return; + } + if (fclose (f) == EOF) + { + pending_error = errno; + pending_error_text = malloc (80 + strlen(CVSADM_ENTSTAT)); + sprintf(pending_error_text, "E cannot close %s", CVSADM_ENTSTAT); + return; + } +} + +static void +serve_sticky (arg) + char *arg; +{ + FILE *f; + f = fopen (CVSADM_TAG, "w+"); + if (f == NULL) + { + pending_error = errno; + pending_error_text = malloc (80 + strlen(CVSADM_TAG)); + sprintf(pending_error_text, "E cannot open %s", CVSADM_TAG); + return; + } + if (fprintf (f, "%s\n", arg) < 0) + { + pending_error = errno; + pending_error_text = malloc (80 + strlen(CVSADM_TAG)); + sprintf(pending_error_text, "E cannot write to %s", CVSADM_TAG); + return; + } + if (fclose (f) == EOF) + { + pending_error = errno; + pending_error_text = malloc (80 + strlen(CVSADM_TAG)); + sprintf(pending_error_text, "E cannot close %s", CVSADM_TAG); + return; + } +} + +/* + * Read SIZE bytes from stdin, write them to FILE. + * + * Currently this isn't really used for receiving parts of a file -- + * the file is still sent over in one chunk. But if/when we get + * spiffy in-process gzip support working, perhaps the compressed + * pieces could be sent over as they're ready, if the network is fast + * enough. Or something. + */ +static void +receive_partial_file (size, file) + int size; + int file; +{ + char buf[16*1024], *bufp; + int toread, nread, nwrote; + while (size > 0) + { + toread = sizeof (buf); + if (toread > size) + toread = size; + + nread = fread (buf, 1, toread, stdin); + if (nread <= 0) + { + if (feof (stdin)) + { + pending_error_text = malloc (80); + if (pending_error_text) + { + sprintf (pending_error_text, + "E premature end of file from client"); + pending_error = 0; + } + else + pending_error = ENOMEM; + } + else if (ferror (stdin)) + { + pending_error_text = malloc (40); + if (pending_error_text) + sprintf (pending_error_text, + "E error reading from client"); + pending_error = errno; + } + else + { + pending_error_text = malloc (40); + if (pending_error_text) + sprintf (pending_error_text, + "E short read from client"); + pending_error = 0; + } + return; + } + size -= nread; + bufp = buf; + while (nread) + { + nwrote = write (file, bufp, nread); + if (nwrote < 0) + { + pending_error_text = malloc (40); + if (pending_error_text) + sprintf (pending_error_text, "E unable to write"); + pending_error = errno; + return; + } + nread -= nwrote; + bufp += nwrote; + } + } +} + +/* Receive SIZE bytes, write to filename FILE. */ +static void +receive_file (size, file, gzipped) + int size; + char *file; + int gzipped; +{ + int fd; + char *arg = file; + pid_t gzip_pid = 0; + int gzip_status; + + /* Write the file. */ + fd = open (arg, O_WRONLY | O_CREAT | O_TRUNC, 0600); + if (fd < 0) + { + pending_error_text = malloc (40 + strlen (arg)); + if (pending_error_text) + sprintf (pending_error_text, "E cannot open %s", arg); + pending_error = errno; + return; + } + + /* + * FIXME: This doesn't do anything reasonable with gunzip's stderr, which + * means that if gunzip writes to stderr, it will cause all manner of + * protocol violations. + */ + if (gzipped) + fd = filter_through_gunzip (fd, 0, &gzip_pid); + + receive_partial_file (size, fd); + + if (pending_error_text) + { + char *p = realloc (pending_error_text, + strlen (pending_error_text) + strlen (arg) + 30); + if (p) + { + pending_error_text = p; + sprintf (p + strlen (p), ", file %s", arg); + } + /* else original string is supposed to be unchanged */ + } + + if (close (fd) < 0 && !error_pending ()) + { + pending_error_text = malloc (40 + strlen (arg)); + if (pending_error_text) + sprintf (pending_error_text, "E cannot close %s", arg); + pending_error = errno; + if (gzip_pid) + waitpid (gzip_pid, (int *) 0, 0); + return; + } + + if (gzip_pid) + { + if (waitpid (gzip_pid, &gzip_status, 0) != gzip_pid) + error (1, errno, "waiting for gunzip process %ld", + (long) gzip_pid); + else if (gzip_status != 0) + error (1, 0, "gunzip exited %d", gzip_status); + } +} + +static void +serve_modified (arg) + char *arg; +{ + int size; + char *size_text; + char *mode_text; + + int gzipped = 0; + + if (error_pending ()) return; + + mode_text = read_line (stdin); + if (mode_text == NULL) + { + pending_error_text = malloc (80 + strlen (arg)); + if (pending_error_text) + { + if (feof (stdin)) + sprintf (pending_error_text, + "E end of file reading mode for %s", arg); + else + { + sprintf (pending_error_text, + "E error reading mode for %s", arg); + pending_error = errno; + } + } + else + pending_error = ENOMEM; + return; + } + else if (mode_text == NO_MEM_ERROR) + { + pending_error = ENOMEM; + return; + } + size_text = read_line (stdin); + if (size_text == NULL) + { + pending_error_text = malloc (80 + strlen (arg)); + if (pending_error_text) + { + if (feof (stdin)) + sprintf (pending_error_text, + "E end of file reading size for %s", arg); + else + { + sprintf (pending_error_text, + "E error reading size for %s", arg); + pending_error = errno; + } + } + else + pending_error = ENOMEM; + return; + } + else if (size_text == NO_MEM_ERROR) + { + pending_error = ENOMEM; + return; + } + if (size_text[0] == 'z') + { + gzipped = 1; + size = atoi (size_text + 1); + } + else + size = atoi (size_text); + free (size_text); + + if (size >= 0) + { + receive_file (size, arg, gzipped); + if (error_pending ()) return; + } + + { + int status = change_mode (arg, mode_text); + free (mode_text); + if (status) + { + pending_error_text = malloc (40 + strlen (arg)); + if (pending_error_text) + sprintf (pending_error_text, + "E cannot change mode for %s", arg); + pending_error = status; + return; + } + } +} + +#endif /* SERVER_SUPPORT */ + +#if defined(SERVER_SUPPORT) || defined(CLIENT_SUPPORT) + +int use_unchanged = 0; + +#endif +#ifdef SERVER_SUPPORT + +static void +serve_enable_unchanged (arg) + char *arg; +{ + use_unchanged = 1; +} + +static void +serve_lost (arg) + char *arg; +{ + if (use_unchanged) + { + /* A missing file already indicates it is nonexistent. */ + return; + } + else + { + struct utimbuf ut; + int fd = open (arg, O_WRONLY | O_CREAT | O_TRUNC, 0666); + if (fd < 0 || close (fd) < 0) + { + pending_error = errno; + pending_error_text = malloc (80 + strlen(arg)); + sprintf(pending_error_text, "E cannot open %s", arg); + return; + } + /* + * Set the times to the beginning of the epoch to tell time_stamp() + * that the file was lost. + */ + ut.actime = 0; + ut.modtime = 0; + if (utime (arg, &ut) < 0) + { + pending_error = errno; + pending_error_text = malloc (80 + strlen(arg)); + sprintf(pending_error_text, "E cannot utime %s", arg); + return; + } + } +} + +struct an_entry { + struct an_entry *next; + char *entry; +}; + +static struct an_entry *entries; + +static void +serve_unchanged (arg) + char *arg; +{ + if (error_pending ()) + return; + if (!use_unchanged) + { + /* A missing file already indicates it is unchanged. */ + return; + } + else + { + struct an_entry *p; + char *name; + char *cp; + char *timefield; + + /* Rewrite entries file to have `=' in timestamp field. */ + for (p = entries; p != NULL; p = p->next) + { + name = p->entry + 1; + cp = strchr (name, '/'); + if (cp != NULL + && strlen (arg) == cp - name + && strncmp (arg, name, cp - name) == 0) + { + timefield = strchr (cp + 1, '/') + 1; + if (*timefield != '=') + { + cp = timefield + strlen (timefield); + cp[1] = '\0'; + while (cp > timefield) + { + *cp = cp[-1]; + --cp; + } + *timefield = '='; + } + break; + } + } + } +} + +static void +serve_entry (arg) + char *arg; +{ + struct an_entry *p; + char *cp; + if (error_pending()) return; + p = (struct an_entry *) malloc (sizeof (struct an_entry)); + if (p == NULL) + { + pending_error = ENOMEM; + return; + } + /* Leave space for serve_unchanged to write '=' if it wants. */ + cp = malloc (strlen (arg) + 2); + if (cp == NULL) + { + pending_error = ENOMEM; + return; + } + strcpy (cp, arg); + p->next = entries; + p->entry = cp; + entries = p; +} + +static void +server_write_entries () +{ + FILE *f; + struct an_entry *p; + struct an_entry *q; + + if (entries == NULL) + return; + + f = NULL; + /* Note that we free all the entries regardless of errors. */ + if (!error_pending ()) + { + f = fopen (CVSADM_ENT, "w"); + if (f == NULL) + { + pending_error = errno; + pending_error_text = malloc (80 + strlen(CVSADM_ENT)); + sprintf(pending_error_text, "E cannot open %s", CVSADM_ENT); + } + } + for (p = entries; p != NULL;) + { + if (!error_pending ()) + { + if (fprintf (f, "%s\n", p->entry) < 0) + { + pending_error = errno; + pending_error_text = malloc (80 + strlen(CVSADM_ENT)); + sprintf(pending_error_text, "E cannot write to %s", CVSADM_ENT); + } + } + free (p->entry); + q = p->next; + free (p); + p = q; + } + entries = NULL; + if (f != NULL && fclose (f) == EOF && !error_pending ()) + { + pending_error = errno; + pending_error_text = malloc (80 + strlen(CVSADM_ENT)); + sprintf(pending_error_text, "E cannot close %s", CVSADM_ENT); + } +} + +struct notify_note { + /* Directory in which this notification happens. malloc'd*/ + char *dir; + + /* malloc'd. */ + char *filename; + + /* The following three all in one malloc'd block, pointed to by TYPE. + Each '\0' terminated. */ + /* "E" or "U". */ + char *type; + /* time+host+dir */ + char *val; + char *watches; + + struct notify_note *next; +}; + +static struct notify_note *notify_list; +/* Used while building list, to point to the last node that already exists. */ +static struct notify_note *last_node; + +static void serve_notify PROTO ((char *)); + +static void +serve_notify (arg) + char *arg; +{ + struct notify_note *new; + char *data; + + if (error_pending ()) return; + + new = (struct notify_note *) malloc (sizeof (struct notify_note)); + if (new == NULL) + { + pending_error = ENOMEM; + return; + } + if (dir_name == NULL) + goto error; + new->dir = malloc (strlen (dir_name) + 1); + if (new->dir == NULL) + { + pending_error = ENOMEM; + return; + } + strcpy (new->dir, dir_name); + new->filename = malloc (strlen (arg) + 1); + if (new->filename == NULL) + { + pending_error = ENOMEM; + return; + } + strcpy (new->filename, arg); + + data = read_line (stdin); + if (data == NULL) + { + pending_error_text = malloc (80 + strlen (arg)); + if (pending_error_text) + { + if (feof (stdin)) + sprintf (pending_error_text, + "E end of file reading mode for %s", arg); + else + { + sprintf (pending_error_text, + "E error reading mode for %s", arg); + pending_error = errno; + } + } + else + pending_error = ENOMEM; + } + else if (data == NO_MEM_ERROR) + { + pending_error = ENOMEM; + } + else + { + char *cp; + + new->type = data; + if (data[1] != '\t') + goto error; + data[1] = '\0'; + cp = data + 2; + new->val = cp; + cp = strchr (cp, '\t'); + if (cp == NULL) + goto error; + *cp++ = '+'; + cp = strchr (cp, '\t'); + if (cp == NULL) + goto error; + *cp++ = '+'; + cp = strchr (cp, '\t'); + if (cp == NULL) + goto error; + *cp++ = '\0'; + new->watches = cp; + /* If there is another tab, ignore everything after it, + for future expansion. */ + cp = strchr (cp, '\t'); + if (cp != NULL) + { + *cp = '\0'; + } + + new->next = NULL; + + if (last_node == NULL) + { + notify_list = new; + } + else + last_node->next = new; + last_node = new; + } + return; + error: + pending_error_text = malloc (40); + if (pending_error_text) + strcpy (pending_error_text, + "E Protocol error; misformed Notify request"); + pending_error = 0; + return; +} + +/* Process all the Notify requests that we have stored up. Returns 0 + if successful, if not prints error message (via error()) and + returns negative value. */ +static int +server_notify () +{ + struct notify_note *p; + char *repos; + List *list; + Node *node; + int status; + + while (notify_list != NULL) + { + if (chdir (notify_list->dir) < 0) + { + error (0, errno, "cannot change to %s", notify_list->dir); + return -1; + } + repos = Name_Repository (NULL, NULL); + + /* Now writelock. */ + list = getlist (); + node = getnode (); + node->type = LOCK; + node->key = xstrdup (repos); + status = addnode (list, node); + assert (status == 0); + Writer_Lock (list); + + fileattr_startdir (repos); + + notify_do (*notify_list->type, notify_list->filename, getcaller(), + notify_list->val, notify_list->watches, repos); + + printf ("Notified "); + if (use_dir_and_repos) + { + char *dir = notify_list->dir + strlen (server_temp_dir) + 1; + if (dir[0] == '\0') + fputs (".", stdout); + else + fputs (dir, stdout); + fputs ("/\n", stdout); + } + fputs (repos, stdout); + fputs ("/", stdout); + fputs (notify_list->filename, stdout); + fputs ("\n", stdout); + + p = notify_list->next; + free (notify_list->filename); + free (notify_list->dir); + free (notify_list->type); + free (notify_list); + notify_list = p; + + fileattr_write (); + fileattr_free (); + + /* Remove the writelock. */ + Lock_Cleanup (); + dellist (&list); + } + /* do_cvs_command writes to stdout via write(), not stdio, so better + flush out the buffer. */ + fflush (stdout); + return 0; +} + +static int argument_count; +static char **argument_vector; +static int argument_vector_size; + +static void +serve_argument (arg) + char *arg; +{ + char *p; + + if (error_pending()) return; + + if (argument_vector_size <= argument_count) + { + argument_vector_size *= 2; + argument_vector = + (char **) realloc ((char *)argument_vector, + argument_vector_size * sizeof (char *)); + if (argument_vector == NULL) + { + pending_error = ENOMEM; + return; + } + } + p = malloc (strlen (arg) + 1); + if (p == NULL) + { + pending_error = ENOMEM; + return; + } + strcpy (p, arg); + argument_vector[argument_count++] = p; +} + +static void +serve_argumentx (arg) + char *arg; +{ + char *p; + + if (error_pending()) return; + + p = argument_vector[argument_count - 1]; + p = realloc (p, strlen (p) + 1 + strlen (arg) + 1); + if (p == NULL) + { + pending_error = ENOMEM; + return; + } + strcat (p, "\n"); + strcat (p, arg); + argument_vector[argument_count - 1] = p; +} + +static void +serve_global_option (arg) + char *arg; +{ + if (arg[0] != '-' || arg[1] == '\0' || arg[2] != '\0') + { + error_return: + pending_error_text = malloc (strlen (arg) + 80); + sprintf (pending_error_text, "E Protocol error: bad global option %s", + arg); + return; + } + switch (arg[1]) + { + case 'n': + noexec = 1; + break; + case 'q': + quiet = 1; + break; + case 'r': + cvswrite = 0; + break; + case 'Q': + really_quiet = 1; + break; + case 'l': + logoff = 1; + break; + case 't': + trace = 1; + break; + default: + goto error_return; + } +} + +static void +serve_set (arg) + char *arg; +{ + /* FIXME: This sends errors immediately (I think); they should be + put into pending_error. */ + variable_set (arg); +} + +/* + * We must read data from a child process and send it across the + * network. We do not want to block on writing to the network, so we + * store the data from the child process in memory. A BUFFER + * structure holds the status of one communication, and uses a linked + * list of buffer_data structures to hold data. + */ + +struct buffer +{ + /* Data. */ + struct buffer_data *data; + + /* Last buffer on data chain. */ + struct buffer_data *last; + + /* File descriptor to write to or read from. */ + int fd; + + /* Nonzero if this is an output buffer (sanity check). */ + int output; + + /* Nonzero if the file descriptor is in nonblocking mode. */ + int nonblocking; + + /* Function to call if we can't allocate memory. */ + void (*memory_error) PROTO((struct buffer *)); +}; + +/* Data is stored in lists of these structures. */ + +struct buffer_data +{ + /* Next buffer in linked list. */ + struct buffer_data *next; + + /* + * A pointer into the data area pointed to by the text field. This + * is where to find data that has not yet been written out. + */ + char *bufp; + + /* The number of data bytes found at BUFP. */ + int size; + + /* + * Actual buffer. This never changes after the structure is + * allocated. The buffer is BUFFER_DATA_SIZE bytes. + */ + char *text; +}; + +/* The size we allocate for each buffer_data structure. */ +#define BUFFER_DATA_SIZE (4096) + +#ifdef SERVER_FLOWCONTROL +/* The maximum we'll queue to the remote client before blocking. */ +# ifndef SERVER_HI_WATER +# define SERVER_HI_WATER (2 * 1024 * 1024) +# endif /* SERVER_HI_WATER */ +/* When the buffer drops to this, we restart the child */ +# ifndef SERVER_LO_WATER +# define SERVER_LO_WATER (1 * 1024 * 1024) +# endif /* SERVER_LO_WATER */ +#endif /* SERVER_FLOWCONTROL */ + +/* Linked list of available buffer_data structures. */ +static struct buffer_data *free_buffer_data; + +static void allocate_buffer_datas PROTO((void)); +static inline struct buffer_data *get_buffer_data PROTO((void)); +static int buf_empty_p PROTO((struct buffer *)); +static void buf_output PROTO((struct buffer *, const char *, int)); +static void buf_output0 PROTO((struct buffer *, const char *)); +static inline void buf_append_char PROTO((struct buffer *, int)); +static int buf_send_output PROTO((struct buffer *)); +static int set_nonblock PROTO((struct buffer *)); +static int set_block PROTO((struct buffer *)); +static int buf_send_counted PROTO((struct buffer *)); +static inline void buf_append_data PROTO((struct buffer *, + struct buffer_data *, + struct buffer_data *)); +static int buf_read_file PROTO((FILE *, long, struct buffer_data **, + struct buffer_data **)); +static int buf_input_data PROTO((struct buffer *, int *)); +static void buf_copy_lines PROTO((struct buffer *, struct buffer *, int)); +static int buf_copy_counted PROTO((struct buffer *, struct buffer *)); + +#ifdef SERVER_FLOWCONTROL +static int buf_count_mem PROTO((struct buffer *)); +static int set_nonblock_fd PROTO((int)); +#endif /* SERVER_FLOWCONTROL */ + +/* Allocate more buffer_data structures. */ + +static void +allocate_buffer_datas () +{ + struct buffer_data *alc; + char *space; + int i; + + /* Allocate buffer_data structures in blocks of 16. */ +#define ALLOC_COUNT (16) + + alc = ((struct buffer_data *) + malloc (ALLOC_COUNT * sizeof (struct buffer_data))); + space = (char *) valloc (ALLOC_COUNT * BUFFER_DATA_SIZE); + if (alc == NULL || space == NULL) + return; + for (i = 0; i < ALLOC_COUNT; i++, alc++, space += BUFFER_DATA_SIZE) + { + alc->next = free_buffer_data; + free_buffer_data = alc; + alc->text = space; + } +} + +/* Get a new buffer_data structure. */ + +static inline struct buffer_data * +get_buffer_data () +{ + struct buffer_data *ret; + + if (free_buffer_data == NULL) + { + allocate_buffer_datas (); + if (free_buffer_data == NULL) + return NULL; + } + + ret = free_buffer_data; + free_buffer_data = ret->next; + return ret; +} + +/* See whether a buffer is empty. */ + +static int +buf_empty_p (buf) + struct buffer *buf; +{ + struct buffer_data *data; + + for (data = buf->data; data != NULL; data = data->next) + if (data->size > 0) + return 0; + return 1; +} + +#ifdef SERVER_FLOWCONTROL +/* + * Count how much data is stored in the buffer.. + * Note that each buffer is a malloc'ed chunk BUFFER_DATA_SIZE. + */ + +static int +buf_count_mem (buf) + struct buffer *buf; +{ + struct buffer_data *data; + int mem = 0; + + for (data = buf->data; data != NULL; data = data->next) + mem += BUFFER_DATA_SIZE; + + return mem; +} +#endif /* SERVER_FLOWCONTROL */ + +/* Add data DATA of length LEN to BUF. */ + +static void +buf_output (buf, data, len) + struct buffer *buf; + const char *data; + int len; +{ + if (buf->data != NULL + && (((buf->last->text + BUFFER_DATA_SIZE) + - (buf->last->bufp + buf->last->size)) + >= len)) + { + memcpy (buf->last->bufp + buf->last->size, data, len); + buf->last->size += len; + return; + } + + while (1) + { + struct buffer_data *newdata; + + newdata = get_buffer_data (); + if (newdata == NULL) + { + (*buf->memory_error) (buf); + return; + } + + if (buf->data == NULL) + buf->data = newdata; + else + buf->last->next = newdata; + newdata->next = NULL; + buf->last = newdata; + + newdata->bufp = newdata->text; + + if (len <= BUFFER_DATA_SIZE) + { + newdata->size = len; + memcpy (newdata->text, data, len); + return; + } + + newdata->size = BUFFER_DATA_SIZE; + memcpy (newdata->text, data, BUFFER_DATA_SIZE); + + data += BUFFER_DATA_SIZE; + len -= BUFFER_DATA_SIZE; + } + + /*NOTREACHED*/ +} + +/* Add a '\0' terminated string to BUF. */ + +static void +buf_output0 (buf, string) + struct buffer *buf; + const char *string; +{ + buf_output (buf, string, strlen (string)); +} + +/* Add a single character to BUF. */ + +static inline void +buf_append_char (buf, ch) + struct buffer *buf; + int ch; +{ + if (buf->data != NULL + && (buf->last->text + BUFFER_DATA_SIZE + != buf->last->bufp + buf->last->size)) + { + *(buf->last->bufp + buf->last->size) = ch; + ++buf->last->size; + } + else + { + char b; + + b = ch; + buf_output (buf, &b, 1); + } +} + +/* + * Send all the output we've been saving up. Returns 0 for success or + * errno code. If the buffer has been set to be nonblocking, this + * will just write until the write would block. + */ + +static int +buf_send_output (buf) + struct buffer *buf; +{ + if (! buf->output) + abort (); + + while (buf->data != NULL) + { + struct buffer_data *data; + + data = buf->data; + while (data->size > 0) + { + int nbytes; + + nbytes = write (buf->fd, data->bufp, data->size); + if (nbytes <= 0) + { + int status; + + if (buf->nonblocking + && (nbytes == 0 +#ifdef EWOULDBLOCK + || errno == EWOULDBLOCK +#endif + || errno == EAGAIN)) + { + /* + * A nonblocking write failed to write any data. + * Just return. + */ + return 0; + } + + /* + * An error, or EOF. Throw away all the data and + * return. + */ + if (nbytes == 0) + status = EIO; + else + status = errno; + + buf->last->next = free_buffer_data; + free_buffer_data = buf->data; + buf->data = NULL; + buf->last = NULL; + + return status; + } + + data->size -= nbytes; + data->bufp += nbytes; + } + + buf->data = data->next; + data->next = free_buffer_data; + free_buffer_data = data; + } + + buf->last = NULL; + + return 0; +} + +#ifdef SERVER_FLOWCONTROL +/* + * Set buffer BUF to non-blocking I/O. Returns 0 for success or errno + * code. + */ + +static int +set_nonblock_fd (fd) + int fd; +{ + int flags; + + flags = fcntl (fd, F_GETFL, 0); + if (flags < 0) + return errno; + if (fcntl (fd, F_SETFL, flags | O_NONBLOCK) < 0) + return errno; + return 0; +} +#endif /* SERVER_FLOWCONTROL */ + +static int +set_nonblock (buf) + struct buffer *buf; +{ + int flags; + + if (buf->nonblocking) + return 0; + flags = fcntl (buf->fd, F_GETFL, 0); + if (flags < 0) + return errno; + if (fcntl (buf->fd, F_SETFL, flags | O_NONBLOCK) < 0) + return errno; + buf->nonblocking = 1; + return 0; +} + +/* + * Set buffer BUF to blocking I/O. Returns 0 for success or errno + * code. + */ + +static int +set_block (buf) + struct buffer *buf; +{ + int flags; + + if (! buf->nonblocking) + return 0; + flags = fcntl (buf->fd, F_GETFL, 0); + if (flags < 0) + return errno; + if (fcntl (buf->fd, F_SETFL, flags & ~O_NONBLOCK) < 0) + return errno; + buf->nonblocking = 0; + return 0; +} + +/* + * Send a character count and some output. Returns errno code or 0 for + * success. + * + * Sending the count in binary is OK since this is only used on a pipe + * within the same system. + */ + +static int +buf_send_counted (buf) + struct buffer *buf; +{ + int size; + struct buffer_data *data; + + if (! buf->output) + abort (); + + size = 0; + for (data = buf->data; data != NULL; data = data->next) + size += data->size; + + data = get_buffer_data (); + if (data == NULL) + { + (*buf->memory_error) (buf); + return ENOMEM; + } + + data->next = buf->data; + buf->data = data; + if (buf->last == NULL) + buf->last = data; + + data->bufp = data->text; + data->size = sizeof (int); + + *((int *) data->text) = size; + + return buf_send_output (buf); +} + +/* Append a list of buffer_data structures to an buffer. */ + +static inline void +buf_append_data (buf, data, last) + struct buffer *buf; + struct buffer_data *data; + struct buffer_data *last; +{ + if (data != NULL) + { + if (buf->data == NULL) + buf->data = data; + else + buf->last->next = data; + buf->last = last; + } +} + +/* + * 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 + * succeess differently. Returns 0 on success, or -2 if out of + * memory, or a status code on error. Since the caller happens to + * know the size of the file, it is passed in as SIZE. On success, + * this function sets *RETP and *LASTP, which may be passed to + * buf_append_data. + */ + +static int +buf_read_file (f, size, retp, lastp) + FILE *f; + long size; + struct buffer_data **retp; + struct buffer_data **lastp; +{ + int status; + + *retp = NULL; + *lastp = NULL; + + while (size > 0) + { + struct buffer_data *data; + int get; + + data = get_buffer_data (); + if (data == NULL) + { + status = -2; + goto error_return; + } + + if (*retp == NULL) + *retp = data; + else + (*lastp)->next = data; + data->next = NULL; + *lastp = data; + + data->bufp = data->text; + data->size = 0; + + if (size > BUFFER_DATA_SIZE) + get = BUFFER_DATA_SIZE; + else + get = size; + + errno = EIO; + if (fread (data->text, get, 1, f) != 1) + { + status = errno; + goto error_return; + } + + data->size += get; + size -= get; + } + + return 0; + + error_return: + if (*retp != NULL) + { + (*lastp)->next = free_buffer_data; + free_buffer_data = *retp; + } + return status; +} + +static int +buf_read_file_to_eof (f, retp, lastp) + FILE *f; + struct buffer_data **retp; + struct buffer_data **lastp; +{ + int status; + + *retp = NULL; + *lastp = NULL; + + while (!feof (f)) + { + struct buffer_data *data; + int get, nread; + + data = get_buffer_data (); + if (data == NULL) + { + status = -2; + goto error_return; + } + + if (*retp == NULL) + *retp = data; + else + (*lastp)->next = data; + data->next = NULL; + *lastp = data; + + data->bufp = data->text; + data->size = 0; + + get = BUFFER_DATA_SIZE; + + errno = EIO; + nread = fread (data->text, 1, get, f); + if (nread == 0 && !feof (f)) + { + status = errno; + goto error_return; + } + + data->size = nread; + } + + return 0; + + error_return: + if (*retp != NULL) + { + (*lastp)->next = free_buffer_data; + free_buffer_data = *retp; + } + return status; +} + +static int +buf_chain_length (buf) + struct buffer_data *buf; +{ + int size = 0; + while (buf) + { + size += buf->size; + buf = buf->next; + } + return size; +} + +/* + * Read an arbitrary amount of data from a file descriptor into an + * input buffer. The file descriptor will be in nonblocking mode, and + * we just grab what we can. Return 0 on success, or -1 on end of + * file, or -2 if out of memory, or an error code. If COUNTP is not + * NULL, *COUNTP is set to the number of bytes read. + */ + +static int +buf_input_data (buf, countp) + struct buffer *buf; + int *countp; +{ + if (buf->output) + abort (); + + if (countp != NULL) + *countp = 0; + + while (1) + { + int get; + int nbytes; + + if (buf->data == NULL + || (buf->last->bufp + buf->last->size + == buf->last->text + BUFFER_DATA_SIZE)) + { + struct buffer_data *data; + + data = get_buffer_data (); + if (data == NULL) + { + (*buf->memory_error) (buf); + return -2; + } + + if (buf->data == NULL) + buf->data = data; + else + buf->last->next = data; + data->next = NULL; + buf->last = data; + + data->bufp = data->text; + data->size = 0; + } + + get = ((buf->last->text + BUFFER_DATA_SIZE) + - (buf->last->bufp + buf->last->size)); + nbytes = read (buf->fd, buf->last->bufp + buf->last->size, get); + if (nbytes <= 0) + { + if (nbytes == 0) + { + /* + * This assumes that we are using POSIX or BSD style + * nonblocking I/O. On System V we will get a zero + * return if there is no data, even when not at EOF. + */ + return -1; + } + + if (errno == EAGAIN +#ifdef EWOULDBLOCK + || errno == EWOULDBLOCK +#endif + ) + return 0; + + return errno; + } + + buf->last->size += nbytes; + if (countp != NULL) + *countp += nbytes; + } + + /*NOTREACHED*/ +} + +/* + * Copy lines from an input buffer to an output buffer. This copies + * all complete lines (characters up to a newline) from INBUF to + * OUTBUF. Each line in OUTBUF is preceded by the character COMMAND + * and a space. + */ + +static void +buf_copy_lines (outbuf, inbuf, command) + struct buffer *outbuf; + struct buffer *inbuf; + int command; +{ + if (! outbuf->output || inbuf->output) + abort (); + + while (1) + { + struct buffer_data *data; + struct buffer_data *nldata; + char *nl; + int len; + + /* See if there is a newline in INBUF. */ + nldata = NULL; + nl = NULL; + for (data = inbuf->data; data != NULL; data = data->next) + { + nl = memchr (data->bufp, '\n', data->size); + if (nl != NULL) + { + nldata = data; + break; + } + } + + if (nldata == NULL) + { + /* There are no more lines in INBUF. */ + return; + } + + /* Put in the command. */ + buf_append_char (outbuf, command); + buf_append_char (outbuf, ' '); + + if (inbuf->data != nldata) + { + /* + * Simply move over all the buffers up to the one containing + * the newline. + */ + for (data = inbuf->data; data->next != nldata; data = data->next) + ; + data->next = NULL; + buf_append_data (outbuf, inbuf->data, data); + inbuf->data = nldata; + } + + /* + * If the newline is at the very end of the buffer, just move + * the buffer onto OUTBUF. Otherwise we must copy the data. + */ + len = nl + 1 - nldata->bufp; + if (len == nldata->size) + { + inbuf->data = nldata->next; + if (inbuf->data == NULL) + inbuf->last = NULL; + + nldata->next = NULL; + buf_append_data (outbuf, nldata, nldata); + } + else + { + buf_output (outbuf, nldata->bufp, len); + nldata->bufp += len; + nldata->size -= len; + } + } +} + +/* + * Copy counted data from one buffer to another. The count is an + * integer, host size, host byte order (it is only used across a + * pipe). If there is enough data, it should be moved over. If there + * is not enough data, it should remain on the original buffer. This + * returns the number of bytes it needs to see in order to actually + * copy something over. + */ + +static int +buf_copy_counted (outbuf, inbuf) + struct buffer *outbuf; + struct buffer *inbuf; +{ + if (! outbuf->output || inbuf->output) + abort (); + + while (1) + { + struct buffer_data *data; + int need; + union + { + char intbuf[sizeof (int)]; + int i; + } u; + char *intp; + int count; + struct buffer_data *start; + int startoff; + struct buffer_data *stop; + int stopwant; + + /* See if we have enough bytes to figure out the count. */ + need = sizeof (int); + intp = u.intbuf; + for (data = inbuf->data; data != NULL; data = data->next) + { + if (data->size >= need) + { + memcpy (intp, data->bufp, need); + break; + } + memcpy (intp, data->bufp, data->size); + intp += data->size; + need -= data->size; + } + if (data == NULL) + { + /* We don't have enough bytes to form an integer. */ + return need; + } + + count = u.i; + start = data; + startoff = need; + + /* + * We have an integer in COUNT. We have gotten all the data + * from INBUF in all buffers before START, and we have gotten + * STARTOFF bytes from START. See if we have enough bytes + * remaining in INBUF. + */ + need = count - (start->size - startoff); + if (need <= 0) + { + stop = start; + stopwant = count; + } + else + { + for (data = start->next; data != NULL; data = data->next) + { + if (need <= data->size) + break; + need -= data->size; + } + if (data == NULL) + { + /* We don't have enough bytes. */ + return need; + } + stop = data; + stopwant = need; + } + + /* + * We have enough bytes. Free any buffers in INBUF before + * START, and remove STARTOFF bytes from START, so that we can + * forget about STARTOFF. + */ + start->bufp += startoff; + start->size -= startoff; + + if (start->size == 0) + start = start->next; + + if (stop->size == stopwant) + { + stop = stop->next; + stopwant = 0; + } + + while (inbuf->data != start) + { + data = inbuf->data; + inbuf->data = data->next; + data->next = free_buffer_data; + free_buffer_data = data; + } + + /* + * We want to copy over the bytes from START through STOP. We + * only want STOPWANT bytes from STOP. + */ + + if (start != stop) + { + /* Attach the buffers from START through STOP to OUTBUF. */ + for (data = start; data->next != stop; data = data->next) + ; + inbuf->data = stop; + data->next = NULL; + buf_append_data (outbuf, start, data); + } + + if (stopwant > 0) + { + buf_output (outbuf, stop->bufp, stopwant); + stop->bufp += stopwant; + stop->size -= stopwant; + } + } + + /*NOTREACHED*/ +} + +/* While processing requests, this buffer accumulates data to be sent to + the client, and then once we are in do_cvs_command, we use it + for all the data to be sent. */ +static struct buffer buf_to_net; + +static void serve_questionable PROTO((char *)); + +static void +serve_questionable (arg) + char *arg; +{ + static int initted; + + if (!initted) + { + /* Pick up ignores from CVSROOTADM_IGNORE, $HOME/.cvsignore on server, + and CVSIGNORE on server. */ + ign_setup (); + initted = 1; + } + + if (dir_name == NULL) + { + buf_output0 (&buf_to_net, "E Protocol error: 'Directory' missing"); + return; + } + + if (!ign_name (arg)) + { + char *update_dir; + + buf_output (&buf_to_net, "M ? ", 4); + update_dir = dir_name + strlen (server_temp_dir) + 1; + if (!(update_dir[0] == '.' && update_dir[1] == '\0')) + { + buf_output0 (&buf_to_net, update_dir); + buf_output (&buf_to_net, "/", 1); + } + buf_output0 (&buf_to_net, arg); + buf_output (&buf_to_net, "\n", 1); + } +} + +static void serve_case PROTO ((char *)); + +static void +serve_case (arg) + char *arg; +{ + ign_case = 1; +} + +static struct buffer protocol; + +/* This is the output which we are saving up to send to the server, in the + child process. We will push it through, via the `protocol' buffer, when + we have a complete line. */ +static struct buffer saved_output; +/* Likewise, but stuff which will go to stderr. */ +static struct buffer saved_outerr; + +static void +protocol_memory_error (buf) + struct buffer *buf; +{ + error (1, ENOMEM, "Virtual memory exhausted"); +} + +/* + * Process IDs of the subprocess, or negative if that subprocess + * does not exist. + */ +static pid_t command_pid; + +static void +outbuf_memory_error (buf) + struct buffer *buf; +{ + static const char msg[] = "E Fatal server error\n\ +error ENOMEM Virtual memory exhausted.\n"; + if (command_pid > 0) + kill (command_pid, SIGTERM); + + /* + * We have arranged things so that printing this now either will + * be legal, or the "E fatal error" line will get glommed onto the + * end of an existing "E" or "M" response. + */ + + /* If this gives an error, not much we could do. syslog() it? */ + write (STDOUT_FILENO, msg, sizeof (msg) - 1); + server_cleanup (0); + exit (EXIT_FAILURE); +} + +static void +input_memory_error (buf) + struct buffer *buf; +{ + outbuf_memory_error (buf); +} + +/* Execute COMMAND in a subprocess with the approriate funky things done. */ + +static struct fd_set_wrapper { fd_set fds; } command_fds_to_drain; +static int max_command_fd; + +#ifdef SERVER_FLOWCONTROL +static int flowcontrol_pipe[2]; +#endif /* SERVER_FLOWCONTROL */ + +static void +do_cvs_command (command) + int (*command) PROTO((int argc, char **argv)); +{ + /* + * The following file descriptors are set to -1 if that file is not + * currently open. + */ + + /* Data on these pipes is a series of '\n'-terminated lines. */ + int stdout_pipe[2]; + int stderr_pipe[2]; + + /* + * Data on this pipe is a series of counted (see buf_send_counted) + * packets. Each packet must be processed atomically (i.e. not + * interleaved with data from stdout_pipe or stderr_pipe). + */ + int protocol_pipe[2]; + + int dev_null_fd = -1; + + int errs; + + command_pid = -1; + stdout_pipe[0] = -1; + stdout_pipe[1] = -1; + stderr_pipe[0] = -1; + stderr_pipe[1] = -1; + protocol_pipe[0] = -1; + protocol_pipe[1] = -1; + + server_write_entries (); + + if (print_pending_error ()) + goto free_args_and_return; + + (void) server_notify (); + + /* + * We use a child process which actually does the operation. This + * is so we can intercept its standard output. Even if all of CVS + * were written to go to some special routine instead of writing + * to stdout or stderr, we would still need to do the same thing + * for the RCS commands. + */ + + if (pipe (stdout_pipe) < 0) + { + print_error (errno); + goto error_exit; + } + if (pipe (stderr_pipe) < 0) + { + print_error (errno); + goto error_exit; + } + if (pipe (protocol_pipe) < 0) + { + print_error (errno); + goto error_exit; + } +#ifdef SERVER_FLOWCONTROL + if (pipe (flowcontrol_pipe) < 0) + { + print_error (errno); + goto error_exit; + } + set_nonblock_fd (flowcontrol_pipe[0]); + set_nonblock_fd (flowcontrol_pipe[1]); +#endif /* SERVER_FLOWCONTROL */ + + dev_null_fd = open ("/dev/null", O_RDONLY); + if (dev_null_fd < 0) + { + print_error (errno); + goto error_exit; + } + + /* Don't use vfork; we're not going to exec(). */ + command_pid = fork (); + if (command_pid < 0) + { + print_error (errno); + goto error_exit; + } + if (command_pid == 0) + { + int exitstatus; + + /* Since we're in the child, and the parent is going to take + care of packaging up our error messages, we can clear this + flag. */ + error_use_protocol = 0; + + protocol.data = protocol.last = NULL; + protocol.fd = protocol_pipe[1]; + protocol.output = 1; + protocol.nonblocking = 0; + protocol.memory_error = protocol_memory_error; + + saved_output.data = saved_output.last = NULL; + saved_output.fd = -1; + saved_output.output = 0; + saved_output.nonblocking = 0; + saved_output.memory_error = protocol_memory_error; + saved_outerr = saved_output; + + if (dup2 (dev_null_fd, STDIN_FILENO) < 0) + error (1, errno, "can't set up pipes"); + if (dup2 (stdout_pipe[1], STDOUT_FILENO) < 0) + error (1, errno, "can't set up pipes"); + if (dup2 (stderr_pipe[1], STDERR_FILENO) < 0) + error (1, errno, "can't set up pipes"); + close (stdout_pipe[0]); + close (stderr_pipe[0]); + close (protocol_pipe[0]); +#ifdef SERVER_FLOWCONTROL + close (flowcontrol_pipe[1]); +#endif /* SERVER_FLOWCONTROL */ + + /* + * Set this in .bashrc if you want to give yourself time to attach + * to the subprocess with a debugger. + */ + if (getenv ("CVS_SERVER_SLEEP")) + { + int secs = atoi (getenv ("CVS_SERVER_SLEEP")); + sleep (secs); + } + + exitstatus = (*command) (argument_count, argument_vector); + + /* + * When we exit, that will close the pipes, giving an EOF to + * the parent. + */ + exit (exitstatus); + } + + /* OK, sit around getting all the input from the child. */ + { + struct buffer stdoutbuf; + struct buffer stderrbuf; + struct buffer protocol_inbuf; + /* Number of file descriptors to check in select (). */ + int num_to_check; + int count_needed = 0; +#ifdef SERVER_FLOWCONTROL + int have_flowcontrolled = 0; +#endif /* SERVER_FLOWCONTROL */ + + FD_ZERO (&command_fds_to_drain.fds); + num_to_check = stdout_pipe[0]; + FD_SET (stdout_pipe[0], &command_fds_to_drain.fds); + if (stderr_pipe[0] > num_to_check) + num_to_check = stderr_pipe[0]; + FD_SET (stderr_pipe[0], &command_fds_to_drain.fds); + if (protocol_pipe[0] > num_to_check) + num_to_check = protocol_pipe[0]; + FD_SET (protocol_pipe[0], &command_fds_to_drain.fds); + if (STDOUT_FILENO > num_to_check) + num_to_check = STDOUT_FILENO; + max_command_fd = num_to_check; + /* + * File descriptors are numbered from 0, so num_to_check needs to + * be one larger than the largest descriptor. + */ + ++num_to_check; + if (num_to_check > FD_SETSIZE) + { + printf ("E internal error: FD_SETSIZE not big enough.\nerror \n"); + goto error_exit; + } + + stdoutbuf.data = stdoutbuf.last = NULL; + stdoutbuf.fd = stdout_pipe[0]; + stdoutbuf.output = 0; + stdoutbuf.nonblocking = 0; + stdoutbuf.memory_error = input_memory_error; + + stderrbuf.data = stderrbuf.last = NULL; + stderrbuf.fd = stderr_pipe[0]; + stderrbuf.output = 0; + stderrbuf.nonblocking = 0; + stderrbuf.memory_error = input_memory_error; + + protocol_inbuf.data = protocol_inbuf.last = NULL; + protocol_inbuf.fd = protocol_pipe[0]; + protocol_inbuf.output = 0; + protocol_inbuf.nonblocking = 0; + protocol_inbuf.memory_error = input_memory_error; + + set_nonblock (&buf_to_net); + set_nonblock (&stdoutbuf); + set_nonblock (&stderrbuf); + set_nonblock (&protocol_inbuf); + + if (close (stdout_pipe[1]) < 0) + { + print_error (errno); + goto error_exit; + } + stdout_pipe[1] = -1; + + if (close (stderr_pipe[1]) < 0) + { + print_error (errno); + goto error_exit; + } + stderr_pipe[1] = -1; + + if (close (protocol_pipe[1]) < 0) + { + print_error (errno); + goto error_exit; + } + protocol_pipe[1] = -1; + +#ifdef SERVER_FLOWCONTROL + if (close (flowcontrol_pipe[0]) < 0) + { + print_error (errno); + goto error_exit; + } + flowcontrol_pipe[0] = -1; +#endif /* SERVER_FLOWCONTROL */ + + if (close (dev_null_fd) < 0) + { + print_error (errno); + goto error_exit; + } + dev_null_fd = -1; + + while (stdout_pipe[0] >= 0 + || stderr_pipe[0] >= 0 + || protocol_pipe[0] >= 0) + { + fd_set readfds; + fd_set writefds; + int numfds; +#ifdef SERVER_FLOWCONTROL + int bufmemsize; + + /* + * See if we are swamping the remote client and filling our VM. + * Tell child to hold off if we do. + */ + bufmemsize = buf_count_mem (&buf_to_net); + if (!have_flowcontrolled && (bufmemsize > SERVER_HI_WATER)) + { + if (write(flowcontrol_pipe[1], "S", 1) == 1) + have_flowcontrolled = 1; + } + else if (have_flowcontrolled && (bufmemsize < SERVER_LO_WATER)) + { + if (write(flowcontrol_pipe[1], "G", 1) == 1) + have_flowcontrolled = 0; + } +#endif /* SERVER_FLOWCONTROL */ + + FD_ZERO (&readfds); + FD_ZERO (&writefds); + if (! buf_empty_p (&buf_to_net)) + FD_SET (STDOUT_FILENO, &writefds); + + if (stdout_pipe[0] >= 0) + { + FD_SET (stdout_pipe[0], &readfds); + } + if (stderr_pipe[0] >= 0) + { + FD_SET (stderr_pipe[0], &readfds); + } + if (protocol_pipe[0] >= 0) + { + FD_SET (protocol_pipe[0], &readfds); + } + + do { + /* This used to select on exceptions too, but as far + as I know there was never any reason to do that and + SCO doesn't let you select on exceptions on pipes. */ + numfds = select (num_to_check, &readfds, &writefds, + (fd_set *)0, (struct timeval *)NULL); + if (numfds < 0 + && errno != EINTR) + { + print_error (errno); + goto error_exit; + } + } while (numfds < 0); + + if (FD_ISSET (STDOUT_FILENO, &writefds)) + { + /* What should we do with errors? syslog() them? */ + buf_send_output (&buf_to_net); + } + + if (stdout_pipe[0] >= 0 + && (FD_ISSET (stdout_pipe[0], &readfds))) + { + int status; + + status = buf_input_data (&stdoutbuf, (int *) NULL); + + buf_copy_lines (&buf_to_net, &stdoutbuf, 'M'); + + if (status == -1) + stdout_pipe[0] = -1; + else if (status > 0) + { + print_error (status); + goto error_exit; + } + + /* What should we do with errors? syslog() them? */ + buf_send_output (&buf_to_net); + } + + if (stderr_pipe[0] >= 0 + && (FD_ISSET (stderr_pipe[0], &readfds))) + { + int status; + + status = buf_input_data (&stderrbuf, (int *) NULL); + + buf_copy_lines (&buf_to_net, &stderrbuf, 'E'); + + if (status == -1) + stderr_pipe[0] = -1; + else if (status > 0) + { + print_error (status); + goto error_exit; + } + + /* What should we do with errors? syslog() them? */ + buf_send_output (&buf_to_net); + } + + if (protocol_pipe[0] >= 0 + && (FD_ISSET (protocol_pipe[0], &readfds))) + { + int status; + int count_read; + + status = buf_input_data (&protocol_inbuf, &count_read); + + /* + * We only call buf_copy_counted if we have read + * enough bytes to make it worthwhile. This saves us + * from continually recounting the amount of data we + * have. + */ + count_needed -= count_read; + if (count_needed <= 0) + count_needed = buf_copy_counted (&buf_to_net, + &protocol_inbuf); + + if (status == -1) + protocol_pipe[0] = -1; + else if (status > 0) + { + print_error (status); + goto error_exit; + } + + /* What should we do with errors? syslog() them? */ + buf_send_output (&buf_to_net); + } + } + + /* + * OK, we've gotten EOF on all the pipes. If there is + * anything left on stdoutbuf or stderrbuf (this could only + * happen if there was no trailing newline), send it over. + */ + if (! buf_empty_p (&stdoutbuf)) + { + buf_append_char (&stdoutbuf, '\n'); + buf_copy_lines (&buf_to_net, &stdoutbuf, 'M'); + } + if (! buf_empty_p (&stderrbuf)) + { + buf_append_char (&stderrbuf, '\n'); + buf_copy_lines (&buf_to_net, &stderrbuf, 'E'); + } + if (! buf_empty_p (&protocol_inbuf)) + buf_output0 (&buf_to_net, + "E Protocol error: uncounted data discarded\n"); + + errs = 0; + + while (command_pid > 0) + { + int status; + pid_t waited_pid; + waited_pid = waitpid (command_pid, &status, 0); + if (waited_pid < 0) + { + /* + * Intentionally ignoring EINTR. Other errors + * "can't happen". + */ + continue; + } + + if (WIFEXITED (status)) + errs += WEXITSTATUS (status); + else + { + int sig = WTERMSIG (status); + /* + * This is really evil, because signals might be numbered + * differently on the two systems. We should be using + * signal names (either of the "Terminated" or the "SIGTERM" + * variety). But cvs doesn't currently use libiberty...we + * could roll our own.... FIXME. + */ + printf ("E Terminated with fatal signal %d\n", sig); + + /* Test for a core dump. Is this portable? */ + if (status & 0x80) + { + printf ("E Core dumped; preserving %s on server.\n\ +E CVS locks may need cleaning up.\n", + server_temp_dir); + dont_delete_temp = 1; + } + ++errs; + } + if (waited_pid == command_pid) + command_pid = -1; + } + + /* + * OK, we've waited for the child. By now all CVS locks are free + * and it's OK to block on the network. + */ + set_block (&buf_to_net); + buf_send_output (&buf_to_net); + } + + if (errs) + /* We will have printed an error message already. */ + printf ("error \n"); + else + printf ("ok\n"); + goto free_args_and_return; + + error_exit: + if (command_pid > 0) + kill (command_pid, SIGTERM); + + while (command_pid > 0) + { + pid_t waited_pid; + waited_pid = waitpid (command_pid, (int *) 0, 0); + if (waited_pid < 0 && errno == EINTR) + continue; + if (waited_pid == command_pid) + command_pid = -1; + } + + close (dev_null_fd); + close (protocol_pipe[0]); + close (protocol_pipe[1]); + close (stderr_pipe[0]); + close (stderr_pipe[1]); + close (stdout_pipe[0]); + close (stdout_pipe[1]); + + free_args_and_return: + /* Now free the arguments. */ + { + /* argument_vector[0] is a dummy argument, we don't mess with it. */ + char **cp; + for (cp = argument_vector + 1; + cp < argument_vector + argument_count; + ++cp) + free (*cp); + + argument_count = 1; + } + return; +} + +#ifdef SERVER_FLOWCONTROL +/* + * Called by the child at convenient points in the server's execution for + * the server child to block.. ie: when it has no locks active. + */ +void +server_pause_check() +{ + int paused = 0; + char buf[1]; + + while (read (flowcontrol_pipe[0], buf, 1) == 1) + { + if (*buf == 'S') /* Stop */ + paused = 1; + else if (*buf == 'G') /* Go */ + paused = 0; + else + return; /* ??? */ + } + while (paused) { + int numfds, numtocheck; + fd_set fds; + + FD_ZERO (&fds); + FD_SET (flowcontrol_pipe[0], &fds); + numtocheck = flowcontrol_pipe[0] + 1; + + do { + numfds = select (numtocheck, &fds, (fd_set *)0, + (fd_set *)0, (struct timeval *)NULL); + if (numfds < 0 + && errno != EINTR) + { + print_error (errno); + return; + } + } while (numfds < 0); + + if (FD_ISSET (flowcontrol_pipe[0], &fds)) + { + while (read (flowcontrol_pipe[0], buf, 1) == 1) + { + if (*buf == 'S') /* Stop */ + paused = 1; + else if (*buf == 'G') /* Go */ + paused = 0; + else + return; /* ??? */ + } + } + } +} +#endif /* SERVER_FLOWCONTROL */ + +static void output_dir PROTO((char *, char *)); + +static void +output_dir (update_dir, repository) + char *update_dir; + char *repository; +{ + if (use_dir_and_repos) + { + if (update_dir[0] == '\0') + buf_output0 (&protocol, "."); + else + buf_output0 (&protocol, update_dir); + buf_output0 (&protocol, "/\n"); + } + buf_output0 (&protocol, repository); + buf_output0 (&protocol, "/"); +} + +/* + * Entries line that we are squirreling away to send to the client when + * we are ready. + */ +static char *entries_line; + +/* + * File which has been Scratch_File'd, we are squirreling away that fact + * to inform the client when we are ready. + */ +static char *scratched_file; + +/* + * The scratched_file will need to be removed as well as having its entry + * removed. + */ +static int kill_scratched_file; + +void +server_register (name, version, timestamp, options, tag, date, conflict) + char *name; + char *version; + char *timestamp; + char *options; + char *tag; + char *date; + char *conflict; +{ + int len; + + if (options == NULL) + options = ""; + + if (trace) + { + (void) fprintf (stderr, + "%c-> server_register(%s, %s, %s, %s, %s, %s, %s)\n", + (server_active) ? 'S' : ' ', /* silly */ + name, version, timestamp, options, tag ? tag : "", + date ? date : "", conflict ? conflict : ""); + } + + if (entries_line != NULL) + { + /* + * If CVS decides to Register it more than once (which happens + * on "cvs update foo/foo.c" where foo and foo.c are already + * checked out), use the last of the entries lines Register'd. + */ + free (entries_line); + } + + /* + * I have reports of Scratch_Entry and Register both happening, in + * two different cases. Using the last one which happens is almost + * surely correct; I haven't tracked down why they both happen (or + * even verified that they are for the same file). + */ + if (scratched_file != NULL) + { + free (scratched_file); + scratched_file = NULL; + } + + len = (strlen (name) + strlen (version) + strlen (options) + 80); + if (tag) + len += strlen (tag); + if (date) + len += strlen (date); + + entries_line = xmalloc (len); + sprintf (entries_line, "/%s/%s/", name, version); + if (conflict != NULL) + { + strcat (entries_line, "+="); + } + strcat (entries_line, "/"); + strcat (entries_line, options); + strcat (entries_line, "/"); + if (tag != NULL) + { + strcat (entries_line, "T"); + strcat (entries_line, tag); + } + else if (date != NULL) + { + strcat (entries_line, "D"); + strcat (entries_line, date); + } +} + +void +server_scratch (fname) + char *fname; +{ + /* + * I have reports of Scratch_Entry and Register both happening, in + * two different cases. Using the last one which happens is almost + * surely correct; I haven't tracked down why they both happen (or + * even verified that they are for the same file). + */ + if (entries_line != NULL) + { + free (entries_line); + entries_line = NULL; + } + + if (scratched_file != NULL) + { + buf_output0 (&protocol, + "E CVS server internal error: duplicate Scratch_Entry\n"); + buf_send_counted (&protocol); + return; + } + scratched_file = xstrdup (fname); + kill_scratched_file = 1; +} + +void +server_scratch_entry_only () +{ + kill_scratched_file = 0; +} + +/* Print a new entries line, from a previous server_register. */ +static void +new_entries_line () +{ + if (entries_line) + { + buf_output0 (&protocol, entries_line); + buf_output (&protocol, "\n", 1); + } + else + /* Return the error message as the Entries line. */ + buf_output0 (&protocol, + "CVS server internal error: Register missing\n"); + free (entries_line); + entries_line = NULL; +} + +static void +serve_ci (arg) + char *arg; +{ + do_cvs_command (commit); +} + +static void +checked_in_response (file, update_dir, repository) + char *file; + char *update_dir; + char *repository; +{ + if (supported_response ("Mode")) + { + struct stat sb; + char *mode_string; + + if (stat (file, &sb) < 0) + { + /* Not clear to me why the file would fail to exist, but it + was happening somewhere in the testsuite. */ + if (!existence_error (errno)) + error (0, errno, "cannot stat %s", file); + } + else + { + buf_output0 (&protocol, "Mode "); + mode_string = mode_to_string (sb.st_mode); + buf_output0 (&protocol, mode_string); + buf_output0 (&protocol, "\n"); + free (mode_string); + } + } + + buf_output0 (&protocol, "Checked-in "); + output_dir (update_dir, repository); + buf_output0 (&protocol, file); + buf_output (&protocol, "\n", 1); + new_entries_line (); +} + +void +server_checked_in (file, update_dir, repository) + char *file; + char *update_dir; + char *repository; +{ + if (noexec) + return; + if (scratched_file != NULL && entries_line == NULL) + { + /* + * This happens if we are now doing a "cvs remove" after a previous + * "cvs add" (without a "cvs ci" in between). + */ + buf_output0 (&protocol, "Remove-entry "); + output_dir (update_dir, repository); + buf_output0 (&protocol, file); + buf_output (&protocol, "\n", 1); + free (scratched_file); + scratched_file = NULL; + } + else + { + checked_in_response (file, update_dir, repository); + } + buf_send_counted (&protocol); +} + +void +server_update_entries (file, update_dir, repository, updated) + char *file; + char *update_dir; + char *repository; + enum server_updated_arg4 updated; +{ + if (noexec) + return; + if (updated == SERVER_UPDATED) + checked_in_response (file, update_dir, repository); + else + { + if (!supported_response ("New-entry")) + return; + buf_output0 (&protocol, "New-entry "); + output_dir (update_dir, repository); + buf_output0 (&protocol, file); + buf_output (&protocol, "\n", 1); + new_entries_line (); + } + + buf_send_counted (&protocol); +} + +static void +serve_update (arg) + char *arg; +{ + do_cvs_command (update); +} + +static void +serve_diff (arg) + char *arg; +{ + do_cvs_command (diff); +} + +static void +serve_log (arg) + char *arg; +{ + do_cvs_command (cvslog); +} + +static void +serve_add (arg) + char *arg; +{ + do_cvs_command (add); +} + +static void +serve_remove (arg) + char *arg; +{ + do_cvs_command (cvsremove); +} + +static void +serve_status (arg) + char *arg; +{ + do_cvs_command (status); +} + +static void +serve_rdiff (arg) + char *arg; +{ + do_cvs_command (patch); +} + +static void +serve_tag (arg) + char *arg; +{ + do_cvs_command (tag); +} + +static void +serve_rtag (arg) + char *arg; +{ + do_cvs_command (rtag); +} + +static void +serve_import (arg) + char *arg; +{ + do_cvs_command (import); +} + +static void +serve_admin (arg) + char *arg; +{ + do_cvs_command (admin); +} + +static void +serve_history (arg) + char *arg; +{ + do_cvs_command (history); +} + +static void +serve_release (arg) + char *arg; +{ + do_cvs_command (release); +} + +static void serve_watch_on PROTO ((char *)); + +static void +serve_watch_on (arg) + char *arg; +{ + do_cvs_command (watch_on); +} + +static void serve_watch_off PROTO ((char *)); + +static void +serve_watch_off (arg) + char *arg; +{ + do_cvs_command (watch_off); +} + +static void serve_watch_add PROTO ((char *)); + +static void +serve_watch_add (arg) + char *arg; +{ + do_cvs_command (watch_add); +} + +static void serve_watch_remove PROTO ((char *)); + +static void +serve_watch_remove (arg) + char *arg; +{ + do_cvs_command (watch_remove); +} + +static void serve_watchers PROTO ((char *)); + +static void +serve_watchers (arg) + char *arg; +{ + do_cvs_command (watchers); +} + +static void serve_editors PROTO ((char *)); + +static void +serve_editors (arg) + char *arg; +{ + do_cvs_command (editors); +} + +static int noop PROTO ((int, char **)); + +static int +noop (argc, argv) + int argc; + char **argv; +{ + return 0; +} + +static void serve_noop PROTO ((char *)); + +static void +serve_noop (arg) + char *arg; +{ + do_cvs_command (noop); +} + +static void serve_init PROTO ((char *)); + +static void +serve_init (arg) + char *arg; +{ + CVSroot = malloc (strlen (arg) + 1); + if (CVSroot == NULL) + { + pending_error = ENOMEM; + return; + } + strcpy (CVSroot, arg); + + do_cvs_command (init); +} + +static void serve_annotate PROTO ((char *)); + +static void +serve_annotate (arg) + char *arg; +{ + do_cvs_command (annotate); +} + +static void +serve_co (arg) + char *arg; +{ + char *tempdir; + int status; + + if (print_pending_error ()) + return; + + if (!isdir (CVSADM)) + { + /* + * The client has not sent a "Repository" line. Check out + * into a pristine directory. + */ + tempdir = malloc (strlen (server_temp_dir) + 80); + if (tempdir == NULL) + { + printf ("E Out of memory\n"); + return; + } + strcpy (tempdir, server_temp_dir); + strcat (tempdir, "/checkout-dir"); + status = mkdir_p (tempdir); + if (status != 0 && status != EEXIST) + { + printf ("E Cannot create %s\n", tempdir); + print_error (errno); + free (tempdir); + return; + } + + if (chdir (tempdir) < 0) + { + printf ("E Cannot change to directory %s\n", tempdir); + print_error (errno); + free (tempdir); + return; + } + free (tempdir); + } + do_cvs_command (checkout); +} + +static void +serve_export (arg) + char *arg; +{ + /* Tell checkout() to behave like export not checkout. */ + command_name = "export"; + serve_co (arg); +} + +void +server_copy_file (file, update_dir, repository, newfile) + char *file; + char *update_dir; + char *repository; + char *newfile; +{ + if (!supported_response ("Copy-file")) + return; + buf_output0 (&protocol, "Copy-file "); + output_dir (update_dir, repository); + buf_output0 (&protocol, file); + buf_output0 (&protocol, "\n"); + buf_output0 (&protocol, newfile); + buf_output0 (&protocol, "\n"); +} + +void +server_updated (file, update_dir, repository, updated, file_info, checksum) + char *file; + char *update_dir; + char *repository; + enum server_updated_arg4 updated; + struct stat *file_info; + unsigned char *checksum; +{ + char *short_pathname; + + if (noexec) + return; + + short_pathname = xmalloc (strlen (update_dir) + strlen (file) + 10); + if (update_dir[0] == '\0') + strcpy (short_pathname, file); + else + sprintf (short_pathname, "%s/%s", update_dir, file); + + 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 (stat (file, &sb) < 0) + { + 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", short_pathname); + } + + if (checksum != NULL) + { + static int checksum_supported = -1; + + if (checksum_supported == -1) + { + checksum_supported = supported_response ("Checksum"); + } + + if (checksum_supported) + { + int i; + char buf[3]; + + buf_output0 (&protocol, "Checksum "); + for (i = 0; i < 16; i++) + { + sprintf (buf, "%02x", (unsigned int) checksum[i]); + buf_output0 (&protocol, buf); + } + buf_append_char (&protocol, '\n'); + } + } + + if (updated == SERVER_UPDATED) + buf_output0 (&protocol, "Updated "); + else if (updated == SERVER_MERGED) + buf_output0 (&protocol, "Merged "); + else if (updated == SERVER_PATCHED) + buf_output0 (&protocol, "Patched "); + else + abort (); + output_dir (update_dir, repository); + buf_output0 (&protocol, file); + buf_output (&protocol, "\n", 1); + + new_entries_line (); + + { + char *mode_string; + + /* FIXME: When we check out files the umask of the server + (set in .bashrc if rsh is in use, or set in main.c in + the kerberos case, I think) 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); + buf_output0 (&protocol, mode_string); + buf_output0 (&protocol, "\n"); + free (mode_string); + } + + list = last = NULL; + size = 0; + if (sb.st_size > 0) + { + /* Throughout this section we use binary mode to read the + file we are sending. The client handles any line ending + translation if necessary. */ + + if (gzip_level + /* + * For really tiny files, the gzip process startup + * time will outweigh the compression savings. This + * might be computable somehow; using 100 here is just + * a first approximation. + */ + && sb.st_size > 100) + { + int status, fd, gzip_status; + pid_t gzip_pid; + + fd = open (file, O_RDONLY | OPEN_BINARY, 0); + if (fd < 0) + error (1, errno, "reading %s", short_pathname); + fd = filter_through_gzip (fd, 1, gzip_level, &gzip_pid); + f = fdopen (fd, "rb"); + status = buf_read_file_to_eof (f, &list, &last); + size = buf_chain_length (list); + if (status == -2) + (*protocol.memory_error) (&protocol); + else if (status != 0) + error (1, ferror (f) ? errno : 0, "reading %s", + short_pathname); + if (fclose (f) == EOF) + error (1, errno, "reading %s", short_pathname); + if (waitpid (gzip_pid, &gzip_status, 0) == -1) + error (1, errno, "waiting for gzip process %ld", + (long) gzip_pid); + else if (gzip_status != 0) + error (1, 0, "gzip exited %d", gzip_status); + /* Prepending length with "z" is flag for using gzip here. */ + buf_output0 (&protocol, "z"); + } + else + { + long status; + + size = sb.st_size; + f = fopen (file, "rb"); + if (f == NULL) + error (1, errno, "reading %s", short_pathname); + status = buf_read_file (f, sb.st_size, &list, &last); + if (status == -2) + (*protocol.memory_error) (&protocol); + else if (status != 0) + error (1, ferror (f) ? errno : 0, "reading %s", + short_pathname); + if (fclose (f) == EOF) + error (1, errno, "reading %s", short_pathname); + } + } + + sprintf (size_text, "%lu\n", size); + buf_output0 (&protocol, size_text); + + buf_append_data (&protocol, list, last); + /* Note we only send a newline here if the file ended with one. */ + + /* + * Avoid using up too much disk space for temporary files. + * A file which does not exist indicates that the file is up-to-date, + * which is now the case. If this is SERVER_MERGED, the file is + * not up-to-date, and we indicate that by leaving the file there. + * I'm thinking of cases like "cvs update foo/foo.c foo". + */ + if ((updated == SERVER_UPDATED || updated == SERVER_PATCHED) + /* But if we are joining, we'll need the file when we call + join_file. */ + && !joining ()) + unlink (file); + } + else if (scratched_file != NULL && entries_line == NULL) + { + if (strcmp (scratched_file, file) != 0) + error (1, 0, + "CVS server internal error: `%s' vs. `%s' scratched", + scratched_file, + file); + free (scratched_file); + scratched_file = NULL; + + if (kill_scratched_file) + buf_output0 (&protocol, "Removed "); + else + buf_output0 (&protocol, "Remove-entry "); + output_dir (update_dir, repository); + buf_output0 (&protocol, file); + buf_output (&protocol, "\n", 1); + } + else if (scratched_file == NULL && entries_line == NULL) + { + /* + * This can happen with death support if we were processing + * a dead file in a checkout. + */ + } + else + error (1, 0, + "CVS server internal error: Register *and* Scratch_Entry.\n"); + buf_send_counted (&protocol); + done: + free (short_pathname); +} + +void +server_set_entstat (update_dir, repository) + char *update_dir; + char *repository; +{ + static int set_static_supported = -1; + if (set_static_supported == -1) + set_static_supported = supported_response ("Set-static-directory"); + if (!set_static_supported) return; + + buf_output0 (&protocol, "Set-static-directory "); + output_dir (update_dir, repository); + buf_output0 (&protocol, "\n"); + buf_send_counted (&protocol); +} + +void +server_clear_entstat (update_dir, repository) + char *update_dir; + char *repository; +{ + static int clear_static_supported = -1; + if (clear_static_supported == -1) + clear_static_supported = supported_response ("Clear-static-directory"); + if (!clear_static_supported) return; + + if (noexec) + return; + + buf_output0 (&protocol, "Clear-static-directory "); + output_dir (update_dir, repository); + buf_output0 (&protocol, "\n"); + buf_send_counted (&protocol); +} + +void +server_set_sticky (update_dir, repository, tag, date) + char *update_dir; + char *repository; + char *tag; + char *date; +{ + static int set_sticky_supported = -1; + if (set_sticky_supported == -1) + set_sticky_supported = supported_response ("Set-sticky"); + if (!set_sticky_supported) return; + + if (noexec) + return; + + if (tag == NULL && date == NULL) + { + buf_output0 (&protocol, "Clear-sticky "); + output_dir (update_dir, repository); + buf_output0 (&protocol, "\n"); + } + else + { + buf_output0 (&protocol, "Set-sticky "); + output_dir (update_dir, repository); + buf_output0 (&protocol, "\n"); + if (tag != NULL) + { + buf_output0 (&protocol, "T"); + buf_output0 (&protocol, tag); + } + else + { + buf_output0 (&protocol, "D"); + buf_output0 (&protocol, date); + } + buf_output0 (&protocol, "\n"); + } + buf_send_counted (&protocol); +} + +struct template_proc_data +{ + char *update_dir; + char *repository; +}; + +/* Here as a static until we get around to fixing Parse_Info to pass along + a void * for it. */ +static struct template_proc_data *tpd; + +static int +template_proc (repository, template) + char *repository; + char *template; +{ + FILE *fp; + char buf[1024]; + size_t n; + struct stat sb; + struct template_proc_data *data = tpd; + + if (!supported_response ("Template")) + /* Might want to warn the user that the rcsinfo feature won't work. */ + return 0; + buf_output0 (&protocol, "Template "); + output_dir (data->update_dir, data->repository); + buf_output0 (&protocol, "\n"); + + fp = fopen (template, "rb"); + if (fp == NULL) + { + error (0, errno, "Couldn't open rcsinfo template file %s", template); + return 1; + } + if (fstat (fileno (fp), &sb) < 0) + { + error (0, errno, "cannot stat rcsinfo template file %s", template); + return 1; + } + sprintf (buf, "%ld\n", (long) sb.st_size); + buf_output0 (&protocol, buf); + while (!feof (fp)) + { + n = fread (buf, 1, sizeof buf, fp); + buf_output (&protocol, buf, n); + if (ferror (fp)) + { + error (0, errno, "cannot read rcsinfo template file %s", template); + (void) fclose (fp); + return 1; + } + } + if (fclose (fp) < 0) + error (0, errno, "cannot close rcsinfo template file %s", template); + return 0; +} + +void +server_template (update_dir, repository) + char *update_dir; + char *repository; +{ + struct template_proc_data data; + data.update_dir = update_dir; + data.repository = repository; + tpd = &data; + (void) Parse_Info (CVSROOTADM_RCSINFO, repository, template_proc, 1); +} + +static void +serve_gzip_contents (arg) + char *arg; +{ + int level; + level = atoi (arg); + if (level == 0) + level = 6; + gzip_level = level; +} + +static void +serve_ignore (arg) + char *arg; +{ + /* + * Just ignore this command. This is used to support the + * update-patches command, which is not a real command, but a signal + * to the client that update will accept the -u argument. + */ +} + +static int +expand_proc (pargc, argv, where, mwhere, mfile, shorten, + local_specified, omodule, msg) + int *pargc; + char **argv; + char *where; + char *mwhere; + char *mfile; + int shorten; + int local_specified; + char *omodule; + char *msg; +{ + int i; + char *dir = argv[0]; + + /* If mwhere has been specified, the thing we're expanding is a + module -- just return its name so the client will ask for the + right thing later. If it is an alias or a real directory, + mwhere will not be set, so send out the appropriate + expansion. */ + + if (mwhere != NULL) + { + printf ("Module-expansion %s", mwhere); + if (mfile != NULL) + { + printf ("/%s", mfile); + } + printf ("\n"); + } + else + { + /* We may not need to do this anymore -- check the definition + of aliases before removing */ + if (*pargc == 1) + printf ("Module-expansion %s\n", dir); + else + for (i = 1; i < *pargc; ++i) + printf ("Module-expansion %s/%s\n", dir, argv[i]); + } + return 0; +} + +static void +serve_expand_modules (arg) + char *arg; +{ + int i; + int err; + DBM *db; + err = 0; + + /* + * FIXME: error handling is bogus; do_module can write to stdout and/or + * stderr and we're not using do_cvs_command. + */ + + server_expanding = 1; + db = open_module (); + for (i = 1; i < argument_count; i++) + err += do_module (db, argument_vector[i], + CHECKOUT, "Updating", expand_proc, + NULL, 0, 0, 0, + (char *) NULL); + close_module (db); + server_expanding = 0; + { + /* argument_vector[0] is a dummy argument, we don't mess with it. */ + char **cp; + for (cp = argument_vector + 1; + cp < argument_vector + argument_count; + ++cp) + free (*cp); + + argument_count = 1; + } + if (err) + /* We will have printed an error message already. */ + printf ("error \n"); + else + printf ("ok\n"); +} + +void +server_prog (dir, name, which) + char *dir; + char *name; + enum progs which; +{ + if (!supported_response ("Set-checkin-prog")) + { + printf ("E \ +warning: this client does not support -i or -u flags in the modules file.\n"); + return; + } + switch (which) + { + case PROG_CHECKIN: + printf ("Set-checkin-prog "); + break; + case PROG_UPDATE: + printf ("Set-update-prog "); + break; + } + printf ("%s\n%s\n", dir, name); +} + +static void +serve_checkin_prog (arg) + char *arg; +{ + FILE *f; + f = fopen (CVSADM_CIPROG, "w+"); + if (f == NULL) + { + pending_error = errno; + pending_error_text = malloc (80 + strlen(CVSADM_CIPROG)); + sprintf(pending_error_text, "E cannot open %s", CVSADM_CIPROG); + return; + } + if (fprintf (f, "%s\n", arg) < 0) + { + pending_error = errno; + pending_error_text = malloc (80 + strlen(CVSADM_CIPROG)); + sprintf(pending_error_text, "E cannot write to %s", CVSADM_CIPROG); + return; + } + if (fclose (f) == EOF) + { + pending_error = errno; + pending_error_text = malloc (80 + strlen(CVSADM_CIPROG)); + sprintf(pending_error_text, "E cannot close %s", CVSADM_CIPROG); + return; + } +} + +static void +serve_update_prog (arg) + char *arg; +{ + FILE *f; + f = fopen (CVSADM_UPROG, "w+"); + if (f == NULL) + { + pending_error = errno; + pending_error_text = malloc (80 + strlen(CVSADM_UPROG)); + sprintf(pending_error_text, "E cannot open %s", CVSADM_UPROG); + return; + } + if (fprintf (f, "%s\n", arg) < 0) + { + pending_error = errno; + pending_error_text = malloc (80 + strlen(CVSADM_UPROG)); + sprintf(pending_error_text, "E cannot write to %s", CVSADM_UPROG); + return; + } + if (fclose (f) == EOF) + { + pending_error = errno; + pending_error_text = malloc (80 + strlen(CVSADM_UPROG)); + sprintf(pending_error_text, "E cannot close %s", CVSADM_UPROG); + return; + } +} + +static void serve_valid_requests PROTO((char *arg)); + +#endif /* SERVER_SUPPORT */ +#if defined(SERVER_SUPPORT) || defined(CLIENT_SUPPORT) + +/* + * Parts of this table are shared with the client code, + * but the client doesn't need to know about the handler + * functions. + */ + +struct request requests[] = +{ +#ifdef SERVER_SUPPORT +#define REQ_LINE(n, f, s) {n, f, s} +#else +#define REQ_LINE(n, f, s) {n, s} +#endif + + REQ_LINE("Root", serve_root, rq_essential), + REQ_LINE("Valid-responses", serve_valid_responses, rq_essential), + REQ_LINE("valid-requests", serve_valid_requests, rq_essential), + REQ_LINE("Repository", serve_repository, rq_essential), + REQ_LINE("Directory", serve_directory, rq_optional), + REQ_LINE("Max-dotdot", serve_max_dotdot, rq_optional), + REQ_LINE("Static-directory", serve_static_directory, rq_optional), + REQ_LINE("Sticky", serve_sticky, rq_optional), + REQ_LINE("Checkin-prog", serve_checkin_prog, rq_optional), + REQ_LINE("Update-prog", serve_update_prog, rq_optional), + REQ_LINE("Entry", serve_entry, rq_essential), + REQ_LINE("Modified", serve_modified, rq_essential), + REQ_LINE("Lost", serve_lost, rq_optional), + REQ_LINE("UseUnchanged", serve_enable_unchanged, rq_enableme), + REQ_LINE("Unchanged", serve_unchanged, rq_optional), + REQ_LINE("Notify", serve_notify, rq_optional), + REQ_LINE("Questionable", serve_questionable, rq_optional), + REQ_LINE("Case", serve_case, rq_optional), + REQ_LINE("Argument", serve_argument, rq_essential), + REQ_LINE("Argumentx", serve_argumentx, rq_essential), + REQ_LINE("Global_option", serve_global_option, rq_optional), + REQ_LINE("Set", serve_set, rq_optional), + REQ_LINE("expand-modules", serve_expand_modules, rq_optional), + REQ_LINE("ci", serve_ci, rq_essential), + REQ_LINE("co", serve_co, rq_essential), + REQ_LINE("update", serve_update, rq_essential), + REQ_LINE("diff", serve_diff, rq_optional), + REQ_LINE("log", serve_log, rq_optional), + REQ_LINE("add", serve_add, rq_optional), + REQ_LINE("remove", serve_remove, rq_optional), + REQ_LINE("update-patches", serve_ignore, rq_optional), + REQ_LINE("gzip-file-contents", serve_gzip_contents, rq_optional), + REQ_LINE("status", serve_status, rq_optional), + REQ_LINE("rdiff", serve_rdiff, rq_optional), + REQ_LINE("tag", serve_tag, rq_optional), + REQ_LINE("rtag", serve_rtag, rq_optional), + REQ_LINE("import", serve_import, rq_optional), + REQ_LINE("admin", serve_admin, rq_optional), + REQ_LINE("export", serve_export, rq_optional), + REQ_LINE("history", serve_history, rq_optional), + REQ_LINE("release", serve_release, rq_optional), + REQ_LINE("watch-on", serve_watch_on, rq_optional), + REQ_LINE("watch-off", serve_watch_off, rq_optional), + REQ_LINE("watch-add", serve_watch_add, rq_optional), + REQ_LINE("watch-remove", serve_watch_remove, rq_optional), + REQ_LINE("watchers", serve_watchers, rq_optional), + REQ_LINE("editors", serve_editors, rq_optional), + REQ_LINE("init", serve_init, rq_optional), + REQ_LINE("annotate", serve_annotate, rq_optional), + REQ_LINE("noop", serve_noop, rq_optional), + REQ_LINE(NULL, NULL, rq_optional) + +#undef REQ_LINE +}; + +#endif /* SERVER_SUPPORT or CLIENT_SUPPORT */ +#ifdef SERVER_SUPPORT + +static void +serve_valid_requests (arg) + char *arg; +{ + struct request *rq; + if (print_pending_error ()) + return; + printf ("Valid-requests"); + for (rq = requests; rq->name != NULL; rq++) + if (rq->func != NULL) + printf (" %s", rq->name); + printf ("\nok\n"); +} + +#ifdef sun +/* + * Delete temporary files. SIG is the signal making this happen, or + * 0 if not called as a result of a signal. + */ +static int command_pid_is_dead; +static void wait_sig (sig) + int sig; +{ + int status; + pid_t r = wait (&status); + if (r == command_pid) + command_pid_is_dead++; +} +#endif + +void +server_cleanup (sig) + int sig; +{ + /* Do "rm -rf" on the temp directory. */ + int len; + char *cmd; + char *temp_dir; + + if (dont_delete_temp) + return; + + /* What a bogus kludge. This disgusting code makes all kinds of + assumptions about SunOS, and is only for a bug in that system. + So only enable it on Suns. */ +#ifdef sun + if (command_pid > 0) { + /* To avoid crashes on SunOS due to bugs in SunOS tmpfs + triggered by the use of rename() in RCS, wait for the + subprocess to die. Unfortunately, this means draining output + while waiting for it to unblock the signal we sent it. Yuck! */ + int status; + pid_t r; + + signal (SIGCHLD, wait_sig); + if (sig) + /* Perhaps SIGTERM would be more correct. But the child + process will delay the SIGINT delivery until its own + children have exited. */ + kill (command_pid, SIGINT); + /* The caller may also have sent a signal to command_pid, so + always try waiting. First, though, check and see if it's still + there.... */ + do_waitpid: + r = waitpid (command_pid, &status, WNOHANG); + if (r == 0) + ; + else if (r == command_pid) + command_pid_is_dead++; + else if (r == -1) + switch (errno) { + case ECHILD: + command_pid_is_dead++; + break; + case EINTR: + goto do_waitpid; + } + else + /* waitpid should always return one of the above values */ + abort (); + while (!command_pid_is_dead) { + struct timeval timeout; + struct fd_set_wrapper readfds; + char buf[100]; + int i; + + /* Use a non-zero timeout to avoid eating up CPU cycles. */ + timeout.tv_sec = 2; + timeout.tv_usec = 0; + readfds = command_fds_to_drain; + switch (select (max_command_fd + 1, &readfds.fds, + (fd_set *)0, (fd_set *)0, + &timeout)) { + case -1: + if (errno != EINTR) + abort (); + case 0: + /* timeout */ + break; + case 1: + for (i = 0; i <= max_command_fd; i++) + { + if (!FD_ISSET (i, &readfds.fds)) + continue; + /* this fd is non-blocking */ + while (read (i, buf, sizeof (buf)) >= 1) + ; + } + break; + default: + abort (); + } + } + } +#endif + + /* This might be set by the user in ~/.bashrc, ~/.cshrc, etc. */ + temp_dir = getenv ("TMPDIR"); + if (temp_dir == NULL || temp_dir[0] == '\0') + temp_dir = "/tmp"; + chdir(temp_dir); + + len = strlen (server_temp_dir) + 80; + cmd = malloc (len); + if (cmd == NULL) + { + printf ("E Cannot delete %s on server; out of memory\n", + server_temp_dir); + return; + } + sprintf (cmd, "rm -rf %s", server_temp_dir); + system (cmd); + free (cmd); +} + +int server_active = 0; +int server_expanding = 0; + +int +server (argc, argv) + int argc; + char **argv; +{ + if (argc == -1) + { + static const char *const msg[] = + { + "Usage: %s %s\n", + " Normally invoked by a cvs client on a remote machine.\n", + NULL + }; + usage (msg); + } + /* Ignore argc and argv. They might be from .cvsrc. */ + + /* Since we're in the server parent process, error should use the + protocol to report error messages. */ + error_use_protocol = 1; + + /* + * Put Rcsbin at the start of PATH, so that rcs programs can find + * themselves. + */ +#ifdef HAVE_PUTENV + if (Rcsbin != NULL && *Rcsbin) + { + char *p; + char *env; + + p = getenv ("PATH"); + if (p != NULL) + { + env = malloc (strlen (Rcsbin) + strlen (p) + sizeof "PATH=:"); + if (env != NULL) + sprintf (env, "PATH=%s:%s", Rcsbin, p); + } + else + { + env = malloc (strlen (Rcsbin) + sizeof "PATH="); + if (env != NULL) + sprintf (env, "PATH=%s", Rcsbin); + } + if (env == NULL) + { + printf ("E Fatal server error, aborting.\n\ +error ENOMEM Virtual memory exhausted.\n"); + exit (EXIT_FAILURE); + } + putenv (env); + } +#endif + + /* OK, now figure out where we stash our temporary files. */ + { + char *p; + + /* This might be set by the user in ~/.bashrc, ~/.cshrc, etc. */ + char *temp_dir = getenv ("TMPDIR"); + if (temp_dir == NULL || temp_dir[0] == '\0') + temp_dir = "/tmp"; + + server_temp_dir = malloc (strlen (temp_dir) + 80); + if (server_temp_dir == NULL) + { + /* + * Strictly speaking, we're not supposed to output anything + * now. But we're about to exit(), give it a try. + */ + printf ("E Fatal server error, aborting.\n\ +error ENOMEM Virtual memory exhausted.\n"); + exit (EXIT_FAILURE); + } + strcpy (server_temp_dir, temp_dir); + + /* Remove a trailing slash from TMPDIR if present. */ + p = server_temp_dir + strlen (server_temp_dir) - 1; + if (*p == '/') + *p = '\0'; + + /* + * I wanted to use cvs-serv/PID, but then you have to worry about + * the permissions on the cvs-serv directory being right. So + * use cvs-servPID. + */ + strcat (server_temp_dir, "/cvs-serv"); + + p = server_temp_dir + strlen (server_temp_dir); + sprintf (p, "%ld", (long) getpid ()); + } + + (void) SIG_register (SIGHUP, server_cleanup); + (void) SIG_register (SIGINT, server_cleanup); + (void) SIG_register (SIGQUIT, server_cleanup); + (void) SIG_register (SIGPIPE, server_cleanup); + (void) SIG_register (SIGTERM, server_cleanup); + + /* Now initialize our argument vector (for arguments from the client). */ + + /* Small for testing. */ + argument_vector_size = 1; + argument_vector = + (char **) malloc (argument_vector_size * sizeof (char *)); + if (argument_vector == NULL) + { + /* + * Strictly speaking, we're not supposed to output anything + * now. But we're about to exit(), give it a try. + */ + printf ("E Fatal server error, aborting.\n\ +error ENOMEM Virtual memory exhausted.\n"); + exit (EXIT_FAILURE); + } + + argument_count = 1; + argument_vector[0] = "Dummy argument 0"; + + buf_to_net.data = buf_to_net.last = NULL; + buf_to_net.fd = STDOUT_FILENO; + buf_to_net.output = 1; + buf_to_net.nonblocking = 0; + buf_to_net.memory_error = outbuf_memory_error; + + server_active = 1; + + while (1) + { + char *cmd, *orig_cmd; + struct request *rq; + + orig_cmd = cmd = read_line (stdin); + if (cmd == NULL) + break; + if (cmd == NO_MEM_ERROR) + { + printf ("E Fatal server error, aborting.\n\ +error ENOMEM Virtual memory exhausted.\n"); + break; + } + for (rq = requests; rq->name != NULL; ++rq) + if (strncmp (cmd, rq->name, strlen (rq->name)) == 0) + { + int len = strlen (rq->name); + if (cmd[len] == '\0') + cmd += len; + else if (cmd[len] == ' ') + cmd += len + 1; + else + /* + * The first len characters match, but it's a different + * command. e.g. the command is "cooperate" but we matched + * "co". + */ + continue; + (*rq->func) (cmd); + break; + } + if (rq->name == NULL) + { + if (!print_pending_error ()) + printf ("error unrecognized request `%s'\n", cmd); + } + free (orig_cmd); + } + server_cleanup (0); + return 0; +} + + +#ifdef AUTH_SERVER_SUPPORT + +extern char *crypt PROTO((const char *, const char *)); + +/* This was test code, which we may need again. */ +#if 0 + /* If we were invoked this way, then stdin comes from the + client and stdout/stderr writes to it. */ + int c; + while ((c = getc (stdin)) != EOF && c != '*') + { + printf ("%c", toupper (c)); + fflush (stdout); + } + exit (0); +#endif /* 1/0 */ + + +/* + * 0 means no entry found for this user. + * 1 means entry found and password matches. + * 2 means entry found, but password does not match. + */ +int +check_repository_password (username, password, repository, host_user_ptr) + char *username, *password, *repository, **host_user_ptr; +{ + int retval = 0; + FILE *fp; + char *filename; + char *linebuf; + int found_it = 0; + int namelen; + + filename = xmalloc (strlen (repository) + + 1 + + strlen ("CVSROOT") + + 1 + + strlen ("passwd") + + 1); + + strcpy (filename, repository); + strcat (filename, "/CVSROOT"); + strcat (filename, "/passwd"); + + fp = fopen (filename, "r"); + if (fp == NULL) + { + if (!existence_error (errno)) + error (0, errno, "cannot open %s", filename); + return 0; + } + + /* Look for a relevant line -- one with this user's name. */ + namelen = strlen (username); + while (1) + { + linebuf = read_line(fp); + if (linebuf == NULL) + { + free (linebuf); + break; + } + if (linebuf == NO_MEM_ERROR) + { + error (0, errno, "out of memory"); + break; + } + if ((strncmp (linebuf, username, namelen) == 0) + && (linebuf[namelen] == ':')) + { + found_it = 1; + break; + } + free (linebuf); + + } + if (ferror (fp)) + error (0, errno, "cannot read %s", filename); + if (fclose (fp) < 0) + error (0, errno, "cannot close %s", filename); + + /* If found_it != 0, then linebuf contains the information we need. */ + if (found_it) + { + char *found_password; + + strtok (linebuf, ":"); + found_password = strtok (NULL, ": \n"); + *host_user_ptr = strtok (NULL, ": \n"); + if (*host_user_ptr == NULL) *host_user_ptr = username; + if (strcmp (found_password, crypt (password, found_password)) == 0) + retval = 1; + else + retval = 2; + } + else + { + *host_user_ptr = NULL; + retval = 0; + } + + free (filename); + + return retval; +} + + +/* Return a hosting username if password matches, else NULL. */ +char * +check_password (username, password, repository) + char *username, *password, *repository; +{ + int rc; + char *host_user; + + /* First we see if this user has a password in the CVS-specific + password file. If so, that's enough to authenticate with. If + not, we'll check /etc/passwd. */ + + rc = check_repository_password (username, password, repository, + &host_user); + + if (rc == 1) + return host_user; + else if (rc == 2) + return 0; + else if (rc == 0) + { + /* No cvs password found, so try /etc/passwd. */ + + struct passwd *pw; + char *found_passwd; + + pw = getpwnam (username); + if (pw == NULL) + { + printf ("E Fatal error, aborting.\n\ +error 0 %s: no such user\n", username); + exit (EXIT_FAILURE); + } + found_passwd = pw->pw_passwd; + + if (found_passwd && *found_passwd) + return ((! strcmp (found_passwd, crypt (password, found_passwd))) + ? username : NULL); + else if (password && *password) + return username; + else + return NULL; + } + else + { + /* Something strange happened. We don't know what it was, but + we certainly won't grant authorization. */ + return NULL; + } +} + + +/* Read username and password from client (i.e., stdin). + If correct, then switch to run as that user and send an ACK to the + client via stdout, else send NACK and die. */ +void +authenticate_connection () +{ + char tmp[PATH_MAX]; + char repository[PATH_MAX]; + char username[PATH_MAX]; + char password[PATH_MAX]; + char *host_user; + char *descrambled_password; + struct passwd *pw; + int verify_and_exit = 0; + + /* The Authentication Protocol. Client sends: + * + * BEGIN AUTH REQUEST\n + * <REPOSITORY>\n + * <USERNAME>\n + * <PASSWORD>\n + * END AUTH REQUEST\n + * + * Server uses above information to authenticate, then sends + * + * I LOVE YOU\n + * + * if it grants access, else + * + * I HATE YOU\n + * + * if it denies access (and it exits if denying). + * + * When the client is "cvs login", the user does not desire actual + * repository access, but would like to confirm the password with + * the server. In this case, the start and stop strings are + * + * BEGIN VERIFICATION REQUEST\n + * + * and + * + * END VERIFICATION REQUEST\n + * + * On a verification request, the server's responses are the same + * (with the obvious semantics), but it exits immediately after + * sending the response in both cases. + * + * Why is the repository sent? Well, note that the actual + * client/server protocol can't start up until authentication is + * successful. But in order to perform authentication, the server + * needs to look up the password in the special CVS passwd file, + * before trying /etc/passwd. So the client transmits the + * repository as part of the "authentication protocol". The + * repository will be redundantly retransmitted later, but that's no + * big deal. + */ + + /* Since we're in the server parent process, error should use the + protocol to report error messages. */ + error_use_protocol = 1; + + /* Make sure the protocol starts off on the right foot... */ + fgets (tmp, PATH_MAX, stdin); + if (strcmp (tmp, "BEGIN VERIFICATION REQUEST\n") == 0) + verify_and_exit = 1; + else if (strcmp (tmp, "BEGIN AUTH REQUEST\n") != 0) + error (1, 0, "bad auth protocol start: %s", tmp); + + /* Get the three important pieces of information in order. */ + fgets (repository, PATH_MAX, stdin); + fgets (username, PATH_MAX, stdin); + fgets (password, PATH_MAX, stdin); + + /* Make them pure. */ + strip_trailing_newlines (repository); + strip_trailing_newlines (username); + strip_trailing_newlines (password); + + /* ... and make sure the protocol ends on the right foot. */ + fgets (tmp, PATH_MAX, stdin); + if (strcmp (tmp, + verify_and_exit ? + "END VERIFICATION REQUEST\n" : "END AUTH REQUEST\n") + != 0) + { + error (1, 0, "bad auth protocol end: %s", tmp); + } + + /* We need the real cleartext before we hash it. */ + descrambled_password = descramble (password); + host_user = check_password (username, descrambled_password, repository); + if (host_user) + { + printf ("I LOVE YOU\n"); + fflush (stdout); + memset (descrambled_password, 0, strlen (descrambled_password)); + free (descrambled_password); + } + else + { + printf ("I HATE YOU\n"); + fflush (stdout); + memset (descrambled_password, 0, strlen (descrambled_password)); + free (descrambled_password); + exit (EXIT_FAILURE); + } + + /* Don't go any farther if we're just responding to "cvs login". */ + if (verify_and_exit) + exit (0); + + /* Switch to run as this user. */ + pw = getpwnam (host_user); + if (pw == NULL) + { + error (1, 0, + "fatal error, aborting.\nerror 0 %s: no such user\n", + username); + } + +#if HAVE_INITGROUPS + initgroups (pw->pw_name, pw->pw_gid); +#endif /* HAVE_INITGROUPS */ + + setgid (pw->pw_gid); + setuid (pw->pw_uid); + /* Inhibit access by randoms. Don't want people randomly + changing our temporary tree before we check things in. */ + umask (077); + +#if HAVE_PUTENV + /* Set LOGNAME and USER in the environment, in case they are + already set to something else. */ + { + char *env; + + env = xmalloc (sizeof "LOGNAME=" + strlen (username)); + (void) sprintf (env, "LOGNAME=%s", username); + (void) putenv (env); + + env = xmalloc (sizeof "USER=" + strlen (username)); + (void) sprintf (env, "USER=%s", username); + (void) putenv (env); + } +#endif /* HAVE_PUTENV */ +} + +#endif /* AUTH_SERVER_SUPPORT */ + + +#endif /* SERVER_SUPPORT */ + +/* Output LEN bytes at STR. If LEN is zero, then output up to (not including) + the first '\0' byte. Should not be called from the server parent process + (yet at least, in the future it might be extended so that works). */ + +void +cvs_output (str, len) + char *str; + size_t len; +{ + if (len == 0) + len = strlen (str); + if (error_use_protocol) + /* Eventually we'll probably want to make it so this case works, + but for now, callers who want to output something with + error_use_protocol in effect can just printf the "M foo" + themselves. */ + abort (); +#ifdef SERVER_SUPPORT + if (server_active) + { + buf_output (&saved_output, str, len); + buf_copy_lines (&protocol, &saved_output, 'M'); + buf_send_counted (&protocol); + } + else +#endif + { + size_t written; + size_t to_write = len; + char *p = str; + + while (to_write > 0) + { + written = fwrite (str, 1, to_write, stdout); + if (written == 0) + break; + p += written; + to_write -= written; + } + } +} + +/* Like CVS_OUTPUT but output is for stderr not stdout. */ + +void +cvs_outerr (str, len) + char *str; + size_t len; +{ + if (len == 0) + len = strlen (str); + if (error_use_protocol) + /* Eventually we'll probably want to make it so this case works, + but for now, callers who want to output something with + error_use_protocol in effect can just printf the "E foo" + themselves. */ + abort (); +#ifdef SERVER_SUPPORT + if (server_active) + { + buf_output (&saved_outerr, str, len); + buf_copy_lines (&protocol, &saved_outerr, 'E'); + buf_send_counted (&protocol); + } + else +#endif + { + size_t written; + size_t to_write = len; + char *p = str; + + while (to_write > 0) + { + written = fwrite (str, 1, to_write, stderr); + if (written == 0) + break; + p += written; + to_write -= written; + } + } +} diff --git a/contrib/cvs/src/server.h b/contrib/cvs/src/server.h new file mode 100644 index 0000000..30ddb8c --- /dev/null +++ b/contrib/cvs/src/server.h @@ -0,0 +1,138 @@ +/* Interface between the server and the rest of CVS. */ + +/* Miscellaneous stuff which isn't actually particularly server-specific. */ +#ifndef STDIN_FILENO +#define STDIN_FILENO 0 +#define STDOUT_FILENO 1 +#define STDERR_FILENO 2 +#endif + +#ifdef SERVER_SUPPORT + +/* + * Nonzero if we are using the server. Used by various places to call + * server-specific functions. + */ +extern int server_active; +extern int server_expanding; + +/* Server functions exported to the rest of CVS. */ + +/* Run the server. */ +extern int server PROTO((int argc, char **argv)); + +/* We have a new Entries line for a file. TAG or DATE can be NULL. */ +extern void server_register + PROTO((char *name, char *version, char *timestamp, + char *options, char *tag, char *date, char *conflict)); + +/* + * We want to nuke the Entries line for a file, and (unless + * server_scratch_entry_only is subsequently called) the file itself. + */ +extern void server_scratch PROTO((char *name)); + +/* + * The file which just had server_scratch called on it needs to have only + * the Entries line removed, not the file itself. + */ +extern void server_scratch_entry_only PROTO((void)); + +/* + * We just successfully checked in FILE (which is just the bare + * filename, with no directory). REPOSITORY is the directory for the + * repository. + */ +extern void server_checked_in + PROTO((char *file, char *update_dir, char *repository)); + +extern void server_copy_file + PROTO((char *file, char *update_dir, char *repository, char *newfile)); + +/* + * We just successfully updated FILE (bare filename, no directory). + * REPOSITORY is the directory for the repository. This is called + * after server_register or server_scratch, in the latter case the + * file is to be removed. 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). + */ +enum server_updated_arg4 {SERVER_UPDATED, SERVER_MERGED, SERVER_PATCHED}; +extern void server_updated + PROTO((char *file, char *update_dir, char *repository, + enum server_updated_arg4 updated, struct stat *, + unsigned char *checksum)); + +/* Set the Entries.Static flag. */ +extern void server_set_entstat PROTO((char *update_dir, char *repository)); +/* Clear it. */ +extern void server_clear_entstat PROTO((char *update_dir, char *repository)); + +/* Set or clear a per-directory sticky tag or date. */ +extern void server_set_sticky PROTO((char *update_dir, char *repository, + char *tag, + char *date)); +/* Send Template response. */ +extern void server_template PROTO ((char *, char *)); + +extern void server_update_entries + PROTO((char *file, char *update_dir, char *repository, + enum server_updated_arg4 updated)); + +enum progs {PROG_CHECKIN, PROG_UPDATE}; +extern void server_prog PROTO((char *, char *, enum progs)); +extern void server_cleanup PROTO((int sig)); + +#ifdef SERVER_FLOWCONTROL +/* Pause if it's convenient to avoid memory blowout */ +extern void server_pause_check PROTO((void)); +#endif /* SERVER_FLOWCONTROL */ + +#endif /* SERVER_SUPPORT */ + +/* Stuff shared with the client. */ +struct request +{ + /* Name of the request. */ + char *name; + +#ifdef SERVER_SUPPORT + /* + * Function to carry out the request. ARGS is the text of the command + * after name and, if present, a single space, have been stripped off. + */ + void (*func) PROTO((char *args)); +#endif + + /* Stuff for use by the client. */ + enum { + /* + * Failure to implement this request can imply a fatal + * error. This should be set only for commands which were in the + * original version of the protocol; it should not be set for new + * commands. + */ + rq_essential, + + /* Some servers might lack this request. */ + rq_optional, + + /* + * Set by the client to one of the following based on what this + * server actually supports. + */ + rq_supported, + rq_not_supported, + + /* + * If the server supports this request, and we do too, tell the + * server by making the request. + */ + rq_enableme + } status; +}; + +/* Table of requests ending with an entry with a NULL name. */ +extern struct request requests[]; + +extern int use_unchanged; diff --git a/contrib/cvs/src/status.c b/contrib/cvs/src/status.c new file mode 100644 index 0000000..277da0c --- /dev/null +++ b/contrib/cvs/src/status.c @@ -0,0 +1,279 @@ +/* + * Copyright (c) 1992, Brian Berliner and Jeff Polk + * Copyright (c) 1989-1992, Brian Berliner + * + * You may distribute under the terms of the GNU General Public License as + * specified in the README file that comes with the CVS 1.4 kit. + * + * Status Information + */ + +#include "cvs.h" + +static Dtype status_dirproc PROTO((char *dir, char *repos, char *update_dir)); +static int status_fileproc PROTO((struct file_info *finfo)); +static int tag_list_proc PROTO((Node * p, void *closure)); + +static int local = 0; +static int long_format = 0; +static RCSNode *xrcsnode; + +static const char *const status_usage[] = +{ + "Usage: %s %s [-vlR] [files...]\n", + "\t-v\tVerbose format; includes tag information for the file\n", + "\t-l\tProcess this directory only (not recursive).\n", + "\t-R\tProcess directories recursively.\n", + NULL +}; + +int +status (argc, argv) + int argc; + char **argv; +{ + int c; + int err = 0; + + if (argc == -1) + usage (status_usage); + + optind = 1; + while ((c = getopt (argc, argv, "vlR")) != -1) + { + switch (c) + { + case 'v': + long_format = 1; + break; + case 'l': + local = 1; + break; + case 'R': + local = 0; + break; + case '?': + default: + usage (status_usage); + break; + } + } + argc -= optind; + argv += optind; + + wrap_setup (); + +#ifdef CLIENT_SUPPORT + if (client_active) { + start_server (); + + ign_setup (); + + if (long_format) + send_arg("-v"); + if (local) + send_arg("-l"); + + send_file_names (argc, argv, SEND_EXPAND_WILD); + /* XXX This should only need to send file info; the file + contents themselves will not be examined. */ + send_files (argc, argv, local, 0); + + send_to_server ("status\012", 0); + err = get_responses_and_close (); + + return err; + } +#endif + + /* start the recursion processor */ + err = start_recursion (status_fileproc, (FILESDONEPROC) NULL, status_dirproc, + (DIRLEAVEPROC) NULL, argc, argv, local, + W_LOCAL, 0, 1, (char *) NULL, 1, 0); + + return (err); +} + +/* + * display the status of a file + */ +/* ARGSUSED */ +static int +status_fileproc (finfo) + struct file_info *finfo; +{ + Ctype status; + char *sstat; + Vers_TS *vers; + + status = Classify_File (finfo->file, (char *) NULL, (char *) NULL, (char *) NULL, + 1, 0, finfo->repository, finfo->entries, finfo->rcs, &vers, + finfo->update_dir, 0); + switch (status) + { + case T_UNKNOWN: + sstat = "Unknown"; + break; + case T_CHECKOUT: + sstat = "Needs Checkout"; + break; +#ifdef SERVER_SUPPORT + case T_PATCH: + sstat = "Needs Patch"; + break; +#endif + case T_CONFLICT: + sstat = "Unresolved Conflict"; + break; + case T_ADDED: + sstat = "Locally Added"; + break; + case T_REMOVED: + sstat = "Locally Removed"; + break; + case T_MODIFIED: + if (vers->ts_conflict) + sstat = "Unresolved Conflict"; + else + sstat = "Locally Modified"; + break; + case T_REMOVE_ENTRY: + sstat = "Entry Invalid"; + break; + case T_UPTODATE: + sstat = "Up-to-date"; + break; + case T_NEEDS_MERGE: + sstat = "Needs Merge"; + break; + default: + sstat = "Classify Error"; + break; + } + + (void) printf ("===================================================================\n"); + if (vers->ts_user == NULL) + (void) printf ("File: no file %s\t\tStatus: %s\n\n", finfo->file, sstat); + else + (void) printf ("File: %-17s\tStatus: %s\n\n", finfo->file, sstat); + + if (vers->vn_user == NULL) + (void) printf (" Working revision:\tNo entry for %s\n", finfo->file); + else if (vers->vn_user[0] == '0' && vers->vn_user[1] == '\0') + (void) printf (" Working revision:\tNew file!\n"); +#ifdef SERVER_SUPPORT + else if (server_active) + (void) printf (" Working revision:\t%s\n", vers->vn_user); +#endif + else + (void) printf (" Working revision:\t%s\t%s\n", vers->vn_user, + vers->ts_rcs); + + if (vers->vn_rcs == NULL) + (void) printf (" Repository revision:\tNo revision control file\n"); + else + (void) printf (" Repository revision:\t%s\t%s\n", vers->vn_rcs, + vers->srcfile->path); + + if (vers->entdata) + { + Entnode *edata; + + edata = vers->entdata; + if (edata->tag) + { + if (vers->vn_rcs == NULL) + (void) printf ( + " Sticky Tag:\t\t%s - MISSING from RCS file!\n", + edata->tag); + else + { + if (isdigit (edata->tag[0])) + (void) printf (" Sticky Tag:\t\t%s\n", edata->tag); + else + { + char *branch = NULL; + + if (RCS_isbranch (finfo->rcs, edata->tag)) + branch = RCS_whatbranch(finfo->rcs, edata->tag); + + (void) printf (" Sticky Tag:\t\t%s (%s: %s)\n", + edata->tag, + branch ? "branch" : "revision", + branch ? branch : vers->vn_rcs); + + if (branch) + free (branch); + } + } + } + else if (!really_quiet) + (void) printf (" Sticky Tag:\t\t(none)\n"); + + if (edata->date) + (void) printf (" Sticky Date:\t\t%s\n", edata->date); + else if (!really_quiet) + (void) printf (" Sticky Date:\t\t(none)\n"); + + if (edata->options && edata->options[0]) + (void) printf (" Sticky Options:\t%s\n", edata->options); + else if (!really_quiet) + (void) printf (" Sticky Options:\t(none)\n"); + + if (long_format && vers->srcfile) + { + List *symbols = RCS_symbols(vers->srcfile); + + (void) printf ("\n Existing Tags:\n"); + if (symbols) + { + xrcsnode = finfo->rcs; + (void) walklist (symbols, tag_list_proc, NULL); + } + else + (void) printf ("\tNo Tags Exist\n"); + } + } + + (void) printf ("\n"); + freevers_ts (&vers); + return (0); +} + +/* + * Print a warm fuzzy message + */ +/* ARGSUSED */ +static Dtype +status_dirproc (dir, repos, update_dir) + char *dir; + char *repos; + char *update_dir; +{ + if (!quiet) + error (0, 0, "Examining %s", update_dir); + return (R_PROCESS); +} + +/* + * Print out a tag and its type + */ +static int +tag_list_proc (p, closure) + Node *p; + void *closure; +{ + char *branch = NULL; + + if (RCS_isbranch (xrcsnode, p->key)) + branch = RCS_whatbranch(xrcsnode, p->key) ; + + (void) printf ("\t%-25.25s\t(%s: %s)\n", p->key, + branch ? "branch" : "revision", + branch ? branch : p->data); + + if (branch) + free (branch); + + return (0); +} diff --git a/contrib/cvs/src/subr.c b/contrib/cvs/src/subr.c new file mode 100644 index 0000000..8ed9177 --- /dev/null +++ b/contrib/cvs/src/subr.c @@ -0,0 +1,318 @@ +/* + * Copyright (c) 1992, Brian Berliner and Jeff Polk + * Copyright (c) 1989-1992, Brian Berliner + * + * You may distribute under the terms of the GNU General Public License as + * specified in the README file that comes with the CVS 1.4 kit. + * + * Various useful functions for the CVS support code. + */ + +#include "cvs.h" + +extern char *getlogin (); + +/* + * malloc some data and die if it fails + */ +char * +xmalloc (bytes) + size_t bytes; +{ + char *cp; + + /* Parts of CVS try to xmalloc zero bytes and then free it. Some + systems have a malloc which returns NULL for zero byte + allocations but a free which can't handle NULL, so compensate. */ + if (bytes == 0) + bytes = 1; + + cp = malloc (bytes); + if (cp == NULL) + error (1, 0, "can not allocate %lu bytes", (unsigned long) bytes); + return (cp); +} + +/* + * realloc data and die if it fails [I've always wanted to have "realloc" do + * a "malloc" if the argument is NULL, but you can't depend on it. Here, I + * can *force* it. + */ +void * +xrealloc (ptr, bytes) + void *ptr; + size_t bytes; +{ + char *cp; + + if (!ptr) + cp = malloc (bytes); + else + cp = realloc (ptr, bytes); + + if (cp == NULL) + error (1, 0, "can not reallocate %lu bytes", (unsigned long) bytes); + return (cp); +} + +/* + * Duplicate a string, calling xmalloc to allocate some dynamic space + */ +char * +xstrdup (str) + const char *str; +{ + char *s; + + if (str == NULL) + return ((char *) NULL); + s = xmalloc (strlen (str) + 1); + (void) strcpy (s, str); + return (s); +} + +/* Remove trailing newlines from STRING, destructively. */ +void +strip_trailing_newlines (str) + char *str; +{ + int len; + len = strlen (str) - 1; + + while (str[len] == '\n') + str[len--] = '\0'; +} + +/* + * Recover the space allocated by Find_Names() and line2argv() + */ +void +free_names (pargc, argv) + int *pargc; + char **argv; +{ + register int i; + + for (i = 0; i < *pargc; i++) + { /* only do through *pargc */ + free (argv[i]); + } + *pargc = 0; /* and set it to zero when done */ +} + +/* + * Convert a line into argc/argv components and return the result in the + * arguments as passed. Use free_names() to return the memory allocated here + * back to the free pool. + */ +void +line2argv (pargc, argv, line) + int *pargc; + char **argv; + char *line; +{ + char *cp; + + *pargc = 0; + for (cp = strtok (line, " \t"); cp; cp = strtok ((char *) NULL, " \t")) + { + argv[*pargc] = xstrdup (cp); + (*pargc)++; + } +} + +/* + * Returns the number of dots ('.') found in an RCS revision number + */ +int +numdots (s) + const char *s; +{ + int dots = 0; + + for (; *s; s++) + { + if (*s == '.') + dots++; + } + return (dots); +} + +/* + * Get the caller's login from his uid. If the real uid is "root" try LOGNAME + * USER or getlogin(). If getlogin() and getpwuid() both fail, return + * the uid as a string. + */ +char * +getcaller () +{ + static char uidname[20]; + struct passwd *pw; + char *name; + uid_t uid; + + uid = getuid (); + if (uid == (uid_t) 0) + { + /* super-user; try getlogin() to distinguish */ + if (((name = getlogin ()) || (name = getenv("LOGNAME")) || + (name = getenv("USER"))) && *name) + return (name); + } + if ((pw = (struct passwd *) getpwuid (uid)) == NULL) + { + (void) sprintf (uidname, "uid%lu", (unsigned long) uid); + return (uidname); + } + return (pw->pw_name); +} + +#ifdef lint +#ifndef __GNUC__ +/* ARGSUSED */ +time_t +get_date (date, now) + char *date; + struct timeb *now; +{ + time_t foo = 0; + + return (foo); +} +#endif +#endif + +/* Given two revisions, find their greatest common ancestor. If the + two input revisions exist, then rcs guarantees that the gca will + exist. */ + +char * +gca (rev1, rev2) + char *rev1; + char *rev2; +{ + int dots; + char gca[PATH_MAX]; + char *p[2]; + int j[2]; + + if (rev1 == NULL || rev2 == NULL) + { + error (0, 0, "sanity failure in gca"); + abort(); + } + + /* walk the strings, reading the common parts. */ + gca[0] = '\0'; + p[0] = rev1; + p[1] = rev2; + do + { + int i; + char c[2]; + char *s[2]; + + for (i = 0; i < 2; ++i) + { + /* swap out the dot */ + s[i] = strchr (p[i], '.'); + if (s[i] != NULL) { + c[i] = *s[i]; + } + + /* read an int */ + j[i] = atoi (p[i]); + + /* swap back the dot... */ + if (s[i] != NULL) { + *s[i] = c[i]; + p[i] = s[i] + 1; + } + else + { + /* or mark us at the end */ + p[i] = NULL; + } + + } + + /* use the lowest. */ + (void) sprintf (gca + strlen (gca), "%d.", + j[0] < j[1] ? j[0] : j[1]); + + } while (j[0] == j[1] + && p[0] != NULL + && p[1] != NULL); + + /* back up over that last dot. */ + gca[strlen(gca) - 1] = '\0'; + + /* numbers differ, or we ran out of strings. we're done with the + common parts. */ + + dots = numdots (gca); + if (dots == 0) + { + /* revisions differ in trunk major number. */ + + char *q; + char *s; + + s = (j[0] < j[1]) ? p[0] : p[1]; + + if (s == NULL) + { + /* we only got one number. this is strange. */ + error (0, 0, "bad revisions %s or %s", rev1, rev2); + abort(); + } + else + { + /* we have a minor number. use it. */ + q = gca + strlen (gca); + + *q++ = '.'; + for ( ; *s != '.' && *s != '\0'; ) + *q++ = *s++; + + *q = '\0'; + } + } + else if ((dots & 1) == 0) + { + /* if we have an even number of dots, then we have a branch. + remove the last number in order to make it a revision. */ + + char *s; + + s = strrchr(gca, '.'); + *s = '\0'; + } + + return (xstrdup (gca)); +} + +/* + * Sanity checks and any required fix-up on message passed to RCS via '-m'. + * RCS 5.7 requires that a non-total-whitespace, non-null message be provided + * with '-m'. Returns the original argument or a pointer to readonly + * static storage. + */ +char * +make_message_rcslegal (message) + char *message; +{ + if ((message == NULL) || (*message == '\0') || isspace (*message)) + { + char *t; + + if (message) + for (t = message; *t; t++) + if (!isspace (*t)) + return message; + + return "*** empty log message ***\n"; + } + + return message; +} diff --git a/contrib/cvs/src/tag.c b/contrib/cvs/src/tag.c new file mode 100644 index 0000000..2e30009 --- /dev/null +++ b/contrib/cvs/src/tag.c @@ -0,0 +1,781 @@ +/* + * Copyright (c) 1992, Brian Berliner and Jeff Polk + * Copyright (c) 1989-1992, Brian Berliner + * + * You may distribute under the terms of the GNU General Public License as + * specified in the README file that comes with the CVS 1.4 kit. + * + * Tag + * + * Add or delete a symbolic name to an RCS file, or a collection of RCS files. + * Uses the checked out revision in the current directory. + */ + +#include "cvs.h" +#include "savecwd.h" + +static int check_fileproc PROTO((struct file_info *finfo)); +static int check_filesdoneproc PROTO((int err, char *repos, char *update_dir)); +static int pretag_proc PROTO((char *repository, char *filter)); +static void masterlist_delproc PROTO((Node *p)); +static void tag_delproc PROTO((Node *p)); +static int pretag_list_proc PROTO((Node *p, void *closure)); + +static Dtype tag_dirproc PROTO((char *dir, char *repos, char *update_dir)); +static int tag_fileproc PROTO((struct file_info *finfo)); + +static char *numtag; +static char *date = NULL; +static char *symtag; +static int delete_flag; /* adding a tag by default */ +static int branch_mode; /* make an automagic "branch" tag */ +static int local; /* recursive by default */ +static int force_tag_match = 1; /* force tag to match by default */ +static int force_tag_move; /* don't force tag to move by default */ + +struct tag_info +{ + Ctype status; + char *rev; + char *tag; + char *options; +}; + +struct master_lists +{ + List *tlist; +}; + +static List *mtlist; +static List *tlist; + +static const char *const tag_usage[] = +{ + "Usage: %s %s [-lRF] [-b] [-d] [-r tag|-D date] tag [files...]\n", + "\t-l\tLocal directory only, not recursive.\n", + "\t-R\tProcess directories recursively.\n", + "\t-d\tDelete the given Tag.\n", + "\t-[rD]\tExisting tag or date.\n", + "\t-f\tForce a head revision if tag etc not found.\n", + "\t-b\tMake the tag a \"branch\" tag, allowing concurrent development.\n", + "\t-F\tMove tag if it already exists\n", + NULL +}; + +int +tag (argc, argv) + int argc; + char **argv; +{ + int c; + int err = 0; + + if (argc == -1) + usage (tag_usage); + + optind = 1; + while ((c = getopt (argc, argv, "FQqlRdr:D:bf")) != -1) + { + switch (c) + { + case 'Q': + case 'q': +#ifdef SERVER_SUPPORT + /* The CVS 1.5 client sends these options (in addition to + Global_option requests), so we must ignore them. */ + if (!server_active) +#endif + error (1, 0, + "-q or -Q must be specified before \"%s\"", + command_name); + break; + case 'l': + local = 1; + break; + case 'R': + local = 0; + break; + case 'd': + delete_flag = 1; + break; + case 'r': + numtag = optarg; + break; + case 'D': + if (date) + free (date); + date = Make_Date (optarg); + break; + case 'f': + force_tag_match = 0; + break; + case 'b': + branch_mode = 1; + break; + case 'F': + force_tag_move = 1; + break; + case '?': + default: + usage (tag_usage); + break; + } + } + argc -= optind; + argv += optind; + + if (argc == 0) + usage (tag_usage); + symtag = argv[0]; + argc--; + argv++; + + if (date && numtag) + error (1, 0, "-r and -D options are mutually exclusive"); + if (delete_flag && branch_mode) + error (0, 0, "warning: -b ignored with -d options"); + RCS_check_tag (symtag); + +#ifdef CLIENT_SUPPORT + if (client_active) + { + /* We're the client side. Fire up the remote server. */ + start_server (); + + ign_setup (); + + if (local) + send_arg("-l"); + if (delete_flag) + send_arg("-d"); + if (branch_mode) + send_arg("-b"); + if (force_tag_move) + send_arg("-F"); + + if (numtag) + option_with_arg ("-r", numtag); + if (date) + client_senddate (date); + + send_arg (symtag); + + send_file_names (argc, argv, SEND_EXPAND_WILD); + /* FIXME: We shouldn't have to send current files, but I'm not sure + whether it works. So send the files -- + it's slower but it works. */ + send_files (argc, argv, local, 0); + send_to_server ("tag\012", 0); + return get_responses_and_close (); + } +#endif + + if (numtag != NULL) + tag_check_valid (numtag, argc, argv, local, 0, ""); + + /* check to make sure they are authorized to tag all the + specified files in the repository */ + + mtlist = getlist(); + err = start_recursion (check_fileproc, check_filesdoneproc, + (DIRENTPROC) NULL, (DIRLEAVEPROC) NULL, + argc, argv, local, W_LOCAL, 0, 1, + (char *) NULL, 1, 0); + + if (err) + { + error (1, 0, "correct the above errors first!"); + } + + /* start the recursion processor */ + err = start_recursion (tag_fileproc, (FILESDONEPROC) NULL, tag_dirproc, + (DIRLEAVEPROC) NULL, argc, argv, local, + W_LOCAL, 0, 1, (char *) NULL, 1, 0); + dellist(&mtlist); + return (err); +} + +/* check file that is to be tagged */ +/* All we do here is add it to our list */ + +static int +check_fileproc (finfo) + struct file_info *finfo; +{ + char *xdir; + Node *p; + Vers_TS *vers; + + if (finfo->update_dir[0] == '\0') + xdir = "."; + else + xdir = finfo->update_dir; + if ((p = findnode (mtlist, xdir)) != NULL) + { + tlist = ((struct master_lists *) p->data)->tlist; + } + else + { + struct master_lists *ml; + + tlist = getlist (); + p = getnode (); + p->key = xstrdup (xdir); + p->type = UPDATE; + ml = (struct master_lists *) + xmalloc (sizeof (struct master_lists)); + ml->tlist = tlist; + p->data = (char *) ml; + p->delproc = masterlist_delproc; + (void) addnode (mtlist, p); + } + /* do tlist */ + p = getnode (); + p->key = xstrdup (finfo->file); + p->type = UPDATE; + p->delproc = tag_delproc; + vers = Version_TS (finfo->repository, (char *) NULL, (char *) NULL, + (char *) NULL, finfo->file, 0, 0, + finfo->entries, finfo->rcs); + if (vers->srcfile == NULL) + { + if (!really_quiet) + error (0, 0, "nothing known about %s", finfo->file); + return (1); + } + p->data = RCS_getversion(vers->srcfile, numtag, date, force_tag_match, 0); + if (p->data != NULL) + { + int addit = 1; + char *oversion; + + oversion = RCS_getversion (vers->srcfile, symtag, (char *) NULL, 1, 0); + if (oversion == NULL) + { + if (delete_flag) + { + addit = 0; + } + } + else if (strcmp(oversion, p->data) == 0) + { + addit = 0; + } + else if (!force_tag_move) + { + addit = 0; + } + if (oversion != NULL) + { + free(oversion); + } + if (!addit) + { + free(p->data); + p->data = NULL; + } + } + freevers_ts(&vers); + (void) addnode (tlist, p); + return (0); +} + +static int +check_filesdoneproc(err, repos, update_dir) + int err; + char *repos; + char *update_dir; +{ + int n; + Node *p; + + p = findnode(mtlist, update_dir); + if (p != NULL) + { + tlist = ((struct master_lists *) p->data)->tlist; + } + else + { + tlist = (List *) NULL; + } + if ((tlist == NULL) || (tlist->list->next == tlist->list)) + { + return (err); + } + if ((n = Parse_Info(CVSROOTADM_TAGINFO, repos, pretag_proc, 1)) > 0) + { + error (0, 0, "Pre-tag check failed"); + err += n; + } + return (err); +} + +static int +pretag_proc(repository, filter) + char *repository; + char *filter; +{ + if (filter[0] == '/') + { + char *s, *cp; + + s = xstrdup(filter); + for (cp=s; *cp; cp++) + { + if (isspace(*cp)) + { + *cp = '\0'; + break; + } + } + if (!isfile(s)) + { + error (0, errno, "cannot find pre-tag filter '%s'", s); + free(s); + return (1); + } + free(s); + } + run_setup("%s %s %s %s", + filter, + symtag, + delete_flag ? "del" : force_tag_move ? "mov" : "add", + repository); + walklist(tlist, pretag_list_proc, NULL); + return (run_exec(RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL|RUN_REALLY)); +} + +static void +masterlist_delproc(p) + Node *p; +{ + struct master_lists *ml; + + ml = (struct master_lists *)p->data; + dellist(&ml->tlist); + free(ml); + return; +} + +static void +tag_delproc(p) + Node *p; +{ + if (p->data != NULL) + { + free(p->data); + p->data = NULL; + } + return; +} + +static int +pretag_list_proc(p, closure) + Node *p; + void *closure; +{ + if (p->data != NULL) + { + run_arg(p->key); + run_arg(p->data); + } + return (0); +} + + +/* + * Called to tag a particular file (the currently checked out version is + * tagged with the specified tag - or the specified tag is deleted). + */ +/* ARGSUSED */ +static int +tag_fileproc (finfo) + struct file_info *finfo; +{ + char *version, *oversion; + char *nversion = NULL; + char *rev; + Vers_TS *vers; + int retcode = 0; + + vers = Version_TS (finfo->repository, (char *) NULL, (char *) NULL, (char *) NULL, + finfo->file, 0, 0, finfo->entries, finfo->rcs); + + if ((numtag != NULL) || (date != NULL)) + { + nversion = RCS_getversion(vers->srcfile, + numtag, + date, + force_tag_match, 0); + if (nversion == NULL) + { + freevers_ts (&vers); + return (0); + } + } + if (delete_flag) + { + + /* + * If -d is specified, "force_tag_match" is set, so that this call to + * RCS_getversion() will return a NULL version string if the symbolic + * tag does not exist in the RCS file. + * + * This is done here because it's MUCH faster than just blindly calling + * "rcs" to remove the tag... trust me. + */ + + version = RCS_getversion (vers->srcfile, symtag, (char *) NULL, 1, 0); + if (version == NULL || vers->srcfile == NULL) + { + freevers_ts (&vers); + return (0); + } + free (version); + + if ((retcode = RCS_deltag(vers->srcfile->path, symtag, 1)) != 0) + { + if (!quiet) + error (0, retcode == -1 ? errno : 0, + "failed to remove tag %s from %s", symtag, + vers->srcfile->path); + freevers_ts (&vers); + return (1); + } + + /* warm fuzzies */ + if (!really_quiet) + { + (void) printf ("D %s\n", finfo->fullname); + } + + freevers_ts (&vers); + return (0); + } + + /* + * If we are adding a tag, we need to know which version we have checked + * out and we'll tag that version. + */ + if (nversion == NULL) + { + version = vers->vn_user; + } + else + { + version = nversion; + } + if (version == NULL) + { + freevers_ts (&vers); + return (0); + } + else if (strcmp (version, "0") == 0) + { + if (!quiet) + error (0, 0, "couldn't tag added but un-commited file `%s'", finfo->file); + freevers_ts (&vers); + return (0); + } + else if (version[0] == '-') + { + if (!quiet) + error (0, 0, "skipping removed but un-commited file `%s'", finfo->file); + freevers_ts (&vers); + return (0); + } + else if (vers->srcfile == NULL) + { + if (!quiet) + error (0, 0, "cannot find revision control file for `%s'", finfo->file); + freevers_ts (&vers); + return (0); + } + + /* + * As an enhancement for the case where a tag is being re-applied to a + * large number of files, make one extra call to RCS_getversion to see + * if the tag is already set in the RCS file. If so, check to see if it + * needs to be moved. If not, do nothing. This will likely save a lot of + * time when simply moving the tag to the "current" head revisions of a + * module -- which I have found to be a typical tagging operation. + */ + rev = branch_mode ? RCS_magicrev (vers->srcfile, version) : version; + oversion = RCS_getversion (vers->srcfile, symtag, (char *) NULL, 1, 0); + if (oversion != NULL) + { + int isbranch = RCS_isbranch (finfo->rcs, symtag); + + /* + * if versions the same and neither old or new are branches don't have + * to do anything + */ + if (strcmp (version, oversion) == 0 && !branch_mode && !isbranch) + { + free (oversion); + freevers_ts (&vers); + return (0); + } + + if (!force_tag_move) + { + /* we're NOT going to move the tag */ + (void) printf ("W %s", finfo->fullname); + + (void) printf (" : %s already exists on %s %s", + symtag, isbranch ? "branch" : "version", oversion); + (void) printf (" : NOT MOVING tag to %s %s\n", + branch_mode ? "branch" : "version", rev); + free (oversion); + freevers_ts (&vers); + return (0); + } + free (oversion); + } + + if ((retcode = RCS_settag(vers->srcfile->path, symtag, rev)) != 0) + { + error (1, retcode == -1 ? errno : 0, + "failed to set tag %s to revision %s in %s", + symtag, rev, vers->srcfile->path); + freevers_ts (&vers); + return (1); + } + + /* more warm fuzzies */ + if (!really_quiet) + { + (void) printf ("T %s\n", finfo->fullname); + } + + if (nversion != NULL) + { + free (nversion); + } + freevers_ts (&vers); + return (0); +} + +/* + * Print a warm fuzzy message + */ +/* ARGSUSED */ +static Dtype +tag_dirproc (dir, repos, update_dir) + char *dir; + char *repos; + char *update_dir; +{ + if (!quiet) + error (0, 0, "%s %s", delete_flag ? "Untagging" : "Tagging", update_dir); + return (R_PROCESS); +} + +/* Code relating to the val-tags file. Note that this file has no way + of knowing when a tag has been deleted. The problem is that there + is no way of knowing whether a tag still exists somewhere, when we + delete it some places. Using per-directory val-tags files (in + CVSREP) might be better, but that might slow down the process of + verifying that a tag is correct (maybe not, for the likely cases, + if carefully done), and/or be harder to implement correctly. */ + +struct val_args { + char *name; + int found; +}; + +/* Pass as a static until we get around to fixing start_recursion to pass along + a void * where we can stash it. */ +static struct val_args *val_args_static; + +static int val_fileproc PROTO ((struct file_info *finfo)); + +static int +val_fileproc (finfo) + struct file_info *finfo; +{ + RCSNode *rcsdata; + struct val_args *args = val_args_static; + char *tag; + + if ((rcsdata = finfo->rcs) == NULL) + /* Not sure this can happen, after all we passed only + W_REPOS | W_ATTIC. */ + return 0; + + tag = RCS_gettag (rcsdata, args->name, 1, 0); + if (tag != NULL) + { + /* FIXME: should find out a way to stop the search at this point. */ + args->found = 1; + free (tag); + } + return 0; +} + +static Dtype val_direntproc PROTO ((char *, char *, char *)); + +static Dtype +val_direntproc (dir, repository, update_dir) + char *dir; + char *repository; + char *update_dir; +{ + /* This is not quite right--it doesn't get right the case of "cvs + update -d -r foobar" where foobar is a tag which exists only in + files in a directory which does not exist yet, but which is + about to be created. */ + if (isdir (dir)) + return 0; + return R_SKIP_ALL; +} + +/* Check to see whether NAME is a valid tag. If so, return. If not + print an error message and exit. ARGC, ARGV, LOCAL, and AFLAG specify + which files we will be operating on. + + REPOSITORY is the repository if we need to cd into it, or NULL if + we are already there, or "" if we should do a W_LOCAL recursion. + Sorry for three cases, but the "" case is needed in case the + working directories come from diverse parts of the repository, the + NULL case avoids an unneccesary chdir, and the non-NULL, non-"" + case is needed for checkout, where we don't want to chdir if the + tag is found in CVSROOTADM_VALTAGS, but there is not (yet) any + local directory. */ +void +tag_check_valid (name, argc, argv, local, aflag, repository) + char *name; + int argc; + char **argv; + int local; + int aflag; + char *repository; +{ + DBM *db; + char *valtags_filename; + int err; + datum mytag; + struct val_args the_val_args; + struct saved_cwd cwd; + int which; + + /* Numeric tags require only a syntactic check. */ + if (isdigit (name[0])) + { + char *p; + for (p = name; *p != '\0'; ++p) + { + if (!(isdigit (*p) || *p == '.')) + error (1, 0, "\ +Numeric tag %s contains characters other than digits and '.'", name); + } + return; + } + + mytag.dptr = name; + mytag.dsize = strlen (name); + + valtags_filename = xmalloc (strlen (CVSroot) + sizeof CVSROOTADM + + sizeof CVSROOTADM_HISTORY + 20); + strcpy (valtags_filename, CVSroot); + strcat (valtags_filename, "/"); + strcat (valtags_filename, CVSROOTADM); + strcat (valtags_filename, "/"); + strcat (valtags_filename, CVSROOTADM_VALTAGS); + db = dbm_open (valtags_filename, O_RDWR, 0666); + if (db == NULL) + { + if (!existence_error (errno)) + error (1, errno, "cannot read %s", valtags_filename); + + /* If the file merely fails to exist, we just keep going and create + it later if need be. */ + } + else + { + datum val; + + val = dbm_fetch (db, mytag); + if (val.dptr != NULL) + { + /* Found. The tag is valid. */ + dbm_close (db); + free (valtags_filename); + return; + } + /* FIXME: should check errors somehow (add dbm_error to myndbm.c?). */ + } + + /* We didn't find the tag in val-tags, so look through all the RCS files + to see whether it exists there. Yes, this is expensive, but there + is no other way to cope with a tag which might have been created + by an old version of CVS, from before val-tags was invented. */ + + the_val_args.name = name; + the_val_args.found = 0; + val_args_static = &the_val_args; + + which = W_REPOS | W_ATTIC; + + if (repository != NULL) + { + if (repository[0] == '\0') + which |= W_LOCAL; + else + { + if (save_cwd (&cwd)) + exit (EXIT_FAILURE); + if (chdir (repository) < 0) + error (1, errno, "cannot change to %s directory", repository); + } + } + + err = start_recursion (val_fileproc, (FILESDONEPROC) NULL, + val_direntproc, (DIRLEAVEPROC) NULL, + argc, argv, local, which, aflag, + 1, NULL, 1, 0); + if (repository != NULL && repository[0] != '\0') + { + if (restore_cwd (&cwd, NULL)) + exit (EXIT_FAILURE); + free_cwd (&cwd); + } + + if (!the_val_args.found) + error (1, 0, "no such tag %s", name); + else + { + /* The tags is valid but not mentioned in val-tags. Add it. */ + datum value; + + if (noexec) + { + if (db != NULL) + dbm_close (db); + free (valtags_filename); + return; + } + + if (db == NULL) + { + mode_t omask; + omask = umask (cvsumask); + db = dbm_open (valtags_filename, O_RDWR | O_CREAT | O_TRUNC, 0666); + (void) umask (omask); + + if (db == NULL) + { + error (0, errno, "cannot create %s", valtags_filename); + free (valtags_filename); + return; + } + } + value.dptr = "y"; + value.dsize = 1; + if (dbm_store (db, mytag, value, DBM_REPLACE) < 0) + error (0, errno, "cannot store %s into %s", name, + valtags_filename); + dbm_close (db); + } + free (valtags_filename); +} diff --git a/contrib/cvs/src/update.c b/contrib/cvs/src/update.c new file mode 100644 index 0000000..2478316 --- /dev/null +++ b/contrib/cvs/src/update.c @@ -0,0 +1,1830 @@ +/* + * Copyright (c) 1992, Brian Berliner and Jeff Polk + * Copyright (c) 1989-1992, Brian Berliner + * + * You may distribute under the terms of the GNU General Public License as + * specified in the README file that comes with the CVS 1.4 kit. + * + * "update" updates the version in the present directory with respect to the RCS + * repository. The present version must have been created by "checkout". The + * user can keep up-to-date by calling "update" whenever he feels like it. + * + * The present version can be committed by "commit", but this keeps the version + * in tact. + * + * Arguments following the options are taken to be file names to be updated, + * rather than updating the entire directory. + * + * Modified or non-existent RCS files are checked out and reported as U + * <user_file> + * + * Modified user files are reported as M <user_file>. If both the RCS file and + * the user file have been modified, the user file is replaced by the result + * of rcsmerge, and a backup file is written for the user in .#file.version. + * If this throws up irreconcilable differences, the file is reported as C + * <user_file>, and as M <user_file> otherwise. + * + * Files added but not yet committed are reported as A <user_file>. Files + * removed but not yet committed are reported as R <user_file>. + * + * If the current directory contains subdirectories that hold concurrent + * versions, these are updated too. If the -d option was specified, new + * directories added to the repository are automatically created and updated + * as well. + */ + +#include "cvs.h" +#ifdef SERVER_SUPPORT +#include "md5.h" +#endif +#include "watch.h" +#include "fileattr.h" +#include "edit.h" + +static int checkout_file PROTO((char *file, char *repository, List *entries, + RCSNode *rcsnode, Vers_TS *vers_ts, char *update_dir)); +#ifdef SERVER_SUPPORT +static int patch_file PROTO((char *file, char *repository, List *entries, + RCSNode*rcsnode, Vers_TS *vers_ts, char *update_dir, + int *docheckout, struct stat *file_info, + unsigned char *checksum)); +#endif +static int isemptydir PROTO((char *dir)); +static int merge_file PROTO((char *file, char *repository, List *entries, + Vers_TS *vers, char *update_dir)); +static int scratch_file PROTO((char *file, char *repository, List * entries, + char *update_dir)); +static Dtype update_dirent_proc PROTO((char *dir, char *repository, char *update_dir)); +static int update_dirleave_proc PROTO((char *dir, int err, char *update_dir)); +static int update_fileproc PROTO ((struct file_info *)); +static int update_filesdone_proc PROTO((int err, char *repository, + char *update_dir)); +static int write_letter PROTO((char *file, int letter, char *update_dir)); +#ifdef SERVER_SUPPORT +static void join_file PROTO((char *file, RCSNode *rcsnode, Vers_TS *vers_ts, + char *update_dir, List *entries, char *repository)); +#else +static void join_file PROTO((char *file, RCSNode *rcsnode, Vers_TS *vers_ts, + char *update_dir, List *entries)); +#endif + +static char *options = NULL; +static char *tag = NULL; +static char *date = NULL; +static char *join_rev1, *date_rev1; +static char *join_rev2, *date_rev2; +static int aflag = 0; +static int force_tag_match = 1; +static int update_build_dirs = 0; +static int update_prune_dirs = 0; +static int pipeout = 0; +#ifdef SERVER_SUPPORT +static int patches = 0; +#endif +static List *ignlist = (List *) NULL; +static time_t last_register_time; +static const char *const update_usage[] = +{ + "Usage: %s %s [-APdflRp] [-k kopt] [-r rev|-D date] [-j rev]\n", + " [-I ign] [-W spec] [files...]\n", + "\t-A\tReset any sticky tags/date/kopts.\n", + "\t-P\tPrune empty directories.\n", + "\t-d\tBuild directories, like checkout does.\n", + "\t-f\tForce a head revision match if tag/date not found.\n", + "\t-l\tLocal directory only, no recursion.\n", + "\t-R\tProcess directories recursively.\n", + "\t-p\tSend updates to standard output.\n", + "\t-k kopt\tUse RCS kopt -k option on checkout.\n", + "\t-r rev\tUpdate using specified revision/tag.\n", + "\t-D date\tSet date to update from.\n", + "\t-j rev\tMerge in changes made between current revision and rev.\n", + "\t-I ign\tMore files to ignore (! to reset).\n", + "\t-W spec\tWrappers specification line.\n", + NULL +}; + +/* + * update is the argv,argc based front end for arg parsing + */ +int +update (argc, argv) + int argc; + char **argv; +{ + int c, err; + int local = 0; /* recursive by default */ + int which; /* where to look for files and dirs */ + + if (argc == -1) + usage (update_usage); + + ign_setup (); + wrap_setup (); + + /* parse the args */ + optind = 1; + while ((c = getopt (argc, argv, "ApPflRQqduk:r:D:j:I:W:")) != -1) + { + switch (c) + { + case 'A': + aflag = 1; + break; + case 'I': + ign_add (optarg, 0); + break; + case 'W': + wrap_add (optarg, 0); + break; + case 'k': + if (options) + free (options); + options = RCS_check_kflag (optarg); + break; + case 'l': + local = 1; + break; + case 'R': + local = 0; + break; + case 'Q': + case 'q': +#ifdef SERVER_SUPPORT + /* The CVS 1.5 client sends these options (in addition to + Global_option requests), so we must ignore them. */ + if (!server_active) +#endif + error (1, 0, + "-q or -Q must be specified before \"%s\"", + command_name); + break; + case 'd': + update_build_dirs = 1; + break; + case 'f': + force_tag_match = 0; + break; + case 'r': + tag = optarg; + break; + case 'D': + date = Make_Date (optarg); + break; + case 'P': + update_prune_dirs = 1; + break; + case 'p': + pipeout = 1; + noexec = 1; /* so no locks will be created */ + break; + case 'j': + if (join_rev2) + error (1, 0, "only two -j options can be specified"); + if (join_rev1) + join_rev2 = optarg; + else + join_rev1 = optarg; + break; + case 'u': +#ifdef SERVER_SUPPORT + if (server_active) + patches = 1; + else +#endif + usage (update_usage); + break; + case '?': + default: + usage (update_usage); + break; + } + } + argc -= optind; + argv += optind; + +#ifdef CLIENT_SUPPORT + if (client_active) + { + /* The first pass does the regular update. If we receive at least + one patch which failed, we do a second pass and just fetch + those files whose patches failed. */ + do + { + int status; + + start_server (); + + if (local) + send_arg("-l"); + if (update_build_dirs) + send_arg("-d"); + if (pipeout) + send_arg("-p"); + if (!force_tag_match) + send_arg("-f"); + if (aflag) + send_arg("-A"); + if (update_prune_dirs) + send_arg("-P"); + client_prune_dirs = update_prune_dirs; + option_with_arg ("-r", tag); + if (date) + client_senddate (date); + if (join_rev1) + option_with_arg ("-j", join_rev1); + if (join_rev2) + option_with_arg ("-j", join_rev2); + + /* If the server supports the command "update-patches", that means + that it knows how to handle the -u argument to update, which + means to send patches instead of complete files. */ + if (failed_patches == NULL) + { + struct request *rq; + + for (rq = requests; rq->name != NULL; rq++) + { + if (strcmp (rq->name, "update-patches") == 0) + { + if (rq->status == rq_supported) + { + send_arg("-u"); + } + break; + } + } + } + + if (failed_patches == NULL) + { + send_file_names (argc, argv, SEND_EXPAND_WILD); + send_files (argc, argv, local, aflag); + } + else + { + int i; + + (void) printf ("%s client: refetching unpatchable files\n", + program_name); + + if (toplevel_wd[0] != '\0' + && chdir (toplevel_wd) < 0) + { + error (1, errno, "could not chdir to %s", toplevel_wd); + } + + for (i = 0; i < failed_patches_count; i++) + (void) unlink_file (failed_patches[i]); + send_file_names (failed_patches_count, failed_patches, 0); + send_files (failed_patches_count, failed_patches, local, + aflag); + } + + failed_patches = NULL; + failed_patches_count = 0; + + send_to_server ("update\012", 0); + + status = get_responses_and_close (); + if (status != 0) + return status; + + } while (failed_patches != NULL); + + return 0; + } +#endif + + if (tag != NULL) + tag_check_valid (tag, argc, argv, local, aflag, ""); + /* FIXME: We don't call tag_check_valid on join_rev1 and join_rev2 + yet (make sure to handle ':' correctly if we do, though). */ + + /* + * If we are updating the entire directory (for real) and building dirs + * as we go, we make sure there is no static entries file and write the + * tag file as appropriate + */ + if (argc <= 0 && !pipeout) + { + if (update_build_dirs) + { + if (unlink_file (CVSADM_ENTSTAT) < 0 && ! existence_error (errno)) + error (1, errno, "cannot remove file %s", CVSADM_ENTSTAT); +#ifdef SERVER_SUPPORT + if (server_active) + server_clear_entstat (".", Name_Repository (NULL, NULL)); +#endif + } + + /* keep the CVS/Tag file current with the specified arguments */ + if (aflag || tag || date) + { + WriteTag ((char *) NULL, tag, date); +#ifdef SERVER_SUPPORT + if (server_active) + server_set_sticky (".", Name_Repository (NULL, NULL), tag, date); +#endif + } + } + + /* look for files/dirs locally and in the repository */ + which = W_LOCAL | W_REPOS; + + /* look in the attic too if a tag or date is specified */ + if (tag != NULL || date != NULL || joining()) + which |= W_ATTIC; + + /* call the command line interface */ + err = do_update (argc, argv, options, tag, date, force_tag_match, + local, update_build_dirs, aflag, update_prune_dirs, + pipeout, which, join_rev1, join_rev2, (char *) NULL); + + /* free the space Make_Date allocated if necessary */ + if (date != NULL) + free (date); + + return (err); +} + +/* + * Command line interface to update (used by checkout) + */ +int +do_update (argc, argv, xoptions, xtag, xdate, xforce, local, xbuild, xaflag, + xprune, xpipeout, which, xjoin_rev1, xjoin_rev2, preload_update_dir) + int argc; + char **argv; + char *xoptions; + char *xtag; + char *xdate; + int xforce; + int local; + int xbuild; + int xaflag; + int xprune; + int xpipeout; + int which; + char *xjoin_rev1; + char *xjoin_rev2; + char *preload_update_dir; +{ + int err = 0; + char *cp; + + /* fill in the statics */ + options = xoptions; + tag = xtag; + date = xdate; + force_tag_match = xforce; + update_build_dirs = xbuild; + aflag = xaflag; + update_prune_dirs = xprune; + pipeout = xpipeout; + + /* setup the join support */ + join_rev1 = xjoin_rev1; + join_rev2 = xjoin_rev2; + if (join_rev1 && (cp = strchr (join_rev1, ':')) != NULL) + { + *cp++ = '\0'; + date_rev1 = Make_Date (cp); + } + else + date_rev1 = (char *) NULL; + if (join_rev2 && (cp = strchr (join_rev2, ':')) != NULL) + { + *cp++ = '\0'; + date_rev2 = Make_Date (cp); + } + else + date_rev2 = (char *) NULL; + + /* call the recursion processor */ + err = start_recursion (update_fileproc, update_filesdone_proc, + update_dirent_proc, update_dirleave_proc, + argc, argv, local, which, aflag, 1, + preload_update_dir, 1, 0); + + /* see if we need to sleep before returning */ + if (last_register_time) + { + time_t now; + + (void) time (&now); + if (now == last_register_time) + sleep (1); /* to avoid time-stamp races */ + } + + return (err); +} + +/* + * 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 + * instantiation. file is the file name we are to operate on. update_dir is + * set to the path relative to where we started (for pretty printing). + * repository is the repository. entries and srcfiles are the pre-parsed + * entries and source control files. + * + * This routine decides what needs to be done for each file and does the + * appropriate magic for checkout + */ +static int +update_fileproc (finfo) + struct file_info *finfo; +{ + int retval; + Ctype status; + Vers_TS *vers; + + status = Classify_File (finfo->file, tag, date, options, force_tag_match, + aflag, finfo->repository, finfo->entries, finfo->rcs, &vers, + finfo->update_dir, pipeout); + if (pipeout) + { + /* + * We just return success without doing anything if any of the really + * funky cases occur + * + * If there is still a valid RCS file, do a regular checkout type + * operation + */ + switch (status) + { + case T_UNKNOWN: /* unknown file was explicitly asked + * about */ + case T_REMOVE_ENTRY: /* needs to be un-registered */ + case T_ADDED: /* added but not committed */ + retval = 0; + break; + case T_CONFLICT: /* old punt-type errors */ + retval = 1; + break; + case T_UPTODATE: /* file was already up-to-date */ + case T_NEEDS_MERGE: /* needs merging */ + case T_MODIFIED: /* locally modified */ + case T_REMOVED: /* removed but not committed */ + case T_CHECKOUT: /* needs checkout */ +#ifdef SERVER_SUPPORT + case T_PATCH: /* needs patch */ +#endif + retval = checkout_file (finfo->file, finfo->repository, finfo->entries, finfo->rcs, + vers, finfo->update_dir); + break; + + default: /* can't ever happen :-) */ + error (0, 0, + "unknown file status %d for file %s", status, finfo->file); + retval = 0; + break; + } + } + else + { + switch (status) + { + case T_UNKNOWN: /* unknown file was explicitly asked + * about */ + case T_UPTODATE: /* file was already up-to-date */ + retval = 0; + break; + case T_CONFLICT: /* old punt-type errors */ + retval = 1; + (void) write_letter (finfo->file, 'C', finfo->update_dir); + break; + case T_NEEDS_MERGE: /* needs merging */ + if (noexec) + { + retval = 1; + (void) write_letter (finfo->file, 'C', finfo->update_dir); + } + else + { + if (wrap_merge_is_copy (finfo->file)) + /* Should we be warning the user that we are + * overwriting the user's copy of the file? */ + retval = checkout_file (finfo->file, finfo->repository, finfo->entries, + finfo->rcs, vers, finfo->update_dir); + else + retval = merge_file (finfo->file, finfo->repository, finfo->entries, + vers, finfo->update_dir); + } + break; + case T_MODIFIED: /* locally modified */ + retval = 0; + if (vers->ts_conflict) + { + char *filestamp; + int retcode; + + /* + * If the timestamp has changed and no conflict indicators + * are found, it isn't a 'C' any more. + */ +#ifdef SERVER_SUPPORT + if (server_active) + retcode = vers->ts_conflict[0] != '='; + else { + filestamp = time_stamp (finfo->file); + retcode = strcmp (vers->ts_conflict, filestamp); + free (filestamp); + } +#else + filestamp = time_stamp (finfo->file); + retcode = strcmp (vers->ts_conflict, filestamp); + free (filestamp); +#endif + + if (retcode) + { + /* + * If the timestamps differ, look for Conflict + * indicators to see if 'C' anyway. + */ + run_setup ("%s", GREP); + run_arg (RCS_MERGE_PAT); + run_arg (finfo->file); + retcode = run_exec (RUN_TTY, DEVNULL, + RUN_TTY,RUN_NORMAL); + if (retcode == -1) + { + error (1, errno, + "fork failed while examining conflict in `%s'", + finfo->fullname); + } + } + if (!retcode) + { + (void) write_letter (finfo->file, 'C', finfo->update_dir); + retval = 1; + } + else + { + /* Reregister to clear conflict flag. */ + Register (finfo->entries, finfo->file, vers->vn_rcs, vers->ts_rcs, + vers->options, vers->tag, + vers->date, (char *)0); + } + } + if (!retval) + retval = write_letter (finfo->file, 'M', finfo->update_dir); + break; +#ifdef SERVER_SUPPORT + case T_PATCH: /* needs patch */ + if (patches) + { + int docheckout; + struct stat file_info; + unsigned char checksum[16]; + + retval = patch_file (finfo->file, finfo->repository, finfo->entries, finfo->rcs, + vers, finfo->update_dir, &docheckout, + &file_info, checksum); + if (! docheckout) + { + if (server_active && retval == 0) + server_updated (finfo->file, finfo->update_dir, finfo->repository, + SERVER_PATCHED, &file_info, + checksum); + break; + } + } + /* Fall through. */ + /* If we're not running as a server, just check the + file out. It's simpler and faster than starting up + two new processes (diff and patch). */ + /* Fall through. */ +#endif + case T_CHECKOUT: /* needs checkout */ + retval = checkout_file (finfo->file, finfo->repository, finfo->entries, finfo->rcs, + vers, finfo->update_dir); +#ifdef SERVER_SUPPORT + if (server_active && retval == 0) + server_updated (finfo->file, finfo->update_dir, finfo->repository, + SERVER_UPDATED, (struct stat *) NULL, + (unsigned char *) NULL); +#endif + break; + case T_ADDED: /* added but not committed */ + retval = write_letter (finfo->file, 'A', finfo->update_dir); + break; + case T_REMOVED: /* removed but not committed */ + retval = write_letter (finfo->file, 'R', finfo->update_dir); + break; + case T_REMOVE_ENTRY: /* needs to be un-registered */ + retval = scratch_file (finfo->file, finfo->repository, finfo->entries, finfo->update_dir); +#ifdef SERVER_SUPPORT + if (server_active && retval == 0) + server_updated (finfo->file, finfo->update_dir, finfo->repository, + SERVER_UPDATED, (struct stat *) NULL, + (unsigned char *) NULL); +#endif + break; + default: /* can't ever happen :-) */ + error (0, 0, + "unknown file status %d for file %s", status, finfo->file); + retval = 0; + break; + } + } + + /* only try to join if things have gone well thus far */ + if (retval == 0 && join_rev1) +#ifdef SERVER_SUPPORT + join_file (finfo->file, finfo->rcs, vers, finfo->update_dir, finfo->entries, finfo->repository); +#else + join_file (finfo->file, finfo->rcs, vers, finfo->update_dir, finfo->entries); +#endif + + /* if this directory has an ignore list, add this file to it */ + if (ignlist) + { + Node *p; + + p = getnode (); + p->type = FILES; + p->key = xstrdup (finfo->file); + if (addnode (ignlist, p) != 0) + freenode (p); + } + + freevers_ts (&vers); + return (retval); +} + +static void update_ignproc PROTO ((char *, char *)); + +static void +update_ignproc (file, dir) + char *file; + char *dir; +{ + (void) write_letter (file, '?', dir); +} + +/* ARGSUSED */ +static int +update_filesdone_proc (err, repository, update_dir) + int err; + char *repository; + char *update_dir; +{ + /* if this directory has an ignore list, process it then free it */ + if (ignlist) + { + ignore_files (ignlist, update_dir, update_ignproc); + dellist (&ignlist); + } + + /* Clean up CVS admin dirs if we are export */ + if (strcmp (command_name, "export") == 0) + { + /* I'm not sure the existence_error is actually possible (except + in cases where we really should print a message), but since + this code used to ignore all errors, I'll play it safe. */ + if (unlink_file_dir (CVSADM) < 0 && !existence_error (errno)) + error (0, errno, "cannot remove %s directory", CVSADM); + } +#ifdef SERVER_SUPPORT + else if (!server_active && !pipeout) +#else + else if (!pipeout) +#endif /* SERVER_SUPPORT */ + { + /* If there is no CVS/Root file, add one */ + if (!isfile (CVSADM_ROOT)) + Create_Root( (char *) NULL, CVSroot ); + } + + return (err); +} + +/* + * update_dirent_proc () is called back by the recursion processor before a + * sub-directory is processed for update. In this case, update_dirent proc + * will probably create the directory unless -d isn't specified and this is a + * new directory. A return code of 0 indicates the directory should be + * processed by the recursion code. A return of non-zero indicates the + * recursion code should skip this directory. + */ +static Dtype +update_dirent_proc (dir, repository, update_dir) + char *dir; + char *repository; + char *update_dir; +{ + if (ignore_directory (update_dir)) + { + /* print the warm fuzzy message */ + if (!quiet) + error (0, 0, "Ignoring %s", update_dir); + return R_SKIP_ALL; + } + + if (!isdir (dir)) + { + /* if we aren't building dirs, blow it off */ + if (!update_build_dirs) + return (R_SKIP_ALL); + + if (noexec) + { + /* ignore the missing dir if -n is specified */ + error (0, 0, "New directory `%s' -- ignored", dir); + return (R_SKIP_ALL); + } + else + { + /* otherwise, create the dir and appropriate adm files */ + make_directory (dir); + Create_Admin (dir, update_dir, repository, tag, date); + } + } + /* Do we need to check noexec here? */ + else if (!pipeout) + { + char *cvsadmdir; + + /* The directory exists. Check to see if it has a CVS + subdirectory. */ + + cvsadmdir = xmalloc (strlen (dir) + 80); + strcpy (cvsadmdir, dir); + strcat (cvsadmdir, "/"); + strcat (cvsadmdir, CVSADM); + + if (!isdir (cvsadmdir)) + { + /* We cannot successfully recurse into a directory without a CVS + subdirectory. Generally we will have already printed + "? foo". */ + free (cvsadmdir); + return R_SKIP_ALL; + } + free (cvsadmdir); + } + + /* + * If we are building dirs and not going to stdout, we make sure there is + * no static entries file and write the tag file as appropriate + */ + if (!pipeout) + { + if (update_build_dirs) + { + char tmp[PATH_MAX]; + + (void) sprintf (tmp, "%s/%s", dir, CVSADM_ENTSTAT); + if (unlink_file (tmp) < 0 && ! existence_error (errno)) + error (1, errno, "cannot remove file %s", tmp); +#ifdef SERVER_SUPPORT + if (server_active) + server_clear_entstat (update_dir, repository); +#endif + } + + /* keep the CVS/Tag file current with the specified arguments */ + if (aflag || tag || date) + { + WriteTag (dir, tag, date); +#ifdef SERVER_SUPPORT + if (server_active) + server_set_sticky (update_dir, repository, tag, date); +#endif + } + + /* initialize the ignore list for this directory */ + ignlist = getlist (); + } + + /* print the warm fuzzy message */ + if (!quiet) + error (0, 0, "Updating %s", update_dir); + + return (R_PROCESS); +} + +/* + * update_dirleave_proc () is called back by the recursion code upon leaving + * a directory. It will prune empty directories if needed and will execute + * any appropriate update programs. + */ +/* ARGSUSED */ +static int +update_dirleave_proc (dir, err, update_dir) + char *dir; + int err; + char *update_dir; +{ + FILE *fp; + + /* run the update_prog if there is one */ + if (err == 0 && !pipeout && !noexec && + (fp = fopen (CVSADM_UPROG, "r")) != NULL) + { + char *cp; + char *repository; + char line[MAXLINELEN]; + + repository = Name_Repository ((char *) NULL, update_dir); + if (fgets (line, sizeof (line), fp) != NULL) + { + if ((cp = strrchr (line, '\n')) != NULL) + *cp = '\0'; + run_setup ("%s %s", line, repository); + (void) printf ("%s %s: Executing '", program_name, command_name); + run_print (stdout); + (void) printf ("'\n"); + (void) run_exec (RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL); + } + (void) fclose (fp); + free (repository); + } + + /* FIXME: chdir ("..") loses with symlinks. */ + /* Prune empty dirs on the way out - if necessary */ + (void) chdir (".."); + if (update_prune_dirs && isemptydir (dir)) + { + /* I'm not sure the existence_error is actually possible (except + in cases where we really should print a message), but since + this code used to ignore all errors, I'll play it safe. */ + if (unlink_file_dir (dir) < 0 && !existence_error (errno)) + error (0, errno, "cannot remove %s directory", dir); + } + + return (err); +} + +/* + * Returns 1 if the argument directory is completely empty, other than the + * existence of the CVS directory entry. Zero otherwise. + */ +static int +isemptydir (dir) + char *dir; +{ + DIR *dirp; + struct dirent *dp; + + if ((dirp = opendir (dir)) == NULL) + { + error (0, 0, "cannot open directory %s for empty check", dir); + return (0); + } + while ((dp = readdir (dirp)) != NULL) + { + if (strcmp (dp->d_name, ".") != 0 && strcmp (dp->d_name, "..") != 0 && + strcmp (dp->d_name, CVSADM) != 0) + { + (void) closedir (dirp); + return (0); + } + } + (void) closedir (dirp); + return (1); +} + +/* + * scratch the Entries file entry associated with a file + */ +static int +scratch_file (file, repository, entries, update_dir) + char *file; + char *repository; + List *entries; + char *update_dir; +{ + history_write ('W', update_dir, "", file, repository); + Scratch_Entry (entries, file); + (void) unlink_file (file); + return (0); +} + +/* + * check out a file - essentially returns the result of the fork on "co". + */ +static int +checkout_file (file, repository, entries, rcsnode, vers_ts, update_dir) + char *file; + char *repository; + List *entries; + RCSNode *rcsnode; + Vers_TS *vers_ts; + char *update_dir; +{ + char backup[PATH_MAX]; + int set_time, retval = 0; + int retcode = 0; + int status; + int file_is_dead; + + /* don't screw with backup files if we're going to stdout */ + if (!pipeout) + { + (void) sprintf (backup, "%s/%s%s", CVSADM, CVSPREFIX, file); + if (isfile (file)) + rename_file (file, backup); + else + (void) unlink_file (backup); + } + + file_is_dead = RCS_isdead (vers_ts->srcfile, vers_ts->vn_rcs); + + if (!file_is_dead) + { + /* + * if we are checking out to stdout, print a nice message to + * stderr, and add the -p flag to the command */ + if (pipeout) + { + if (!quiet) + { + (void) fprintf (stderr, "\ +===================================================================\n"); + if (update_dir[0]) + (void) fprintf (stderr, "Checking out %s/%s\n", + update_dir, file); + else + (void) fprintf (stderr, "Checking out %s\n", file); + (void) fprintf (stderr, "RCS: %s\n", vers_ts->srcfile->path); + (void) fprintf (stderr, "VERS: %s\n", vers_ts->vn_rcs); + (void) fprintf (stderr, "***************\n"); + } + } + + status = RCS_checkout (vers_ts->srcfile->path, + pipeout ? NULL : file, vers_ts->vn_tag, + vers_ts->options, RUN_TTY, 0, 0); + } + if (file_is_dead || status == 0) + { + if (!pipeout) + { + Vers_TS *xvers_ts; + int resurrecting; + + resurrecting = 0; + + if (file_is_dead && joining()) + { + if (RCS_getversion (vers_ts->srcfile, join_rev1, + date_rev1, 1, 0) + || (join_rev2 != NULL && + RCS_getversion (vers_ts->srcfile, join_rev2, + date_rev2, 1, 0))) + { + /* when joining, we need to get dead files checked + out. Try harder. */ + /* I think that RCS_FLAGS_FORCE is here only because + passing -f to co used to enable checking out + a dead revision in the old version of death + support which used a hacked RCS instead of using + the RCS state. */ + retcode = RCS_checkout (vers_ts->srcfile->path, file, + vers_ts->vn_rcs, + vers_ts->options, RUN_TTY, + RCS_FLAGS_FORCE, 0); + if (retcode != 0) + { + error (retcode == -1 ? 1 : 0, + retcode == -1 ? errno : 0, + "could not check out %s", file); + (void) unlink_file (backup); + return (retcode); + } + file_is_dead = 0; + resurrecting = 1; + } + else + { + /* If the file is dead and does not contain either of + the join revisions, then we don't want to check it + out. */ + return 0; + } + } + + if (cvswrite == TRUE + && !file_is_dead + && !fileattr_get (file, "_watched")) + xchmod (file, 1); + + { + /* A newly checked out file is never under the spell + of "cvs edit". If we think we were editing it + from a previous life, clean up. Would be better to + check for same the working directory instead of + same user, but that is hairy. */ + + struct addremove_args args; + + editor_set (file, getcaller (), NULL); + + memset (&args, 0, sizeof args); + args.remove_temp = 1; + watch_modify_watchers (file, &args); + } + + /* set the time from the RCS file iff it was unknown before */ + if (vers_ts->vn_user == NULL || + strncmp (vers_ts->ts_rcs, "Initial", 7) == 0) + { + set_time = 1; + } + else + set_time = 0; + + wrap_fromcvs_process_file (file); + + xvers_ts = Version_TS (repository, options, tag, date, file, + force_tag_match, set_time, entries, rcsnode); + if (strcmp (xvers_ts->options, "-V4") == 0) + xvers_ts->options[0] = '\0'; + + (void) time (&last_register_time); + + if (file_is_dead) + { + if (xvers_ts->vn_user != NULL) + { + if (update_dir[0] == '\0') + error (0, 0, + "warning: %s is not (any longer) pertinent", + file); + else + error (0, 0, + "warning: %s/%s is not (any longer) pertinent", + update_dir, file); + } + Scratch_Entry (entries, file); + if (unlink_file (file) < 0 && ! existence_error (errno)) + { + if (update_dir[0] == '\0') + error (0, errno, "cannot remove %s", file); + else + error (0, errno, "cannot remove %s/%s", update_dir, + file); + } + } + else + Register (entries, file, + resurrecting ? "0" : xvers_ts->vn_rcs, + xvers_ts->ts_user, xvers_ts->options, + xvers_ts->tag, xvers_ts->date, + (char *)0); /* Clear conflict flag on fresh checkout */ + + /* fix up the vers structure, in case it is used by join */ + if (join_rev1) + { + if (vers_ts->vn_user != NULL) + free (vers_ts->vn_user); + if (vers_ts->vn_rcs != NULL) + free (vers_ts->vn_rcs); + vers_ts->vn_user = xstrdup (xvers_ts->vn_rcs); + vers_ts->vn_rcs = xstrdup (xvers_ts->vn_rcs); + } + + /* If this is really Update and not Checkout, recode history */ + if (strcmp (command_name, "update") == 0) + history_write ('U', update_dir, xvers_ts->vn_rcs, file, + repository); + + freevers_ts (&xvers_ts); + + if (!really_quiet && !file_is_dead) + { + write_letter (file, 'U', update_dir); + } + } + } + else + { + int old_errno = errno; /* save errno value over the rename */ + + if (!pipeout && isfile (backup)) + rename_file (backup, file); + + error (retcode == -1 ? 1 : 0, retcode == -1 ? old_errno : 0, + "could not check out %s", file); + + retval = retcode; + } + + if (!pipeout) + (void) unlink_file (backup); + + return (retval); +} + +#ifdef SERVER_SUPPORT +/* Patch a file. Runs rcsdiff. This is only done when running as the + * server. The hope is that the diff will be smaller than the file + * itself. + */ +static int +patch_file (file, repository, entries, rcsnode, vers_ts, update_dir, + docheckout, file_info, checksum) + char *file; + char *repository; + List *entries; + RCSNode *rcsnode; + Vers_TS *vers_ts; + char *update_dir; + int *docheckout; + struct stat *file_info; + unsigned char *checksum; +{ + char backup[PATH_MAX]; + char file1[PATH_MAX]; + char file2[PATH_MAX]; + int retval = 0; + int retcode = 0; + int fail; + FILE *e; + + *docheckout = 0; + + if (pipeout || joining ()) + { + *docheckout = 1; + return 0; + } + + (void) sprintf (backup, "%s/%s%s", CVSADM, CVSPREFIX, file); + if (isfile (file)) + rename_file (file, backup); + else + (void) unlink_file (backup); + + (void) sprintf (file1, "%s/%s%s-1", CVSADM, CVSPREFIX, file); + (void) sprintf (file2, "%s/%s%s-2", CVSADM, CVSPREFIX, file); + + fail = 0; + + /* We need to check out both revisions first, to see if either one + has a trailing newline. Because of this, we don't use rcsdiff, + but just use diff. */ + if (noexec) + retcode = 0; + else + retcode = RCS_checkout (vers_ts->srcfile->path, NULL, + vers_ts->vn_user, + vers_ts->options, file1, 0, 0); + if (retcode != 0) + fail = 1; + else + { + e = fopen (file1, "r"); + if (e == NULL) + fail = 1; + else + { + if (fseek (e, (long) -1, SEEK_END) == 0 + && getc (e) != '\n') + { + fail = 1; + } + fclose (e); + } + } + + if (! fail) + { + /* Check it out into file, and then move to file2, so that we + can get the right modes into *FILE_INFO. We can't check it + out directly into file2 because co doesn't understand how + to do that. */ + retcode = RCS_checkout (vers_ts->srcfile->path, file, + vers_ts->vn_rcs, + vers_ts->options, RUN_TTY, 0, 0); + if (retcode != 0) + fail = 1; + else + { + if (!isreadable (file)) + { + /* File is dead. */ + fail = 1; + } + else + { + rename_file (file, file2); + if (cvswrite == TRUE + && !fileattr_get (file, "_watched")) + xchmod (file2, 1); + e = fopen (file2, "r"); + if (e == NULL) + fail = 1; + else + { + struct MD5Context context; + int nl; + unsigned char buf[8192]; + unsigned len; + + nl = 0; + + /* Compute the MD5 checksum and make sure there is + a trailing newline. */ + MD5Init (&context); + while ((len = fread (buf, 1, sizeof buf, e)) != 0) + { + nl = buf[len - 1] == '\n'; + MD5Update (&context, buf, len); + } + MD5Final (checksum, &context); + + if (ferror (e) || ! nl) + { + fail = 1; + } + + fclose (e); + } + } + } + } + + retcode = 0; + if (! fail) + { + /* FIXME: This whole thing with diff/patch is rather more + convoluted than necessary (lots of forks and execs, need to + worry about versions of diff and patch, etc.). Also, we + send context lines which aren't needed (in the rare case in + which the diff doesn't apply, the checksum would catches it). + Solution perhaps is to librarify the RCS routines which apply + deltas or something equivalent. */ + /* This is -c, not -u, because we have no way of knowing which + DIFF is in use. */ + run_setup ("%s -c %s %s", DIFF, file1, file2); + + /* A retcode of 0 means no differences. 1 means some differences. */ + if ((retcode = run_exec (RUN_TTY, file, RUN_TTY, RUN_NORMAL)) != 0 + && retcode != 1) + { + fail = 1; + } + else + { +#define BINARY "Binary" + char buf[sizeof BINARY]; + unsigned int c; + + /* Check the diff output to make sure patch will be handle it. */ + e = fopen (file, "r"); + if (e == NULL) + error (1, errno, "could not open diff output file %s", file); + c = fread (buf, 1, sizeof BINARY - 1, e); + buf[c] = '\0'; + if (strcmp (buf, BINARY) == 0) + { + /* These are binary files. We could use diff -a, but + patch can't handle that. */ + fail = 1; + } + fclose (e); + } + } + + if (! fail) + { + Vers_TS *xvers_ts; + + /* This stuff is just copied blindly from checkout_file. I + don't really know what it does. */ + xvers_ts = Version_TS (repository, options, tag, date, file, + force_tag_match, 0, entries, rcsnode); + if (strcmp (xvers_ts->options, "-V4") == 0) + xvers_ts->options[0] = '\0'; + + Register (entries, file, xvers_ts->vn_rcs, + xvers_ts->ts_user, xvers_ts->options, + xvers_ts->tag, xvers_ts->date, NULL); + + if (stat (file2, file_info) < 0) + error (1, errno, "could not stat %s", file2); + + /* If this is really Update and not Checkout, recode history */ + if (strcmp (command_name, "update") == 0) + history_write ('P', update_dir, xvers_ts->vn_rcs, file, + repository); + + freevers_ts (&xvers_ts); + + if (!really_quiet) + { + write_letter (file, 'P', update_dir); + } + } + else + { + int old_errno = errno; /* save errno value over the rename */ + + if (isfile (backup)) + rename_file (backup, file); + + if (retcode != 0 && retcode != 1) + error (retcode == -1 ? 1 : 0, retcode == -1 ? old_errno : 0, + "could not diff %s", file); + + *docheckout = 1; + retval = retcode; + } + + (void) unlink_file (backup); + (void) unlink_file (file1); + (void) unlink_file (file2); + + return (retval); +} +#endif + +/* + * Several of the types we process only print a bit of information consisting + * of a single letter and the name. + */ +static int +write_letter (file, letter, update_dir) + char *file; + int letter; + char *update_dir; +{ + if (!really_quiet) + { + char buf[2]; + buf[0] = letter; + buf[1] = ' '; + cvs_output (buf, 2); + if (update_dir[0]) + { + cvs_output (update_dir, 0); + cvs_output ("/", 1); + } + cvs_output (file, 0); + cvs_output ("\n", 1); + } + return (0); +} + +/* + * Do all the magic associated with a file which needs to be merged + */ +static int +merge_file (file, repository, entries, vers, update_dir) + char *file; + char *repository; + List *entries; + Vers_TS *vers; + char *update_dir; +{ + char user[PATH_MAX]; + char backup[PATH_MAX]; + int status; + int retcode = 0; + + /* + * The users currently modified file is moved to a backup file name + * ".#filename.version", so that it will stay around for a few days + * before being automatically removed by some cron daemon. The "version" + * is the version of the file that the user was most up-to-date with + * before the merge. + */ + (void) sprintf (backup, "%s%s.%s", BAKPREFIX, file, vers->vn_user); + if (update_dir[0]) + (void) sprintf (user, "%s/%s", update_dir, file); + else + (void) strcpy (user, file); + + (void) unlink_file (backup); + copy_file (file, backup); + xchmod (file, 1); + + status = RCS_merge(vers->srcfile->path, + vers->options, vers->vn_user, vers->vn_rcs); + if (status != 0 && status != 1) + { + error (0, status == -1 ? errno : 0, + "could not merge revision %s of %s", vers->vn_user, user); + error (status == -1 ? 1 : 0, 0, "restoring %s from backup file %s", + user, backup); + rename_file (backup, file); + return (1); + } + + if (strcmp (vers->options, "-V4") == 0) + vers->options[0] = '\0'; + (void) time (&last_register_time); + { + char *cp = 0; + + if (status) + cp = time_stamp (file); + Register (entries, file, vers->vn_rcs, vers->ts_rcs, vers->options, + vers->tag, vers->date, cp); + if (cp) + free (cp); + } + + /* fix up the vers structure, in case it is used by join */ + if (join_rev1) + { + if (vers->vn_user != NULL) + free (vers->vn_user); + vers->vn_user = xstrdup (vers->vn_rcs); + } + +#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 (file, update_dir, repository, backup); + server_updated (file, update_dir, repository, SERVER_MERGED, + (struct stat *) NULL, (unsigned char *) NULL); + } +#endif + + if (!noexec && !xcmp (backup, file)) + { + printf ("%s already contains the differences between %s and %s\n", + user, vers->vn_user, vers->vn_rcs); + history_write ('G', update_dir, vers->vn_rcs, file, repository); + return (0); + } + + if (status == 1) + { + if (!noexec) + error (0, 0, "conflicts found in %s", user); + + write_letter (file, 'C', update_dir); + + history_write ('C', update_dir, vers->vn_rcs, file, repository); + + } + else if (retcode == -1) + { + error (1, errno, "fork failed while examining update of %s", user); + } + else + { + write_letter (file, 'M', update_dir); + history_write ('G', update_dir, vers->vn_rcs, file, repository); + } + return (0); +} + +/* + * Do all the magic associated with a file which needs to be joined + * (-j option) + */ +static void +#ifdef SERVER_SUPPORT +join_file (file, rcsnode, vers, update_dir, entries, repository) + char *repository; +#else +join_file (file, rcsnode, vers, update_dir, entries) +#endif + char *file; + RCSNode *rcsnode; + Vers_TS *vers; + char *update_dir; + List *entries; +{ + char user[PATH_MAX]; + char backup[PATH_MAX]; + char *options; + int status; + + char *rev1; + char *rev2; + char *jrev1; + char *jrev2; + char *jdate1; + char *jdate2; + + jrev1 = join_rev1; + jrev2 = join_rev2; + jdate1 = date_rev1; + jdate2 = date_rev2; + + if (wrap_merge_is_copy (file)) + { + /* FIXME: Should be including update_dir in message. */ + error (0, 0, + "Cannot merge %s because it is a merge-by-copy file.", file); + return; + } + + /* determine if we need to do anything at all */ + if (vers->srcfile == NULL || + vers->srcfile->path == NULL) + { + return; + } + + /* in all cases, use two revs. */ + + /* if only one rev is specified, it becomes the second rev */ + if (jrev2 == NULL) + { + jrev2 = jrev1; + jrev1 = NULL; + jdate2 = jdate1; + jdate1 = NULL; + } + + /* The file in the working directory doesn't exist in CVS/Entries. + FIXME: Shouldn't this case result in additional processing (if + the file was added going from rev1 to rev2, then do the equivalent + of a "cvs add")? (yes; easier said than done.. :-) */ + if (vers->vn_user == NULL) + { + /* No merge possible YET. */ + if (jdate2 != NULL) + error (0, 0, + "file %s is present in revision %s as of %s", + file, jrev2, jdate2); + else + error (0, 0, + "file %s is present in revision %s", + file, jrev2); + return; + } + + /* Fix for bug CVS/193: + * Used to dump core if the file had been removed on the current branch. + */ + if (strcmp(vers->vn_user, "0") == 0) + { + error(0, 0, + "file %s has been deleted", + file); + return; + } + + /* convert the second rev spec, walking branches and dates. */ + + rev2 = RCS_getversion (vers->srcfile, jrev2, jdate2, 1, 0); + if (rev2 == NULL) + { + if (!quiet) + { + if (jdate2 != NULL) + error (0, 0, + "cannot find revision %s as of %s in file %s", + jrev2, jdate2, file); + else + error (0, 0, + "cannot find revision %s in file %s", + jrev2, file); + } + return; + } + + /* skip joining identical revs */ + if (strcmp (rev2, vers->vn_user) == 0) + { + /* No merge necessary. */ + free (rev2); + return; + } + + if (jrev1 == NULL) + { + char *tst; + /* if the first rev is missing, then it is implied to be the + greatest common ancestor of both the join rev, and the + checked out rev. */ + + /* FIXME: What is this check for '!' about? If it is legal to + have '!' in the first character of vn_user, it isn't + documented at struct vers_ts in cvs.h. */ + tst = vers->vn_user; + if (*tst == '!') + { + /* file was dead. merge anyway and pretend it's been + added. */ + ++tst; + Register (entries, file, "0", vers->ts_user, vers->options, + vers->tag, (char *) 0, (char *) 0); + } + rev1 = gca (tst, rev2); + if (rev1 == NULL) + { + /* this should not be possible */ + error (0, 0, "bad gca"); + abort(); + } + + tst = RCS_gettag (vers->srcfile, rev2, 1, 0); + if (tst == NULL) + { + /* this should not be possible. */ + error (0, 0, "cannot find gca"); + abort(); + } + + free (tst); + + /* these two cases are noops */ + if (strcmp (rev1, rev2) == 0) + { + free (rev1); + free (rev2); + return; + } + } + else + { + /* otherwise, convert the first rev spec, walking branches and + dates. */ + + rev1 = RCS_getversion (vers->srcfile, jrev1, jdate1, 1, 0); + if (rev1 == NULL) + { + if (!quiet) { + if (jdate1 != NULL) + error (0, 0, + "cannot find revision %s as of %s in file %s", + jrev1, jdate1, file); + else + error (0, 0, + "cannot find revision %s in file %s", + jrev1, file); + } + return; + } + } + + /* do the join */ + +#if 0 + dome { + /* special handling when two revisions are specified */ + if (join_rev1 && join_rev2) + { + rev = RCS_getversion (vers->srcfile, join_rev2, date_rev2, 1, 0); + if (rev == NULL) + { + if (!quiet && date_rev2 == NULL) + error (0, 0, + "cannot find revision %s in file %s", join_rev2, file); + return; + } + + baserev = RCS_getversion (vers->srcfile, join_rev1, date_rev1, 1, 0); + if (baserev == NULL) + { + if (!quiet && date_rev1 == NULL) + error (0, 0, + "cannot find revision %s in file %s", join_rev1, file); + free (rev); + return; + } + + /* + * nothing to do if: + * second revision matches our BASE revision (vn_user) && + * both revisions are on the same branch + */ + if (strcmp (vers->vn_user, rev) == 0 && + numdots (baserev) == numdots (rev)) + { + /* might be the same branch. take a real look */ + char *dot = strrchr (baserev, '.'); + int len = (dot - baserev) + 1; + + if (strncmp (baserev, rev, len) == 0) + return; + } + } + else + { + rev = RCS_getversion (vers->srcfile, join_rev1, date_rev1, 1, 0); + if (rev == NULL) + return; + if (strcmp (rev, vers->vn_user) == 0) /* no merge necessary */ + { + free (rev); + return; + } + + baserev = RCS_whatbranch (file, join_rev1, rcsnode); + if (baserev) + { + char *cp; + + /* we get a branch -- turn it into a revision, or NULL if trunk */ + if ((cp = strrchr (baserev, '.')) == NULL) + { + free (baserev); + baserev = (char *) NULL; + } + else + *cp = '\0'; + } + } + if (baserev && strcmp (baserev, rev) == 0) + { + /* they match -> nothing to do */ + free (rev); + free (baserev); + return; + } + } +#endif + + /* OK, so we have two revisions; continue on */ + +#ifdef SERVER_SUPPORT + if (server_active && !isreadable (file)) + { + int retcode; + /* The file is up to date. Need to check out the current contents. */ + retcode = RCS_checkout (vers->srcfile->path, "", vers->vn_user, NULL, + RUN_TTY, 0, 0); + if (retcode != 0) + error (1, retcode == -1 ? errno : 0, + "failed to check out %s file", file); + } +#endif + + /* + * The users currently modified file is moved to a backup file name + * ".#filename.version", so that it will stay around for a few days + * before being automatically removed by some cron daemon. The "version" + * is the version of the file that the user was most up-to-date with + * before the merge. + */ + (void) sprintf (backup, "%s%s.%s", BAKPREFIX, file, vers->vn_user); + if (update_dir[0]) + (void) sprintf (user, "%s/%s", update_dir, file); + else + (void) strcpy (user, file); + + (void) unlink_file (backup); + copy_file (file, backup); + xchmod (file, 1); + + options = vers->options; +#ifdef HAVE_RCS5 +#if 0 + if (*options == '\0') + options = "-kk"; /* to ignore keyword expansions */ +#endif +#endif + + status = RCS_merge (vers->srcfile->path, options, rev1, rev2); + if (status != 0 && status != 1) + { + error (0, status == -1 ? errno : 0, + "could not merge revision %s of %s", rev2, user); + error (status == -1 ? 1 : 0, 0, "restoring %s from backup file %s", + user, backup); + rename_file (backup, file); + } + free (rev1); + free (rev2); + +#ifdef SERVER_SUPPORT + /* + * If we're in server mode, then we need to re-register the file + * even if there were no conflicts (status == 0). + * This tells server_updated() to send the modified file back to + * the client. + */ + if (status == 1 || (status == 0 && server_active)) +#else + if (status == 1) +#endif + { + char *cp = 0; + + if (status) + cp = time_stamp (file); + Register (entries, file, vers->vn_rcs, vers->ts_rcs, vers->options, + vers->tag, vers->date, cp); + if (cp) + free(cp); + } + +#ifdef SERVER_SUPPORT + if (server_active) + { + server_copy_file (file, update_dir, repository, backup); + server_updated (file, update_dir, repository, SERVER_MERGED, + (struct stat *) NULL, (unsigned char *) NULL); + } +#endif +} + +int +joining () +{ + return (join_rev1 != NULL); +} diff --git a/contrib/cvs/src/update.h b/contrib/cvs/src/update.h new file mode 100644 index 0000000..bad6562 --- /dev/null +++ b/contrib/cvs/src/update.h @@ -0,0 +1,21 @@ +/* Declarations for update.c. + + 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. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ + +int do_update PROTO((int argc, char *argv[], char *xoptions, char *xtag, + char *xdate, int xforce, int local, int xbuild, + int xaflag, int xprune, int xpipeout, int which, + char *xjoin_rev1, char *xjoin_rev2, char *preload_update_dir)); +int joining PROTO((void)); diff --git a/contrib/cvs/src/vers_ts.c b/contrib/cvs/src/vers_ts.c new file mode 100644 index 0000000..34983a1 --- /dev/null +++ b/contrib/cvs/src/vers_ts.c @@ -0,0 +1,354 @@ +/* + * Copyright (c) 1992, Brian Berliner and Jeff Polk + * Copyright (c) 1989-1992, Brian Berliner + * + * You may distribute under the terms of the GNU General Public License as + * specified in the README file that comes with the CVS 1.4 kit. + */ + +#include "cvs.h" + +#ifdef SERVER_SUPPORT +static void time_stamp_server PROTO((char *, Vers_TS *)); +#endif + +/* + * Fill in and return a Vers_TS structure "user" is the name of the local + * file; entries is the entries file - preparsed for our pleasure. rcs is + * the current source control file - preparsed for our pleasure. + */ +Vers_TS * +Version_TS (repository, options, tag, date, user, force_tag_match, + set_time, entries, rcs) + char *repository; + char *options; + char *tag; + char *date; + char *user; + int force_tag_match; + int set_time; + List *entries; + RCSNode *rcs; +{ + Node *p; + RCSNode *rcsdata; + Vers_TS *vers_ts; + struct stickydirtag *sdtp; + + /* get a new Vers_TS struct */ + vers_ts = (Vers_TS *) xmalloc (sizeof (Vers_TS)); + memset ((char *) vers_ts, 0, sizeof (*vers_ts)); + + /* + * look up the entries file entry and fill in the version and timestamp + * if entries is NULL, there is no entries file so don't bother trying to + * look it up (used by checkout -P) + */ + if (entries == NULL) + { + sdtp = NULL; + p = NULL; + } + else + { + p = findnode_fn (entries, user); + sdtp = (struct stickydirtag *) entries->list->data; /* list-private */ + } + + if (p != NULL) + { + Entnode *entdata = (Entnode *) p->data; + + vers_ts->vn_user = xstrdup (entdata->version); + vers_ts->ts_rcs = xstrdup (entdata->timestamp); + vers_ts->ts_conflict = xstrdup (entdata->conflict); + if (!tag) + { + if (!(sdtp && sdtp->aflag)) + vers_ts->tag = xstrdup (entdata->tag); + } + if (!date) + { + if (!(sdtp && sdtp->aflag)) + vers_ts->date = xstrdup (entdata->date); + } + if (!options || (options && *options == '\0')) + { + if (!(sdtp && sdtp->aflag)) + vers_ts->options = xstrdup (entdata->options); + } + vers_ts->entdata = entdata; + } + + /* + * -k options specified on the command line override (and overwrite) + * options stored in the entries file + */ + if (options) + vers_ts->options = xstrdup (options); + else if (!vers_ts->options) + { + if (sdtp && sdtp->aflag == 0) + vers_ts->options = xstrdup (sdtp->options); + else if (rcs != NULL) + { + /* If no keyword expansion was specified on command line, + use whatever was in the rcs file (if there is one). This + is how we, if we are the server, tell the client whether + a file is binary. */ + char *rcsexpand = RCS_getexpand (rcs); + if (rcsexpand != NULL) + { + vers_ts->options = xmalloc (strlen (rcsexpand) + 3); + strcpy (vers_ts->options, "-k"); + strcat (vers_ts->options, rcsexpand); + } + } + } + if (!vers_ts->options) + vers_ts->options = xstrdup (""); + + /* + * if tags were specified on the command line, they override what is in + * the Entries file + */ + if (tag || date) + { + vers_ts->tag = xstrdup (tag); + vers_ts->date = xstrdup (date); + } + else if (!vers_ts->entdata && (sdtp && sdtp->aflag == 0)) + { + if (!vers_ts->tag) + vers_ts->tag = xstrdup (sdtp->tag); + if (!vers_ts->date) + vers_ts->date = xstrdup (sdtp->date); + } + + /* Now look up the info on the source controlled file */ + if (rcs != NULL) + { + rcsdata = rcs; + rcsdata->refcount++; + } + else if (repository != NULL) + rcsdata = RCS_parse (user, repository); + else + rcsdata = NULL; + + if (rcsdata != NULL) + { + /* squirrel away the rcsdata pointer for others */ + vers_ts->srcfile = rcsdata; + + if (vers_ts->tag && strcmp (vers_ts->tag, TAG_BASE) == 0) + { + vers_ts->vn_rcs = xstrdup (vers_ts->vn_user); + vers_ts->vn_tag = xstrdup (vers_ts->vn_user); + } + else + { + vers_ts->vn_rcs = RCS_getversion (rcsdata, vers_ts->tag, + vers_ts->date, force_tag_match, 1); + if (vers_ts->vn_rcs == NULL) + vers_ts->vn_tag = NULL; + else + { + char *colon = strchr (vers_ts->vn_rcs, ':'); + if (colon) + { + vers_ts->vn_tag = xstrdup (colon+1); + *colon = '\0'; + } + else + vers_ts->vn_tag = xstrdup (vers_ts->vn_rcs); + } + } + + /* + * If the source control file exists and has the requested revision, + * get the Date the revision was checked in. If "user" exists, set + * its mtime. + */ + if (set_time) + { + struct utimbuf t; + + memset ((char *) &t, 0, sizeof (t)); + if (vers_ts->vn_rcs && + (t.actime = t.modtime = RCS_getrevtime (rcsdata, + vers_ts->vn_rcs, (char *) 0, 0)) != -1) + (void) utime (user, &t); + } + } + + /* get user file time-stamp in ts_user */ + if (entries != (List *) NULL) + { +#ifdef SERVER_SUPPORT + if (server_active) + time_stamp_server (user, vers_ts); + else +#endif + vers_ts->ts_user = time_stamp (user); + } + + return (vers_ts); +} + +#ifdef SERVER_SUPPORT + +/* Set VERS_TS->TS_USER to time stamp for FILE. */ + +/* Separate these out to keep the logic below clearer. */ +#define mark_lost(V) ((V)->ts_user = 0) +#define mark_unchanged(V) ((V)->ts_user = xstrdup ((V)->ts_rcs)) + +static void +time_stamp_server (file, vers_ts) + char *file; + Vers_TS *vers_ts; +{ + struct stat sb; + char *cp; + + if (stat (file, &sb) < 0) + { + if (! existence_error (errno)) + error (1, errno, "cannot stat temp file"); + if (use_unchanged) + { + /* Missing file means lost or unmodified; check entries + file to see which. + + XXX FIXME - If there's no entries file line, we + wouldn't be getting the file at all, so consider it + lost. I don't know that that's right, but it's not + clear to me that either choice is. Besides, would we + have an RCS string in that case anyways? */ + if (vers_ts->entdata == NULL) + mark_lost (vers_ts); + else if (vers_ts->entdata->timestamp + && vers_ts->entdata->timestamp[0] == '=') + mark_unchanged (vers_ts); + else + mark_lost (vers_ts); + } + else + { + /* Missing file in the temp directory means that the file + was not modified. */ + mark_unchanged (vers_ts); + } + } + else if (sb.st_mtime == 0) + { + if (use_unchanged) + /* We shouldn't reach this case any more! */ + abort (); + + /* Special code used by server.c to indicate the file was lost. */ + mark_lost (vers_ts); + } + else + { + struct tm *tm_p; + struct tm local_tm; + + vers_ts->ts_user = xmalloc (25); + /* We want to use the same timestamp format as is stored in the + st_mtime. For unix (and NT I think) this *must* be universal + time (UT), so that files don't appear to be modified merely + because the timezone has changed. For VMS, or hopefully other + systems where gmtime returns NULL, the modification time is + stored in local time, and therefore it is not possible to cause + st_mtime to be out of sync by changing the timezone. */ + tm_p = gmtime (&sb.st_mtime); + if (tm_p) + { + memcpy (&local_tm, tm_p, sizeof (local_tm)); + cp = asctime (&local_tm); /* copy in the modify time */ + } + else + cp = ctime (&sb.st_mtime); + + cp[24] = 0; + (void) strcpy (vers_ts->ts_user, cp); + } +} + +#endif /* SERVER_SUPPORT */ +/* + * Gets the time-stamp for the file "file" and returns it in space it + * allocates + */ +char * +time_stamp (file) + char *file; +{ + struct stat sb; + char *cp; + char *ts; + + if (stat (file, &sb) < 0) + { + ts = NULL; + } + else + { + struct tm *tm_p; + struct tm local_tm; + ts = xmalloc (25); + /* We want to use the same timestamp format as is stored in the + st_mtime. For unix (and NT I think) this *must* be universal + time (UT), so that files don't appear to be modified merely + because the timezone has changed. For VMS, or hopefully other + systems where gmtime returns NULL, the modification time is + stored in local time, and therefore it is not possible to cause + st_mtime to be out of sync by changing the timezone. */ + tm_p = gmtime (&sb.st_mtime); + if (tm_p) + { + memcpy (&local_tm, tm_p, sizeof (local_tm)); + cp = asctime (&local_tm); /* copy in the modify time */ + } + else + cp = ctime(&sb.st_mtime); + + cp[24] = 0; + (void) strcpy (ts, cp); + } + + return (ts); +} + +/* + * free up a Vers_TS struct + */ +void +freevers_ts (versp) + Vers_TS **versp; +{ + if ((*versp)->srcfile) + freercsnode (&((*versp)->srcfile)); + if ((*versp)->vn_user) + free ((*versp)->vn_user); + if ((*versp)->vn_rcs) + free ((*versp)->vn_rcs); + if ((*versp)->vn_tag) + free ((*versp)->vn_tag); + if ((*versp)->ts_user) + free ((*versp)->ts_user); + if ((*versp)->ts_rcs) + free ((*versp)->ts_rcs); + if ((*versp)->options) + free ((*versp)->options); + if ((*versp)->tag) + free ((*versp)->tag); + if ((*versp)->date) + free ((*versp)->date); + if ((*versp)->ts_conflict) + free ((*versp)->ts_conflict); + free ((char *) *versp); + *versp = (Vers_TS *) NULL; +} diff --git a/contrib/cvs/src/version.c b/contrib/cvs/src/version.c new file mode 100644 index 0000000..4848e82 --- /dev/null +++ b/contrib/cvs/src/version.c @@ -0,0 +1,29 @@ +/* + * Copyright (c) 1994 david d `zoo' zuhn + * Copyright (c) 1994 Free Software Foundation, Inc. + * Copyright (c) 1992, Brian Berliner and Jeff Polk + * Copyright (c) 1989-1992, Brian Berliner + * + * You may distribute under the terms of the GNU General Public License as + * specified in the README file that comes with this CVS source distribution. + * + * version.c - the CVS version number + */ + +#include "cvs.h" + +char *version_string = "\nConcurrent Versions System (CVS) 1.8.1"; + +#ifdef CLIENT_SUPPORT +#ifdef SERVER_SUPPORT +char *config_string = " (client/server)\n"; +#else +char *config_string = " (client)\n"; +#endif +#else +#ifdef SERVER_SUPPORT +char *config_string = " (server)\n"; +#else +char *config_string = "\n"; +#endif +#endif diff --git a/contrib/cvs/src/watch.c b/contrib/cvs/src/watch.c new file mode 100644 index 0000000..0873489 --- /dev/null +++ b/contrib/cvs/src/watch.c @@ -0,0 +1,521 @@ +/* Implementation for "cvs watch add", "cvs watchers", and related commands + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2, or (at your option) + any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ + +#include "cvs.h" +#include "edit.h" +#include "fileattr.h" +#include "watch.h" + +const char *const watch_usage[] = +{ + "Usage: %s %s [on|off|add|remove] [-l] [-a action] [files...]\n", + "on/off: turn on/off read-only checkouts of files\n", + "add/remove: add or remove notification on actions\n", + "-l (on/off/add/remove): Local directory only, not recursive\n", + "-a (add/remove): Specify what actions, one of\n", + " edit,unedit,commit,all,none\n", + NULL +}; + +static struct addremove_args the_args; + +void +watch_modify_watchers (file, what) + char *file; + struct addremove_args *what; +{ + char *curattr = fileattr_get0 (file, "_watchers"); + char *p; + char *pend; + char *nextp; + char *who; + int who_len; + char *mycurattr; + char *mynewattr; + size_t mynewattr_size; + + int add_edit_pending; + int add_unedit_pending; + int add_commit_pending; + int remove_edit_pending; + int remove_unedit_pending; + int remove_commit_pending; + int add_tedit_pending; + int add_tunedit_pending; + int add_tcommit_pending; + + who = getcaller (); + who_len = strlen (who); + + /* Look for current watcher types for this user. */ + mycurattr = NULL; + if (curattr != NULL) + { + p = curattr; + while (1) { + if (strncmp (who, p, who_len) == 0 + && p[who_len] == '>') + { + /* Found this user. */ + mycurattr = p + who_len + 1; + } + p = strchr (p, ','); + if (p == NULL) + break; + ++p; + } + } + if (mycurattr != NULL) + { + mycurattr = xstrdup (mycurattr); + p = strchr (mycurattr, ','); + if (p != NULL) + *p = '\0'; + } + + /* Now copy mycurattr to mynewattr, making the requisite modifications. + Note that we add a dummy '+' to the start of mynewattr, to reduce + special cases (but then we strip it off when we are done). */ + + mynewattr_size = sizeof "+edit+unedit+commit+tedit+tunedit+tcommit"; + if (mycurattr != NULL) + mynewattr_size += strlen (mycurattr); + mynewattr = xmalloc (mynewattr_size); + mynewattr[0] = '\0'; + + add_edit_pending = what->adding && what->edit; + add_unedit_pending = what->adding && what->unedit; + add_commit_pending = what->adding && what->commit; + remove_edit_pending = !what->adding && what->edit; + remove_unedit_pending = !what->adding && what->unedit; + remove_commit_pending = !what->adding && what->commit; + add_tedit_pending = what->add_tedit; + add_tunedit_pending = what->add_tunedit; + add_tcommit_pending = what->add_tcommit; + + /* Copy over existing watch types, except those to be removed. */ + p = mycurattr; + while (p != NULL) + { + pend = strchr (p, '+'); + if (pend == NULL) + { + pend = p + strlen (p); + nextp = NULL; + } + else + nextp = pend + 1; + + /* Process this item. */ + if (pend - p == 4 && strncmp ("edit", p, 4) == 0) + { + if (!remove_edit_pending) + strcat (mynewattr, "+edit"); + add_edit_pending = 0; + } + else if (pend - p == 6 && strncmp ("unedit", p, 6) == 0) + { + if (!remove_unedit_pending) + strcat (mynewattr, "+unedit"); + add_unedit_pending = 0; + } + else if (pend - p == 6 && strncmp ("commit", p, 6) == 0) + { + if (!remove_commit_pending) + strcat (mynewattr, "+commit"); + add_commit_pending = 0; + } + else if (pend - p == 5 && strncmp ("tedit", p, 5) == 0) + { + if (!what->remove_temp) + strcat (mynewattr, "+tedit"); + add_tedit_pending = 0; + } + else if (pend - p == 7 && strncmp ("tunedit", p, 7) == 0) + { + if (!what->remove_temp) + strcat (mynewattr, "+tunedit"); + add_tunedit_pending = 0; + } + else if (pend - p == 7 && strncmp ("tcommit", p, 7) == 0) + { + if (!what->remove_temp) + strcat (mynewattr, "+tcommit"); + add_tcommit_pending = 0; + } + else + { + char *mp; + + /* Copy over any unrecognized watch types, for future + expansion. */ + mp = mynewattr + strlen (mynewattr); + *mp++ = '+'; + strncpy (mp, p, pend - p); + *(mp + (pend - p)) = '\0'; + } + + /* Set up for next item. */ + p = nextp; + } + + /* Add in new watch types. */ + if (add_edit_pending) + strcat (mynewattr, "+edit"); + if (add_unedit_pending) + strcat (mynewattr, "+unedit"); + if (add_commit_pending) + strcat (mynewattr, "+commit"); + if (add_tedit_pending) + strcat (mynewattr, "+tedit"); + if (add_tunedit_pending) + strcat (mynewattr, "+tunedit"); + if (add_tcommit_pending) + strcat (mynewattr, "+tcommit"); + + { + char *curattr_new; + + curattr_new = + fileattr_modify (curattr, + who, + mynewattr[0] == '\0' ? NULL : mynewattr + 1, + '>', + ','); + /* If the attribute is unchanged, don't rewrite the attribute file. */ + if (!((curattr_new == NULL && curattr == NULL) + || (curattr_new != NULL + && curattr != NULL + && strcmp (curattr_new, curattr) == 0))) + fileattr_set (file, + "_watchers", + curattr_new); + if (curattr_new != NULL) + free (curattr_new); + } + + if (curattr != NULL) + free (curattr); + if (mycurattr != NULL) + free (mycurattr); + if (mynewattr != NULL) + free (mynewattr); +} + +static int addremove_fileproc PROTO ((struct file_info *finfo)); + +static int +addremove_fileproc (finfo) + struct file_info *finfo; +{ + watch_modify_watchers (finfo->file, &the_args); + return 0; +} + +static int addremove_filesdoneproc PROTO ((int, char *, char *)); + +static int +addremove_filesdoneproc (err, repository, update_dir) + int err; + char *repository; + char *update_dir; +{ + if (the_args.setting_default) + watch_modify_watchers (NULL, &the_args); + return err; +} + +static int watch_addremove PROTO ((int argc, char **argv)); + +static int +watch_addremove (argc, argv) + int argc; + char **argv; +{ + int c; + int local = 0; + int err; + int a_omitted; + + a_omitted = 1; + the_args.commit = 0; + the_args.edit = 0; + the_args.unedit = 0; + optind = 1; + while ((c = getopt (argc, argv, "la:")) != -1) + { + switch (c) + { + case 'l': + local = 1; + break; + case 'a': + a_omitted = 0; + if (strcmp (optarg, "edit") == 0) + the_args.edit = 1; + else if (strcmp (optarg, "unedit") == 0) + the_args.unedit = 1; + else if (strcmp (optarg, "commit") == 0) + the_args.commit = 1; + else if (strcmp (optarg, "all") == 0) + { + the_args.edit = 1; + the_args.unedit = 1; + the_args.commit = 1; + } + else if (strcmp (optarg, "none") == 0) + { + the_args.edit = 0; + the_args.unedit = 0; + the_args.commit = 0; + } + else + usage (watch_usage); + break; + case '?': + default: + usage (watch_usage); + break; + } + } + argc -= optind; + argv += optind; + + if (a_omitted) + { + the_args.edit = 1; + the_args.unedit = 1; + the_args.commit = 1; + } + +#ifdef CLIENT_SUPPORT + if (client_active) + { + start_server (); + ign_setup (); + + if (local) + send_arg ("-l"); + /* FIXME: copes poorly with "all" if server is extended to have + new watch types and client is still running an old version. */ + if (the_args.edit) + { + send_arg ("-a"); + send_arg ("edit"); + } + if (the_args.unedit) + { + send_arg ("-a"); + send_arg ("unedit"); + } + if (the_args.commit) + { + send_arg ("-a"); + send_arg ("commit"); + } + if (!the_args.edit && !the_args.unedit && !the_args.commit) + { + send_arg ("-a"); + send_arg ("none"); + } + send_file_names (argc, argv, SEND_EXPAND_WILD); + /* FIXME: We shouldn't have to send current files, but I'm not sure + whether it works. So send the files -- + it's slower but it works. */ + send_files (argc, argv, local, 0); + send_to_server (the_args.adding ? + "watch-add\012" : "watch-remove\012", + 0); + return get_responses_and_close (); + } +#endif /* CLIENT_SUPPORT */ + + the_args.setting_default = (argc <= 0); + + lock_tree_for_write (argc, argv, local, 0); + + err = start_recursion (addremove_fileproc, addremove_filesdoneproc, + (DIRENTPROC) NULL, (DIRLEAVEPROC) NULL, + argc, argv, local, W_LOCAL, 0, 0, (char *)NULL, + 1, 0); + + lock_tree_cleanup (); + return err; +} + +int +watch_add (argc, argv) + int argc; + char **argv; +{ + the_args.adding = 1; + return watch_addremove (argc, argv); +} + +int +watch_remove (argc, argv) + int argc; + char **argv; +{ + the_args.adding = 0; + return watch_addremove (argc, argv); +} + +int +watch (argc, argv) + int argc; + char **argv; +{ + if (argc <= 1) + usage (watch_usage); + if (strcmp (argv[1], "on") == 0) + { + --argc; + ++argv; + return watch_on (argc, argv); + } + else if (strcmp (argv[1], "off") == 0) + { + --argc; + ++argv; + return watch_off (argc, argv); + } + else if (strcmp (argv[1], "add") == 0) + { + --argc; + ++argv; + return watch_add (argc, argv); + } + else if (strcmp (argv[1], "remove") == 0) + { + --argc; + ++argv; + return watch_remove (argc, argv); + } + else + usage (watch_usage); + return 0; +} + +static const char *const watchers_usage[] = +{ + "Usage: %s %s [files...]\n", + NULL +}; + +static int watchers_fileproc PROTO ((struct file_info *finfo)); + +static int +watchers_fileproc (finfo) + struct file_info *finfo; +{ + char *them; + char *p; + + them = fileattr_get0 (finfo->file, "_watchers"); + if (them == NULL) + return 0; + + fputs (finfo->fullname, stdout); + + p = them; + while (1) + { + putc ('\t', stdout); + while (*p != '>' && *p != '\0') + putc (*p++, stdout); + if (*p == '\0') + { + /* Only happens if attribute is misformed. */ + putc ('\n', stdout); + break; + } + ++p; + putc ('\t', stdout); + while (1) + { + while (*p != '+' && *p != ',' && *p != '\0') + putc (*p++, stdout); + if (*p == '\0') + { + putc ('\n', stdout); + goto out; + } + if (*p == ',') + { + ++p; + break; + } + ++p; + putc ('\t', stdout); + } + putc ('\n', stdout); + } + out:; + return 0; +} + +int +watchers (argc, argv) + int argc; + char **argv; +{ + int local = 0; + int c; + + if (argc == -1) + usage (watchers_usage); + + optind = 1; + while ((c = getopt (argc, argv, "l")) != -1) + { + switch (c) + { + case 'l': + local = 1; + break; + case '?': + default: + usage (watchers_usage); + break; + } + } + argc -= optind; + argv += optind; + +#ifdef CLIENT_SUPPORT + if (client_active) + { + start_server (); + ign_setup (); + + if (local) + send_arg ("-l"); + send_file_names (argc, argv, SEND_EXPAND_WILD); + /* FIXME: We shouldn't have to send current files, but I'm not sure + whether it works. So send the files -- + it's slower but it works. */ + send_files (argc, argv, local, 0); + send_to_server ("watchers\012", 0); + return get_responses_and_close (); + } +#endif /* CLIENT_SUPPORT */ + + return start_recursion (watchers_fileproc, (FILESDONEPROC) NULL, + (DIRENTPROC) NULL, (DIRLEAVEPROC) NULL, + argc, argv, local, W_LOCAL, 0, 1, (char *)NULL, + 1, 0); +} diff --git a/contrib/cvs/src/watch.h b/contrib/cvs/src/watch.h new file mode 100644 index 0000000..d279c71 --- /dev/null +++ b/contrib/cvs/src/watch.h @@ -0,0 +1,56 @@ +/* Interface to "cvs watch add", "cvs watchers", and related features + + 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. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ + +extern const char *const watch_usage[]; + +/* Flags to pass between the various functions making up the + add/remove code. All in a single structure in case there is some + need to make the code reentrant some day. */ + +struct addremove_args { + /* A flag for each watcher type. */ + int edit; + int unedit; + int commit; + + /* Are we adding or removing (non-temporary) edit,unedit,and/or commit + watches? */ + int adding; + + /* Should we add a temporary edit watch? */ + int add_tedit; + /* Should we add a temporary unedit watch? */ + int add_tunedit; + /* Should we add a temporary commit watch? */ + int add_tcommit; + + /* Should we remove all temporary watches? */ + int remove_temp; + + /* Should we set the default? This is here for passing among various + routines in watch.c (a good place for it if there is ever any reason + to make the stuff reentrant), not for watch_modify_watchers. */ + int setting_default; +}; + +/* Modify the watchers for FILE. *WHAT tells what to do to them. + If FILE is NULL, modify default args (WHAT->SETTING_DEFAULT is + not used). */ +extern void watch_modify_watchers PROTO ((char *file, + struct addremove_args *what)); + +extern int watch_add PROTO ((int argc, char **argv)); +extern int watch_remove PROTO ((int argc, char **argv)); diff --git a/contrib/cvs/src/wrapper.c b/contrib/cvs/src/wrapper.c new file mode 100644 index 0000000..8a6ff94 --- /dev/null +++ b/contrib/cvs/src/wrapper.c @@ -0,0 +1,374 @@ +#include "cvs.h" + +/* + Original Author: athan@morgan.com <Andrew C. Athan> 2/1/94 + Modified By: vdemarco@bou.shl.com + + This package was written to support the NEXTSTEP concept of + "wrappers." These are essentially directories that are to be + treated as "files." This package allows such wrappers to be + "processed" on the way in and out of CVS. The intended use is to + wrap up a wrapper into a single tar, such that that tar can be + treated as a single binary file in CVS. To solve the problem + effectively, it was also necessary to be able to prevent rcsmerge + application at appropriate times. + + ------------------ + Format of wrapper file ($CVSROOT/CVSROOT/cvswrappers or .cvswrappers) + + wildcard [option value][option value]... + + where option is one of + -f from cvs filter value: path to filter + -t to cvs filter value: path to filter + -m update methodology value: MERGE or COPY + + and value is a single-quote delimited value. + + E.g: + *.nib -f 'gunzipuntar' -t 'targzip' -m 'COPY' +*/ + + +typedef struct { + char *wildCard; + char *tocvsFilter; + char *fromcvsFilter; + char *conflictHook; + WrapMergeMethod mergeMethod; +} WrapperEntry; + +static WrapperEntry **wrap_list=NULL; +static WrapperEntry **wrap_saved_list=NULL; + +static int wrap_size=0; +static int wrap_count=0; +static int wrap_tempcount=0; +static int wrap_saved_count=0; +static int wrap_saved_tempcount=0; + +#define WRAPPER_GROW 8 + +void wrap_add_entry PROTO((WrapperEntry *e,int temp)); +void wrap_kill PROTO((void)); +void wrap_kill_temp PROTO((void)); +void wrap_free_entry PROTO((WrapperEntry *e)); +void wrap_free_entry_internal PROTO((WrapperEntry *e)); +void wrap_restore_saved PROTO((void)); + +void wrap_setup() +{ + char file[PATH_MAX]; + struct passwd *pw; + + /* Then add entries found in repository, if it exists */ + (void) sprintf (file, "%s/%s/%s", CVSroot, CVSROOTADM, CVSROOTADM_WRAPPER); + if (isfile (file)){ + wrap_add_file(file,0); + } + + /* Then add entries found in home dir, (if user has one) and file exists */ + if ((pw = (struct passwd *) getpwuid (getuid ())) && pw->pw_dir){ + (void) sprintf (file, "%s/%s", pw->pw_dir, CVSDOTWRAPPER); + if (isfile (file)){ + wrap_add_file (file, 0); + } + } + + /* Then add entries found in CVSWRAPPERS environment variable. */ + wrap_add (getenv (WRAPPER_ENV), 0); +} + +/* + * Open a file and read lines, feeding each line to a line parser. Arrange + * for keeping a temporary list of wrappers at the end, if the "temp" + * argument is set. + */ +void +wrap_add_file (file, temp) + const char *file; + int temp; +{ + FILE *fp; + char line[1024]; + + wrap_restore_saved(); + wrap_kill_temp(); + + /* load the file */ + if (!(fp = fopen (file, "r"))) + return; + while (fgets (line, sizeof (line), fp)) + wrap_add (line, temp); + (void) fclose (fp); +} + +void +wrap_kill() +{ + wrap_kill_temp(); + while(wrap_count) + wrap_free_entry(wrap_list[--wrap_count]); +} + +void +wrap_kill_temp() +{ + WrapperEntry **temps=wrap_list+wrap_count; + + while(wrap_tempcount) + wrap_free_entry(temps[--wrap_tempcount]); +} + +void +wrap_free_entry(e) + WrapperEntry *e; +{ + wrap_free_entry_internal(e); + free(e); +} + +void +wrap_free_entry_internal(e) + WrapperEntry *e; +{ + free(e->wildCard); + if(e->tocvsFilter) + free(e->tocvsFilter); + if(e->fromcvsFilter) + free(e->fromcvsFilter); + if(e->conflictHook) + free(e->conflictHook); +} + +void +wrap_restore_saved() +{ + if(!wrap_saved_list) + return; + + wrap_kill(); + + free(wrap_list); + + wrap_list=wrap_saved_list; + wrap_count=wrap_saved_count; + wrap_tempcount=wrap_saved_tempcount; + + wrap_saved_list=NULL; + wrap_saved_count=0; + wrap_saved_tempcount=0; +} + +void +wrap_add (line, isTemp) + char *line; + int isTemp; +{ + char *temp; + char ctemp; + WrapperEntry e; + char opt; + + if (!line || line[0] == '#') + return; + + memset (&e, 0, sizeof(e)); + + /* Search for the wild card */ + while(*line && isspace(*line)) + ++line; + for(temp=line;*line && !isspace(*line);++line) + ; + if(temp==line) + return; + + ctemp=*line; + *line='\0'; + + e.wildCard=xstrdup(temp); + *line=ctemp; + + while(*line){ + /* Search for the option */ + while(*line && *line!='-') + ++line; + if(!*line) + break; + ++line; + if(!*line) + break; + opt=*line; + + /* Search for the filter commandline */ + for(++line;*line && *line!='\'';++line); + if(!*line) + break; + + for(temp=++line;*line && (*line!='\'' || line[-1]=='\\');++line) + ; + + if(line==temp+1) + break; + + ctemp=*line; + *line='\0'; + switch(opt){ + case 'f': + if(e.fromcvsFilter) + free(e.fromcvsFilter); + /* FIXME: error message should say where the bad value + came from. */ + e.fromcvsFilter=expand_path (temp, "<wrapper>", 0); + if (!e.fromcvsFilter) + error (1, 0, "Correct above errors first"); + break; + case 't': + if(e.tocvsFilter) + free(e.tocvsFilter); + /* FIXME: error message should say where the bad value + came from. */ + e.tocvsFilter=expand_path (temp, "<wrapper>", 0); + if (!e.tocvsFilter) + error (1, 0, "Correct above errors first"); + break; + case 'c': + if(e.conflictHook) + free(e.conflictHook); + /* FIXME: error message should say where the bad value + came from. */ + e.conflictHook=expand_path (temp, "<wrapper>", 0); + if (!e.conflictHook) + error (1, 0, "Correct above errors first"); + break; + case 'm': + if(*temp=='C' || *temp=='c') + e.mergeMethod=WRAP_COPY; + else + e.mergeMethod=WRAP_MERGE; + break; + default: + break; + } + *line=ctemp; + if(!*line)break; + ++line; + } + + wrap_add_entry(&e, isTemp); +} + +void +wrap_add_entry(e, temp) + WrapperEntry *e; + int temp; +{ + int x; + if(wrap_count+wrap_tempcount>=wrap_size){ + wrap_size += WRAPPER_GROW; + wrap_list = (WrapperEntry **) xrealloc ((char *) wrap_list, + wrap_size * + sizeof (WrapperEntry *)); + } + + if(!temp && wrap_tempcount){ + for(x=wrap_count+wrap_tempcount-1;x>=wrap_count;--x) + wrap_list[x+1]=wrap_list[x]; + } + + x=(temp ? wrap_count+(wrap_tempcount++):(wrap_count++)); + wrap_list[x]=(WrapperEntry *)xmalloc(sizeof(WrapperEntry)); + wrap_list[x]->wildCard=e->wildCard; + wrap_list[x]->fromcvsFilter=e->fromcvsFilter; + wrap_list[x]->tocvsFilter=e->tocvsFilter; + wrap_list[x]->conflictHook=e->conflictHook; + wrap_list[x]->mergeMethod=e->mergeMethod; +} + +/* Return 1 if the given filename is a wrapper filename */ +int +wrap_name_has (name,has) + const char *name; + WrapMergeHas has; +{ + int x,count=wrap_count+wrap_saved_count; + char *temp; + + for(x=0;x<count;++x) + if (fnmatch (wrap_list[x]->wildCard, name, 0) == 0){ + switch(has){ + case WRAP_TOCVS: + temp=wrap_list[x]->tocvsFilter; + break; + case WRAP_FROMCVS: + temp=wrap_list[x]->fromcvsFilter; + break; + case WRAP_CONFLICT: + temp=wrap_list[x]->conflictHook; + break; + default: + abort (); + } + if(temp==NULL) + return (0); + else + return (1); + } + return (0); +} + +WrapperEntry * +wrap_matching_entry (name) + const char *name; +{ + int x,count=wrap_count+wrap_saved_count; + + for(x=0;x<count;++x) + if (fnmatch (wrap_list[x]->wildCard, name, 0) == 0) + return wrap_list[x]; + return (WrapperEntry *)NULL; +} + +char * +wrap_tocvs_process_file(fileName) + const char *fileName; +{ + WrapperEntry *e=wrap_matching_entry(fileName); + static char buf[L_tmpnam+1]; + + if(e==NULL || e->tocvsFilter==NULL) + return NULL; + + tmpnam(buf); + + run_setup(e->tocvsFilter,fileName,buf); + run_exec(RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL|RUN_REALLY ); + + return buf; +} + +int +wrap_merge_is_copy (fileName) + const char *fileName; +{ + WrapperEntry *e=wrap_matching_entry(fileName); + if(e==NULL || e->mergeMethod==WRAP_MERGE) + return 0; + + return 1; +} + +char * +wrap_fromcvs_process_file(fileName) + const char *fileName; +{ + WrapperEntry *e=wrap_matching_entry(fileName); + static char buf[PATH_MAX]; + + if(e==NULL || e->fromcvsFilter==NULL) + return NULL; + + run_setup(e->fromcvsFilter,fileName); + run_exec(RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL ); + return buf; +} diff --git a/contrib/cvs/stamp-h.in b/contrib/cvs/stamp-h.in new file mode 100644 index 0000000..bb5e5e3 --- /dev/null +++ b/contrib/cvs/stamp-h.in @@ -0,0 +1 @@ +Wed Oct 5 15:45:29 EDT 1994 diff --git a/contrib/cvs/tools/ChangeLog b/contrib/cvs/tools/ChangeLog new file mode 100644 index 0000000..c6b6e1c7d --- /dev/null +++ b/contrib/cvs/tools/ChangeLog @@ -0,0 +1,7 @@ +Sun Apr 14 11:07:43 1996 Karl Fogel <kfogel@floss.red-bean.com> + + * .cvsignore: new file. + + * Makefile.in (subdir): `tools', not `contrib'. + + * Added ChangeLog (this file), and subdir `pcl-cvs'. diff --git a/contrib/cvs/tools/Makefile.in b/contrib/cvs/tools/Makefile.in new file mode 100644 index 0000000..c83d1a8 --- /dev/null +++ b/contrib/cvs/tools/Makefile.in @@ -0,0 +1,76 @@ +# Makefile for GNU CVS auxiliary tools. +# Do not use this makefile directly, but only from `../Makefile'. +# Copyright (C) 1986, 1988-1990 Free Software Foundation, Inc. + +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2, or (at your option) +# any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +# $CVSid: @(#)Makefile.in 1.6 94/10/22 $ + +SHELL = /bin/sh + +srcdir = @srcdir@ +top_srcdir = @top_srcdir@ +VPATH = @srcdir@ + +prefix = @prefix@ +exec_prefix = @exec_prefix@ + +# Where to put the manual pages. +mandir = $(prefix)/man + +# Use cp if you don't have install. +INSTALL = @INSTALL@ + +DISTFILES = ChangeLog README .cvsignore Makefile.in + +all: Makefile +.PHONY: all + +install: all + @echo "pcl-cvs not installed" +.PHONY: install + +tags: +.PHONY: tags + +TAGS: +.PHONY: TAGS + +ls: + @echo $(DISTFILES) +.PHONY: ls + +clean: + /bin/rm -f *.o core +.PHONY: clean + +distclean: clean + rm -f Makefile pcl-cvs/Makefile +.PHONY: distclean + +realclean: distclean +.PHONY: realclean + +dist-dir: + mkdir ${DISTDIR} + for i in ${DISTFILES}; do \ + ln $(srcdir)/$${i} ${DISTDIR}; \ + done + cd pcl-cvs; ${MAKE} dist-dir DISTDIR="../${DISTDIR}/pcl-cvs" +.PHONY: dist-dir + +subdir = tools +Makefile: ../config.status Makefile.in + cd .. && CONFIG_FILES=$(subdir)/$@ CONFIG_HEADERS= ./config.status diff --git a/contrib/cvs/tools/README b/contrib/cvs/tools/README new file mode 100644 index 0000000..af7bd7e --- /dev/null +++ b/contrib/cvs/tools/README @@ -0,0 +1,5 @@ + This subdirectory contains tools that can be used with CVS. +Note that they will not necessarily be installed when you "make +install" from the top-level of the CVS source tree. + +pcl-cvs ............................. an Emacs interface to CVS. diff --git a/contrib/cvs/tools/pcl-cvs/ChangeLog b/contrib/cvs/tools/pcl-cvs/ChangeLog new file mode 100644 index 0000000..0192b93 --- /dev/null +++ b/contrib/cvs/tools/pcl-cvs/ChangeLog @@ -0,0 +1,891 @@ +Mon Apr 15 01:34:27 1996 Karl Fogel <kfogel@floss.red-bean.com> + + * Makefile: removed. Why was this in the repository in the first + place? + + * Makefile.in (BATCHFLAGS): don't pass -q to Emacs when compiling, + because Emacs probably can't find Elib's cookie.el[c] if we do + that. (Actually, it still can't, but that may be due to a bug in + Emacs). + + * INSTALL: reflect changed location of elib in the CVS dist. + +Sun Apr 14 12:18:12 1996 Karl Fogel <kfogel@floss.red-bean.com> + + * pcl-cvs.el (cookie): Changed "(load \"cookie\")" to + "(require 'cookie)", since elib is now included again. + Moved "(provide 'pcl-cvs)" to the end of the file, so it's not + provided if the package didn't load successfully. + + * Makefile.in (subdir): tools/pcl-cvs now, not contrib/pcl-cvs. + (BATCHFLAGS): removed "-n" from BATCHFLAGS. Emacs 19.30 does not + know about this flag. + (OBJDIR_DISTFILES): don't include .elc files here, add comment + explaining why. + +Thu Apr 11 20:22:34 1996 Karl Fogel <kfogel@floss.red-bean.com> + + * pcl-cvs.el (cvs-mode-map): conform to Emacs 19 keybinding + conventions by not binding any regular letters under C-c. + +Fri Feb 9 14:29:07 1996 Jim Blandy <jimb@totoro.cyclic.com> + + * Makefile.in (mostlyclean clean realclean): Remove 'realclean' + from this target list; it's already defined later in the file. + +Tue Jan 23 13:02:24 1996 Greg A. Woods <woods@most.weird.com> + + * pcl-cvs.el (pcl-cvs-bugs-address): change the default address + as suggested by Per Cederqvist. + * pcl-cvs.el: removed comments refering to Signum, etc. + +Sun Jan 21 12:51:12 1996 Greg A. Woods <woods@most.weird.com> + + * pcl-cvs.el (cvs-parse-stderr): fix typo (missing '\') that was + causing occasional un-reported, un-traced, failures that simply + said something like "RE missing '\(' or '\\('" -- hopefully this + is the last such bug! + +Tue Jan 16 13:57:16 1996 Jim Kingdon <kingdon@harvey.cyclic.com> + + * Makefile.in: Rename "dist" target back to "dist-dir". The + latter is what actually gets used. + (pcl-cvs.dvi): Restore srcdir to pcl-cvs.texinfo. Fix typo + (pcl-cvs.texifo -> pcl-cvs.texinfo). + (TEXINDEX,TEX,SET_TEXINPUTS): New variables. + (.el.elc): Copy .el file to build dir so .elc file gets put there. + (dist-dir): Fix typo (cvs.info -> pcl-cvs.info). + * cookie.el: New file, copied from elib 1.0. + * README: Remove note about requiring elib; it claimed that CVS + contained a copy of elib, but it lied. + * pcl-cvs.el: Change (require 'cookie) to (load "cookie.el"). + * pcl-cvs-lucid.el: Change (require 'pcl-cvs) to (load "pcl-cvs.el"). + +Fri Jan 12 10:32:14 1996 Greg A. Woods <woods@most.weird.com> + + * pcl-cvs.elc, pcl-cvs-lucid.elc: removed + + * pcl-cvs.el: run through the spell checker... + - noted some free variables in comments + (cvs-inhibit-copyright-message): move this above + cvs-startup-message to keep the compiler quiet + + * compile-all.el: removed (use make for dependency checking!) + + * Makefile.in: tweak various comments and echo messages... + (elcfiles): removed this target. + (.SUFFIXES, .el.elc): added support for elisp files. + (CORE): new macro -- list of files all .elc depend on [still empty] + (BATCHFLAGS): new macro -- flags to pass to emacs + (OBJDIR_DISTFILES): added ELCFILES to be shipped in distribution + + * README: fix the RCS Id. + + * INSTALL: re-copy formatted makeinfo output from pcl-cvs.info, + just to keep everything in proper synchronisation. + + * pcl-cvs.texinfo (Pcl-cvs installation): update to match Karl's + new wording from INSTALL. + +Wed Jan 10 22:04:35 1996 Karl Fogel <kfogel@floss.red-bean.com> + + * INSTALL: make first item read a little more smoothly. + + * README: note that pcl-cvs has been tested under 19.30. + +Wed Jan 10 17:59:00 1996 Greg A. Woods <woods@most.weird.com> + + * ChangeLog.woods: these are changes integrated in from my + own pcl-cvs repository module, and based on the original PCL-CVS + Version 1.05 release. They include most, if not all, of the + changes from the Cygnus and Cyclic CVS contrib versions of + PCL-CVS (i.e. the changes noted below). + +Sat Dec 30 15:01:45 1995 Karl Fogel <kfogel@floss.cyclic.com> + + * pcl-cvs.el (cvs-changelog-ours-p): check that + `add-log-full-name' and `add-log-mailing-address' are non-nil, in + addition to checking that they are boundp. + +Thu Dec 21 16:45:48 1995 Karl Fogel <kfogel@occs.cs.oberlin.edu> + + * pcl-cvs.el (cvs-parse-stderr): ignore kerberos connection + failure, since CVS will automatically try rsh next. I think this + is okay because if a person needs to know that kerberos failed, + then chances are the rsh failed too, and *that* error message will + clue them in that something's afoot. + +Wed Nov 22 11:01:50 1995 Joshua Cowan <jcowan@hermit.reslife.okstate.edu> + + * pcl-cvs.el (cvs-changelog-ours-p): use `user-full-name' if + `add-log-full-name' unbound, as not every uses the stuff in + add-log.el. Same with `add-log-mailing-address'. + (cvs-changelog-entries): change to `change-log-mode' unless + already in it. + +Sun Jul 9 20:57:11 1995 Karl Fogel <kfogel@floss.cyclic.com> + + * "/bin/rmdir" as default, not "/usr/local/bin/rmdir". + +Fri Jun 16 15:24:34 1995 Jim Kingdon (kingdon@cyclic.com) + + * pcl-cvs.elc, pcl-cvs-lucid.elc: Added. + + * Makefile.in: Rename from Makefile and set srcdir. + +Thu May 18 17:10:27 1995 Jim Blandy <jimb@totoro.bio.indiana.edu> + + Automatically guess CVS log entries from ChangeLog contents. + * pcl-cvs.el (cvs-mode-changelog-commit): New command. + (cvs-changelog-full-paragraphs): New variable. + (cvs-changelog-name, cvs-narrow-changelog, + cvs-changelog-paragraph, cvs-changelog-subparagraph, + cvs-changelog-entry, cvs-changelog-ours-p, cvs-relative-path, + cvs-changelog-entries, cvs-changelog-insert-entries, cvs-union, + cvs-insert-changelog-entries, cvs-edit-delete-common-indentation): + New functions. + (cvs-mode-map): Bind 'C' to cvs-mode-changelog-commit. + (cvs-mode): Mention cvs-mode-changelog-commit in docstring. + + Give the info files names ending in ".info". + * Makefile (INFOFILES, install_info): Change pcl-cvs to + pcl-cvs.info. + (pcl-cvs.info): Target renamed from pcl-cvs. + (DISTFILES): pcl-cvs removed; we handle the info files explicitly + in the dist-dir target. + (dist-dir): Depend on pcl-cvs.info. Distribute pcl-cvs.info*. + * pcl-cvs.texinfo: Change @setfilename appropriately. + * INSTALL: Updated. + * .cvsignore: Correctly ignore the info files. + + * README: Note that pcl-cvs has been tested under 19.28, and that + the "cookie" naming conflict was resolved in 19.11. + + * Makefile (pcl-cvs-lucid.elc): Changed this target from + pcl-cvs-lucid.el. That's a source file, for goodness' sake! + +Tue May 9 13:56:50 1995 Jim Blandy <jimb@totoro.bio.indiana.edu> + + * Change references to "Cygnus's remote CVS" to "Cyclic CVS". + +Wed May 3 13:55:27 1995 Jim Blandy <jimb@totoro.bio.indiana.edu> + + * pcl-cvs.el (cvs-parse-stderr): Handle colons after both + "rcsmerge" and "warning". + +Fri Apr 28 22:38:14 1995 Jim Blandy <jimb@totoro.bio.indiana.edu> + + * Makefile (ELFILES): Include pcl-cvs-startup.el. + (info, pcl-cvs): Call makeinfo appropriately for modern versions. + (pcl-cvs.aux): List dependency on pcl-cvs.texinfo. + (pcl-cvs.ps): New target. + (DVIPS): New variable. + (dist-dir): Renamed from dist, updated to accept DISTDIR value + passed from parent. + (DISTFILES): New varible. + (pcl-cvs.elc, pcl-cvs-lucid.elc): Add targets to elcfiles target. + +Tue Apr 25 21:33:49 1995 Jim Blandy <jimb@totoro.bio.indiana.edu> + + * pcl-cvs.el: (cvs-parse-stderr): Recognize "conflicts" as well as + "overlaps" before "during merge." + +Thu Feb 16 12:17:20 1995 Jim Blandy <jimb@totoro.bio.indiana.edu> + + * pcl-cvs.el (cvs-parse-stderr): Recognize "conflicts found in..." + messages attributed to "cvs server", as well as "cvs update". + +Sat Feb 4 01:47:01 1995 Jim Blandy <jimb@totoro.bio.indiana.edu> + + * pcl-cvs.el: Deal with the 'P' action, produced by remote CVS. + (cvs-parse-stdout): Treat 'P' like 'U' --- file is updated. + +Tue Jan 31 23:31:39 1995 Jim Blandy <jimb@totoro.bio.indiana.edu> + + * pcl-cvs.el (cvs-cvsroot-required): New variable. + (cvs-do-update): If cvs-cvsroot-required is not set, don't complain if + CVSROOT and cvs-cvsroot are both unset. + +Sun Jan 22 21:22:22 1995 Jim Blandy <jimb@totoro.bio.indiana.edu> + + * pcl-cvs.el (cvs-parse-stderr): + Some changes for Cygnus's Remote CVS. Treat + messages like "cvs server: Updating DIRECTORY" as we treat those like + "cvs update: Updating DIRECTORY". Ignore other messages starting with + "cvs server". + + * pcl-cvs.el (cvs-parse-stderr): Re-indent. + + * .cvsignore: Add ignore list for Texinfo litter. + + * Makefile (lispdir): Set appropriately for totoro. + * pcl-cvs.el (cvs-program, cvs-diff-program, cvs-rmdir-program): Same. + +Tue Jun 1 00:00:03 1993 Per Cederqvist (ceder@lysator.liu.se) + + * Release 1.05. (This release was promised before the end of May, + but I didn't quite make it. No, I didn't fake the date above). + +Mon May 31 01:32:25 1993 Per Cederqvist (ceder@lysator.liu.se) + + * Removed the elib sub-directory. Users must now get the Elib + library separately. + * pcl-cvs.texinfo: Document it. + + * pcl-cvs-lucid.el: A new version, supplied by Jamie Zawinsky, + added. + + * pcl-cvs Id 68: Transform RCS keywords + * Makefile (pcl-cvs-$(VER)): Remove the $ signs in most files in + the distribution. + + * pcl-cvs Id 76: Extra " in cvs-mode-add. + * pcl-cvs.el (cvs-mode-add): Don't add the extra level of quotes + around the log message, since it doesn't work with CVS. + + * pcl-cvs Id 56: '-d <CVSROOT>' support in pcl-cvs + * pcl-cvs.el (cvs-change-cvsroot): New function. + + * pcl-cvs Id 77: *cvs* isn't cleared properly + * pcl-cvs.el (cvs-do-update): Always erase the *cvs* buffer and + re-create the collection. + + * pcl-cvs.el (cvs-do-update): Set mode-line-process in the *cvs* + buffer. + * pcl-cvs.el (cvs-mode): Reset mode-line-process. + + * pcl-cvs Id 59: sort .cvsignore alphabetically! + * pcl-cvs.el (cvs-sort-ignore-file): New variable. + * pcl-cvs.el (cvs-mode-ignore): Use it. + * pcl-cvs.texinfo: Document it. + + * pcl-cvs Id 75: Require final newline. + * pcl-cvs.el (cvs-commit-buffer-require-final-newline): New + variable. + * pcl-cvs.el (cvs-edit-done): Use it. + * pcl-cvs.texinfo: Document it. + + * pcl-cvs Id 72: make clean deletes lucid-emacs.el + * dist-makefile (ELCFILES): Fixed a typo. + + * pcl-cvs Id 46: "cvs remove f" "touch f" "cvs update f" -> parse err. + * pcl-cvs.el (cvs-fileinfo->type): New type: REM-EXIST. + * pcl-cvs.el (cvs-shadow-entry-p): A REMOVED that follows a + REM-EXIST is a shadow. + * pcl-cvs.el (cvs-parse-stderr): Recognize the "should be removed + and is still there" message. + * pcl-cvs.el (cvs-pp): Recognize REM-EXIST. + * pcl-cvs.el (cvs-mode-undo-local-changes): Recognize and complain + about REM-EXIST. Defensive test added: complain about unknown types. + + * pcl-cvs.el (cvs-mode-add): Add an extra level of quotes around + the log message. This is apparently needed by RCVS. <This change + has been removed. --ceder>. + + * pcl-cvs.el (cvs-parse-stderr): Ignore output from RCVS. + +Tue Apr 27 00:48:40 1993 Per Cederqvist (ceder@lysator.liu.se) + + * pcl-cvs.el (cvs-startup-message): Now a defconst instead of a + defvar. + * pcl-cvs.el (cvs-mode-commit): Add a defvar for it. + + * dist-makefile (EMACS): Use $(EMACS) instead of hard-coding 'emacs'. + +Sat Apr 17 12:47:10 1993 Per Cederqvist (ceder@lysator.liu.se) + + * Release 1.04. + + * pcl-cvs.texinfo: Updated the Contributors node. + + * pcl-cvs Id 58: Lucid GNU Emacs support + * pcl-cvs-lucid.el: New file, contributed by the people at Lucid. + * pcl-cvs.el: Autoload pcl-cvs-lucid if running in an Lucid GNU + Emacs. + * compile-all.el: (files-to-compile): Add pcl-cvs-lucid. + * dist-makefile (ELFILES, ELCFILES): Dito. + + * pcl-cvs Id 55: cvs-diff-backup swaps old and new version. + * pcl-cvs.el (cvs-diff-backup-extractor): Old version should be + first. + * pcl-cvs.el (cvs-mode-diff-backup): Call cvs-backup-diffable + correctly. + + * pcl-cvs Id 64: elib substitute + * dist-makefile (install): Warn about Elib. + * pcl-cvs.texinfo: Talk about Elib. + + * pcl-cvs Id 50: Committing the *commit* buffer twice. + * pcl-cvs.el (cvs-edit-done): Report an error if cvs-commit-list + is empty, and empty it when the commit is done. + + * pcl-cvs Id 56: '-d <CVSROOT>' support. + * pcl-cvs.el (cvs-cvsroot): New variable. + * pcl-cvs.el (cvs-do-update, all callers of cvs-execute-list): Use + it everywhere CVS is called, to override CVSROOT. + * pcl-cvs.texinfo (Customization): Document it. + +Thu Apr 1 00:34:55 1993 Per Cederqvist (ceder@lysator.liu.se) + + * pcl-cvs.el (cvs-retrieve-revision-to-tmpfile): Exit status nil + from call-process means everything was successful in some Emacs + versions. + + * pcl-cvs.el (cvs-mode-map): Bind "q" to bury-buffer. + * pcl-cvs.texinfo: Document it. + +Thu Mar 11 00:05:03 1993 Per Cederqvist (ceder@lysator.liu.se) + + * Release 1.03-Emerge (not released). + + * Makefile (pcl-cvs-$(VER)): Don't includ elib-dll-debug.el in the + distribution. (It's included as elib/dll-debug.el). + + * pcl-cvs.el (cvs-mode): Document the "e" key (cvs-mode-emerge). + +Tue Mar 9 00:02:57 1993 Per Cederqvist (ceder@lysator.liu.se) + + * pcl-cvs.texinfo (Emerge): New node. + + * pcl-cvs.el (cvs-kill-buffer-visiting): New function. + + * pcl-cvs.el (cvs-mode-emerge): Handle Conflict and Merged files. + + * pcl-cvs.el (cvs-retrieve-revision-to-tmpfile): Handle any revision. + + * pcl-cvs.el (cvs-fileinfo-*): Store base-revision instead of + backup-file. + + * pcl-cvs.el (cvs-backup-diffable): The file is only diffable if + the backup file is readable. + + * pcl-cvs.el (cvs-mode-map): Bind "e" to cvs-mode-emerge instead + of cvs-mode-find-file (which is anyhow bound to "f"). + +Mon Mar 8 23:06:52 1993 Per Cederqvist (ceder@lysator.liu.se) + + * pcl-cvs.el (cvs-mode-emerge): New function. Currently only + handles emerge of Modified files. + + * pcl-cvs.el (cvs-retrieve-revision-to-tmpfile): New function. + +Sun Jan 24 20:07:18 1993 Per Cederqvist (ceder@lysator.liu.se) + + * elib-dll-debug.el: Moved to elib. + +Mon Jan 18 00:35:59 1993 Per Cederqvist (ceder@mauritz) + + * pcl-cvs.el (cvs-do-update): Added a probably unnecessary sit-for. + + * Release 1.03-Elib-0.05.1 (not released). + + * Elib 0.05 compatibility: + * elib-dll-debug.el, pcl-cvs-buffer.el, test-dll.el: Fix the + require strings. + * pcl-cvs.el (cvs-pp): Insert the string. + + * Release 1.03-Elib-0.05 (not released). + + * elib: New directory, containing the parts of elib that are + required for pcl-cvs. Changes to the files in that directory + that are present in Elib are documented in the ChangeLog of + Elib, not here. + * Makefile (pcl-cvs-$(VER)): Copy the new dir to the distribution. + * dist-makefile (ELFILES, ELCFILES): Don't include the Elib files. + +Fri Jan 8 02:43:49 1993 Per Cederqvist (ceder@konrad) + + * pcl-cvs.el (cvs-mode-map): Bind "e" to cvs-mode-find-file, like + in dired. + +Sun Jan 3 23:25:13 1993 Per Cederqvist (ceder@konrad) + + * elib-dll.el, elib-node.el, cookie.el: Moved to the elib package. + Pcl-cvs now requires elib. + +Tue Dec 29 22:06:57 1992 Per Cederqvist (ceder@konrad) + + * pcl-cvs.el: Tracked the latest (last?) rename of all functions + in cookie.el. + +Thu Sep 24 00:29:16 1992 Per Cederqvist (ceder@robert) + + * pcl-cvs.texinfo (Archives): This version is not distributed with + CVS 1.3, so don't claim that it is. + +Fri Aug 21 15:17:08 1992 Per Cederqvist (ceder@maskros) + + * pcl-cvs.el (cvs-parse-stderr): Fixed two "(set head" that should + be "(setq head". + +Thu Aug 20 05:53:58 1992 Per Cederqvist (ceder@robin) + + * cookie.el: Changes to this file is documented in the ChangeLog + of elib in the future. + +Tue Aug 18 03:30:28 1992 Per Cederqvist (ceder@robin) + + * pcl-cvs.el: Don't use cookie-last-tin (which no longer exists). + + * cookie.el: Use prefix cookie:: for internal functions. + + * cookie.el: (cookie:enter-after, cookie:enter-before, + cookie:nth-cookie): Implemented. + * cookie.el: No longer define (impl). + + * cookie.el: More renames: + cookie:next-cookie -> cookie:goto-next-tin + cookie:previous-cookie -> cookie:goto-previous-tin + tin-next -> cookie:next-tin + tin-previous -> cookie:previous-tin + tin-nth -> cookie:nth-tin + tin-delete -> cookie:delete-tin + cookie:collect -> cookie:collect-cookies + cookie:tin-collect -> cookie:collect-tins + (new) -> cookie:tin-collect-cookies + (new) -> cookie:tin-collect-tins + cookie:refresh -> cookie:refresh-all + tin-invalidate-tins -> cookie:invalidate-tins + +Mon Aug 17 01:39:49 1992 Per Cederqvist (ceder@robin) + + * cookie.el (cookie:set-buffer-bind-dll-let*): New macro. Used in + many places instead of cookie:set-buffer-bind-dll. + * cookie.el (cookie:set-buffer-bind-dll): Renamed the macro + cookie:set-buffer to this. + + * pcl-cvs.el (cvs-use-temp-buffer): Set default-directory. + +Sun Aug 16 20:51:30 1992 Per Cederqvist (ceder@robin) + + * pcl-cvs.el (cvs-add-sub): Fixed call to cvs-add-file-update-buffer. + +Sat Aug 8 20:28:21 1992 Per Cederqvist (ceder@robin) + + * Release 1.03-Cookie-II (not released). + + * pcl-cvs.el (cvs-mode-diff-cvs): Don't care about the exit status + from ``cvs diff''. + + * pcl-cvs.el (cvs-mode): Document cvs-mode-undo-local-changes. + * pcl-cvs.el (cvs-diffable): New function. + + * pcl-cvs.el: Use the new cookie package. + * pcl-cvs.el (cvs-cookie-handle): New variable. + * pcl-cvs.el (cvs-do-update): User the new cookie:create + interface, and cookie:clear if the buffer already existed. Make + the buffer read-only. + * pcl-cvs.el (cvs-mode-next-line, cvs-mode-previous-line): New + functions (used instead of cookie:next-cookie and + cookie:previous-cookie). + + * cookie.el: Major redesign. The handle that is passed to all + cookie functions is now a new datatype, and not the buffer that + the cookies resides in. This way it is possible to have more than + one set of cookies in a buffer. Things that used to be + buffer-local variables are now fields in the handle data type. + cookie-last-tin is no longer available. + * cookie.el (cookie:create): The buffer is not cleared, nor set to + be read-only. + * cookie.el (cookie:next-cookie, cookie:previous-cookie): Since + the first argument is now a handle and not a buffer, these can no + longer be called interactively. You have to write a small wrapper + about them. + * cookie.el (cookie:buffer): New function. + +Tue Aug 4 03:02:25 1992 Per Cederqvist (ceder@robert) + + * pcl-cvs.texinfo (Bugs): Renamed "Reporting bugs and ideas" to + "Bugs" and added a table of known bugs/FAQ:s. + +Mon Aug 3 00:19:39 1992 Per Cederqvist (ceder@robert) + + * pcl-cvs.el, pcl-cvs.texinfo: Big Renaming Time! + The commands that operate in the *cvs* buffer: + cvs-add-change-log-entry-other-window -> cvs-mode-add-change-log-entry-other-window + cvs-mark-all-files -> cvs-mode-mark-all-files + cvs-revert-updated-buffers -> cvs-mode-revert-updated-buffers + cvs-undo-local-changes -> cvs-mode-undo-local-changes + cvs-unmark-up -> cvs-mode-unmark-up + cvs-acknowledge -> cvs-mode-acknowledge + cvs-unmark-all-files -> cvs-mode-unmark-all-files + cvs-add -> cvs-mode-add + cvs-diff-backup -> cvs-mode-diff-backup + cvs-commit -> cvs-mode-commit + cvs-diff-cvs -> cvs-mode-diff-cvs + cvs-find-file -> cvs-mode-find-file + cvs-update-no-prompt -> cvs-mode-update-no-prompt + cvs-ignore -> cvs-mode-ignore + cvs-log -> cvs-mode-log + cvs-mark -> cvs-mode-mark + cvs-find-file-other-window -> cvs-mode-find-file-other-window + cvs-remove-file -> cvs-mode-remove-file + cvs-status -> cvs-mode-status + cvs-remove-handled -> cvs-mode-remove-handled + cvs-unmark -> cvs-mode-unmark + + * pcl-cvs.el (cvs-cvs-diff-flags): Variable deleted. + * pcl-cvs.el (cvs-diff-cvs): Use cvs-diff-flags instead. + * pcl-cvs.texinfo (Customization): Update the doc. + + * pcl-cvs.el (cvs-diff-cvs): Handle exit status 0 (no diffs), 1 + (diffs) and other (error). + * pcl-cvs.el (cvs-execute-list): Add support for this kind of + thing. + + * Revert buffers for committed files: + * pcl-cvs.el (cvs-auto-revert-after-commit): New variable. + * pcl-cvs.texinfo (Committing changes, Customization): Document + it. + * pcl-cvs.el (cvs-after-commit-function): New function. + + * pcl-cvs.el (cvs-execute-list): Return the exit status or nil. + * pcl-cvs.el (cvs-edit-done, cvs-diff-cvs, cvs-remove-file, + cvs-undo-local-changes, cvs-add, cvs-status, cvs-log): Use the + exit status to generate an error message. + + + * pcl-cvs.el (cvs-do-update): It should be "cvs -n update -l", not + "cvs -l update -n". Put the -n and/or -l in the message that is + displayed in the *cvs* buffer during the update. + +Sat Aug 1 00:55:49 1992 Per Cederqvist (ceder@robert) + + * cookie.el (cookie-sort): New function. + + * cookie.el (cookie-clear): Rewritten. No longer clears all local + variables. + +Tue Jul 28 17:21:17 1992 Per Cederqvist (ceder@robin) + + * pcl-cvs.el (cvs-parse-stderr): Try to handle the output from RCS + when it is compiled without DIFF3_BIN and a conflict occurs. + + * pcl-cvs.texinfo (Getting Started): Fixed typo. + + * pcl-cvs-startup.el (cvs-update-other-window): Make the autoload + be interactive. + +Mon Jul 27 19:36:40 1992 Per Cederqvist (ceder@robin) + + * pcl-cvs.el (cvs-revert-updated-buffers, cvs-revert-fileinfo): + New functions. + * pcl-cvs.texinfo (Reverting your buffers): Document it. + + * pcl-cvs.el (cvs-fileinfo->full-path): New function. + * pcl-cvs.el (cvs-full-path): Use it. + + * cookie.el (cookie-map, cookie-map-reverse): Better doc- + string. Removed the unused local variable 'result'. + + * compile-all.el: Renamed elib-files to files-to-compare. + * compile-all.el (compile-pcl-cvs): Bind load-path in a let + statement instead of globally. + +Thu Jul 23 19:02:41 1992 Per Cederqvist (ceder@robin) + + * pcl-cvs.el (cvs-do-update): Check that CVSROOT is set. + * pcl-cvs.el (cvs-diff-cvs): Check that cvs-cvs-diff-flags is a + list. + * pcl-cvs.el (cvs-diff-backup): Check that cvs-diff-flags is a + list. + +Tue Jul 21 11:27:39 1992 Per Cederqvist (ceder@robin) + + * pcl-cvs.el (cvs-parse-error): Make the *cvs* buffer writeable + before trying to write the email message. Require sendmail before + trying to switch to mail-mode. + + * pcl-cvs.el (cvs-do-update): Check that cvs-program exists. + + * pcl-cvs.el (cvs-skip-line): Fixed bracketing error. + +Mon Jul 20 10:31:51 1992 Per Cederqvist (ceder@robin) + + * Release 1.03. + + * pcl-cvs.el, cookie.el: Indentation fixes. + + * Makefile (pcl-cvs-$(VER)): Include NEWS in the distribution. + + * pcl-cvs.el (cvs-rm-program): Deleted. + * pcl-cvs.el (cvs-rmdir-program, cvs-lock-file): New variables. + + * Handle lock files in a nicer way: + * pcl-cvs.el (cvs-update-filter, cvs-delete-lock, + cvs-lock-file-p): New functions. + * pcl-cvs.el (cvs-do-update, cvs-sentinel): Redirect stdout to the + temporary file, not stderr. Use cvs-update-filter. + * pcl-cvs.el (cvs-parse-update): New arguments. + * pcl-cvs.el (cvs-parse-buffer): Renamed to cvs-parse-update. + * pcl-cvs.el (cvs-stderr-file): Renamed to cvs-stdout-file. + * pcl-cvs.texinfo (Miscellaneous commands, Updating the + directory): Document cvs-delete-lock. + + * pcl-cvs.el (cvs-mode): Don't reset buffer-read-only. + + * pcl-cvs.el (cvs-find-file-other-window): Don't save-some-buffers. + +Thu Jul 16 00:19:58 1992 Per Cederqvist (ceder@robin) + + * pcl-cvs.el, test-cookie-el: Use the new names from cookie.el. + + * cookie.el: Big Renaming Time! + External functions: + cookie-next -> tin-next + cookie-previous -> tin-previous + cookie-nth -> tin-nth + cookie-delete -> tin-delete + cookie-filter-tins -> tin-filter + cookie-get-selection -> tin-get-selection + cookie-start-marker -> tin-start-marker + cookie-end-marker -> tin-end-marker + cookie-invalidate-tins -> tin-invalidate-tins + cookie-collect-tins -> tin-collect + cookie-collect-cookies -> cookie-collect + Internal functions: + cookie-create-tin -> cookie-create-wrapper + cookie-tin-start-marker -> cookie-wrapper-start-marker + cookie-tin-cookie-safe -> cookie-wrapper-cookie-safe + cookie-tin-cookie -> cookie-wrapper-cookie + set-cookie-tin-start-marker -> cookie-wrapper-set-start-marker + set-cookie-tin-cookie -> cookie-wrapper-set-cookie + cookie-tin-p -> cookie-wrapper-p + cookie-create-tin-and-insert -> cookie-create-wrapper-and-insert + + * pcl-cvs.el (cvs-find-file, cvs-find-file-other-window): Signal + an appropriate error message if the *cvs* buffer is empty. + + * cookie.el (cookie-create): Make the buffer read-only. + * cookie.el (cookie-create-tin-and-insert, cookie-refresh, + cookie-delete-tin-internal, cookie-refresh-tin): Bind + buffer-read-only to nil while changing the contents of + the buffer. + + * pcl-cvs.el (cvs-byte-compile-files): New function. + * pcl-cvs.texinfo (Miscellaneous commands): Document it. + + * pcl-cvs.el (cvs-diff-ignore-marks): New variable. + * pcl-cvs.el (cvs-diff-cvs, cvs-diff-backup): Don't consider + marked files to be selected if a prefix argument is given XOR the + variable cvs-diff-ignore-marks is non-nil. + * pcl-cvs.el (cvs-get-marked): New optional argument `ignore-marks'. + * pcl-cvs.texinfo (Customization, Viewing differences): Document + this behaviour. + + * pcl-cvs.el (cvs-undo-local-changes): New function. + * pcl-cvs.texinfo (Undoing changes): Document + cvs-undo-local-changes. + * pcl-cvs.el (cvs-mode-map): cvs-unmark-all-files moved from "U" + to "ESC DEL". cvs-undo-local-changes bound to "U". + * pcl-cvs.texinfo (Marking files): Document ESC DEL. + + * pcl-cvs.el (cvs-skip-line): New arguments. All callers updated. + Now calls cvs-parse-error if a parse error occurs. + * pcl-cvs.el (cvs-parse-error): New function that creates a bug + report. + * pcl-cvs.el (cvs-parse-stderr, cvs-parse-stdout): New arguments. + The only caller (cvs-parse-buffer) updated. Call cvs-parse-error + in case of parse error. + + * pcl-cvs.el (pcl-cvs-version): New variable. + + * cookie.el (cookie-create): Kill all local variables in the buffer. + +Fri Jul 10 11:17:40 1992 Per Cederqvist (ceder@robin) + + * Release 1.03beta1. + +Thu Jul 9 03:12:00 1992 Per Cederqvist (ceder@robin) + + * pcl-cvs.el (cvs-update-running): New variable. + * pcl-cvs.el (cvs-do-update): Use it instead of the previous local + variable cvs-process (that no longer exists). Make sure that only + one `cvs update' runs at any given moment. + * pcl-cvs.el (cvs-sentinel): Reset cvs-update-running when the + update process exits. + + * pcl-cvs.el (cvs-update): Switch to the *cvs* buffer. + * pcl-cvs.el (cvs-update-other-window): New function. + * pcl-cvs-startup.el (cvs-update-other-window): Added a autoload + for it. + * pcl-cvs.el (cvs-do-update): Don't pop up any buffer in a window + - let cvs-update or cvs-update-other-window handle that. Also + don't kill the *cvs* buffer, but rather insert a "Running cvs..." + message into it. + * pcl-cvs.el (cvs-parse-buffer): Don't change the window + configuration. + + * pcl-cvs.el (cvs-create-fileinfo, cvs-pp, cvs-fileninfo->type): + New type for a fileinfo: MESSAGE. + + * pcl-cvs.el (cvs-cvs-buffer): Deleted the variable. Use + cvs-buffer-name instead. (I no longer have any plans to allow more + than one cvs update to run at the same time - things only get + confusing). Changed all places where cvs-cvs-buffer was used. + + * pcl-cvs.el: Take care of update programs (the -u option in the + modules file): + * pcl-cvs.el (cvs-update-prog-output-skip-regexp): New variable. + * pcl-cvs.el (cvs-parse-stdout): Skip output from the update + program (using cvs-update-prog-output-skip-regexp). + * pcl-cvs.texinfo (Future enhancements): Document that the + solution is not as good as it should be. + * pcl-cvs.texinfo (Customization): Document the variable. + +Wed Jul 8 20:29:44 1992 Per Cederqvist (ceder@robin) + + * pcl-cvs.el (cvs-do-update): Check that this-dir really exists + and is a directory, and that this-dir/CVS exists and is a + directory. + +Tue Jul 7 01:02:24 1992 Per Cederqvist (ceder@robin) + + * pcl-cvs.texinfo (Customization): Document TMPDIR. + + * This chunk of modifications should make it possible to run + pcl-cvs on hosts that do not line-buffer stdout (such as + DECstation). They work by diverting stdout and stderr from + `cvs update' and later sorting them together. + * pcl-cvs.el (cvs-parse-stderr): Don't fail to parse conflict + data. + * pcl-cvs.el (cvs-remove-stdout-shadows, cvs-shadow-entry-p): New + functions. + * pcl-cvs.el (cvs-parse-buffer): Use it. + * pcl-cvs.el (cvs-remove-empty-directories): New function. + * pcl-cvs.el (cvs-remove-handled, cvs-parse-buffer): Use it. + * pcl-cvs.el (cvs-get-current-dir): New argument ROOT-DIR. All + calls to cvs-get-current-dir updated. + * pcl-cvs.el (cvs-do-update): Allocate a tmp file. Use cvs-shell + (typically /bin/sh) to redirect stderr from CVS to the tmp file. + * pcl-cvs.el (cvs-sentinel): Handle the tmp file. Remove it when + it is parsed. + * pcl-cvs.el (cvs-parse-buffer): New argument STDERR-BUFFER. All + calls to cvs-parse-buffer updated. Rewritten to handle the + separation of stderr and stdout. + * pcl-cvs.el (cvs-shell, cvs-stderr-file): New variables. + * pcl-cvs.el (cvs-compare-fileinfos, cvs-parse-stderr, + cvs-parse-stdout): New functions. + + * pcl-cvs.el (cvs-parse-buffer): Some modifications for output + from RCS 5.6. + +Tue Apr 7 09:11:27 1992 Per Cederqvist (ceder@leopold) + + * Release 1.02. + + * pcl-cvs.el (cvs-diff-backup, cvs-edit-done, cvs-status): Call + save-some-buffers. + + * pcl-cvs.el (cvs-diff-backup-extractor): Fixed syntax error. + + * Makefile, README, compile-all.el, dist-makefile, pcl-cvs.el, + pcl-cvs.texinfo (XXRELEASEXX): A magic string that is substituted + for the current release number when a distribution is made. + (Release 1.01 says that it is release 1.00). + + * pcl-cvs.el (cvs-find-file): Added missing pair of parenthesis. + +Mon Mar 30 14:25:26 1992 Per Cederqvist (ceder@leopold) + + * Release 1.01. + + * pcl-cvs.el (cvs-parse-buffer): The message when waiting for a + lock has been changed. + +Sun Mar 29 05:29:57 1992 Per Cederqvist (ceder@leopold) + + * Release 1.00. + + * pcl-cvs.el (cvs-do-update, cvs-sentinel, cvs-parse-buffer): + Major rewrite of buffer and window selection and handling. + The *cvs* buffer is now killed whenever a new "cvs update" is + initiated. The -update buffer is replaced with the *cvs* + buffer when the update is completed. + +Sat Mar 28 21:03:05 1992 Per Cederqvist (ceder@robin) + + * pcl-cvs.el (cvs-delete-unused-temporary-buffers): Fixed it. + + * pcl-cvs.el (cvs-auto-remove-handled): New variable. + * pcl-cvs.el (cvs-edit-done): Use it. + * pcl-cvs.texinfo (Customization, Removing handled entries): + Document it. + + * pcl-cvs.el (cvs-mode): Turn of the undo feature. It really + isn't useful in a cookie buffer... + + * pcl-cvs.el (cvs-edit-done): Committing a file now looks more + like diffing a file. The window handling is better. + * pcl-cvs.el (cvs-use-temp-buffer): The &optional switch is no + longer needed. + +Mon Mar 23 00:20:33 1992 Per Cederqvist (ceder@robin) + + * Release 0.97. + + * pcl-cvs.el (default-directory): Make sure it always ends in a + slash. fileinfo->dir does NOT end in a slash, and I had forgotten + to call file-name-as-directory in various places. + + * pcl-cvs.el (cvs-diff-backup-extractor): Signal an error if a + fileinfo without backup file is given. + + * pcl-cvs.el (cvs-mode): Added documentation. + + * pcl-cvs.el (cvs-execute-list): Fix the order of files in the + same directory. + + * pcl-cvs.el (cvs-log-flags, cvs-status-flags): New variables. + * pcl-cvs.el (cvs-log, cvs-status): Use them. + * pcl-cvs.texinfo (Customization): Document them. + + * pcl-cvs.el (cvs-diff-backup): Filter non-backup-diffable files + at an earlier stage, like cvs-commit does. + + * pcl-cvs.el (cvs-diff-flags): New variable. + * pcl-cvs.el (cvs-diff-backup): Use it. + * pcl-cvs.texinfo (Customization): Document it. + + * pcl-cvs.el (cvs-execute-single-file-list): Remove &rest before + last argument. No callers needed updating. + + * pcl-cvs.el (cvs-execute-list): Remove the &rest before the last + argument (constant-args). Update all callers of cvs-execute-list + to use the new calling convention. + * pcl-cvs.el (cvs-cvs-diff-flags): Now a list of strings instead + of a string. + * pcl-cvs.texinfo (Customization): Document the change to + cvs-cvs-diff-flags. + + * Release 0.96. + + * pcl-cvs.el (cvs-cvs-diff-flags): New variable. + * pcl-cvs.el (cvs-diff-cvs): Use it. + * pcl-cvs.texinfo (Customization, Viewing differences): Document it. + + * pcl-cvs.el (cvs-use-temp-buffe): Don't switch to the temporary + buffer. Use display-buffer and set-buffer instead. This way + cvs-log, cvs-status, cvs-diff-cvs and friends don't select the + temporary buffer. The cursor will remain in the *cvs* buffer. + +Sun Mar 22 21:50:18 1992 Per Cederqvist (ceder@robin) + + * pcl-cvs.el (cvs-find-file, cvs-find-file-other-window): Don't + prompt when reading in a directory in dired. + + * Makefile (pcl-cvs-$(VER)): Include pcl-cvs-startup.el in the + distribution. + + * dist-makefile (pcl-cvs.dvi): Don't fail even if texindex does + not exist. + + * pcl-cvs.texinfo (@setchapternewpage): Changed from 'off' to 'on'. + * pcl-cvs.texinfo (Variable index): Joined into function index. + * pcl-cvs.texinfo (Key index): add a description about the key. + * pcl-cvs.texinfo: Many other small changes. + +Wed Mar 18 01:58:38 1992 Per Cederqvist (ceder@leopold) + + * Use GNU General Public License version 2. + diff --git a/contrib/cvs/tools/pcl-cvs/INSTALL b/contrib/cvs/tools/pcl-cvs/INSTALL new file mode 100644 index 0000000..56ff80d --- /dev/null +++ b/contrib/cvs/tools/pcl-cvs/INSTALL @@ -0,0 +1,94 @@ +This text is copied from the TeXinfo manual for pcl-cvs. + +Installation of the pcl-cvs program +=================================== + + 1. Possibly edit the file `Makefile' to reflect the situation at your + site. We say "possibly" because the version of pcl-cvs included + with CVS uses a configuration mechanism integrated with the overall + mechanisms used by the CVS build and install procedures. Thus the + file `Makefile' will be generated automatically from the file + `Makefile.in', and it should not be necessary to edit it further. + + If you do have to edit the `Makefile', the only things you have to + change is the definition of `lispdir' and `infodir'. The elisp + files will be copied to `lispdir', and the info file(s) to + `infodir'. + + 2. Configure pcl-cvs.el + + There are a couple of pathnames that you have to check to make + sure that they match your system. They appear early in the file + `pcl-cvs.el'. + + *NOTE:* If your system is running emacs 18.57 or earlier you MUST + uncomment the line that says: + (setq delete-exited-processes nil) + + Setting `delete-exited-processes' to `nil' works around a bug in + emacs that causes it to dump core. The bug was fixed in emacs + 18.58. + + 3. Release 1.05 and later of pcl-cvs requires parts of the Elib + library, version 1.0 or later. Elib is available via anonymous + ftp from prep.ai.mit.edu in `pub/gnu/elib-1.0.tar.gz', and from a + lot of other sites that mirror prep. Get Elib, and install it, + before proceeding. + + *NOTE:* The version of pcl-cvs included with CVS includes a copy + of Elib in the sub-directory `elib' under the `contrib/elib' + directory. + + 4. Type `make install' in the source directory. This will + byte-compile all `.el' files and copy the `*.elc' files into the + directory you specified in step 1. + + If you want to install the `*.el' files too, you can type `make + install-el' to do so. + + If you only want to create the compiled elisp files, but don't + want to install them, you can type `make' without parameters. + + 5. Edit the file `default.el' in your emacs lisp directory (usually + `/usr/gnu/lib/emacs/site-lisp' or something similar) and enter the + contents of the file `pcl-cvs-startup.el' into it. It contains a + couple of `auto-load's that facilitates the use of pcl-cvs. + + +Installation of the on-line manual. +=================================== + + 1. Create the info file(s) `pcl-cvs.info*' from `pcl-cvs.texinfo' by + typing `make info'. If you don't have the program `makeinfo' you + can get it by anonymous ftp from e.g. `prep.ai.mit.edu' as + `pub/gnu/texinfo-3.7.tar.gz' (there might be a newer version there + when you read this). + + 2. Install the info file(s) `pcl-cvs.info*' into your standard `info' + directory. You should be able to do this by typing `make + install-info'. + + 3. Edit the file `dir' in the `info' directory and enter one line to + contain a pointer to the info file(s) `pcl-cvs.info*'. The line + can, for instance, look like this: + + * Pcl-cvs: (pcl-cvs). An Emacs front-end to CVS. + +How to make typeset documentation from pcl-cvs.texinfo +====================================================== + + If you have TeX installed at your site, you can make a typeset manual +from `pcl-cvs.texinfo'. + + 1. Run TeX by typing ``make pcl-cvs.dvi''. You will not get the + indices unless you have the `texindex' program. + + 2. Convert the resulting device independent file `pcl-cvs.dvi' to a + form which your printer can output and print it. If you have a + postscript printer there is a program, `dvi2ps', which does. There + is also a program which comes together with TeX, `dvips', which + you can use. + + +-- +#ident "@(#)cvs/contrib/pcl-cvs:$Name: $Id$" diff --git a/contrib/cvs/tools/pcl-cvs/Makefile.in b/contrib/cvs/tools/pcl-cvs/Makefile.in new file mode 100644 index 0000000..ca881d0 --- /dev/null +++ b/contrib/cvs/tools/pcl-cvs/Makefile.in @@ -0,0 +1,238 @@ +# Makefile for pcl-cvs, an Emacs interface to CVS. +# NOTE: pcl-cvs requires Elib to run. See ../../contrib/elib/. + +# +#ident "@(#)original: dist-makefile,v 1.19 1993/05/31 22:43:45 ceder Exp " +# +#ident "@(#)elisp/pcl-cvs:$Name: $:$Id: Makefile.in,v 1.3 1996/04/15 06:33:20 kfogel Exp $" +# +# Makefile for pcl-cvs release 1.05-CVS-$Name: $. +# Copyright (C) 1992, 1993 Per Cederqvist +# +# 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 of the License, 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. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +SHELL = /bin/sh + +#### Start of system configuration section. #### + +srcdir = @srcdir@ +top_srcdir = @top_srcdir@ +VPATH = @srcdir@ + +prefix = @prefix@ +exec_prefix = @exec_prefix@ + +# Where to put the system-wide supplementary files +libdir = $(prefix)/lib + +# Where to put the Info files +infodir = $(prefix)/info + +# Where to put the manual pages. +mandir = $(prefix)/man + +# Used to batch-byte-compile files. +EMACS = emacs +# compile with noninteractive environment +BATCHFLAGS = -batch + +# This is the directory in which the ELCFILES will be installed. +lispdir = $(libdir)/emacs/site-lisp + +#### End of system configuration section. #### + +srcdir = @srcdir@ +top_srcdir = @top_srcdir@ +VPATH = @srcdir@ + +# Just in case... +SHELL = /bin/sh +@SET_MAKE@ + +DISTFILES = \ + .cvsignore ChangeLog INSTALL Makefile.in NEWS README \ + ${ELFILES} \ + pcl-cvs.texinfo texinfo.tex + + +# OBJDIR_DISTFILES used to include the byte-compiled elisp files, but +# this seems wrong because the person building the dist cannot have +# made the appropriate site-specific modifications to pcl-cvs.el. +# Therefore, I've taken the .elc files out of OBJDIR_DISTFILES for +# now, pending the Right Solution to this problem (which probably +# involves moving the site-specific modification section of pcl-cvs.el +# to a separate file and having autoconf generate as much of the file +# as possible). -Karl +# +# OBJDIR_DISTFILES = $(ELCFILES) pcl-cvs.aux pcl-cvs.ps +OBJDIR_DISTFILES = pcl-cvs.aux pcl-cvs.ps + + +# files that contain key macro definitions. almost everything +# depends on them because the byte-compiler inlines macro +# expansions. everything also depends on the byte compiler +# options file since this might do odd things like turn off +# certain compiler optimizations. +CORE = + +ELFILES = pcl-cvs.el pcl-cvs-lucid.el pcl-cvs-startup.el +ELCFILES = pcl-cvs.elc pcl-cvs-lucid.elc +INFOFILES = pcl-cvs.info* +TEXTMPS = pcl-cvs.aux pcl-cvs.log pcl-cvs.toc pcl-cvs.dvi pcl-cvs.cp \ + pcl-cvs.fn pcl-cvs.vr pcl-cvs.tp pcl-cvs.ky pcl-cvs.pg \ + pcl-cvs.cps pcl-cvs.fns pcl-cvs.kys pcl-cvs.pgs pcl-cvs.tps \ + pcl-cvs.vrs + +# Use cp if you don't have install. +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ + +MAKEINFO = makeinfo + +SET_TEXINPUTS = TEXINPUTS=.:$(srcdir):$$TEXINPUTS + +# Don Knuth's TeX formatter +TEX = tex + +# auxiliary program for sorting Texinfo indices +TEXINDEX = texindex + +DVIPS = dvips +DVIPSFLAGS = + +# CYGNUS LOCAL: install does not depend on info +all: $(ELCFILES) # info +.PHONY: all + +.SUFFIXES: .el .elc +# We copy the .el file to the build dir--is there a cleaner way to get +# emacs to compile the .el file from srcdir and put the .elc in the build dir? +# (that is also why we have separate rules for pcl-cvs.elc and +# pcl-cvs-lucid.elc rather than just using a .el.elc rule). +pcl-cvs.elc: pcl-cvs.el + @echo "You can probably ignore free variable and unknown function warnings..." + if test -f pcl-cvs.el; then \ + : OK, we are building in srcdir ; \ + else \ + ln $(srcdir)/pcl-cvs.el . ; \ + fi + $(EMACS) $(BATCHFLAGS) -f batch-byte-compile pcl-cvs.el +pcl-cvs-lucid.elc: pcl-cvs-lucid.el + @echo "You can probably ignore free variable and unknown function warnings..." + if test -f pcl-cvs-lucid.el; then \ + : OK, we are building in srcdir ; \ + else \ + ln $(srcdir)/pcl-cvs-lucid.el . ; \ + fi + $(EMACS) $(BATCHFLAGS) -f batch-byte-compile pcl-cvs-lucid.el + +check installcheck: + @echo "$@ not supported in this makefile..." +.PHONY: check installcheck + +# CYGNUS LOCAL: install does not depend on install-info +install: install-elc # install-info install-el + +install-el: $(ELFILES) + for i in $(ELFILES) ; do \ + $(INSTALL_DATA) $$i $(lispdir)/$$i ; \ + done + +install-elc: $(ELCFILES) + for i in $(ELCFILES) ; do \ + $(INSTALL_DATA) $$i $(lispdir)/$$i ; \ + done + +install-info: info + test -f pcl-cvs.info || cd $(srcdir); \ + for i in *.info* ; do \ + $(INSTALL_DATA) $$i $(infodir)/$$i ; \ + done + +.PHONY: install install-el install-elc install-info + +# mkinstalldirs isn't supported for CVS yet.... +installdirs: $(top_srcdir)/mkinstalldirs + $(SHELL) $(top_srcdir)/mkinstalldirs $(lispdir) $(infodir) +.PHONY: installdirs + +uninstall: + @echo "$@ not yet supported in this makefile..." +.PHONY: uninstall + +info: pcl-cvs.info +.PHONY: info + +pcl-cvs.info: pcl-cvs.texinfo + $(MAKEINFO) ${srcdir}/pcl-cvs.texinfo -o pcl-cvs.info + +dvi: pcl-cvs.dvi +.PHONY: dvi + +# this mess seems to be necessary to make the index right... +pcl-cvs.dvi pcl-cvs.aux: pcl-cvs.texinfo + $(SET_TEXINPUTS) $(TEX) $(srcdir)/pcl-cvs.texinfo + $(SET_TEXINPUTS) $(TEX) $(srcdir)/pcl-cvs.texinfo + -$(TEXINDEX) pcl-cvs.cp pcl-cvs.fn pcl-cvs.vr pcl-cvs.tp pcl-cvs.ky \ + pcl-cvs.pg + $(SET_TEXINPUTS) $(TEX) $(srcdir)/pcl-cvs.texinfo + +pcl-cvs.ps: pcl-cvs.dvi + $(DVIPS) $(DVIPSFLAGS) pcl-cvs.dvi -o pcl-cvs.ps + +mostlyclean clean: + rm -f *~ core $(ELCFILES) $(INFOFILES) $(TEXTMPS) +.PHONY: mostlyclean clean + +distclean: clean + rm -f Makefile tags TAGS +.PHONY: distclean + +realclean maintainer-clean: distclean + rm -f pcl-cvs.info* pcl-cvs.ps +.PHONY: realclean maintainer-clean + +# you can't use ctags for lisp... +tags TAGS: + etags *.el +.PHONY: tags + +ls: + @echo $(DISTFILES) +.PHONY: ls + +dist-dir: ${OBJDIR_DISTFILES} ${DISTFILES} pcl-cvs.info + mkdir ${DISTDIR} + for i in ${DISTFILES}; do \ + ln $(srcdir)/$${i} ${DISTDIR}; \ + done + ln ${OBJDIR_DISTFILES} ${DISTDIR} + if [ -f pcl-cvs.info-1 ]; \ + then ln -f pcl-cvs.info-* ${DISTDIR}; \ + else : Pacify Ultrix sh; \ + fi +.PHONY: dist-dir + +subdir = tools/pcl-cvs +Makefile: ../../config.status Makefile.in + cd ../.. && CONFIG_FILES=$(subdir)/$@ CONFIG_HEADERS= ./config.status + +# CYGNUS LOCAL: don't depend on auto-re-config +#../config.status: ../configure +# cd .. ; $(SHELL) config.status --recheck + +# CYGNUS LOCAL: don't depend on auto-re-config +#../configure: ../configure.in +# cd $(top_srcdir) ; autoconf diff --git a/contrib/cvs/tools/pcl-cvs/NEWS b/contrib/cvs/tools/pcl-cvs/NEWS new file mode 100644 index 0000000..48b0b66 --- /dev/null +++ b/contrib/cvs/tools/pcl-cvs/NEWS @@ -0,0 +1,149 @@ +This is the NEWS file for pcl-cvs, an Emacs elisp front-end to CVS. + +User-visible changes in the un-official CVS release of pcl-cvs +from the official 1.05 release to 1.05-CVS-$Name: $: + +* Support for using ChangeLog files, including hooks to automatically + guess CVS log entries from ChangeLog contents. + +* Support for client/server CVS (versions 1.5 through 1.7 and newer). + +* New commands for tagging files and directory trees (still needs to + be made to run in the background). + +* Better support for recognizing and handling unknown directories. + +* An attempt at new ediff and emerge interfaces (still needs work!), + including attempts to make vendor-branch merging work. + +* In a possibly misguided attempt to make it easier to see the effects + of changes that affect several files, diff output is now stored in a + uniqe buffer for each file. + +* Some commands now have default flags (cvs-*-flags). + +* Proper quoting of command line arguments displayed in *cvs-tmp*. + +* More hacking with getting CVSROOT right, though probably all + pointless, since CVS should do the right thing all the time. + +* Elib is back, at least in the CVS distribution. + +* Lots of minor bug fixes, tweaks, cleanup, re-indentation, etc. + +* Some minor tweaks, fixes, re-indentation, etc., in the + documentation. + + +User-visible changes in pcl-cvs from 1.04 to 1.05: + +* Elib is no longer distributed with pcl-cvs. You must get Elib + separately, for instance from ftp.lysator.liu.se in pub/emacs. + +* The Lucid Emacs support works again. + +* A new function, cvs-change-cvsroot, can be used to interactively + switch between CVS repositories. + +* The mode line in the *cvs* buffer now indicates when a "cvs update" + is running. + +* The .cvsignore file is automatically sorted alphabetically (to + reduce the risk of conflicts when two people add different files + simultaneously). This behaviour can be turned off with + cvs-sort-ignore-file. + +* A trailing newline is always added in commit log messages. This + behaviour can be turned off with + cvs-commit-buffer-require-final-newline. + +* This version of pcl-cvs should work together with RCVS. I have not + tested this myself, though. + +* Plus some bug fixes. (Note that the version of cookie.el that is + distributed with pcl-cvs 1.04 contains errors that affects pcl-cvs. + You should get Elib 0.07). + + +User-visible changes in pcl-cvs from 1.03 to 1.04: + +* Support for Emerge. Hitting "e" on a file that is Modified, Merged + or in Conflict will start Emerge, an interactive file merger written + in Emacs Lisp. This requires Emerge version 4. Emerge is not + included in this package. If you can't find it anywhere else, you + can get in from ftp.lysator.liu.se in pub/emacs. This package makes + it a lot easier to resolve conflicts. + +* Emacs will now automatically revert your buffers when the CVS + commands pcl-cvs issues causes the file to change. This automatic + revert never occurs if the buffer contents did not agree with the + file prior to the command. + +* If you are running Lucid GNU Emacs, you will get some fonts and + mouse support. This was contributed from people at Lucid. + +* The variable cvs-cvsroot can be used to select the location if the + repository. You no longer need to exit Emacs, setenv CVSROOT, and + start a new Emacs if you work with multiple repositories. + +* The "q" key can be used to hide the *cvs* buffer. + +* The name of the commands in the *cvs* have changed. If it was called + cvs-foo, it will now be called cvs-mode-foo. See the ChangeLog + entry from Tue Aug 4 03:02:25 1992 for a complete list of changes. + +* The variable cvs-cvs-diff-flags is no longer used. Instead, + cvs-diff-flags is always used. + +* Plus a lot of bug fixes. + + +User-visible changes in pcl-cvs from 1.02 to 1.03: + +* Output from CVS to stdout and stderr is separated and parsed + independently. In that way pcl-cvs should work regardless of + whether stdout is buffered or line-buffered. Pcl-cvs should now + work with CVS 1.3 without modifications on hosts such as + DECstations. + +* Pcl-cvs now fully supports RCS version 5.6 as well as 5.5. + +* New functions: + + + cvs-undo-local-changes ("U") - Undo all your modifications + to a file and get the newest + version from the repository. + + cvs-update-other-window - Similar to cvs-update. + + cvs-byte-compile-files - Byte compile the selected files. + +* cvs-update now displays the *cvs* buffer, which initially contains a + small message ("Running `cvs update' in /foo/bar/gazonk/...") until + the update is ready. The *cvs* buffer no longer pops up when the + update is ready. It often failed to pop up, due to race conditions + that are very hard to solve (and I doubt that they were at all + solvable). + +* cvs-unmark-all-files is moved from "U" to "ESC DEL" to be + "compatible" with dired. + +* cvs-diff ("d") and cvs-diff-backup ("b") can be configured to work + on only the file the cursor is positioned on, and ignore any marked + files. A prefix argument toggles this. + +* Only one `cvs update' can be run at a time. (It was previously + possible to start more than one simultaneously, but pcl-cvs could + not really handle more than one.) + +* Some rudimentary support for programs that CVS runs at update (due + to the -u switch in the modules file). + +* Pcl-cvs now automatically generates a bug report if it can't parse + the output from CVS. + +* The *cvs* buffer is read-only. + +* Pcl-cvs now creates temporary files in $TMPDIR if that environment + variable is set (otherwise it uses /tmp). + +---End of file NEWS--- +#ident "@(#)cvs/contrib/pcl-cvs:$Name: $:$Id: NEWS,v 1.1 1996/04/14 15:17:54 kfogel Exp $" diff --git a/contrib/cvs/tools/pcl-cvs/README b/contrib/cvs/tools/pcl-cvs/README new file mode 100644 index 0000000..9574c14 --- /dev/null +++ b/contrib/cvs/tools/pcl-cvs/README @@ -0,0 +1,25 @@ +This is the readme file for pcl-cvs, release 1.05-CVS-$Name: $. + +Pcl-cvs is a front-end to CVS versions 1.5 through 1.7. It integrates +the most frequently used CVS commands into an emacs interface. + +There may be some configuration that needs to be done in pcl-cvs.el to +get it to work. See the instructions in the file INSTALL. + +Full documentation is in Texinfo format in the file pcl-cvs.texinfo. To +browse this document online, or in the emacs info mode, you will need to +process this file with the makeinfo program, which can also be found on +prep.ai.mit.edu in pub/gnu. + +If you have been using a previous version of pcl-cvs (for instance the +official 1.05 release, or any previous releases) you should read through +the file NEWS to see what has changed. + +This release has been tested under, Emacs 19.28 and Emacs 19.30. + +Per Cederqvist +(updated by Jim Blandy, Greg A. Woods, Karl Fogel) + +-- +#OrigId "@(#) Id: README,v 1.14 1993/05/31 22:43:36 ceder Exp " +#ident "@(#)cvs/contrib/pcl-cvs:$Name: $:$Id: README,v 1.1 1996/04/14 15:17:55 kfogel Exp $" diff --git a/contrib/cvs/tools/pcl-cvs/pcl-cvs-lucid.el b/contrib/cvs/tools/pcl-cvs/pcl-cvs-lucid.el new file mode 100644 index 0000000..8695f67 --- /dev/null +++ b/contrib/cvs/tools/pcl-cvs/pcl-cvs-lucid.el @@ -0,0 +1,134 @@ +;;; Mouse and font support for PCL-CVS 1.3 running in Lucid GNU Emacs +;; @(#) Id: pcl-cvs-lucid.el,v 1.2 1993/05/31 19:37:34 ceder Exp +;; Copyright (C) 1992-1993 Free Software Foundation, Inc. + +;; This file is part of GNU Emacs. + +;; GNU Emacs 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. + +;; GNU Emacs 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. + +;; You should have received a copy of the GNU General Public License +;; along with GNU Emacs; see the file COPYING. If not, write to +;; the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + + +;; This simply adds a menu of the common CVS commands to the menubar and to +;; the right mouse button. Clicking right moves point, and then pops up a +;; menu from which commands can be executed. +;; +;; This could stand to be a lot more clever: for example, the "Commit Changes" +;; command should only be active on files for which there is something to +;; commit. Also, some indication of which files the command applies to +;; (especially in the presence of multiple marked files) would be nice. +;; +;; Middle-click runs find-file. + + +;(require 'pcl-cvs) +(load "pcl-cvs.el") + +(defvar cvs-menu + '("CVS" + ["Find File" cvs-mode-find-file t] + ["Find File Other Window" cvs-mode-find-file-other-window t] + ["Interactively Merge (emerge)" cvs-mode-emerge t] + ["Diff against Repository" cvs-mode-diff-cvs t] + ["Diff against Backup Version" cvs-mode-diff-backup t] + "----" + ["Commit Changes to Repository" cvs-mode-commit t] + ["Revert File from Repository" cvs-mode-undo-local-changes t] + ["Add File to Repository" cvs-mode-add t] + ["Remove File from Repository" cvs-mode-remove-file t] + ["Ignore File" cvs-mode-ignore t] + ["Hide File" cvs-mode-acknowledge t] + ["Hide Handled Files" cvs-mode-remove-handled t] + "----" + ["Add ChangeLog Entry" cvs-mode-add-change-log-entry-other-window t] + ["Show CVS Log" cvs-mode-log t] + ["Show CVS Status" cvs-mode-status t] + "----" + ["Mark File" cvs-mode-mark t] + ["Unmark File" cvs-mode-unmark t] + ["Mark All Files" cvs-mode-mark-all-files t] + ["Unmark All Files" cvs-mode-unmark-all-files t] + "----" + ["Quit" bury-buffer t] + )) + +(defun cvs-menu (e) + (interactive "e") + (mouse-set-point e) + (beginning-of-line) + (or (looking-at "^[* ] ") (error "No CVS file line here")) + (popup-menu cvs-menu)) + +(defun cvs-mouse-find-file (e) + (interactive "e") + (mouse-set-point e) + (beginning-of-line) + (or (looking-at "^[* ] ") (error "No CVS file line here")) + (cvs-mode-find-file (point))) + +(define-key cvs-mode-map 'button3 'cvs-menu) +(define-key cvs-mode-map 'button2 'cvs-mouse-find-file) + +(make-face 'cvs-header-face) +(make-face 'cvs-filename-face) +(make-face 'cvs-status-face) + +(or (face-differs-from-default-p 'cvs-header-face) + (copy-face 'italic 'cvs-header-face)) + +(or (face-differs-from-default-p 'cvs-filename-face) + (copy-face 'bold 'cvs-filename-face)) + +(or (face-differs-from-default-p 'cvs-status-face) + (copy-face 'bold-italic 'cvs-status-face)) + + +(defun pcl-mode-motion-highlight-line (event) + (if (save-excursion + (let* ((window (event-window event)) + (buffer (and window (window-buffer window))) + (point (and buffer (event-point event)))) + (and point + (progn + (set-buffer buffer) + (goto-char point) + (beginning-of-line) + (looking-at "^[* ] "))))) + (mode-motion-highlight-line event))) + +(defconst pcl-cvs-font-lock-keywords + '(("^In directory \\(.+\\)$" 1 cvs-header-face) + ("^[* ] \\w+ +\\(ci\\)" 1 cvs-status-face) + ("^[* ] \\(Conflict\\|Merged\\)" 1 cvs-status-face) + ("^[* ] \\w+ +\\(ci +\\)?\\(.+\\)$" 2 cvs-filename-face) + ) + "Patterns to highlight in the *cvs* buffer.") + +(defun pcl-cvs-fontify () + ;; + ;; set up line highlighting + (require 'mode-motion) + (setq mode-motion-hook 'pcl-mode-motion-highlight-line) + ;; + ;; set up menubar + (if (and current-menubar (not (assoc "CVS" current-menubar))) + (progn + (set-buffer-menubar (copy-sequence current-menubar)) + (add-menu nil "CVS" (cdr cvs-menu)))) + ;; + ;; fontify mousable lines + (set (make-local-variable 'font-lock-keywords) pcl-cvs-font-lock-keywords) + (font-lock-mode 1) + ) + +(add-hook 'cvs-mode-hook 'pcl-cvs-fontify) diff --git a/contrib/cvs/tools/pcl-cvs/pcl-cvs-startup.el b/contrib/cvs/tools/pcl-cvs/pcl-cvs-startup.el new file mode 100644 index 0000000..9db7a5f --- /dev/null +++ b/contrib/cvs/tools/pcl-cvs/pcl-cvs-startup.el @@ -0,0 +1,17 @@ +;;;#ident "@(#)OrigId: pcl-cvs-startup.el,v 1.4 1993/05/31 18:40:33 ceder Exp " +;;; +;;;#ident "@(#)cvs/contrib/pcl-cvs:$Name: $:$Id: pcl-cvs-startup.el,v 1.1 1996/04/14 15:17:59 kfogel Exp $" +;;; +(autoload 'cvs-update "pcl-cvs" + "Run a 'cvs update' in the current working directory. Feed the +output to a *cvs* buffer and run cvs-mode on it. +If optional prefix argument LOCAL is non-nil, 'cvs update -l' is run." + t) + +(autoload 'cvs-update-other-window "pcl-cvs" + "Run a 'cvs update' in the current working directory. Feed the +output to a *cvs* buffer, display it in the other window, and run +cvs-mode on it. + +If optional prefix argument LOCAL is non-nil, 'cvs update -l' is run." + t) diff --git a/contrib/cvs/tools/pcl-cvs/pcl-cvs.el b/contrib/cvs/tools/pcl-cvs/pcl-cvs.el new file mode 100644 index 0000000..269b02f --- /dev/null +++ b/contrib/cvs/tools/pcl-cvs/pcl-cvs.el @@ -0,0 +1,3450 @@ +;;; +;;;#ident "@(#)OrigId: pcl-cvs.el,v 1.93 1993/05/31 22:44:00 ceder Exp " +;;; +;;;#ident "@(#)cvs/contrib/pcl-cvs:$Name: $:$Id: pcl-cvs.el,v 1.2 1996/04/14 20:09:45 kfogel Exp $" +;;; +;;; pcl-cvs.el -- A Front-end to CVS 1.3 or later. +;;; Release 1.05-CVS-$Name: $. +;;; Copyright (C) 1991, 1992, 1993 Per Cederqvist + +;;; 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 of the License, 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. +;;; +;;; You should have received a copy of the GNU General Public License +;;; along with this program; if not, write to the Free Software +;;; Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +;;; See below for installation instructions. + +;;; This package requires ELIB-1.0 to run. Elib is included in the +;;; CVS distribution in the contrib/elib/ subdirectory, but you can +;;; also download it at the following URL: +;;; +;;; ftp://ftp.lysator.liu.se/pub/emacs/elib-1.0.tar.gz +;;; + +;;; There is an TeXinfo file that describes this package. You should read it +;;; to get the most from this package. + +;;; Mail questions and bug reports regarding this version (as included in +;;; CVS-1.7 or newer) to the pcl-cvs support team at <pcl-cvs@cyclic.com>. + +;;; Don't try to use this with CVS 1.2 or earlier. It won't work. Get CVS 1.7 +;;; or newer. Use the version of RCS best suited for the version of CVS you're +;;; using. + +(require 'cookie) ; from ELIB-1.0 +(require 'add-log) ; for all the ChangeLog goodies + +;;; ------------------------------------------------------- +;;; START OF THINGS TO CHECK WHEN INSTALLING + +;; also use $GNU here, since may folks might install CVS as a GNU package +;; +(defvar local-path (cond + ((getenv "LOCAL") + (getenv "LOCAL")) + ((getenv "GNU") + (getenv "GNU")) + (t + "/usr/local")) + "*Path prefix for most locally installed things.") + +;; this isn't likely to be right all the time.... +;; +(defvar local-gnu-path (cond + ((getenv "GNU") + (getenv "GNU")) + (t + "/usr/local")) ; or "/usr/gnu"? + "*Path prefix for locally installed GNU software.") + +(defvar cvs-program (concat local-path "/bin/cvs") + "*Full path to the cvs executable.") + +;; SunOS-4.1.1_U1 has "diff.c 1.12 88/08/04 SMI; from UCB 4.6 86/04/03" +;; +(defvar cvs-diff-program (concat local-gnu-path "/bin/diff") + "*Full path to the best diff program you've got. +NOTE: there are some nasty bugs in the context diff variants of some vendor +versions, such as the one in SunOS-4.1.1_U1") + +(defvar cvs-rmdir-program "/bin/rmdir" + "*Full path to the rmdir program. Typically /bin/rmdir.") + +(defvar cvs-shell "/bin/sh" + "*Full path to a shell that can do redirection on stdout.") + +;;; Options to control various features: + +(defvar cvs-changelog-full-paragraphs t + "If non-nil, include full ChangeLog paragraphs in the CVS log. +This may be set in the ``local variables'' section of a ChangeLog, to +indicate the policy for that ChangeLog. + +A ChangeLog paragraph is a bunch of log text containing no blank lines; +a paragraph usually describes a set of changes with a single purpose, +but perhaps spanning several functions in several files. Changes in +different paragraphs are unrelated. + +You could argue that the CVS log entry for a file should contain the +full ChangeLog paragraph mentioning the change to the file, even though +it may mention other files, because that gives you the full context you +need to understand the change. This is the behaviour you get when this +variable is set to t. + +On the other hand, you could argue that the CVS log entry for a change +should contain only the text for the changes which occurred in that +file, because the CVS log is per-file. This is the behaviour you get +when this variable is set to nil.") + +(defvar cvs-cvsroot-required nil + "*Specifies whether CVS needs to be told where the repository is. + +In CVS 1.3, if your CVSROOT environment variable is not set, and you +do not set the `cvs-cvsroot' lisp variable, CVS will have no idea +where to find the repository, and refuse to run. CVS 1.4 and later +store the repository path with the working directories, so most +operations don't need to be told where the repository is. + +If you work with multiple repositories with CVS 1.4, it's probably +advisable to leave your CVSROOT environment variable unset, set this +variable to nil, and let CVS figure out where the repository is for +itself.") + +(defvar cvs-cvsroot nil + "*Specifies where the (current) cvs master repository is. +Overrides the $CVSROOT variable by sending \" -d dir\" to all cvs commands. +This switch is useful if you have multiple CVS repositories, and are not using +a modern version of CVS that stores the current repository in CVS/Root.") + +;; Uncomment the following line if you are running on 18.57 or earlier. +;(setq delete-exited-processes nil) +;; Emacs version 18.57 and earlier is likely to crash if +;; delete-exited-processes is t, since the sentinel uses lots of +;; memory, and 18.57 forgets to GCPROT a variable if +;; delete-exited-processes is t. + +;;; END OF THINGS TO CHECK WHEN INSTALLING +;;; -------------------------------------------------------- + +(defconst pcl-cvs-version "1.05-CVS-$Name: $" + "A string denoting the current release version of pcl-cvs.") + +;; You are NOT allowed to disable this message by default. However, you +;; are encouraged to inform your users that by adding +;; (setq cvs-inhibit-copyright-message t) +;; to their .emacs they can get rid of it. Just don't add that line +;; to your default.el! +(defvar cvs-inhibit-copyright-message nil + "*Non-nil means don't display a Copyright message in the ``*cvs*'' buffer.") + +(defconst cvs-startup-message + (if cvs-inhibit-copyright-message + "PCL-CVS release 1.05-CVS-$Name: $" + "PCL-CVS release 1.05 from CVS release $Name: $. +Copyright (C) 1992, 1993 Per Cederqvist +Pcl-cvs comes with absolutely no warranty; for details consult the manual. +This is free software, and you are welcome to redistribute it under certain +conditions; again, consult the TeXinfo manual for details.") + "*Startup message for CVS.") + +(defconst pcl-cvs-bugs-address "pcl-cvs-auto-bugs@cyclic.com" + "The destination address used for the default bug report form.") + +(defvar cvs-stdout-file nil + "Name of the file that holds the output that CVS sends to stdout. +This variable is buffer local.") + +(defvar cvs-lock-file nil + "Full path to a lock file that CVS is waiting for (or was waiting for).") + +(defvar cvs-bakprefix ".#" + "The prefix that CVS prepends to files when rcsmerge'ing.") + +(defvar cvs-erase-input-buffer nil + "*Non-nil if input buffers should be cleared before asking for new info.") + +(defvar cvs-auto-remove-handled nil + "*Non-nil if cvs-mode-remove-handled should be called automatically. +If this is set to any non-nil value, entries that do not need to be checked in +will be removed from the *cvs* buffer after every cvs-mode-commit command.") + +(defvar cvs-auto-remove-handled-directories nil + "*Non-nil if cvs-mode-remove-handled and cvs-update should automatically +remove empty directories. +If this is set to any non-nil value, directories that do not contain any files +to be checked in will be removed from the *cvs* buffer.") + +(defvar cvs-sort-ignore-file t + "*Non-nil if cvs-mode-ignore should sort the .cvsignore automatically.") + +(defvar cvs-auto-revert-after-commit t + "*Non-nil if committed buffers should be automatically reverted.") + +(defconst cvs-cursor-column 14 + "Column to position cursor in in cvs-mode. +Column 0 is left-most column.") + +(defvar cvs-mode-map nil + "Keymap for the cvs mode.") + +(defvar cvs-edit-mode-map nil + "Keymap for the cvs edit mode (used when editing cvs log messages).") + +(defvar cvs-buffer-name "*cvs*" + "Name of the cvs buffer.") + +(defvar cvs-commit-prompt-buffer "*cvs-commit-message*" + "Name of buffer in which the user is prompted for a log message when +committing files.") + +(defvar cvs-commit-buffer-require-final-newline t + "*t says silently put a newline at the end of commit log messages. +Non-nil but not t says ask user whether to add a newline in each such case. +nil means don't add newlines.") + +(defvar cvs-temp-buffer-name "*cvs-tmp*" + "*Name of the cvs temporary buffer. +Output from cvs is placed here by synchronous commands.") + +(defvar cvs-diff-ignore-marks nil + "*Non-nil if cvs-diff and cvs-mode-diff-backup should ignore any marked files. +Normally they run diff on the files that are marked (with cvs-mode-mark), +or the file under the cursor if no files are marked. If this variable +is set to a non-nil value they will always run diff on the file on the +current line.") + +;;; (setq cvs-status-flags '("-v")) +(defvar cvs-status-flags '("-v") + "*List of flags to pass to ``cvs status''. Default is \"-v\".") + +;;; (setq cvs-log-flags nil) +(defvar cvs-log-flags nil + "*List of flags to pass to ``cvs log''. Default is none.") + +;;; (setq cvs-tag-flags nil) +(defvar cvs-tag-flags nil + "*List of extra flags to pass to ``cvs tag''. Default is none.") + +;;; (setq cvs-rtag-flags nil) +(defvar cvs-rtag-flags nil + "*List of extra flags to pass to ``cvs rtag''. Default is none.") + +;;; (setq cvs-diff-flags '("-u")) +(defvar cvs-diff-flags '("-u") + "*List of flags to use as flags to pass to ``diff'' and ``cvs diff''. +Used by cvs-mode-diff-cvs and cvs-mode-diff-backup. Default is \"-u\". + +Set this to \"-u\" to get a Unidiff format, or \"-c\" to get context diffs.") + +;;; (setq cvs-update-optional-flags nil) +(defvar cvs-update-optional-flags nil + "*List of strings to use as optional flags to pass to ``cvs update''. Used +by cvs-do-update, called by cvs-update, cvs-update-other-window, +cvs-mode-update-no-prompt, and cvs-examine. Default is none. + +For example set this to \"-j VENDOR_PREV_RELEASE -j VENDOR_TOP_RELEASE\" to +perform an update after a new vendor release has been imported. + +To restrict the update to the current working directory, set this to \"-l\".") + +(defvar cvs-update-prog-output-skip-regexp "$" + "*A regexp that matches the end of the output from all cvs update programs. +That is, output from any programs that are run by CVS (by the flag -u in the +`modules' file - see cvs(5)) when `cvs update' is performed should terminate +with a line that this regexp matches. It is enough that some part of the line +is matched. + +The default (a single $) fits programs without output.") + +;;; -------------------------------------------------------- +;;; The variables below are used internally by pcl-cvs. You should +;;; never change them. + +(defvar cvs-buffers-to-delete nil + "List of temporary buffers that should be discarded as soon as possible. +Due to a bug in emacs 18.57 the sentinel can't discard them reliably.") + +(defvar cvs-update-running nil + "This is set to nil when no process is running, and to +the process when a cvs update process is running.") + +(defvar cvs-cookie-handle nil + "Handle for the cookie structure that is displayed in the *cvs* buffer.") + +(defvar cvs-commit-list nil + "Used internally by pcl-cvs.") + +;;; The cvs data structure: +;;; +;;; When the `cvs update' is ready we parse the output. Every file +;;; that is affected in some way is added as a cookie of fileinfo +;;; (as defined below). +;;; + +;;; cvs-fileinfo + +;;; Constructor: + +(defun cvs-create-fileinfo (type + dir + file-name + full-log) + "Create a fileinfo from all parameters. +Arguments: TYPE DIR FILE-NAME FULL-LOG. +A fileinfo is a vector with the following fields: + +[0] handled True if this file doesn't require further action. +[1] marked t/nil +[2] type One of + UPDATED - file copied from repository + PATCHED - file update with patch from repository + MODIFIED - modified by you, unchanged in + repository + ADDED - added by you, not yet committed + REMOVED - removed by you, not yet committed + CVS-REMOVED- removed, since file no longer exists + in the repository. + MERGED - successful merge + CONFLICT - conflict when merging (if pcl-cvs did it) + REM-CONFLICT-removed in repository, but altered + locally. + MOD-CONFLICT-removed locally, changed in repository. + REM-EXIST - removed locally, but still exists. + DIRCHANGE - A change of directory. + UNKNOWN - An unknown file. + UNKNOWN-DIR- An unknown directory. + MOVE-AWAY - A file that is in the way. + REPOS-MISSING- The directory has vanished from the + repository. + MESSAGE - This is a special fileinfo that is used + to display a text that should be in + full-log. +[3] dir Directory the file resides in. Should not end with slash. +[4] file-name The file name. +[5] backup-file The name of a backup file created during a merge. + Only valid for MERGED and CONFLICT files. +[6] base-revision The revision that the working file was based on. + Only valid for MERGED and CONFLICT files. +[7] head-revision The revision that the newly merged changes came from + Only valid for MERGED and CONFLICT files. +[8] backup-revision The revision of the cvs backup file (original working rev.) + Only valid for MERGED and CONFLICT files. +[9] cvs-diff-buffer A buffer that contains a 'cvs diff file'. +[10] vendor-diff-buffer A buffer that contains a 'diff base-file head-file'. +[11] backup-diff-buffer A buffer that contains a 'diff file backup-file'. +[12] full-log The output from cvs, unparsed. +[13] mod-time Modification time of file used for *-diff-buffer." + + (cons + 'CVS-FILEINFO + (vector nil nil type dir file-name nil nil nil nil nil nil nil full-log nil nil))) + +;;; Selectors: + +(defun cvs-fileinfo->handled (cvs-fileinfo) + "Get the `handled' field from CVS-FILEINFO." + (elt (cdr cvs-fileinfo) 0)) + +(defun cvs-fileinfo->marked (cvs-fileinfo) + "Check if CVS-FILEINFO is marked." + (elt (cdr cvs-fileinfo) 1)) + +(defun cvs-fileinfo->type (cvs-fileinfo) + "Get type from CVS-FILEINFO. +Type is one of UPDATED, PATCHED, MODIFIED, ADDED, REMOVED, CVS-REMOVED, MERGED, +CONFLICT, REM-CONFLICT, MOD-CONFLICT, REM-EXIST, DIRCHANGE, UNKNOWN, +UNKNOWN-DIR, MOVE-AWAY, REPOS-MISSING or MESSAGE." + (elt (cdr cvs-fileinfo) 2)) + +(defun cvs-fileinfo->dir (cvs-fileinfo) + "Get dir from CVS-FILEINFO. +The directory name does not end with a slash." + (elt (cdr cvs-fileinfo) 3)) + +(defun cvs-fileinfo->file-name (cvs-fileinfo) + "Get file-name from CVS-FILEINFO." + (elt (cdr cvs-fileinfo) 4)) + +(defun cvs-fileinfo->backup-file (cvs-fileinfo) + "Get backup-file from CVS-FILEINFO." + (elt (cdr cvs-fileinfo) 5)) + +(defun cvs-fileinfo->base-revision (cvs-fileinfo) + "Get the base revision from CVS-FILEINFO." + (elt (cdr cvs-fileinfo) 6)) + +(defun cvs-fileinfo->head-revision (cvs-fileinfo) + "Get the head revision from CVS-FILEINFO." + (elt (cdr cvs-fileinfo) 7)) + +(defun cvs-fileinfo->backup-revision (cvs-fileinfo) + "Get the backup revision from CVS-FILEINFO." + (elt (cdr cvs-fileinfo) 8)) + +(defun cvs-fileinfo->cvs-diff-buffer (cvs-fileinfo) + "Get cvs-diff-buffer from CVS-FILEINFO." + (elt (cdr cvs-fileinfo) 9)) + +(defun cvs-fileinfo->vendor-diff-buffer (cvs-fileinfo) + "Get backup-diff-buffer from CVS-FILEINFO." + (elt (cdr cvs-fileinfo) 10)) + +(defun cvs-fileinfo->backup-diff-buffer (cvs-fileinfo) + "Get backup-diff-buffer from CVS-FILEINFO." + (elt (cdr cvs-fileinfo) 11)) + +(defun cvs-fileinfo->full-log (cvs-fileinfo) + "Get full-log from CVS-FILEINFO." + (elt (cdr cvs-fileinfo) 12)) + +(defun cvs-fileinfo->mod-time (cvs-fileinfo) + "Get mod-time from CVS-FILEINFO." + (elt (cdr cvs-fileinfo) 13)) + +;;; Modifiers: + +(defun cvs-set-fileinfo->handled (cvs-fileinfo newval) + "Set handled in CVS-FILEINFO to NEWVAL." + (aset (cdr cvs-fileinfo) 0 newval)) + +(defun cvs-set-fileinfo->marked (cvs-fileinfo newval) + "Set marked in CVS-FILEINFO to NEWVAL." + (aset (cdr cvs-fileinfo) 1 newval)) + +(defun cvs-set-fileinfo->type (cvs-fileinfo newval) + "Set type in CVS-FILEINFO to NEWVAL." + (aset (cdr cvs-fileinfo) 2 newval)) + +(defun cvs-set-fileinfo->dir (cvs-fileinfo newval) + "Set dir in CVS-FILEINFO to NEWVAL. +The directory should now end with a slash." + (aset (cdr cvs-fileinfo) 3 newval)) + +(defun cvs-set-fileinfo->file-name (cvs-fileinfo newval) + "Set file-name in CVS-FILEINFO to NEWVAL." + (aset (cdr cvs-fileinfo) 4 newval)) + +(defun cvs-set-fileinfo->backup-file (cvs-fileinfo newval) + "Set backup-file in CVS-FILEINFO to NEWVAL." + (aset (cdr cvs-fileinfo) 5 newval)) + +(defun cvs-set-fileinfo->base-revision (cvs-fileinfo newval) + "Set base-revision in CVS-FILEINFO to NEWVAL." + (aset (cdr cvs-fileinfo) 6 newval)) + +(defun cvs-set-fileinfo->head-revision (cvs-fileinfo newval) + "Set head-revision in CVS-FILEINFO to NEWVAL." + (aset (cdr cvs-fileinfo) 7 newval)) + +(defun cvs-set-fileinfo->backup-revision (cvs-fileinfo newval) + "Set backup-revision in CVS-FILEINFO to NEWVAL." + (aset (cdr cvs-fileinfo) 8 newval)) + +(defun cvs-set-fileinfo->cvs-diff-buffer (cvs-fileinfo newval) + "Set cvs-diff-buffer in CVS-FILEINFO to NEWVAL." + (aset (cdr cvs-fileinfo) 9 newval)) + +(defun cvs-set-fileinfo->vendor-diff-buffer (cvs-fileinfo newval) + "Set vendor-diff-buffer in CVS-FILEINFO to NEWVAL." + (aset (cdr cvs-fileinfo) 10 newval)) + +(defun cvs-set-fileinfo->backup-diff-buffer (cvs-fileinfo newval) + "Set backup-diff-buffer in CVS-FILEINFO to NEWVAL." + (aset (cdr cvs-fileinfo) 11 newval)) + +(defun cvs-set-fileinfo->full-log (cvs-fileinfo newval) + "Set full-log in CVS-FILEINFO to NEWVAL." + (aset (cdr cvs-fileinfo) 12 newval)) + +(defun cvs-set-fileinfo->mod-time (cvs-fileinfo newval) + "Set full-log in CVS-FILEINFO to NEWVAL." + (aset (cdr cvs-fileinfo) 13 newval)) + +;;; Predicate: + +(defun cvs-fileinfo-p (object) + "Return t if OBJECT is a cvs-fileinfo." + (eq (car-safe object) 'CVS-FILEINFO)) + +;;;; End of types. + +;;---------- +(defun cvs-use-temp-buffer () + "Display a temporary buffer in another window and select it. +The selected window will not be changed. The temporary buffer will +be erased and writable." + + (let ((dir default-directory)) + (display-buffer (get-buffer-create cvs-temp-buffer-name)) + (set-buffer cvs-temp-buffer-name) + (setq buffer-read-only nil) + (setq default-directory dir) + (erase-buffer))) + +;;---------- +(defun cvs-examine (directory &optional local) + "Run a 'cvs -n update' in the current working directory. +That is, check what needs to be done, but don't change the disc. +Feed the output to a *cvs* buffer and run cvs-mode on it. +If optional prefix argument LOCAL is non-nil, 'cvs update -l' is run. +WARNING: this doesn't work very well yet...." + + ;; TODO: this should do everything cvs-update does... + ;; for example, for CONFLICT files, it should setup fileinfo appropriately + + (interactive (list (read-file-name "CVS Update (directory): " + nil default-directory nil) + current-prefix-arg)) + (cvs-do-update directory local 'noupdate)) + +;;---------- +(defun cvs-update (directory &optional local) + "Run a 'cvs update' in the current working directory. Feed the +output to a *cvs* buffer and run cvs-mode on it. +If optional prefix argument LOCAL is non-nil, 'cvs update -l' is run." + + (interactive (list (read-file-name "CVS Update (directory): " + nil default-directory nil) + current-prefix-arg)) + (cvs-do-update directory local nil) + (switch-to-buffer cvs-buffer-name)) + +;;---------- +(defun cvs-update-other-window (directory &optional local) + "Run a 'cvs update' in the current working directory. Feed the +output to a *cvs* buffer, display it in the other window, and run +cvs-mode on it. + +If optional prefix argument LOCAL is non-nil, 'cvs update -l' is run." + + (interactive (list (read-file-name "CVS Update other window (directory): " + nil default-directory nil) + current-prefix-arg)) + (cvs-do-update directory local nil) + (switch-to-buffer-other-window cvs-buffer-name)) + +;;---------- +(defun cvs-filter (predicate list &rest extra-args) + "Apply PREDICATE to each element on LIST. +Args: PREDICATE LIST &rest EXTRA-ARGS. + +Return a new list consisting of those elements that PREDICATE +returns non-nil for. + +If more than two arguments are given the remaining args are +passed to PREDICATE." + + ;; Avoid recursion - this should work for LONG lists also! + (let* ((head (cons 'dummy-header nil)) + (tail head)) + (while list + (if (apply predicate (car list) extra-args) + (setq tail (setcdr tail (list (car list))))) + (setq list (cdr list))) + (cdr head))) + +;;---------- +(defun cvs-mode-update-no-prompt () + "Run cvs update in current directory." + + (interactive) + (cvs-do-update default-directory nil nil)) + +;;---------- +(defun cvs-do-update (directory local dont-change-disc) + "Do a 'cvs update' in DIRECTORY. +Args: DIRECTORY LOCAL DONT-CHANGE-DISC. + +If LOCAL is non-nil 'cvs update -l' is executed. +If DONT-CHANGE-DISC is non-nil 'cvs -n update' is executed. +Both LOCAL and DONT-CHANGE-DISC may be non-nil simultaneously. + +*Note*: DONT-CHANGE-DISC does not yet work. The parser gets confused." + + (save-some-buffers) + ;; Ensure that it is safe to do an update. If not, ask user + ;; for confirmation. + (if (and (boundp 'cvs-cookie-handle) (collection-buffer cvs-cookie-handle)) + (if (collection-collect-tin + cvs-cookie-handle + '(lambda (cookie) (eq (cvs-fileinfo->type cookie) 'CONFLICT))) + (if (not + (yes-or-no-p + "Only update if conflicts have been resolved. Continue? ")) + (error "Update aborted by user request.")))) + (if (not (file-exists-p cvs-program)) + (error "%s: file not found (check setting of cvs-program)" + cvs-program)) + (let* ((this-dir (file-name-as-directory (expand-file-name directory))) + (update-buffer (generate-new-buffer + (concat " " (file-name-nondirectory + (substring this-dir 0 -1)) + "-update"))) + (temp-name (make-temp-name + (concat (file-name-as-directory + (or (getenv "TMPDIR") "/tmp")) + "pcl-cvs."))) + (args nil)) + + ;; Check that this-dir exists and is a directory that is under CVS contr. + + (if (not (file-directory-p this-dir)) + (error "%s is not a directory." this-dir)) + (if (not (file-directory-p (concat this-dir "CVS"))) + (error "%s does not contain CVS controlled files." this-dir)) + (if (file-readable-p (concat this-dir "CVS/Root")) + (save-excursion ; read CVS/Root into cvs-cvsroot + (find-file (concat this-dir "CVS/Root")) + (goto-char (point-min)) + (setq cvs-cvsroot (buffer-substring (point) + (progn (end-of-line) (point)))) + (if (not cvs-cvsroot) + (error "Invalid contents of %sCVS/Root" this-dir)) + (kill-buffer (current-buffer))) + (if (and cvs-cvsroot-required + (not (or (getenv "CVSROOT") cvs-cvsroot))) + (error "Both cvs-cvsroot and environment variable CVSROOT are unset, and no CVS/Root."))) + + ;; Check that at most one `cvs update' is run at any time. + + (if (and cvs-update-running (process-status cvs-update-running) + (or (eq (process-status cvs-update-running) 'run) + (eq (process-status cvs-update-running) 'stop))) + (error "Can't run two `cvs update' simultaneously.")) + + (if (not (listp cvs-update-optional-flags)) + (error "cvs-update-optional-flags should be set using cvs-set-update-optional-flags")) + + ;; Generate "-d /master -n update -l". + (setq args (concat (if cvs-cvsroot (concat " -d " cvs-cvsroot)) + (if dont-change-disc " -n ") + " update " + (if local " -l ") + (if cvs-update-optional-flags + (mapconcat 'identity + (copy-sequence cvs-update-optional-flags) + " ")))) + + ;; Set up the buffer that receives the stderr output from "cvs update". + (set-buffer update-buffer) + (setq default-directory this-dir) + (make-local-variable 'cvs-stdout-file) + (setq cvs-stdout-file temp-name) + + (setq cvs-update-running + (let ((process-connection-type nil)) ; Use a pipe, not a pty. + (start-process "cvs" update-buffer cvs-shell "-c" + (concat cvs-program " " args " > " temp-name)))) + + (setq mode-line-process + (concat ": " + (symbol-name (process-status cvs-update-running)))) + (set-buffer-modified-p (buffer-modified-p)) ; Update the mode line. + (set-process-sentinel cvs-update-running 'cvs-sentinel) + (set-process-filter cvs-update-running 'cvs-update-filter) + (set-marker (process-mark cvs-update-running) (point-min)) + + (save-excursion + (set-buffer (get-buffer-create cvs-buffer-name)) + (setq buffer-read-only nil) + (erase-buffer) + (cvs-mode)) + + (setq cvs-cookie-handle + (collection-create + cvs-buffer-name 'cvs-pp + cvs-startup-message ;See comment above cvs-startup-message. + "---------- End -----")) + + (cookie-enter-first + cvs-cookie-handle + (cvs-create-fileinfo + 'MESSAGE nil nil (concat "\n Running `cvs " args "' in " this-dir + "...\n"))) + + (save-excursion + (set-buffer cvs-buffer-name) + (setq mode-line-process + (concat ": " + (symbol-name (process-status cvs-update-running)))) + (set-buffer-modified-p (buffer-modified-p)) ; Update the mode line. + (setq buffer-read-only t)) + + ;; Work around a bug in emacs 18.57 and earlier. + (setq cvs-buffers-to-delete + (cvs-delete-unused-temporary-buffers cvs-buffers-to-delete))) + + ;; The following line is said to improve display updates on some + ;; emacses. It shouldn't be needed, but it does no harm. + (sit-for 0)) + +;;---------- +(defun cvs-delete-unused-temporary-buffers (list) + "Delete all buffers on LIST that is not visible. +Return a list of all buffers that still is alive." + + (cond + ((null list) nil) + ((get-buffer-window (car list)) + (cons (car list) + (cvs-delete-unused-temporary-buffers (cdr list)))) + (t + (kill-buffer (car list)) + (cvs-delete-unused-temporary-buffers (cdr list))))) + +;;---------- +(put 'cvs-mode 'mode-class 'special) + +;;---------- +(defun cvs-mode () + "\\<cvs-mode-map>Mode used for pcl-cvs, a front-end to CVS. + +To get to the \"*cvs*\" buffer you should use ``\\[execute-extended-command] cvs-update''. + +Full documentation is in the Texinfo file. Here are the most useful commands: + +\\[cvs-mode-previous-line] Move up. \\[cvs-mode-next-line] Move down. +\\[cvs-mode-commit] Commit file. \\[cvs-mode-update-no-prompt] Re-update directory. +\\[cvs-mode-mark] Mark file/dir. \\[cvs-mode-unmark] Unmark file/dir. +\\[cvs-mode-mark-all-files] Mark all files. \\[cvs-mode-unmark-all-files] Unmark all files. +\\[cvs-mode-find-file] Edit file/run Dired. \\[cvs-mode-find-file-other-window] Find file or run Dired in other window. +\\[cvs-mode-ignore] Add file to ./.cvsignore. \\[cvs-mode-add-change-log-entry-other-window] Write ChangeLog in other window. +\\[cvs-mode-add] Add to repository. \\[cvs-mode-remove-file] Remove file. +\\[cvs-mode-diff-cvs] Diff with base revision. \\[cvs-mode-diff-backup] Diff backup file. +\\[cvs-mode-ediff] Ediff base rev & backup. \\[cvs-mode-diff-vendor] Show merge from vendor branch. +\\[cvs-mode-emerge] Emerge base rev & backup. \\[cvs-mode-diff-backup] Diff backup file. +\\[cvs-mode-acknowledge] Delete line from buffer. \\[cvs-mode-remove-handled] Remove processed entries. +\\[cvs-mode-log] Run ``cvs log''. \\[cvs-mode-status] Run ``cvs status''. +\\[cvs-mode-tag] Run ``cvs tag''. \\[cvs-mode-rtag] Run ``cvs rtag''. +\\[cvs-mode-changelog-commit] Like \\[cvs-mode-commit], but get default log text from ChangeLog. +\\[cvs-mode-undo-local-changes] Revert the last checked in version - discard your changes to the file. + +Entry to this mode runs cvs-mode-hook. +This description is updated for release 1.05-CVS-$Name: $ of pcl-cvs. + +All bindings: +\\{cvs-mode-map}" + + (interactive) + (setq major-mode 'cvs-mode) + (setq mode-name "CVS") + (setq mode-line-process nil) +;; for older v18 emacs +;;(buffer-flush-undo (current-buffer)) + (buffer-disable-undo (current-buffer)) + (make-local-variable 'goal-column) + (setq goal-column cvs-cursor-column) + (use-local-map cvs-mode-map) + (run-hooks 'cvs-mode-hook)) + +;;---------- +(defun cvs-sentinel (proc msg) + "Sentinel for the cvs update process. +This is responsible for parsing the output from the cvs update when +it is finished." + + (cond + ((null (buffer-name (process-buffer proc))) + ;; buffer killed + (set-process-buffer proc nil)) + ((memq (process-status proc) '(signal exit)) + (let* ((obuf (current-buffer)) + (omax (point-max)) + (opoint (point))) + ;; save-excursion isn't the right thing if + ;; process-buffer is current-buffer + (unwind-protect + (progn + (set-buffer (process-buffer proc)) + (setq mode-line-process + (concat ": " + (symbol-name (process-status proc)))) + (let* ((out-file cvs-stdout-file) + (stdout-buffer (find-file-noselect out-file))) + (save-excursion + (set-buffer stdout-buffer) + (rename-buffer (concat " " + (file-name-nondirectory out-file)) t)) + (cvs-parse-update stdout-buffer (process-buffer proc)) + (setq cvs-buffers-to-delete + (cons (process-buffer proc) + (cons stdout-buffer + cvs-buffers-to-delete))) + (delete-file out-file))) + (set-buffer-modified-p (buffer-modified-p)) + (setq cvs-update-running nil)) + (if (equal obuf (process-buffer proc)) + nil + (set-buffer (process-buffer proc)) + (if (< opoint omax) + (goto-char opoint)) + (set-buffer obuf)))))) + +;;---------- +(defun cvs-update-filter (proc string) + "Filter function for pcl-cvs. +This function gets the output that CVS sends to stderr. It inserts it +into (process-buffer proc) but it also checks if CVS is waiting for a +lock file. If so, it inserts a message cookie in the *cvs* buffer." + + (let ((old-buffer (current-buffer)) + (data (match-data))) + (unwind-protect + (progn + (set-buffer (process-buffer proc)) + (save-excursion + ;; Insert the text, moving the process-marker. + (goto-char (process-mark proc)) + (insert string) + (set-marker (process-mark proc) (point)) + ;; Delete any old lock message + (if (tin-nth cvs-cookie-handle 1) + (tin-delete cvs-cookie-handle + (tin-nth cvs-cookie-handle 1))) + ;; Check if CVS is waiting for a lock. + (beginning-of-line 0) ;Move to beginning of last + ;complete line. + (cond + ((looking-at + "^cvs \\(update\\|server\\): \\[..:..:..\\] waiting for \\(.*\\)lock in \\(.*\\)$") + (setq cvs-lock-file (buffer-substring (match-beginning 3) + (match-end 3))) + (cookie-enter-last + cvs-cookie-handle + (cvs-create-fileinfo + 'MESSAGE nil nil + (concat "\tWaiting for " + (buffer-substring (match-beginning 2) + (match-end 2)) + "lock in " cvs-lock-file + ".\n\t (type M-x cvs-delete-lock to delete it)"))))))) + (store-match-data data) + (set-buffer old-buffer)))) + +;;---------- +(defun cvs-delete-lock () + "Delete the lock file that CVS is waiting for. +Note that this can be dangerous. You should only do this +if you are convinced that the process that created the lock is dead." + + (interactive) + (cond + ((not (or (file-exists-p + (concat (file-name-as-directory cvs-lock-file) "#cvs.lock")) + (cvs-filter (function cvs-lock-file-p) + (directory-files cvs-lock-file)))) + (error "No lock files found.")) + ((yes-or-no-p (concat "Really delete locks in " cvs-lock-file "? ")) + ;; Re-read the directory -- the locks might have disappeared. + (let ((locks (cvs-filter (function cvs-lock-file-p) + (directory-files cvs-lock-file)))) + (while locks + (delete-file (concat (file-name-as-directory cvs-lock-file) + (car locks))) + (setq locks (cdr locks))) + (cvs-remove-directory + (concat (file-name-as-directory cvs-lock-file) "#cvs.lock")))))) + +;;---------- +(defun cvs-remove-directory (dir) + "Remove a directory." + + (if (file-directory-p dir) + (call-process cvs-rmdir-program nil nil nil dir) + (error "Not a directory: %s" dir)) + (if (file-exists-p dir) + (error "Could not remove directory %s" dir))) + +;;---------- +(defun cvs-lock-file-p (file) + "Return true if FILE looks like a CVS lock file." + + (or + (string-match "^#cvs.tfl.[0-9]+$" file) + (string-match "^#cvs.rfl.[0-9]+$" file) + (string-match "^#cvs.wfl.[0-9]+$" file))) + +;;---------- +(defun cvs-quote-multiword-string (str) + "Return STR surrounded in single quotes if it contains whitespace." + (cond ((string-match "[ \t\n]" str) + (concat "'" str "'")) + (t + str))) + +;;---------- +;; this should be in subr.el or some similar place.... +(defun parse-string (str &optional regexp) + "Explode the string STR into a list of words ala strtok(3). Optional REGEXP +defines regexp matching word separator, which defaults to \"[ \\t\\n]+\"." + (let (str-list ; new list + str-token ; "index" of next token + (str-start 0) ; "index" of current token + (str-sep (if regexp + regexp + "[ \t\n]+"))) + (while (setq str-token (string-match str-sep str str-start)) + (setq str-list + (nconc str-list + (list (substring str str-start str-token)))) + (setq str-start (match-end 0))) + ;; tag on the remainder as the final item + (if (not (>= str-start (length str))) + (setq str-list + (nconc str-list + (list (substring str str-start))))) + str-list)) + +;;---------- +(defun cvs-make-list (str) + "Return list of words made from the string STR." + (cond ((string-match "[ \t\n]+" str) + (let ((new-str (parse-string str "[ \t\n]+"))) + ;; this is ugly, but assume if the first element is empty, there are + ;; no more elements. + (cond ((string= (car new-str) "") + nil) + (t + new-str)))) + ((string= str "") + nil) + (t + (list str)))) + +;;---------- +(defun cvs-skip-line (stdout stderr regexp &optional arg) + "Like forward-line, but check that the skipped line matches REGEXP. +Args: STDOUT STDERR REGEXP &optional ARG. + +If it doesn't match REGEXP a bug report is generated and displayed. +STDOUT and STDERR is only used to do that. + +If optional ARG, a number, is given the ARGth parenthesized expression +in the REGEXP is returned as a string. +Point should be in column 1 when this function is called." + + (cond + ((looking-at regexp) + (forward-line 1) + (if arg + (buffer-substring (match-beginning arg) + (match-end arg)))) + (t + (cvs-parse-error stdout + stderr + (if (eq (current-buffer) stdout) + 'STDOUT + 'STDERR) + (point) + regexp)))) + +;;---------- +(defun cvs-get-current-dir (root-dir dirname) + "Return current working directory, suitable for cvs-parse-update. +Args: ROOT-DIR DIRNAME. + +Concatenates ROOT-DIR and DIRNAME to form an absolute path." + + (if (string= "." dirname) + (substring root-dir 0 -1) + (concat root-dir dirname))) + +;;---------- +(defun cvs-compare-fileinfos (a b) + "Compare fileinfo A with fileinfo B and return t if A is `less'." + + (cond + ;; Sort acording to directories. + ((string< (cvs-fileinfo->dir a) (cvs-fileinfo->dir b)) t) + ((not (string= (cvs-fileinfo->dir a) (cvs-fileinfo->dir b))) nil) + ;; The DIRCHANGE entry is always first within the directory. + ((and (eq (cvs-fileinfo->type a) 'DIRCHANGE) + (not (eq (cvs-fileinfo->type b) 'DIRCHANGE))) t) + ((and (eq (cvs-fileinfo->type b) 'DIRCHANGE) + (not (eq (cvs-fileinfo->type a) 'DIRCHANGE))) nil) + ;; All files are sorted by file name. + ((string< (cvs-fileinfo->file-name a) (cvs-fileinfo->file-name b))))) + +;;---------- +(defun cvs-parse-error (stdout-buffer stderr-buffer err-buf pos &optional indicator) + "Handle a parse error when parsing the output from cvs. +Args: STDOUT-BUFFER STDERR-BUFFER ERR-BUF POS &optional INDICATOR. + +ERR-BUF should be 'STDOUT or 'STDERR." + + (setq pos (1- pos)) + (set-buffer cvs-buffer-name) + (setq buffer-read-only nil) + (erase-buffer) + (insert "To: " pcl-cvs-bugs-address "\n") + (insert "Subject: pcl-cvs release" pcl-cvs-version " parse error.\n") + (insert (concat mail-header-separator "\n")) + (insert "This bug report is automatically generated by pcl-cvs\n") + (insert "because it doesn't understand some output from CVS. Below\n") + (insert "is detailed information about the error. Please send\n") + (insert "this, together with any information you think might be\n") + (insert "useful for me to fix the bug, to the address above. But\n") + (insert "please check the \"known problems\" section of the\n") + (insert "documentation first. Note that this buffer contains\n") + (insert "information that you might consider confidential. You\n") + (insert "are encouraged to read through it before sending it.\n") + (insert "\n") + (insert "Press C-c C-c to send this email.\n\n") + (insert "Please state the version of these programs you are using:\n\n") + (insert "RCS: \ndiff: \n\n") + + (let* ((stdout (save-excursion (set-buffer stdout-buffer) (buffer-string))) + (stderr (save-excursion (set-buffer stderr-buffer) (buffer-string))) + (errstr (if (eq err-buf 'STDOUT) stdout stderr)) + (errline-end (string-match "\n" errstr pos)) + (errline (substring errstr pos errline-end))) + (insert (format "Offending line (%d chars): >" (- errline-end pos))) + (insert errline) + (insert "<\n") + (insert "Sent to " (symbol-name err-buf) " at pos " (format "%d\n" pos)) + (if indicator + (insert "Optional args: \"" indicator "\".\n")) + (insert "\nEmacs-version: " (emacs-version) "\n") + (insert "Pcl-cvs Version: " + "@(#)OrigId: pcl-cvs.el,v 1.93 1993/05/31 22:44:00 ceder Exp\n") + (insert "CVS Version: " + "@(#)cvs/contrib/pcl-cvs:$Name: $:$Id: pcl-cvs.el,v 1.2 1996/04/14 20:09:45 kfogel Exp $\n\n") + (insert (format "--- Contents of stdout buffer (%d chars) ---\n" + (length stdout))) + (insert stdout) + (insert "--- End of stdout buffer ---\n") + (insert (format "--- Contents of stderr buffer (%d chars) ---\n" + (length stderr))) + (insert stderr) + (insert "--- End of stderr buffer ---\n") + (insert "\nEnd of bug report.\n") + (require 'sendmail) + (mail-mode) + (error "CVS parse error - please report this bug."))) + +;;---------- +(defun cvs-parse-update (stdout-buffer stderr-buffer) + "Parse the output from `cvs update'. + +Args: STDOUT-BUFFER STDERR-BUFFER. + +This functions parses the from `cvs update' (which should be +separated in its stdout- and stderr-components) and prints a +pretty representation of it in the *cvs* buffer. + +Signals an error if unexpected output was detected in the buffer." + + (let* ((head (cons 'dummy nil)) + (tail (cvs-parse-stderr stdout-buffer stderr-buffer + head default-directory)) + (root-dir default-directory)) + (cvs-parse-stdout stdout-buffer stderr-buffer tail root-dir) + (setq head (sort (cdr head) (function cvs-compare-fileinfos))) + (collection-clear cvs-cookie-handle) + (collection-append-cookies cvs-cookie-handle head) + (cvs-remove-stdout-shadows) + (if cvs-auto-remove-handled-directories + (cvs-remove-empty-directories)) + (set-buffer cvs-buffer-name) + (cvs-mode) + (goto-char (point-min)) + (tin-goto-previous cvs-cookie-handle (point-min) 1) + (setq default-directory root-dir))) + +;;---------- +(defun cvs-remove-stdout-shadows () + "Remove entries in the *cvs* buffer that comes from both stdout and stderr. +If there is two entries for a single file the second one should be +deleted. (Remember that sort uses a stable sort algorithm, so one can +be sure that the stderr entry is always first)." + + (collection-filter-tins cvs-cookie-handle + (function + (lambda (tin) + (not (cvs-shadow-entry-p tin)))))) + +;;---------- +(defun cvs-shadow-entry-p (tin) + "Return non-nil if TIN is a shadow entry. +Args: TIN. + +A TIN is a shadow entry if the previous tin contains the same file." + + (let* ((previous-tin (tin-previous cvs-cookie-handle tin)) + (curr (tin-cookie cvs-cookie-handle tin)) + (prev (and previous-tin + (tin-cookie cvs-cookie-handle previous-tin)))) + (and + prev curr + (string= (cvs-fileinfo->file-name prev) + (cvs-fileinfo->file-name curr)) + (string= (cvs-fileinfo->dir prev) + (cvs-fileinfo->dir curr)) + (or + (and (eq (cvs-fileinfo->type prev) 'CONFLICT) + (eq (cvs-fileinfo->type curr) 'CONFLICT)) + (and (eq (cvs-fileinfo->type prev) 'MERGED) + (eq (cvs-fileinfo->type curr) 'MODIFIED)) + (and (eq (cvs-fileinfo->type prev) 'REM-EXIST) + (eq (cvs-fileinfo->type curr) 'REMOVED)))))) + +;;---------- +(defun cvs-find-backup-file (filename &optional dirname) + "Look for a backup file for FILENAME, optionally in directory DIRNAME, and if +there is one, return the name of the first file found as a string." + + (if (eq dirname nil) + (setq dirname default-directory)) + (car (directory-files dirname nil (concat "^\\" cvs-bakprefix filename + "\\.")))) + +;;---------- +(defun cvs-find-backup-revision (filename) + "Take FILENAME as the name of a cvs backup file and return the revision of +that file as a string." + + (substring filename + (+ 1 (string-match "\\.\\([0-9.]+\\)$" filename)))) + +;;---------- +(defun cvs-parse-stderr (stdout-buffer stderr-buffer head dir) + "Parse the output from CVS that is written to stderr. +Args: STDOUT-BUFFER STDERR-BUFFER HEAD DIR + +STDOUT-BUFFER holds the output that cvs sent to stdout. It is only +used to create a bug report in case there is a parse error. +STDERR-BUFFER is the buffer that holds the output to parse. +HEAD is a cons-cell, the head of the list that is built. +DIR is the directory the `cvs update' was run in. + +This function returns the last cons-cell in the list that is built." + + (save-window-excursion + (set-buffer stderr-buffer) + (goto-char (point-min)) + (let ((current-dir dir) + (root-dir dir)) + + (while (< (point) (point-max)) + (cond + + ;; CVS is descending a subdirectory. + + ((looking-at + "^cvs \\(server\\|update\\): Updating \\(.*\\)$") + (setq current-dir + (cvs-get-current-dir + root-dir + (buffer-substring (match-beginning 2) (match-end 2)))) + (setcdr head (list (cvs-create-fileinfo + 'DIRCHANGE + current-dir + "." ; the old version had nil here??? + (buffer-substring (match-beginning 0) + (match-end 0))))) + (setq head (cdr head)) + (forward-line 1)) + + ;; File removed, since it is removed (by third party) in repository. + + ((or (looking-at + "^cvs \\(update\\|server\\): warning: \\(.*\\) is not (any longer) pertinent") + (looking-at + "^cvs \\(update\\|server\\): \\(.*\\) is no longer in the repository")) + + (setcdr head (list (cvs-create-fileinfo + 'CVS-REMOVED + current-dir + (file-name-nondirectory + (buffer-substring (match-beginning 2) + (match-end 2))) + (buffer-substring (match-beginning 0) + (match-end 0))))) + (setq head (cdr head)) + (forward-line 1)) + + ;; File removed by you, but recreated by cvs. Ignored. Will say + ;; "Updated" on the next line. + + ((looking-at + "^cvs \\(update\\|server\\): warning: .* was lost$") + (forward-line 1)) + + ;; File unknown for some reason. + ;; FIXME: is it really a good idea to add this as unknown here? + + ((looking-at + "cvs \\(update\\|server\\): nothing known about \\(.*\\)$") + (let ((filename (buffer-substring (match-beginning 2) + (match-end 2)))) + (if (file-directory-p filename) + (setcdr head (list (cvs-create-fileinfo + 'UNKNOWN-DIR + current-dir + "." + (buffer-substring (match-beginning 0) + (match-end 0))))) + (setcdr head (list (cvs-create-fileinfo + 'UNKNOWN + current-dir + (file-name-nondirectory filename) + (buffer-substring (match-beginning 0) + (match-end 0))))))) + (setq head (cdr head)) + (forward-line 1)) + + ;; A file that has been created by you, but added to the cvs + ;; repository by another. + + ((looking-at + "^cvs \\(update\\|server\\): move away \\(.*\\); it is in the way$") + (setcdr head (list (cvs-create-fileinfo + 'MOVE-AWAY + current-dir + (file-name-nondirectory + (buffer-substring (match-beginning 2) + (match-end 2))) + (buffer-substring (match-beginning 0) + (match-end 0))))) + (setq head (cdr head)) + (forward-line 1)) + + ;; Cvs waits for a lock. Ignore. + + ((looking-at + "^cvs \\(update\\|server\\): \\[..:..:..\\] waiting for .*lock in ") + (forward-line 1)) + + ;; File removed in repository, but edited by you. + + ((looking-at + "^cvs \\(update\\|server\\): conflict: \\(.*\\) is modified but no longer in the repository$") + (setcdr head (list + (cvs-create-fileinfo + 'REM-CONFLICT + current-dir + (file-name-nondirectory + (buffer-substring (match-beginning 2) + (match-end 2))) + (buffer-substring (match-beginning 0) + (match-end 0))))) + (setq head (cdr head)) + (forward-line 1)) + + ;; File removed in repository, but edited by someone else. + + ((looking-at + "^cvs \\(update\\|server\\): conflict: removed \\(.*\\) was modified by second party") + (setcdr head + (list + (cvs-create-fileinfo + 'MOD-CONFLICT + current-dir + (buffer-substring (match-beginning 1) + (match-end 1)) + (buffer-substring (match-beginning 0) + (match-end 0))))) + (setq head (cdr head)) + (forward-line 1)) + + ;; File removed in repository, but not in local directory. + + ((looking-at + "^cvs \\(update\\|server\\): \\(.*\\) should be removed and is still there") + (setcdr head + (list + (cvs-create-fileinfo + 'REM-EXIST + current-dir + (buffer-substring (match-beginning 2) + (match-end 2)) + (buffer-substring (match-beginning 0) + (match-end 0))))) + (setq head (cdr head)) + (forward-line 1)) + + ;; Error searching for repository + + ((looking-at + "^cvs \\(update\\|server\\): in directory ") + (let ((start (point))) + (forward-line 1) + (cvs-skip-line stdout-buffer stderr-buffer + (regexp-quote "cvs [update aborted]: there is no repository ")) + (setcdr head (list (cvs-create-fileinfo + 'REPOS-MISSING + current-dir + nil + (buffer-substring start (point))))) + (setq head (cdr head)))) + + ;; Silly warning from attempted conflict resolution. Ignored. + ;; FIXME: Should it be? + ;; eg.: "cvs update: cannot find revision APC-web-update in file .cvsignore" + ;; + ((looking-at + "^cvs \\(update\\|server\\): cannot find revision \\(.*\\) in file \\(.*\\)$") + (forward-line 1) + (message "%s" (buffer-substring (match-beginning 0) (match-end 0)))) + + ;; CVS has decided to merge someone elses changes into this document. + ;; About to start an rcsmerge operation... + ;; + ((looking-at + "^RCS file: ") + + ;; skip the "RCS file:" line... + (forward-line 1) + + (let ((complex-start (point)) + base-revision ; the first revision retrieved to merge from + head-revision ; the second revision retrieved to merge from + filename ; the name of the file being merged + backup-file ; the name of the backup of the working file + backup-revision) ; the revision of the original working file + + (setq base-revision + (cvs-skip-line stdout-buffer stderr-buffer + "^retrieving revision \\(.*\\)$" + 1)) + (setq head-revision + (cvs-skip-line stdout-buffer stderr-buffer + "^retrieving revision \\(.*\\)$" + 1)) + (setq filename + (cvs-skip-line stdout-buffer stderr-buffer + "^Merging differences between [0-9.]+ and [0-9.]+ into \\(.*\\)$" + 1)) + (setq backup-file + (cvs-find-backup-file filename current-dir)) + (setq backup-revision + (cvs-find-backup-revision backup-file)) + + ;; Was there a conflict during the merge? + + (cond + + ;;;; From CVS-1.3 & RCS-5.6.0.1 with GNU-Diffutils-2.5: + ;;;; "cvs update -j OLD-REV -j NEW-REV ." + ;; + ;; RCS file: /big/web-CVS/apc/cmd/Main/logout.sh,v + ;; retrieving revision 1.1.1.1 + ;; retrieving revision 1.1.1.2 + ;; Merging differences between 1.1.1.1 and 1.1.1.2 into logout.sh + ;; rcsmerge warning: overlaps during merge + + ((looking-at + ;; Allow both RCS 5.5 and 5.6. (5.6 prints "rcs" and " warning"). + "^\\(rcs\\)?merge[:]*\\( warning\\)?: \\((overlaps\\|conflicts\\) during merge$") + + ;; Yes, this is a conflict. + (cvs-skip-line stdout-buffer stderr-buffer + "^\\(rcs\\)?merge[:]*\\( warning\\)?: \\(overlaps\\|conflicts\\) during merge$") + + ;; this line doesn't seem to appear in all cases -- perhaps only + ;; in "-j A -j B" usage, in which case this indicates ???? + (cvs-skip-line stdout-buffer stderr-buffer + "^cvs \\(update\\|server\\): conflicts found in ") + + (let ((fileinfo + (cvs-create-fileinfo + 'CONFLICT current-dir + filename + (buffer-substring complex-start (point))))) + + ;; squirrel away info about the files that were retrieved for merging + (cvs-set-fileinfo->base-revision fileinfo base-revision) + (cvs-set-fileinfo->head-revision fileinfo head-revision) + (cvs-set-fileinfo->backup-revision fileinfo backup-revision) + (cvs-set-fileinfo->backup-file fileinfo backup-file) + + (setcdr head (list fileinfo)) + (setq head (cdr head)))) + + ;; Was it a conflict, and was RCS compiled without DIFF3_BIN, in + ;; which case this is a failed conflict resolution? + + ((looking-at + ;; Allow both RCS 5.5 and 5.6. (5.6 prints "rcs" and " warning"). + "^\\(rcs\\)?merge\\( warning\\)?: overlaps or other problems during merge$") + + (cvs-skip-line stdout-buffer stderr-buffer + "^\\(rcs\\)?merge\\( warning\\)?: overlaps or other problems during merge$") + (cvs-skip-line stdout-buffer stderr-buffer + "^cvs update: could not merge ") + (cvs-skip-line stdout-buffer stderr-buffer + "^cvs update: restoring .* from backup file ") + (let ((fileinfo + (cvs-create-fileinfo + 'CONFLICT current-dir + filename + (buffer-substring complex-start (point))))) + (setcdr head (list fileinfo)) + (setq head (cdr head)))) + + ;; Not a conflict; it must be a succesful merge. + + (t + (let ((fileinfo + (cvs-create-fileinfo + 'MERGED current-dir + filename + (buffer-substring complex-start (point))))) + (cvs-set-fileinfo->base-revision fileinfo base-revision) + (cvs-set-fileinfo->head-revision fileinfo head-revision) + (cvs-set-fileinfo->backup-revision fileinfo backup-revision) + (cvs-set-fileinfo->backup-file fileinfo backup-file) + (setcdr head (list fileinfo)) + (setq head (cdr head))))))) + + ;; Error messages from CVS (incomplete) + + ((looking-at + "^cvs \\(update\\|server\\): \\(invalid option .*\\)$") + (error "Interface problem with CVS: %s" + (buffer-substring (match-beginning 2) (match-end 2)))) + + ;; network errors + + ;; Kerberos connection attempted but failed. This is not + ;; really an error, as CVS will automatically fall back to + ;; rsh. Plus it tries kerberos, if available, even when rsh + ;; is what you really wanted. + + ((looking-at + "^cvs update: kerberos connect:.*$") + (forward-line 1) + (message "Remote CVS: %s" + (buffer-substring (match-beginning 0) (match-end 0)))) + + ;; And when kerberos *does* fail, cvs prints out some stuff + ;; as it tries rsh. Ignore that stuff too. + + ((looking-at + "^cvs update: trying to start server using rsh$") + (forward-line 1)) + + ((looking-at + "^\\([^:]*\\) Connection timed out") + (error "Remote CVS: %s" + (buffer-substring (match-beginning 0) (match-end 0)))) + + ((looking-at + "^Permission denied.") + (error "Remote CVS: %s" + (buffer-substring (match-beginning 0) (match-end 0)))) + + ((looking-at + "^cvs \\[update aborted\\]: premature end of file from server") + (error "Remote CVS: %s" + (buffer-substring (match-beginning 0) (match-end 0)))) + + ;; Empty line. Probably inserted by mistake by user (or developer :-) + ;; Ignore. + + ((looking-at + "^$") + (forward-line 1)) + + ;; top-level parser (cond) default clause + + (t + (cvs-skip-line stdout-buffer stderr-buffer + "^UN-MATCHABLE-OUTPUT")))))) + + ;; cause this function to return the head of the parser output list + head) + +;;---------- +(defun cvs-parse-stdout (stdout-buffer stderr-buffer head root-dir) + "Parse the output from CVS that is written to stderr. +Args: STDOUT-BUFFER STDERR-BUFFER HEAD ROOT-DIR + +STDOUT-BUFFER is the buffer that holds the output to parse. +STDERR-BUFFER holds the output that cvs sent to stderr. It is only +used to create a bug report in case there is a parse error. + +HEAD is a cons-cell, the head of the list that is built. +ROOT-DIR is the directory the `cvs update' was run in. + +This function doesn't return anything particular." + + (save-window-excursion + (set-buffer stdout-buffer) + (goto-char (point-min)) + (while (< (point) (point-max)) + (cond + + ;; M: The file is modified by the user, and untouched in the repository. + ;; A: The file is "cvs add"ed, but not "cvs ci"ed. + ;; R: The file is "cvs remove"ed, but not "cvs ci"ed. + ;; C: Conflict (only useful if a join was done and stderr has info...) + ;; U: The file is copied from the repository. + ;; ?: Unknown file or directory. + + ((looking-at + "^\\([MARCUP?]\\) \\(.*\\)$") + (let* + ((c (char-after (match-beginning 1))) + (full-path (concat (file-name-as-directory root-dir) + (buffer-substring (match-beginning 2) + (match-end 2)))) + (isdir (file-directory-p full-path)) + (fileinfo (cvs-create-fileinfo + (cond ((eq c ?M) 'MODIFIED) + ((eq c ?A) 'ADDED) + ((eq c ?R) 'REMOVED) + ((eq c ?C) 'CONFLICT) + ((eq c ?U) 'UPDATED) + ((eq c ?P) 'PATCHED) + ((eq c ??) (if isdir + 'UNKNOWN-DIR + 'UNKNOWN))) + (substring (file-name-directory full-path) 0 -1) + (file-name-nondirectory full-path) + (buffer-substring (match-beginning 0) (match-end 0))))) + ;; Updated and Patched files require no further action. + (if (memq c '(?U ?P)) + (cvs-set-fileinfo->handled fileinfo t)) + + ;; Link this last on the list. + (setcdr head (list fileinfo)) + (setq head (cdr head)) + (forward-line 1))) + + ;; Executing a program because of the -u option in modules. + ((looking-at + "^cvs \\(update\\|server\\): Executing") + ;; Skip by any output the program may generate to stdout. + ;; Note that pcl-cvs will get seriously confused if the + ;; program prints anything to stderr. + (re-search-forward cvs-update-prog-output-skip-regexp) + (forward-line 1)) + + (t + (cvs-parse-error stdout-buffer stderr-buffer 'STDOUT (point) + "cvs-parse-stdout")))))) + +;;---------- +(defun cvs-pp (fileinfo) + "Pretty print FILEINFO. Insert a printed representation in current buffer. +For use by the cookie package." + + (let ((a (cvs-fileinfo->type fileinfo)) + (s (if (cvs-fileinfo->marked fileinfo) + "*" " ")) + (f (cvs-fileinfo->file-name fileinfo)) + (ci (if (cvs-fileinfo->handled fileinfo) + " " "ci"))) + (insert + (cond + ((eq a 'UPDATED) + (format "%s Updated %s" s f)) + ((eq a 'PATCHED) + (format "%s Patched %s" s f)) + ((eq a 'MODIFIED) + (format "%s Modified %s %s" s ci f)) + ((eq a 'MERGED) + (format "%s Merged %s %s" s ci f)) + ((eq a 'CONFLICT) + (format "%s Conflict %s" s f)) + ((eq a 'ADDED) + (format "%s Added %s %s" s ci f)) + ((eq a 'REMOVED) + (format "%s Removed %s %s" s ci f)) + ((eq a 'UNKNOWN) + (format "%s Unknown %s" s f)) + ((eq a 'UNKNOWN-DIR) + (format "%s Unknown dir %s" s f)) + ((eq a 'CVS-REMOVED) + (format "%s Removed from repository: %s" s f)) + ((eq a 'REM-CONFLICT) + (format "%s Conflict: Removed from repository, changed by you: %s" s f)) + ((eq a 'MOD-CONFLICT) + (format "%s Conflict: Removed by you, changed in repository: %s" s f)) + ((eq a 'REM-EXIST) + (format "%s Conflict: Removed by you, but still exists: %s" s f)) + ((eq a 'DIRCHANGE) + (format "\nIn directory %s:" (cvs-fileinfo->dir fileinfo))) + ((eq a 'MOVE-AWAY) + (format "%s Move away %s - it is in the way" s f)) + ((eq a 'REPOS-MISSING) + (format " This repository directory is missing! Remove this directory manually.")) + ((eq a 'MESSAGE) + (cvs-fileinfo->full-log fileinfo)) + (t + (format "%s Internal error! %s" s f)))))) + + +;;; You can define your own keymap in .emacs. pcl-cvs.el won't overwrite it. + +(if cvs-mode-map + nil + (setq cvs-mode-map (make-keymap)) + (suppress-keymap cvs-mode-map) + (define-prefix-command 'cvs-mode-map-control-c-prefix) + (define-key cvs-mode-map "\C-?" 'cvs-mode-unmark-up) + (define-key cvs-mode-map "\C-k" 'cvs-mode-acknowledge) + (define-key cvs-mode-map "\C-n" 'cvs-mode-next-line) + (define-key cvs-mode-map "\C-p" 'cvs-mode-previous-line) + ;; ^C- keys are used to set various flags to control CVS features + (define-key cvs-mode-map "\C-c" 'cvs-mode-map-control-c-prefix) + (define-key cvs-mode-map "\C-c\C-c" 'cvs-change-cvsroot) + (define-key cvs-mode-map "\C-c\C-d" 'cvs-set-diff-flags) + (define-key cvs-mode-map "\C-c\C-l" 'cvs-set-log-flags) + (define-key cvs-mode-map "\C-c\C-s" 'cvs-set-status-flags) + (define-key cvs-mode-map "\C-c\C-u" 'cvs-set-update-optional-flags) + ;; M- keys are usually those that operate on modules + (define-key cvs-mode-map "\M-\C-?" 'cvs-mode-unmark-all-files) + (define-key cvs-mode-map "\M-C" 'cvs-mode-rcs2log) ; i.e. "Create a ChangeLog" + (define-key cvs-mode-map "\M-a" 'cvs-mode-admin) + (define-key cvs-mode-map "\M-c" 'cvs-mode-checkout) + (define-key cvs-mode-map "\M-o" 'cvs-mode-checkout-other-window) + (define-key cvs-mode-map "\M-p" 'cvs-mode-rdiff) ; i.e. "create a Patch" + (define-key cvs-mode-map "\M-r" 'cvs-mode-release) + (define-key cvs-mode-map "\M-t" 'cvs-mode-rtag) + ;; keys that operate on files + (define-key cvs-mode-map " " 'cvs-mode-next-line) + (define-key cvs-mode-map "?" 'describe-mode) + (define-key cvs-mode-map "A" 'cvs-mode-add-change-log-entry-other-window) + (define-key cvs-mode-map "B" 'cvs-mode-byte-compile-files) + (define-key cvs-mode-map "C" 'cvs-mode-changelog-commit) + (define-key cvs-mode-map "E" 'cvs-mode-emerge) + (define-key cvs-mode-map "G" 'cvs-update) + (define-key cvs-mode-map "M" 'cvs-mode-mark-all-files) + (define-key cvs-mode-map "Q" 'cvs-examine) + (define-key cvs-mode-map "R" 'cvs-mode-revert-updated-buffers) + (define-key cvs-mode-map "U" 'cvs-mode-undo-local-changes) + (define-key cvs-mode-map "a" 'cvs-mode-add) + (define-key cvs-mode-map "b" 'cvs-mode-diff-backup) + (define-key cvs-mode-map "c" 'cvs-mode-commit) + (define-key cvs-mode-map "d" 'cvs-mode-diff-cvs) + (define-key cvs-mode-map "e" 'cvs-mode-ediff) + (define-key cvs-mode-map "f" 'cvs-mode-find-file) + (define-key cvs-mode-map "g" 'cvs-mode-update-no-prompt) + (define-key cvs-mode-map "i" 'cvs-mode-ignore) + (define-key cvs-mode-map "l" 'cvs-mode-log) + (define-key cvs-mode-map "m" 'cvs-mode-mark) + (define-key cvs-mode-map "n" 'cvs-mode-next-line) + (define-key cvs-mode-map "o" 'cvs-mode-find-file-other-window) + (define-key cvs-mode-map "p" 'cvs-mode-previous-line) + (define-key cvs-mode-map "q" 'bury-buffer) + (define-key cvs-mode-map "r" 'cvs-mode-remove-file) + (define-key cvs-mode-map "s" 'cvs-mode-status) + (define-key cvs-mode-map "t" 'cvs-mode-tag) + (define-key cvs-mode-map "u" 'cvs-mode-unmark) + (define-key cvs-mode-map "v" 'cvs-mode-diff-vendor) + (define-key cvs-mode-map "x" 'cvs-mode-remove-handled)) + +;;---------- +(defun cvs-get-marked (&optional ignore-marks ignore-contents) + "Return a list of all selected tins. +Args: &optional IGNORE-MARKS IGNORE-CONTENTS. + +If there are any marked tins, and IGNORE-MARKS is nil, return them. Otherwise, +if the cursor selects a directory, return all files in it, unless there are +none, in which case just return the directory; or unless IGNORE-CONTENTS is not +nil, in which case also just return the directory. Otherwise return (a list +containing) the file the cursor points to, or an empty list if it doesn't point +to a file at all." + + (cond + ;; Any marked cookies? + ((and (not ignore-marks) + (collection-collect-tin cvs-cookie-handle 'cvs-fileinfo->marked))) + ;; Nope. + ((and (not ignore-contents) + (let ((sel (tin-locate cvs-cookie-handle (point)))) + (cond + ;; If a directory is selected, all it members are returned. + ((and sel (eq (cvs-fileinfo->type (tin-cookie cvs-cookie-handle + sel)) + 'DIRCHANGE)) + (let ((retsel + (collection-collect-tin cvs-cookie-handle + 'cvs-dir-member-p + (cvs-fileinfo->dir (tin-cookie + cvs-cookie-handle sel))))) + (if retsel + retsel + (list sel)))) + (t + (list sel)))))) + (t + (list (tin-locate cvs-cookie-handle (point)))))) + +;;---------- +(defun cvs-dir-member-p (fileinfo dir) + "Return true if FILEINFO represents a file in directory DIR." + + (and (not (eq (cvs-fileinfo->type fileinfo) 'DIRCHANGE)) + (string= (cvs-fileinfo->dir fileinfo) dir))) + +;;---------- +(defun cvs-dir-empty-p (tin) + "Return non-nil if TIN is a directory that is empty. +Args: CVS-BUF TIN." + + (and (eq (cvs-fileinfo->type (tin-cookie cvs-cookie-handle tin)) 'DIRCHANGE) + (or (not (tin-next cvs-cookie-handle tin)) + (eq (cvs-fileinfo->type + (tin-cookie cvs-cookie-handle + (tin-next cvs-cookie-handle tin))) + 'DIRCHANGE)))) + +;;---------- +(defun cvs-mode-revert-updated-buffers () + "Revert any buffers that are UPDATED, PATCHED, MERGED or CONFLICT." + + (interactive) + (cookie-map (function cvs-revert-fileinfo) cvs-cookie-handle)) + +;;---------- +(defun cvs-revert-fileinfo (fileinfo) + "Revert the buffer that holds the file in FILEINFO if it has changed, +and if the type is UPDATED, PATCHED, MERGED or CONFLICT." + + (let* ((type (cvs-fileinfo->type fileinfo)) + (file (cvs-fileinfo->full-path fileinfo)) + (buffer (get-file-buffer file))) + ;; For a revert to happen... + (cond + ((and + ;; ...the type must be one that justifies a revert... + (or (eq type 'UPDATED) + (eq type 'PATCHED) + (eq type 'MERGED) + (eq type 'CONFLICT)) + ;; ...and the user must be editing the file... + buffer) + (save-excursion + (set-buffer buffer) + (cond + ((buffer-modified-p) + (error "%s: edited since last cvs-update." + (buffer-file-name))) + ;; Go ahead and revert the file. + (t (revert-buffer 'dont-use-auto-save-file 'dont-ask)))))))) + +;;---------- +(defun cvs-mode-remove-handled () + "Remove all lines that are handled. +Empty directories are removed." + + (interactive) + ;; Pass one: remove files that are handled. + (collection-filter-cookies cvs-cookie-handle + (function + (lambda (fileinfo) + (not (cvs-fileinfo->handled fileinfo))))) + ;; Pass two: remove empty directories. + (if cvs-auto-remove-handled-directories + (cvs-remove-empty-directories))) + +;;---------- +(defun cvs-remove-empty-directories () + "Remove empty directories in the *cvs* buffer." + + (collection-filter-tins cvs-cookie-handle + (function + (lambda (tin) + (not (cvs-dir-empty-p tin)))))) + +;;---------- +(defun cvs-mode-mark (pos) + "Mark a fileinfo. +Args: POS. + +If the fileinfo is a directory, all the contents of that directory are marked +instead. A directory can never be marked. POS is a buffer position." + + (interactive "d") + (let* ((tin (tin-locate cvs-cookie-handle pos)) + (sel (tin-cookie cvs-cookie-handle tin))) + (cond + ;; Does POS point to a directory? If so, mark all files in that directory. + ((eq (cvs-fileinfo->type sel) 'DIRCHANGE) + (cookie-map + (function (lambda (f dir) + (cond + ((cvs-dir-member-p f dir) + (cvs-set-fileinfo->marked f t) + t)))) ; Tell cookie to redisplay this cookie. + cvs-cookie-handle + (cvs-fileinfo->dir sel))) + (t + (cvs-set-fileinfo->marked sel t) + (tin-invalidate cvs-cookie-handle tin) + (tin-goto-next cvs-cookie-handle pos 1))))) + +;;---------- +(defun cvs-committable (tin) + "Check if the TIN is committable. +It is committable if it + a) is not handled and + b) is either MODIFIED, ADDED, REMOVED, MERGED or CONFLICT." + + (let* ((fileinfo (tin-cookie cvs-cookie-handle tin)) + (type (cvs-fileinfo->type fileinfo))) + (and (not (cvs-fileinfo->handled fileinfo)) + (or (eq type 'MODIFIED) + (eq type 'ADDED) + (eq type 'REMOVED) + (eq type 'MERGED) + (eq type 'CONFLICT))))) + +;;---------- +(defun cvs-mode-commit () + "Check in all marked files, or the current file. +The user will be asked for a log message in a buffer. +If cvs-erase-input-buffer is non-nil that buffer will be erased. +Otherwise mark and point will be set around the entire contents of the +buffer so that it is easy to kill the contents of the buffer with \\[kill-region]." + + (interactive) + (let* ((cvs-buf (current-buffer)) + (marked (cvs-filter (function cvs-committable) + (cvs-get-marked)))) + (if (null marked) + (error "Nothing to commit!") + (pop-to-buffer (get-buffer-create cvs-commit-prompt-buffer)) + (goto-char (point-min)) + + (if cvs-erase-input-buffer + (erase-buffer) + (push-mark (point-max))) + (cvs-edit-mode) + (make-local-variable 'cvs-commit-list) + (setq cvs-commit-list marked) + (message "Press C-c C-c when you are done editing.")))) + +;;---------- +(defun cvs-edit-done () + "Commit the files to the repository." + + (interactive) + (if (null cvs-commit-list) + (error "You have already committed the files")) + (if (and (> (point-max) 1) + (/= (char-after (1- (point-max))) ?\n) + (or (eq cvs-commit-buffer-require-final-newline t) + (and cvs-commit-buffer-require-final-newline + (yes-or-no-p + (format "Buffer %s does not end in newline. Add one? " + (buffer-name)))))) + (save-excursion + (goto-char (point-max)) + (insert ?\n))) + (save-some-buffers) + (let ((cc-list cvs-commit-list) + (cc-buffer (get-buffer cvs-buffer-name)) + (msg-buffer (current-buffer)) + (msg (buffer-substring (point-min) (point-max)))) + (pop-to-buffer cc-buffer) + (bury-buffer msg-buffer) + (cvs-use-temp-buffer) + (message "Committing...") + (if (cvs-execute-list cc-list cvs-program + (if cvs-cvsroot + (list "-d" cvs-cvsroot "commit" "-m" msg) + (list "commit" "-m" msg)) + "Committing %s...") + (error "Something went wrong. Check the %s buffer carefully." + cvs-temp-buffer-name)) + ;; FIXME: don't do any of this if the commit fails. + (let ((ccl cc-list)) + (while ccl + (cvs-after-commit-function (tin-cookie cvs-cookie-handle (car ccl))) + (setq ccl (cdr ccl)))) + (apply 'tin-invalidate cvs-cookie-handle cc-list) + (set-buffer msg-buffer) + (setq cvs-commit-list nil) + (set-buffer cc-buffer) + (if cvs-auto-remove-handled + (cvs-mode-remove-handled))) + + (message "Committing... Done.")) + +;;---------- +(defun cvs-after-commit-function (fileinfo) + "Do everything that needs to be done when FILEINFO has been committed. +The fileinfo->handle is set, and if the buffer is present it is reverted." + + (cvs-set-fileinfo->handled fileinfo t) + (if cvs-auto-revert-after-commit + (let* ((file (cvs-fileinfo->full-path fileinfo)) + (buffer (get-file-buffer file))) + ;; For a revert to happen... + (if buffer + ;; ...the user must be editing the file... + (save-excursion + (set-buffer buffer) + (if (not (buffer-modified-p)) + ;; ...but it must be unmodified. + (revert-buffer 'dont-use-auto-save-file 'dont-ask))))))) + +;;---------- +(defun cvs-execute-list (tin-list program constant-args &optional message-fmt) + "Run PROGRAM on all elements on TIN-LIST. +Args: TIN-LIST PROGRAM CONSTANT-ARGS. + +The PROGRAM will be called with pwd set to the directory the files reside +in. CONSTANT-ARGS should be a list of strings. The arguments given to the +program will be CONSTANT-ARGS followed by all the files (from TIN-LIST) that +resides in that directory. If the files in TIN-LIST resides in different +directories the PROGRAM will be run once for each directory (if all files in +the same directory appears after each other). + +Any output from PROGRAM will be inserted in the current buffer. + +This function return nil if all went well, or the numerical exit status or a +signal name as a string. Note that PROGRAM might be called several times. This +will return non-nil if something goes wrong, but there is no way to know which +process that failed. + +If MESSAGE-FMT is not nil, then message is called to display progress with +MESSAGE-FMT as the string. MESSAGE-FMT should contain one %s for the arg-list +being passed to PROGRAM." + + ;; FIXME: something seems wrong with the error checking here.... + + (let ((exitstatus nil)) + (while tin-list + (let ((current-dir (cvs-fileinfo->dir (tin-cookie cvs-cookie-handle + (car tin-list)))) + arg-list + arg-str) + + ;; Collect all marked files in this directory. + + (while (and tin-list + (string= current-dir + (cvs-fileinfo->dir (tin-cookie cvs-cookie-handle + (car tin-list))))) + (setq arg-list + (cons (cvs-fileinfo->file-name + (tin-cookie cvs-cookie-handle (car tin-list))) + arg-list)) + (setq tin-list (cdr tin-list))) + + (setq arg-list (nreverse arg-list)) + + ;; Execute the command on all the files that were collected. + + (if message-fmt + (message message-fmt + (mapconcat 'cvs-quote-multiword-string + arg-list + " "))) + (setq default-directory (file-name-as-directory current-dir)) + (insert (format "=== cd %s\n" default-directory)) + (insert (format "=== %s %s\n\n" + program + (mapconcat 'cvs-quote-multiword-string + (nconc (copy-sequence constant-args) + arg-list) + " "))) + (let ((res (apply 'call-process program nil t t + (nconc (copy-sequence constant-args) arg-list)))) + ;; Remember the first, or highest, exitstatus. + (if (and (not (and (integerp res) (zerop res))) + (or (null exitstatus) + (and (integerp exitstatus) (= 1 exitstatus)))) + (setq exitstatus res))) + (goto-char (point-max)) + (if message-fmt + (message message-fmt + (mapconcat 'cvs-quote-multiword-string + (nconc (copy-sequence arg-list) '("Done.")) + " "))) + exitstatus)))) + +;;---------- +;;;; +++ not currently used! +(defun cvs-execute-single-file-list (tin-list extractor program constant-args + &optional cleanup message-fmt) + "Run PROGRAM on all elements on TIN-LIST. +Args: TIN-LIST EXTRACTOR PROGRAM CONSTANT-ARGS &optional CLEANUP. + +The PROGRAM will be called with pwd set to the directory the files +reside in. CONSTANT-ARGS is a list of strings to pass as arguments to +PROGRAM. The arguments given to the program will be CONSTANT-ARGS +followed by the list that EXTRACTOR returns. + +EXTRACTOR will be called once for each file on TIN-LIST. It is given +one argument, the cvs-fileinfo. It can return t, which means ignore +this file, or a list of arguments to send to the program. + +If CLEANUP is not nil, the filenames returned by EXTRACTOR are deleted. + +If MESSAGE-FMT is not nil, then message is called to display progress with +MESSAGE-FMT as the string. MESSAGE-FMT should contain one %s for the arg-list +being passed to PROGRAM." + + (while tin-list + (let ((current-dir (file-name-as-directory + (cvs-fileinfo->dir + (tin-cookie cvs-cookie-handle + (car tin-list))))) + (arg-list + (funcall extractor + (tin-cookie cvs-cookie-handle (car tin-list))))) + + ;; Execute the command unless extractor returned t. + + (if (eq arg-list t) + nil + (setq default-directory current-dir) + (insert (format "=== cd %s\n" default-directory)) + (insert (format "=== %s %s\n\n" + program + (mapconcat 'cvs-quote-multiword-string + (nconc (copy-sequence constant-args) + arg-list) + " "))) + (if message-fmt + (message message-fmt (mapconcat 'cvs-quote-multiword-string + arg-list + " "))) + (apply 'call-process program nil t t + (nconc (copy-sequence constant-args) arg-list)) + (goto-char (point-max)) + (if message-fmt + (message message-fmt (mapconcat 'cvs-quote-multiword-string + (nconc arg-list '("Done.")) + " "))) + (if cleanup + (while arg-list +;;;; (kill-buffer ?????) + (delete-file (car arg-list)) + (setq arg-list (cdr arg-list)))))) + (setq tin-list (cdr tin-list)))) + +;;---------- +(defun cvs-edit-mode () + "\\<cvs-edit-mode-map>Mode for editing cvs log messages. +Commands: +\\[cvs-edit-done] checks in the file when you are ready. +This mode is based on fundamental mode." + + (interactive) + (use-local-map cvs-edit-mode-map) + (setq major-mode 'cvs-edit-mode) + (setq mode-name "CVS Log") + (auto-fill-mode 1)) + +;;---------- +(if cvs-edit-mode-map + nil + (setq cvs-edit-mode-map (make-sparse-keymap)) + (define-prefix-command 'cvs-edit-mode-control-c-prefix) + (define-key cvs-edit-mode-map "\C-c" 'cvs-edit-mode-control-c-prefix) + (define-key cvs-edit-mode-map "\C-c\C-c" 'cvs-edit-done)) + +;;---------- +(defun cvs-diffable (tins) + "Return a list of all tins on TINS that it makes sense to run +``cvs diff'' on." + + ;; +++ There is an unnecessary (nreverse) here. Get the list the + ;; other way around instead! + (let ((result nil)) + (while tins + (let ((type (cvs-fileinfo->type + (tin-cookie cvs-cookie-handle (car tins))))) + (if (or (eq type 'MODIFIED) + (eq type 'UPDATED) + (eq type 'PATCHED) + (eq type 'MERGED) + (eq type 'CONFLICT) + (eq type 'REMOVED) ;+++Does this line make sense? + (eq type 'ADDED)) ;+++Does this line make sense? + (setq result (cons (car tins) result))) + (setq tins (cdr tins)))) + (nreverse result))) + +;;---------- +(defun cvs-mode-diff-cvs (&optional ignore-marks) + "Diff the selected files against the head revisions in the repository. + +If the variable cvs-diff-ignore-marks is non-nil any marked files will not be +considered to be selected. An optional prefix argument will invert the +influence from cvs-diff-ignore-marks. + +The flags in the variable cvs-diff-flags will be passed to ``cvs diff''. + +The resulting diffs are placed in the cvs-fileinfo->cvs-diff-buffer." + + (interactive "P") + (if (not (listp cvs-diff-flags)) + (error "cvs-diff-flags should be set using cvs-set-diff-flags.")) + (save-some-buffers) + (message "cvsdiffing...") + (let ((marked-file-list (cvs-diffable + (cvs-get-marked + (or (and ignore-marks (not cvs-diff-ignore-marks)) + (and (not ignore-marks) cvs-diff-ignore-marks)))))) + (while marked-file-list + (let ((fileinfo-to-diff (tin-cookie cvs-cookie-handle + (car marked-file-list))) + (local-def-directory (file-name-as-directory + (cvs-fileinfo->dir + (tin-cookie cvs-cookie-handle + (car marked-file-list)))))) + (message "cvsdiffing %s..." + (cvs-fileinfo->file-name fileinfo-to-diff)) + + ;; FIXME: this seems messy to test and set buffer name at this point.... + (if (not (cvs-fileinfo->cvs-diff-buffer fileinfo-to-diff)) + (cvs-set-fileinfo->cvs-diff-buffer fileinfo-to-diff + (concat "*cvs-diff-" + (cvs-fileinfo->file-name + fileinfo-to-diff) + "-in-" + local-def-directory + "*"))) + (display-buffer (get-buffer-create + (cvs-fileinfo->cvs-diff-buffer fileinfo-to-diff))) + (set-buffer (cvs-fileinfo->cvs-diff-buffer fileinfo-to-diff)) + (setq buffer-read-only nil) + (setq default-directory local-def-directory) + (erase-buffer) + (insert (format "=== cd %s\n" default-directory)) + (insert (format "=== cvs %s\n\n" + (mapconcat 'cvs-quote-multiword-string + (nconc (if cvs-cvsroot + (list "-d" cvs-cvsroot "diff") + '("diff")) + (copy-sequence cvs-diff-flags) + (list (cvs-fileinfo->file-name + fileinfo-to-diff))) + " "))) + (if (apply 'call-process cvs-program nil t t + (nconc (if cvs-cvsroot + (list "-d" cvs-cvsroot "diff") + '("diff")) + (copy-sequence cvs-diff-flags) + (list (cvs-fileinfo->file-name fileinfo-to-diff)))) + (message "cvsdiffing %s... Done." + (cvs-fileinfo->file-name fileinfo-to-diff)) + (message "cvsdiffing %s... No differences found." + (cvs-fileinfo->file-name fileinfo-to-diff))) + (goto-char (point-max)) + (setq marked-file-list (cdr marked-file-list))))) + (message "cvsdiffing... Done.")) + +;;---------- +(defun cvs-mode-diff-backup (&optional ignore-marks) + "Diff the files against the backup file. +This command can be used on files that are marked with \"Merged\" +or \"Conflict\" in the *cvs* buffer. + +If the variable cvs-diff-ignore-marks is non-nil any marked files will +not be considered to be selected. An optional prefix argument will +invert the influence from cvs-diff-ignore-marks. + +The flags in cvs-diff-flags will be passed to ``diff''. + +The resulting diffs are placed in the cvs-fileinfo->backup-diff-buffer." + + (interactive "P") + (if (not (listp cvs-diff-flags)) + (error "cvs-diff-flags should be set using cvs-set-diff-flags.")) + (save-some-buffers) + (let ((marked-file-list (cvs-filter + (function cvs-backup-diffable) + (cvs-get-marked + (or + (and ignore-marks (not cvs-diff-ignore-marks)) + (and (not ignore-marks) cvs-diff-ignore-marks)))))) + (if (null marked-file-list) + (error "No ``Conflict'' or ``Merged'' file selected!")) + (message "backup diff...") + (while marked-file-list + (let ((fileinfo-to-diff (tin-cookie cvs-cookie-handle + (car marked-file-list))) + (local-def-directory (file-name-as-directory + (cvs-fileinfo->dir + (tin-cookie cvs-cookie-handle + (car marked-file-list))))) + (backup-temp-files (cvs-diff-backup-extractor + (tin-cookie cvs-cookie-handle + (car marked-file-list))))) + (message "backup diff %s..." + (cvs-fileinfo->file-name fileinfo-to-diff)) + + ;; FIXME: this seems messy to test and set buffer name at this point.... + (if (not (cvs-fileinfo->backup-diff-buffer fileinfo-to-diff)) + (cvs-set-fileinfo->backup-diff-buffer fileinfo-to-diff + (concat "*cvs-diff-" + (cvs-fileinfo->backup-file + fileinfo-to-diff) + "-to-" + (cvs-fileinfo->file-name + fileinfo-to-diff) + "-in" + local-def-directory + "*"))) + (display-buffer (get-buffer-create + (cvs-fileinfo->backup-diff-buffer fileinfo-to-diff))) + (set-buffer (cvs-fileinfo->backup-diff-buffer fileinfo-to-diff)) + (setq buffer-read-only nil) + (setq default-directory local-def-directory) + (erase-buffer) + (insert (format "=== cd %s\n" default-directory)) + (insert (format "=== %s %s\n\n" + cvs-diff-program + (mapconcat 'cvs-quote-multiword-string + (nconc (copy-sequence cvs-diff-flags) + backup-temp-files) + " "))) + (apply 'call-process cvs-diff-program nil t t + (nconc (copy-sequence cvs-diff-flags) backup-temp-files)) + (goto-char (point-max)) + (message "backup diff %s... Done." + (cvs-fileinfo->file-name fileinfo-to-diff)) + (setq marked-file-list (cdr marked-file-list))))) + (message "backup diff... Done.")) + +;;---------- +(defun cvs-mode-diff-vendor (&optional ignore-marks) + "Diff the revisions merged into the current file. I.e. show what changes +were merged in. + +This command can be used on files that are marked with \"Merged\" +or \"Conflict\" in the *cvs* buffer. + +If the variable cvs-diff-ignore-marks is non-nil any marked files will +not be considered to be selected. An optional prefix argument will +invert the influence from cvs-diff-ignore-marks. + +The flags in cvs-diff-flags will be passed to ``diff''. + +The resulting diffs are placed in the cvs-fileinfo->vendor-diff-buffer." + + (interactive "P") + (if (not (listp cvs-diff-flags)) + (error "cvs-diff-flags should be set using cvs-set-diff-flags.")) + (save-some-buffers) + (let ((marked-file-list (cvs-filter + (function cvs-vendor-diffable) + (cvs-get-marked + (or + (and ignore-marks (not cvs-diff-ignore-marks)) + (and (not ignore-marks) cvs-diff-ignore-marks)))))) + (if (null marked-file-list) + (error "No ``Conflict'' or ``Merged'' file selected!")) + (message "vendor diff...") + (while marked-file-list + (let ((fileinfo-to-diff (tin-cookie cvs-cookie-handle + (car marked-file-list))) + (local-def-directory (file-name-as-directory + (cvs-fileinfo->dir + (tin-cookie cvs-cookie-handle + (car marked-file-list))))) + (vendor-temp-files (cvs-diff-vendor-extractor + (tin-cookie cvs-cookie-handle + (car marked-file-list))))) + (message "vendor diff %s..." + (cvs-fileinfo->file-name fileinfo-to-diff)) + (if (not (cvs-fileinfo->vendor-diff-buffer fileinfo-to-diff)) + (cvs-set-fileinfo->vendor-diff-buffer fileinfo-to-diff + (concat "*cvs-diff-" + (cvs-fileinfo->file-name + fileinfo-to-diff) + "-of-" + (cvs-fileinfo->base-revision + fileinfo-to-diff) + "-to-" + (cvs-fileinfo->head-revision + fileinfo-to-diff) + "-in-" + local-def-directory + "*"))) + (display-buffer (get-buffer-create + (cvs-fileinfo->vendor-diff-buffer fileinfo-to-diff))) + (set-buffer (cvs-fileinfo->vendor-diff-buffer fileinfo-to-diff)) + (setq buffer-read-only nil) + (setq default-directory local-def-directory) + (erase-buffer) + (insert (format "=== cd %s\n" default-directory)) + (insert (format "=== %s %s\n\n" + cvs-diff-program + (mapconcat 'cvs-quote-multiword-string + (nconc (copy-sequence cvs-diff-flags) + vendor-temp-files) + " "))) + (apply 'call-process cvs-diff-program nil t t + (nconc (copy-sequence cvs-diff-flags) vendor-temp-files)) + (goto-char (point-max)) + (message "vendor diff %s... Done." + (cvs-fileinfo->file-name fileinfo-to-diff)) + (while vendor-temp-files + (cvs-kill-buffer-visiting (car vendor-temp-files)) + (delete-file (car vendor-temp-files)) + (setq vendor-temp-files (cdr vendor-temp-files))) + (setq marked-file-list (cdr marked-file-list))))) + (message "vendor diff... Done.")) + +;;---------- +(defun cvs-backup-diffable (tin) + "Check if the TIN is backup-diffable. +It must have a backup file to be diffable." + + (file-readable-p + (cvs-fileinfo->backup-file (tin-cookie cvs-cookie-handle tin)))) + +;;---------- +(defun cvs-vendor-diffable (tin) + "Check if the TIN is vendor-diffable. +It must have head and base revision info to be diffable." + + (and + (cvs-fileinfo->base-revision (tin-cookie cvs-cookie-handle tin)) + (cvs-fileinfo->head-revision (tin-cookie cvs-cookie-handle tin)))) + +;;---------- +(defun cvs-diff-backup-extractor (fileinfo) + "Return the filename and the name of the backup file as a list. +Signal an error if there is no backup file." + + (if (not (file-readable-p (cvs-fileinfo->backup-file fileinfo))) + (error "%s has no backup file." + (concat + (file-name-as-directory (cvs-fileinfo->dir fileinfo)) + (cvs-fileinfo->file-name fileinfo)))) + (list (cvs-fileinfo->backup-file fileinfo) + (cvs-fileinfo->file-name fileinfo))) + +;;---------- +(defun cvs-diff-vendor-extractor (fileinfo) + "Retrieve and return the filenames of the vendor branch revisions as a list. +Signal an error if there is no info for the vendor revisions." + + (list (cvs-retrieve-revision-to-tmpfile fileinfo + (cvs-fileinfo->base-revision + fileinfo)) + (cvs-retrieve-revision-to-tmpfile fileinfo + (cvs-fileinfo->head-revision + fileinfo)))) + +;;---------- +(defun cvs-mode-find-file-other-window (pos) + "Select a buffer containing the file in another window. +Args: POS." + + (interactive "d") + (let ((tin (tin-locate cvs-cookie-handle pos))) + (if tin + (let ((type (cvs-fileinfo->type (tin-cookie cvs-cookie-handle tin)))) + (cond + ((or (eq type 'REMOVED) + (eq type 'CVS-REMOVED)) + (error "Can't visit a removed file.")) + ((eq type 'DIRCHANGE) + (let ((obuf (current-buffer)) + (odir default-directory)) + (setq default-directory + (file-name-as-directory + (cvs-fileinfo->dir + (tin-cookie cvs-cookie-handle tin)))) + (dired-other-window default-directory) + (set-buffer obuf) + (setq default-directory odir))) + (t + (find-file-other-window (cvs-full-path tin))))) + (error "There is no file to find.")))) + +;;---------- +(defun cvs-fileinfo->full-path (fileinfo) + "Return the full path for the file that is described in FILEINFO." + + (concat + (file-name-as-directory + (cvs-fileinfo->dir fileinfo)) + (cvs-fileinfo->file-name fileinfo))) + +;;---------- +(defun cvs-full-path (tin) + "Return the full path for the file that is described in TIN." + + (cvs-fileinfo->full-path (tin-cookie cvs-cookie-handle tin))) + +;;---------- +(defun cvs-mode-find-file (pos) + "Select a buffer containing the file in another window. +Args: POS." + + (interactive "d") + (let* ((cvs-buf (current-buffer)) + (tin (tin-locate cvs-cookie-handle pos))) + (if tin + (let* ((fileinfo (tin-cookie cvs-cookie-handle tin)) + (type (cvs-fileinfo->type fileinfo))) + (cond + ((or (eq type 'REMOVED) + (eq type 'CVS-REMOVED)) + (error "Can't visit a removed file.")) + ((eq type 'DIRCHANGE) + (let ((odir default-directory)) + (setq default-directory + (file-name-as-directory (cvs-fileinfo->dir fileinfo))) + (dired default-directory) + (set-buffer cvs-buf) + (setq default-directory odir))) + (t + (find-file (cvs-full-path tin))))) + (error "There is no file to find.")))) + +;;---------- +(defun cvs-mode-mark-all-files () + "Mark all files. +Directories are not marked." + + (interactive) + (cookie-map (function (lambda (cookie) + (cond + ((not (eq (cvs-fileinfo->type cookie) 'DIRCHANGE)) + (cvs-set-fileinfo->marked cookie t) + t)))) + cvs-cookie-handle)) + +;;---------- +(defun cvs-mode-unmark (pos) + "Unmark a fileinfo. +Args: POS." + + (interactive "d") + (let* ((tin (tin-locate cvs-cookie-handle pos)) + (sel (tin-cookie cvs-cookie-handle tin))) + (cond + ((eq (cvs-fileinfo->type sel) 'DIRCHANGE) + (cookie-map + (function (lambda (f dir) + (cond + ((cvs-dir-member-p f dir) + (cvs-set-fileinfo->marked f nil) + t)))) + cvs-cookie-handle + (cvs-fileinfo->dir sel))) + (t + (cvs-set-fileinfo->marked sel nil) + (tin-invalidate cvs-cookie-handle tin) + (tin-goto-next cvs-cookie-handle pos 1))))) + +;;---------- +(defun cvs-mode-unmark-all-files () + "Unmark all files. +Directories are also unmarked, but that doesn't matter, since +they should always be unmarked." + + (interactive) + (cookie-map (function (lambda (cookie) + (cvs-set-fileinfo->marked cookie nil) + t)) + cvs-cookie-handle)) + +;;---------- +(defun cvs-do-removal (tins) + "Remove files. +Args: TINS. + +TINS is a list of tins that the user wants to delete. The files are deleted. +If the type of the tin is 'UNKNOWN or 'UNKNOWN-DIR the tin is removed from the +buffer. If it is anything else the file is added to a list that should be `cvs +remove'd and the tin is changed to be of type 'REMOVED. + +Returns a list of tins files that should be `cvs remove'd." + + (cvs-use-temp-buffer) + (mapcar 'cvs-insert-full-path tins) + (cond + ((and tins (yes-or-no-p (format "Delete %d files? " (length tins)))) + (let (files-to-remove) + (while tins + (let* ((tin (car tins)) + (fileinfo (tin-cookie cvs-cookie-handle tin)) + (filepath (cvs-full-path tin)) + (type (cvs-fileinfo->type fileinfo))) + (if (or (eq type 'REMOVED) + (eq type 'CVS-REMOVED)) + nil + ;; if it doesn't exist, as a file or directory, ignore it + (cond ((file-directory-p filepath) + (call-process cvs-rmdir-program nil nil nil filepath)) + ((file-exists-p filepath) + (delete-file filepath))) + (if (or (eq type 'UNKNOWN) + (eq type 'UNKNOWN-DIR) + (eq type 'MOVE-AWAY)) + (tin-delete cvs-cookie-handle tin) + (setq files-to-remove (cons tin files-to-remove)) + (cvs-set-fileinfo->type fileinfo 'REMOVED) + (cvs-set-fileinfo->handled fileinfo nil) + (tin-invalidate cvs-cookie-handle tin)))) + (setq tins (cdr tins))) + files-to-remove)) + (t nil))) + +;;---------- +(defun cvs-mode-remove-file () + "Remove all marked files." + + (interactive) + (let ((files-to-remove (cvs-do-removal (cvs-get-marked)))) + (if (null files-to-remove) + nil + (cvs-use-temp-buffer) + (message "removing from repository...") + (if (cvs-execute-list files-to-remove cvs-program + (if cvs-cvsroot + (list "-d" cvs-cvsroot "remove") + '("remove")) + "removing %s from repository...") + (error "CVS exited with non-zero exit status.") + (message "removing from repository... Done."))))) + +;;---------- +(defun cvs-mode-undo-local-changes () + "Undo local changes to all marked files. +The file is removed and `cvs update FILE' is run." + + (interactive) + (let ((tins-to-undo (cvs-get-marked))) + (cvs-use-temp-buffer) + (mapcar 'cvs-insert-full-path tins-to-undo) + (cond + ((and tins-to-undo (yes-or-no-p (format "Undo changes to %d files? " + (length tins-to-undo)))) + (let (files-to-update) + (while tins-to-undo + (let* ((tin (car tins-to-undo)) + (fileinfo (tin-cookie cvs-cookie-handle tin)) + (type (cvs-fileinfo->type fileinfo))) + (cond + ((or + (eq type 'UPDATED) + (eq type 'PATCHED) + (eq type 'MODIFIED) + (eq type 'MERGED) + (eq type 'CONFLICT) + (eq type 'CVS-REMOVED) + (eq type 'REM-CONFLICT) + (eq type 'MOVE-AWAY) + (eq type 'REMOVED)) + (if (not (eq type 'REMOVED)) + (delete-file (cvs-full-path tin))) + (setq files-to-update (cons tin files-to-update)) + (cvs-set-fileinfo->type fileinfo 'UPDATED) + (cvs-set-fileinfo->handled fileinfo t) + (tin-invalidate cvs-cookie-handle tin)) + + ((eq type 'MOD-CONFLICT) + (error "Use cvs-mode-add instead on %s." + (cvs-fileinfo->file-name fileinfo))) + + ((eq type 'REM-CONFLICT) + (error "Can't deal with a file you have removed and recreated.")) + + ((eq type 'DIRCHANGE) + (error "Undo on directories not supported (yet).")) + + ((eq type 'ADDED) + (error "There is no old revision to get for %s" + (cvs-fileinfo->file-name fileinfo))) + (t (error "cvs-mode-undo-local-changes: can't handle an %s" + type))) + + (setq tins-to-undo (cdr tins-to-undo)))) + (cvs-use-temp-buffer) + (message "Re-getting files from repository...") + (if (cvs-execute-list files-to-update cvs-program + (if cvs-cvsroot + (list "-d" cvs-cvsroot "update") + '("update")) + "Re-getting %s from repository...") + (error "CVS exited with non-zero exit status.") + (message "Re-getting files from repository... Done."))))))) + +;;---------- +(defun cvs-mode-acknowledge () + "Remove all marked files from the buffer." + + (interactive) + (mapcar (function (lambda (tin) + (tin-delete cvs-cookie-handle tin))) + (cvs-get-marked))) + +;;---------- +(defun cvs-mode-unmark-up (pos) + "Unmark the file on the previous line. +Takes one argument POS, a buffer position." + + (interactive "d") + (let ((tin (tin-goto-previous cvs-cookie-handle pos 1))) + (cond + (tin + (cvs-set-fileinfo->marked (tin-cookie cvs-cookie-handle tin) + nil) + (tin-invalidate cvs-cookie-handle tin))))) + +;;---------- +(defun cvs-mode-previous-line (arg) + "Go to the previous line. +If a prefix argument is given, move by that many lines." + + (interactive "p") + (tin-goto-previous cvs-cookie-handle (point) arg)) + +;;---------- +(defun cvs-mode-next-line (arg) + "Go to the next line. +If a prefix argument is given, move by that many lines." + + (interactive "p") + (tin-goto-next cvs-cookie-handle (point) arg)) + +;;---------- +(defun cvs-add-file-update-buffer (tin) + "Sub-function to cvs-mode-add. Internal use only. Update the display. Return +non-nil if `cvs add' should be called on this file. +Args: TIN. + +Returns 'DIR, 'ADD, 'ADD-DIR, or 'RESURRECT." + + (let ((fileinfo (tin-cookie cvs-cookie-handle tin))) + (cond + ((eq (cvs-fileinfo->type fileinfo) 'UNKNOWN-DIR) + (cvs-set-fileinfo->full-log fileinfo "new directory added with cvs-mode-add") + 'ADD-DIR) + ((eq (cvs-fileinfo->type fileinfo) 'UNKNOWN) + (cvs-set-fileinfo->type fileinfo 'ADDED) + (cvs-set-fileinfo->full-log fileinfo "new file added with cvs-mode-add") + (tin-invalidate cvs-cookie-handle tin) + 'ADD) + ((eq (cvs-fileinfo->type fileinfo) 'REMOVED) + (cvs-set-fileinfo->type fileinfo 'UPDATED) + (cvs-set-fileinfo->full-log fileinfo "file resurrected with cvs-mode-add") + (cvs-set-fileinfo->handled fileinfo t) + (tin-invalidate cvs-cookie-handle tin) + 'RESURRECT)))) + +;;---------- +(defun cvs-add-sub (cvs-buf candidates) + "Internal use only. +Args: CVS-BUF CANDIDATES. + +CANDIDATES is a list of tins. Updates the CVS-BUF and returns a list of lists. +The first list is unknown tins that shall be `cvs add -m msg'ed. +The second list is unknown directory tins that shall be `cvs add -m msg'ed. +The third list is removed files that shall be `cvs add'ed (resurrected)." + + (let (add add-dir resurrect) + (while candidates + (let ((type (cvs-add-file-update-buffer (car candidates)))) + (cond ((eq type 'ADD) + (setq add (cons (car candidates) add))) + ((eq type 'ADD-DIR) + (setq add-dir (cons (car candidates) add-dir))) + ((eq type 'RESURRECT) + (setq resurrect (cons (car candidates) resurrect))))) + (setq candidates (cdr candidates))) + (list add add-dir resurrect))) + +;;---------- +(defun cvs-mode-add () + "Add marked files to the cvs repository." + + (interactive) + (let* ((buf (current-buffer)) + (marked (cvs-get-marked)) + (result (cvs-add-sub buf marked)) + (added (car result)) + (newdirs (car (cdr result))) + (resurrect (car (cdr (cdr result)))) + (msg (if (or added newdirs) + (read-from-minibuffer "Enter description: ")))) + + (if (or resurrect (or added newdirs)) + (cvs-use-temp-buffer)) + + (cond (resurrect + (message "Resurrecting files from repository...") + (if (cvs-execute-list resurrect + cvs-program + (if cvs-cvsroot + (list "-d" cvs-cvsroot "add") + '("add")) + "Resurrecting %s from repository...") + (error "CVS exited with non-zero exit status.") + (message "Resurrecting files from repository... Done.")))) + + (cond (added + (message "Adding new files to repository...") + (if (cvs-execute-list added + cvs-program + (if cvs-cvsroot + (list "-d" cvs-cvsroot "add" "-m" msg) + (list "add" "-m" msg)) + "Adding %s to repository...") + (error "CVS exited with non-zero exit status.") + (message "Adding new files to repository... Done.")))) + + (cond (newdirs + (message "Adding new directories to repository...") + (if (cvs-execute-list newdirs + cvs-program + (if cvs-cvsroot + (list "-d" cvs-cvsroot "add" "-m" msg) + (list "add" "-m" msg)) + "Adding %s to repository...") + (error "CVS exited with non-zero exit status.") + (while newdirs + (let* ((tin (car newdirs)) + (fileinfo (tin-cookie cvs-cookie-handle tin)) + (newdir (cvs-fileinfo->file-name fileinfo))) + (cvs-set-fileinfo->dir fileinfo + (concat (cvs-fileinfo->dir fileinfo) + "/" + newdir)) + (cvs-set-fileinfo->type fileinfo 'DIRCHANGE) + (cvs-set-fileinfo->file-name fileinfo ".") + (tin-invalidate cvs-cookie-handle tin) + (setq newdirs (cdr newdirs)))) + ;; FIXME: this should really run cvs-update-no-prompt on the + ;; subdir and insert everthing in the current list. + (message "You must re-update to visit the new directories.")))))) + +;;---------- +(defun cvs-mode-ignore () + "Arrange so that CVS ignores the selected files and directories. +This command ignores files/dirs that are flagged as `Unknown'." + + (interactive) + (mapcar (function (lambda (tin) + (let* ((fileinfo (tin-cookie cvs-cookie-handle tin)) + (type (cvs-fileinfo->type fileinfo))) + (cond ((or (eq type 'UNKNOWN) + (eq type 'UNKNOWN-DIR)) + (cvs-append-to-ignore fileinfo) + (tin-delete cvs-cookie-handle tin)))))) + (cvs-get-marked))) + +;;---------- +(defun cvs-append-to-ignore (fileinfo) + "Append the file in fileinfo to the .cvsignore file" + + (save-window-excursion + (set-buffer (find-file-noselect (concat (file-name-as-directory + (cvs-fileinfo->dir fileinfo)) + ".cvsignore"))) + (goto-char (point-max)) + (if (not (zerop (current-column))) + (insert "\n")) + (insert (cvs-fileinfo->file-name fileinfo) "\n") + (if cvs-sort-ignore-file + (sort-lines nil (point-min) (point-max))) + (save-buffer))) + +;;---------- +(defun cvs-mode-status () + "Show cvs status for all marked files." + + (interactive) + (save-some-buffers) + (if (not (listp cvs-status-flags)) + (error "cvs-status-flags should be set using cvs-set-status-flags.")) + (let ((marked (cvs-get-marked nil t))) + (cvs-use-temp-buffer) + (message "Running cvs status ...") + (if (cvs-execute-list marked + cvs-program + (append (if cvs-cvsroot (list "-d" cvs-cvsroot)) + (list "-Q" "status") + cvs-status-flags) + "Running cvs -Q status %s...") + (error "CVS exited with non-zero exit status.") + (message "Running cvs -Q status ... Done.")))) + +;;---------- +(defun cvs-mode-log () + "Display the cvs log of all selected files." + + (interactive) + (if (not (listp cvs-log-flags)) + (error "cvs-log-flags should be set using cvs-set-log-flags.")) + (let ((marked (cvs-get-marked nil t))) + (cvs-use-temp-buffer) + (message "Running cvs log ...") + (if (cvs-execute-list marked + cvs-program + (append (if cvs-cvsroot (list "-d" cvs-cvsroot)) + (list "log") + cvs-log-flags) + "Running cvs log %s...") + (error "CVS exited with non-zero exit status.") + (message "Running cvs log ... Done.")))) + +;;---------- +(defun cvs-mode-tag () + "Run 'cvs tag' on all selected files." + + (interactive) + (if (not (listp cvs-tag-flags)) + (error "cvs-tag-flags should be set using cvs-set-tag-flags.")) + (let ((marked (cvs-get-marked nil t)) + (tag-args (cvs-make-list (read-string "Tag name (and flags): ")))) + (cvs-use-temp-buffer) + (message "Running cvs tag ...") + (if (cvs-execute-list marked + cvs-program + (append (if cvs-cvsroot (list "-d" cvs-cvsroot)) + (list "tag") + cvs-tag-flags + tag-args) + "Running cvs tag %s...") + (error "CVS exited with non-zero exit status.") + (message "Running cvs tag ... Done.")))) + +;;---------- +(defun cvs-mode-rtag () + "Run 'cvs rtag' on all selected files." + + (interactive) + (if (not (listp cvs-rtag-flags)) + (error "cvs-rtag-flags should be set using cvs-set-rtag-flags.")) + (let ((marked (cvs-get-marked nil t)) + ;; FIXME: should give selection from the modules file + (module-name (read-string "Module name: ")) + ;; FIXME: should also ask for an existing tag *or* date + (rtag-args (cvs-make-list (read-string "Tag name (and flags): ")))) + (cvs-use-temp-buffer) + (message "Running cvs rtag ...") + (if (cvs-execute-list marked + cvs-program + (append (if cvs-cvsroot (list "-d" cvs-cvsroot)) + (list "rtag") + cvs-rtag-flags + rtag-args + (list module-name)) + "Running cvs rtag %s...") + (error "CVS rtag exited with non-zero exit status.") + (message "Running cvs rtag ... Done.")))) + +;;---------- +(defun cvs-mode-byte-compile-files () + "Run byte-compile-file on all selected files that end in '.el'." + + (interactive) + (let ((marked (cvs-get-marked))) + (while marked + (let ((filename (cvs-full-path (car marked)))) + (if (string-match "\\.el$" filename) + (byte-compile-file filename))) + (setq marked (cdr marked))))) + +;;---------- +(defun cvs-insert-full-path (tin) + "Insert full path to the file described in TIN in the current buffer." + + (insert (format "%s\n" (cvs-full-path tin)))) + +;;---------- +(defun cvs-mode-add-change-log-entry-other-window (pos) + "Add a ChangeLog entry in the ChangeLog of the current directory. +Args: POS." + + (interactive "d") + (let* ((cvs-buf (current-buffer)) + (odir default-directory) + (obfname buffer-file-name) + (tin (tin-locate cvs-cookie-handle pos)) + (fileinfo (tin-cookie cvs-cookie-handle tin)) + (fname (cvs-fileinfo->file-name fileinfo)) + (dname (file-name-as-directory (cvs-fileinfo->dir fileinfo)))) + (setq change-log-default-name nil) ; this rarely correct in 19.28 + (setq buffer-file-name (cond (fname + fname) + (t + nil))) + (setq default-directory (cond (dname + dname) + (t + odir))) + (add-change-log-entry-other-window) + (set-buffer cvs-buf) + (setq default-directory odir) + (setq buffer-file-name obfname))) + +;;---------- +(defun print-cvs-tin (foo) + "Debug utility." + + (let ((cookie (tin-cookie cvs-cookie-handle foo)) + (stream (get-buffer-create "pcl-cvs-debug"))) + (princ "==============\n" stream) + (princ (cvs-fileinfo->file-name cookie) stream) + (princ "\n" stream) + (princ (cvs-fileinfo->dir cookie) stream) + (princ "\n" stream) + (princ (cvs-fileinfo->full-log cookie) stream) + (princ "\n" stream) + (princ (cvs-fileinfo->marked cookie) stream) + (princ "\n" stream))) + +;;---------- +;; NOTE: the variable cvs-emerge-tmp-head-file will be "free" when compiling +(defun cvs-mode-emerge (pos) + "Emerge appropriate revisions of the selected file. +Args: POS." + + (interactive "d") + (let* ((cvs-buf (current-buffer)) + (tin (tin-locate cvs-cookie-handle pos))) + (if (boundp 'cvs-emerge-tmp-head-file) + (error "There can only be one emerge session active at a time.")) + (if tin + (let* ((fileinfo (tin-cookie cvs-cookie-handle tin)) + (type (cvs-fileinfo->type fileinfo))) + (cond + ((eq type 'MODIFIED) ; merge repository head rev. with working file + (require 'emerge) + (setq cvs-emerge-tmp-head-file ; trick to prevent multiple runs + (cvs-retrieve-revision-to-tmpfile fileinfo)) + (unwind-protect + (if (not (emerge-files + t ; arg + (cvs-fileinfo->full-path fileinfo) ; file-A + ;; this is an un-avoidable compiler reference to a free variable + cvs-emerge-tmp-head-file ; file-B + (cvs-fileinfo->full-path fileinfo) ; file-out + nil ; start-hooks + '(lambda () ; quit-hooks + (delete-file cvs-emerge-tmp-head-file) + (makunbound 'cvs-emerge-tmp-head-file)))) + (error "Emerge session failed")))) + + ;; re-do the same merge rcsmerge supposedly just did.... + ((or (eq type 'MERGED) + (eq type 'CONFLICT)) ; merge backup-working=A, head=B, base=ancestor + (require 'emerge) + (setq cvs-emerge-tmp-head-file ; trick to prevent multiple runs + (cvs-retrieve-revision-to-tmpfile fileinfo + (cvs-fileinfo->head-revision + fileinfo))) + (let ((cvs-emerge-tmp-backup-working-file + (cvs-fileinfo->backup-file fileinfo)) + (cvs-emerge-tmp-ancestor-file + (cvs-retrieve-revision-to-tmpfile fileinfo + (cvs-fileinfo->base-revision + fileinfo)))) + (unwind-protect + (if (not (emerge-files-with-ancestor + t ; arg + cvs-emerge-tmp-backup-working-file ; file-A + ;; this is an un-avoidable compiler reference to a free variable + cvs-emerge-tmp-head-file ; file-B + cvs-emerge-tmp-ancestor-file ; file-ancestor + (cvs-fileinfo->full-path fileinfo) ; file-out + nil ; start-hooks + '(lambda () ; quit-hooks + (delete-file cvs-emerge-tmp-backup-file) + (delete-file cvs-emerge-tmp-ancestor-file) + (delete-file cvs-emerge-tmp-head-file) + (makunbound 'cvs-emerge-tmp-head-file)))) + (error "Emerge session failed"))))) + (t + (error "Can only e-merge \"Modified\", \"Merged\" or \"Conflict\" files")))) + (error "There is no file to e-merge.")))) + +;;---------- +;; NOTE: the variable ediff-version may be "free" when compiling +(defun cvs-mode-ediff (pos) + "Ediff appropriate revisions of the selected file. +Args: POS." + + (interactive "d") + (if (boundp 'cvs-ediff-tmp-head-file) + (error "There can only be one ediff session active at a time.")) + (require 'ediff) + (if (and (boundp 'ediff-version) + (>= (string-to-number ediff-version) 2.0)) ; FIXME real number? + (run-ediff-from-cvs-buffer pos) + (cvs-old-ediff-interface pos))) + +(defun cvs-old-ediff-interface (pos) + "Emerge like interface for older ediffs. +Args: POS" + + (let* ((cvs-buf (current-buffer)) + (tin (tin-locate cvs-cookie-handle pos))) + (if tin + (let* ((fileinfo (tin-cookie cvs-cookie-handle tin)) + (type (cvs-fileinfo->type fileinfo))) + (cond + ((eq type 'MODIFIED) ; diff repository head rev. with working file + ;; should this be inside the unwind-protect, and should the + ;; makeunbound be an unwindform? + (setq cvs-ediff-tmp-head-file ; trick to prevent multiple runs + (cvs-retrieve-revision-to-tmpfile fileinfo)) + (unwind-protect + (if (not (ediff-files ; check correct ordering of args + (cvs-fileinfo->full-path fileinfo) ; file-A + ;; this is an un-avoidable compiler reference to a free variable + cvs-ediff-tmp-head-file ; file-B + '(lambda () ; startup-hooks + (make-local-hook 'ediff-cleanup-hooks) + (add-hook 'ediff-cleanup-hooks + '(lambda () + (ediff-janitor) + (delete-file cvs-ediff-tmp-head-file) + (makunbound 'cvs-ediff-tmp-head-file)) + nil t)))) + (error "Ediff session failed")))) + + ;; look at the merge rcsmerge supposedly just did.... + ((or (eq type 'MERGED) + (eq type 'CONFLICT)) ; diff backup-working=A, head=B, base=ancestor + (if (not (boundp 'ediff-version)) + (error "ediff version way too old for 3-way diff")) + (if (<= (string-to-number ediff-version) 1.9) ; FIXME real number? + (error "ediff version %s too old for 3-way diff" ediff-version)) + (setq cvs-ediff-tmp-head-file ; trick to prevent multiple runs + (cvs-retrieve-revision-to-tmpfile fileinfo + (cvs-fileinfo->head-revision + fileinfo))) + (let ((cvs-ediff-tmp-backup-working-file + (cvs-fileinfo->backup-file fileinfo)) + (cvs-ediff-tmp-ancestor-file + (cvs-retrieve-revision-to-tmpfile fileinfo + (cvs-fileinfo->base-revision + fileinfo)))) + (unwind-protect + (if (not (ediff-files3 ; check correct ordering of args + cvs-ediff-tmp-backup-working-file ; file-A + ;; this is an un-avoidable compiler reference to a free variable + cvs-ediff-tmp-head-file ; file-B + cvs-ediff-tmp-ancestor-file ; file-ancestor + '(lambda () ; start-hooks + (make-local-hook 'ediff-cleanup-hooks) + (add-hook 'ediff-cleanup-hooks + '(lambda () + (ediff-janitor) + (delete-file cvs-ediff-tmp-backup-file) + (delete-file cvs-ediff-tmp-ancestor-file) + (delete-file cvs-ediff-tmp-head-file) + (makunbound 'cvs-ediff-tmp-head-file)) + nil t)))) + (error "Ediff session failed"))))) + + ((not (or (eq type 'UNKNOWN) + (eq type 'UNKNOWN-DIR))) ; i.e. UPDATED or PATCHED ???? + ;; this should really diff the current working file with the previous + ;; rev. on the current branch (i.e. not the head, since that's what + ;; the current file should be) + (setq cvs-ediff-tmp-head-file ; trick to prevent multiple runs + (cvs-retrieve-revision-to-tmpfile fileinfo + (read-string "Rev #/tag to diff against: " + (cvs-fileinfo->head-revision + fileinfo)))) + (unwind-protect + (if (not (ediff-files ; check correct ordering of args + (cvs-fileinfo->full-path fileinfo) ; file-A + ;; this is an un-avoidable compiler reference to a free variable + cvs-ediff-tmp-head-file ; file-B + '(lambda () ; startup-hooks + (make-local-hook 'ediff-cleanup-hooks) + (add-hook 'ediff-cleanup-hooks + '(lambda () + (ediff-janitor) + (delete-file cvs-ediff-tmp-head-file) + (makunbound 'cvs-ediff-tmp-head-file)) + nil t)))) + (error "Ediff session failed")))) + (t + (error "Can not ediff \"Unknown\" files")))) + (error "There is no file to ediff.")))) + +;;---------- +(defun cvs-retrieve-revision-to-tmpfile (fileinfo &optional revision) + "Retrieve the latest revision of the file in FILEINFO to a temporary file. +If second optional argument REVISION is given, retrieve that revision instead." + + (let + ((temp-name (make-temp-name + (concat (file-name-as-directory + (or (getenv "TMPDIR") "/tmp")) + "pcl-cvs." revision)))) + (cvs-kill-buffer-visiting temp-name) + (if (and revision + (stringp revision) + (not (string= revision ""))) + (message "Retrieving revision %s..." revision) + (message "Retrieving latest revision...")) + (let ((res (call-process cvs-shell nil nil nil "-c" + (concat cvs-program " update -p " + (if (and revision + (stringp revision) + (not (string= revision ""))) + (concat "-r " revision " ") + "") + (cvs-fileinfo->full-path fileinfo) + " > " temp-name)))) + (if (and res (not (and (integerp res) (zerop res)))) + (error "Something went wrong retrieving revision %s: %s" + revision res)) + + (if revision + (message "Retrieving revision %s... Done." revision) + (message "Retrieving latest revision... Done.")) + (save-excursion + (set-buffer (find-file-noselect temp-name)) + (rename-buffer (concat " " (file-name-nondirectory temp-name)) t)) + temp-name))) + +;;---------- +(defun cvs-kill-buffer-visiting (filename) + "If there is any buffer visiting FILENAME, kill it (without confirmation)." + + (let ((l (buffer-list))) + (while l + (if (string= (buffer-file-name (car l)) filename) + (kill-buffer (car l))) + (setq l (cdr l))))) + +;;---------- +(defun cvs-change-cvsroot () + "Ask for a new cvsroot." + + (interactive) + (cvs-set-cvsroot (read-file-name "New CVSROOT: " cvs-cvsroot))) + +;;---------- +(defun cvs-set-cvsroot (newroot) + "Change the cvsroot." + + (if (or (file-directory-p (expand-file-name "CVSROOT" newroot)) + (y-or-n-p (concat "Warning: no CVSROOT found inside repository." + " Change cvs-cvsroot anyhow?"))) + (setq cvs-cvsroot newroot))) + +;;---------- +(defun cvs-set-diff-flags () + "Ask for new setting of cvs-diff-flags." + + (interactive) + (let ((old-value (mapconcat 'identity + (copy-sequence cvs-diff-flags) " "))) + (setq cvs-diff-flags + (cvs-make-list (read-string "Diff flags: " old-value))))) + +;;---------- +(defun cvs-set-update-optional-flags () + "Ask for new setting of cvs-update-optional-flags." + + (interactive) + (let ((old-value (mapconcat 'identity + (copy-sequence cvs-update-optional-flags) " "))) + (setq cvs-update-optional-flags + (cvs-make-list (read-string "Update optional flags: " old-value))))) + +;;---------- +(defun cvs-set-status-flags () + "Ask for new setting of cvs-status-flags." + + (interactive) + (let ((old-value (mapconcat 'identity + (copy-sequence cvs-status-flags) " "))) + (setq cvs-status-flags + (cvs-make-list (read-string "Status flags: " old-value))))) + +;;---------- +(defun cvs-set-log-flags () + "Ask for new setting of cvs-log-flags." + + (interactive) + (let ((old-value (mapconcat 'identity + (copy-sequence cvs-log-flags) " "))) + (setq cvs-log-flags + (cvs-make-list (read-string "Log flags: " old-value))))) + +;;---------- +(defun cvs-set-tag-flags () + "Ask for new setting of cvs-tag-flags." + + (interactive) + (let ((old-value (mapconcat 'identity + (copy-sequence cvs-tag-flags) " "))) + (setq cvs-tag-flags + (cvs-make-list (read-string "Tag flags: " old-value))))) + +;;---------- +(defun cvs-set-rtag-flags () + "Ask for new setting of cvs-rtag-flags." + + (interactive) + (let ((old-value (mapconcat 'identity + (copy-sequence cvs-rtag-flags) " "))) + (setq cvs-rtag-flags + (cvs-make-list (read-string "Rtag flags: " old-value))))) + +;;---------- +(if (string-match "Lucid" emacs-version) + (progn + (autoload 'pcl-cvs-fontify "pcl-cvs-lucid") + (add-hook 'cvs-mode-hook 'pcl-cvs-fontify))) + +(defun cvs-changelog-name (directory) + "Return the name of the ChangeLog file that handles DIRECTORY. +This is in DIRECTORY or one of its parents. +Signal an error if we can't find an appropriate ChangeLog file." + (let ((dir (file-name-as-directory directory)) + file) + (while (and dir + (not (file-exists-p + (setq file (expand-file-name "ChangeLog" dir))))) + (let ((last dir)) + (setq dir (file-name-directory (directory-file-name dir))) + (if (equal last dir) + (setq dir nil)))) + (or dir + (error "Can't find ChangeLog for %s" directory)) + file)) + +(defun cvs-narrow-changelog () + "Narrow to the top page of the current buffer, a ChangeLog file. +Actually, the narrowed region doesn't include the date line. +A \"page\" in a ChangeLog file is the area between two dates." + (or (eq major-mode 'change-log-mode) + (error "cvs-narrow-changelog: current buffer isn't a ChangeLog")) + + (goto-char (point-min)) + + ;; Skip date line and subsequent blank lines. + (forward-line 1) + (if (looking-at "[ \t\n]*\n") + (goto-char (match-end 0))) + + (let ((start (point))) + (forward-page 1) + (narrow-to-region start (point)) + (goto-char (point-min)))) + +(defun cvs-changelog-paragraph () + "Return the bounds of the ChangeLog paragraph containing point. +If we are between paragraphs, return the previous paragraph." + (save-excursion + (beginning-of-line) + (if (looking-at "^[ \t]*$") + (skip-chars-backward " \t\n" (point-min))) + (list (progn + (if (re-search-backward "^[ \t]*\n" nil 'or-to-limit) + (goto-char (match-end 0))) + (point)) + (if (re-search-forward "^[ \t\n]*$" nil t) + (match-beginning 0) + (point))))) + +(defun cvs-changelog-subparagraph () + "Return the bounds of the ChangeLog subparagraph containing point. +A subparagraph is a block of non-blank lines beginning with an asterisk. +If we are between sub-paragraphs, return the previous subparagraph." + (save-excursion + (end-of-line) + (if (search-backward "*" nil t) + (list (progn (beginning-of-line) (point)) + (progn + (forward-line 1) + (if (re-search-forward "^[ \t]*[\n*]" nil t) + (match-beginning 0) + (point-max)))) + (list (point) (point))))) + +(defun cvs-changelog-entry () + "Return the bounds of the ChangeLog entry containing point. +The variable `cvs-changelog-full-paragraphs' decides whether an +\"entry\" is a paragraph or a subparagraph; see its documentation string +for more details." + (if cvs-changelog-full-paragraphs + (cvs-changelog-paragraph) + (cvs-changelog-subparagraph))) + +;; NOTE: the variable user-full-name may be "free" when compiling +(defun cvs-changelog-ours-p () + "See if ChangeLog entry at point is for the current user, today. +Return non-nil iff it is." + ;; Code adapted from add-change-log-entry. + (looking-at (concat (regexp-quote (substring (current-time-string) + 0 10)) + ".* " + (regexp-quote (substring (current-time-string) -4)) + "[ \t]+" + (regexp-quote (if (and (boundp 'add-log-full-name) + add-log-full-name) + add-log-full-name + user-full-name)) + " <" + (regexp-quote (if (and (boundp 'add-log-mailing-address) + add-log-mailing-address) + add-log-mailing-address + user-mail-address))))) + +(defun cvs-relative-path (base child) + "Return a directory path relative to BASE for CHILD. +If CHILD doesn't seem to be in a subdirectory of BASE, just return +the full path to CHILD." + (let ((base (file-name-as-directory (expand-file-name base))) + (child (expand-file-name child))) + (or (string= base (substring child 0 (length base))) + (error "cvs-relative-path: %s isn't in %s" child base)) + (substring child (length base)))) + +(defun cvs-changelog-entries (file) + "Return the ChangeLog entries for FILE, and the ChangeLog they came from. +The return value looks like this: + (LOGBUFFER (ENTRYSTART . ENTRYEND) ...) +where LOGBUFFER is the name of the ChangeLog buffer, and each +\(ENTRYSTART . ENTRYEND\) pair is a buffer region." + (save-excursion + (set-buffer (find-file-noselect + (cvs-changelog-name + (file-name-directory + (expand-file-name file))))) + (or (eq major-mode 'change-log-mode) + (change-log-mode)) + (goto-char (point-min)) + (if (looking-at "[ \t\n]*\n") + (goto-char (match-end 0))) + (if (not (cvs-changelog-ours-p)) + (list (current-buffer)) + (save-restriction + (cvs-narrow-changelog) + (goto-char (point-min)) + + ;; Search for the name of FILE relative to the ChangeLog. If that + ;; doesn't occur anywhere, they're not using full relative + ;; filenames in the ChangeLog, so just look for FILE; we'll accept + ;; some false positives. + (let ((pattern (cvs-relative-path + (file-name-directory buffer-file-name) file))) + (if (or (string= pattern "") + (not (save-excursion + (search-forward pattern nil t)))) + (setq pattern file)) + + (let (texts) + (while (search-forward pattern nil t) + (let ((entry (cvs-changelog-entry))) + (setq texts (cons entry texts)) + (goto-char (elt entry 1)))) + + (cons (current-buffer) texts))))))) + +(defun cvs-changelog-insert-entries (buffer regions) + "Insert those regions in BUFFER specified in REGIONS. +Sort REGIONS front-to-back first." + (let ((regions (sort regions 'car-less-than-car)) + (last)) + (while regions + (if (and last (< last (car (car regions)))) + (newline)) + (setq last (elt (car regions) 1)) + (apply 'insert-buffer-substring buffer (car regions)) + (setq regions (cdr regions))))) + +(defun cvs-union (set1 set2) + "Return the union of SET1 and SET2, according to `equal'." + (while set2 + (or (member (car set2) set1) + (setq set1 (cons (car set2) set1))) + (setq set2 (cdr set2))) + set1) + +(defun cvs-insert-changelog-entries (files) + "Given a list of files FILES, insert the ChangeLog entries for them." + (let ((buffer-entries nil)) + + ;; Add each buffer to buffer-entries, and associate it with the list + ;; of entries we want from that file. + (while files + (let* ((entries (cvs-changelog-entries (car files))) + (pair (assq (car entries) buffer-entries))) + (if pair + (setcdr pair (cvs-union (cdr pair) (cdr entries))) + (setq buffer-entries (cons entries buffer-entries)))) + (setq files (cdr files))) + + ;; Now map over each buffer in buffer-entries, sort the entries for + ;; each buffer, and extract them as strings. + (while buffer-entries + (cvs-changelog-insert-entries (car (car buffer-entries)) + (cdr (car buffer-entries))) + (if (and (cdr buffer-entries) (cdr (car buffer-entries))) + (newline)) + (setq buffer-entries (cdr buffer-entries))))) + +(defun cvs-edit-delete-common-indentation () + "Unindent the current buffer rigidly until at least one line is flush left." + (save-excursion + (let ((common 100000)) + (goto-char (point-min)) + (while (< (point) (point-max)) + (if (not (looking-at "^[ \t]*$")) + (setq common (min common (current-indentation)))) + (forward-line 1)) + (indent-rigidly (point-min) (point-max) (- common))))) + +(defun cvs-mode-changelog-commit () + "Check in all marked files, or the current file. +Ask the user for a log message in a buffer. + +This is just like `\\[cvs-mode-commit]', except that it tries to provide +appropriate default log messages by looking at the ChangeLog. The +idea is to write your ChangeLog entries first, and then use this +command to commit your changes. + +To select default log text, we: +- find the ChangeLog entries for the files to be checked in, +- verify that the top entry in the ChangeLog is on the current date + and by the current user; if not, we don't provide any default text, +- search the ChangeLog entry for paragraphs containing the names of + the files we're checking in, and finally +- use those paragraphs as the log text." + + (interactive) + + (let* ((cvs-buf (current-buffer)) + (marked (cvs-filter (function cvs-committable) + (cvs-get-marked)))) + (if (null marked) + (error "Nothing to commit!") + (pop-to-buffer (get-buffer-create cvs-commit-prompt-buffer)) + (goto-char (point-min)) + + (erase-buffer) + (cvs-insert-changelog-entries + (mapcar (lambda (tin) + (let ((cookie (tin-cookie cvs-cookie-handle tin))) + (expand-file-name + (cvs-fileinfo->file-name cookie) + (cvs-fileinfo->dir cookie)))) + marked)) + (cvs-edit-delete-common-indentation) + + (cvs-edit-mode) + (make-local-variable 'cvs-commit-list) + (setq cvs-commit-list marked) + (message "Press C-c C-c when you are done editing.")))) + +(provide 'pcl-cvs) + +;;;; end of file pcl-cvs.el diff --git a/contrib/cvs/tools/pcl-cvs/pcl-cvs.texinfo b/contrib/cvs/tools/pcl-cvs/pcl-cvs.texinfo new file mode 100644 index 0000000..8bd3619 --- /dev/null +++ b/contrib/cvs/tools/pcl-cvs/pcl-cvs.texinfo @@ -0,0 +1,1565 @@ +\input texinfo @c -*-texinfo-*- + +@comment OrigId: pcl-cvs.texinfo,v 1.45 1993/05/31 22:38:15 ceder Exp +@comment @@(#)cvs/contrib/pcl-cvs:$Name: $:$Id: pcl-cvs.texinfo,v 1.1 1996/04/14 15:18:04 kfogel Exp $ + +@comment Documentation for the GNU Emacs CVS mode. +@comment Copyright (C) 1992 Per Cederqvist + +@comment This file is part of the pcl-cvs distribution. + +@comment Pcl-cvs is free software; you can redistribute it and/or modify +@comment it under the terms of the GNU General Public License as published by +@comment the Free Software Foundation; either version 1, or (at your option) +@comment any later version. + +@comment Pcl-cvs is distributed in the hope that it will be useful, +@comment but WITHOUT ANY WARRANTY; without even the implied warranty of +@comment MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +@comment GNU General Public License for more details. + +@comment You should have received a copy of the GNU General Public License +@comment along with pcl-cvs; see the file COPYING. If not, write to +@comment the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + +@setfilename pcl-cvs.info +@settitle Pcl-cvs - The Emacs Front-End to CVS +@setchapternewpage on + +@ifinfo +Copyright @copyright{} 1992 Per Cederqvist + +Permission is granted to make and distribute verbatim copies of +this manual provided the copyright notice and this permission notice +are preserved on all copies. + +@ignore +Permission is granted to process this file through Tex and print the +results, provided the printed document carries copying permission +notice identical to this one except for the removal of this paragraph +(this paragraph not being relevant to the printed manual). + +@end ignore +Permission is granted to copy and distribute modified versions of this +manual under the conditions for verbatim copying, provided also that the +section entitled ``GNU General Public License'' is included exactly as +in the original, and provided that the entire resulting derived work is +distributed under the terms of a permission notice identical to this one. + +Permission is granted to copy and distribute translations of this manual +into another language, under the above conditions for modified versions, +except that the section entitled ``GNU General Public License'' and +this permission notice may be included in translations approved by the +Free Software Foundation instead of in the original English. +@end ifinfo + +@synindex vr fn +@comment The titlepage section does not appear in the Info file. +@titlepage +@sp 4 +@comment The title is printed in a large font. +@center @titlefont{User's Guide} +@sp +@center @titlefont{to} +@sp +@center @titlefont{pcl-cvs - the Emacs Front-End to CVS} +@sp 2 +@center release 1.05-CVS-$Name: $ +@comment -release- +@sp 3 +@center Per Cederqvist +@sp 3 +@center last updated 20 Nov 1995 +@comment -date- + +@comment The following two commands start the copyright page +@comment for the printed manual. This will not appear in the Info file. +@page +@vskip 0pt plus 1filll +Copyright @copyright{} 1992 Per Cederqvist + +Permission is granted to make and distribute verbatim copies of +this manual provided the copyright notice and this permission notice +are preserved on all copies. + +Permission is granted to copy and distribute modified versions of this +manual under the conditions for verbatim copying, provided also that the +section entitled ``GNU General Public License'' is included exactly as +in the original, and provided that the entire resulting derived work is +distributed under the terms of a permission notice identical to this one. + +Permission is granted to copy and distribute translations of this manual +into another language, under the above conditions for modified versions, +except that the section entitled ``GNU General Public License'' and +this permission notice may be included in translations approved by the +Free Software Foundation instead of in the original English. +@end titlepage + +@comment ================================================================ +@comment The real text starts here +@comment ================================================================ + +@node Top, Installation, (dir), (dir) +@comment node-name, next, previous, up + + +@ifinfo +This info manual describes pcl-cvs which is a GNU Emacs front-end to +CVS. It works with CVS versions 1.5 through 1.7 and newer, and possibly +CVS-1.3 and CVS-1.4A2. This manual is updated to release +1.05-CVS-$Name: $ of pcl-cvs. +@end ifinfo +@comment -release- + +@menu +* Installation:: How to install pcl-cvs on your system. +* About pcl-cvs:: Authors and ftp sites. + +* Getting started:: An introduction with a walk-through example. +* Buffer contents:: An explanation of the buffer contents. +* Commands:: All commands, grouped by type. + +* Customization:: How you can tailor pcl-cvs to suit your needs. +* Future enhancements:: Future enhancements of pcl-cvs. +* Bugs:: Bugs (known and unknown). +* COPYING:: GNU General Public License +* Function and Variable Index:: List of functions and variables. +* Concept Index:: List of concepts. +* Key Index:: List of keystrokes. + + --- The Detailed Node Listing --- + +Installation + +* Pcl-cvs installation:: How to install pcl-cvs on your system. +* On-line manual installation:: How to install the on-line manual. +* Typeset manual installation:: How to create typeset documentation + about pcl-cvs. + +About pcl-cvs + +* Contributors:: Contributors to pcl-cvs. +* Archives:: Where can I get a copy of Pcl-Cvs? + +Buffer contents + +* File status:: The meaning of the second field. +* Selected files:: How selection works. + +Commands + +* Updating the directory:: Commands to update the local directory +* Movement commands:: How to move up and down in the buffer +* Marking files:: How to mark files that other commands + will later operate on. +* Committing changes:: Checking in your modifications to the + CVS repository. +* Editing files:: Loading files into Emacs. +* Getting info about files:: Display the log and status of files. +* Adding and removing files:: Adding and removing files +* Undoing changes:: Undoing changes +* Removing handled entries:: Uninteresting lines can easily be removed. +* Ignoring files:: Telling CVS to ignore generated files. +* Viewing differences:: Commands to @samp{diff} different versions. +* Invoking Ediff:: Running @samp{ediff} from @samp{*cvs*} buffer. +* Invoking Emerge:: Running @samp{emerge} from @samp{*cvs*} buffer. +* Reverting your buffers:: Reverting your buffers +* Miscellaneous commands:: Miscellaneous commands +@end menu + + +@node Installation, About pcl-cvs, Top, Top +@comment node-name, next, previous, up + +@chapter Installation +@cindex Installation + +This section describes the installation of pcl-cvs, the GNU Emacs CVS +front-end. You should install not only the elisp files themselves, but +also the on-line documentation so that your users will know how to use +it. You can create typeset documentation from the file +@file{pcl-cvs.texinfo} as well as an on-line info file. The following +steps are also described in the file @file{INSTALL} in the source +directory. + +@menu +* Pcl-cvs installation:: How to install pcl-cvs on your system. +* On-line manual installation:: How to install the on-line manual. +* Typeset manual installation:: How to create typeset documentation + about pcl-cvs. +@end menu + + +@node Pcl-cvs installation, On-line manual installation, Installation, Installation +@comment node-name, next, previous, up +@section Installation of the pcl-cvs program +@cindex Installation of elisp files + +@enumerate +@item +Possibly edit the file @file{Makefile} to reflect the situation at your +site. We say "possibly" because the version of pcl-cvs included with +CVS uses a configuration mechanism integrated with the overall +mechanisms used by the CVS build and install procedures. Thus the file +@code{Makefile} will be generated automatically from the file +@code{Makefile.in}, and it should not be necessary to edit it further. + +If you do have to edit the @file{Makefile}, the only things you have to +change is the definition of @code{lispdir} and @code{infodir}. The +elisp files will be copied to @code{lispdir}, and the info file(s) to +@code{infodir}. + +@item +Configure pcl-cvs.el + +There are a couple of pathnames that you have to check to make sure that +they match your system. They appear early in the file +@samp{pcl-cvs.el}. + +@strong{NOTE:} If your system is running emacs 18.57 or earlier you MUST +uncomment the line that says: +@example +(setq delete-exited-processes nil) +@end example + +Setting @code{delete-exited-processes} to @code{nil} works around a bug +in emacs that causes it to dump core. The bug was fixed in emacs +18.58.@refill + +@item +Release 1.05 and later of pcl-cvs requires parts of the Elib library, +version 1.0 or later. Elib is available via anonymous ftp from +prep.ai.mit.edu in @file{pub/gnu/elib-1.0.tar.gz}, and from a lot of +other sites that mirror prep. Get Elib, and install it, before +proceeding. + +@strong{NOTE:} The version of pcl-cvs included with CVS includes a copy +of Elib in the sub-directory @file{elib} under the +@file{contrib/pcl-cvs} directory. + +@item +Type @samp{make install} in the source directory. This will +byte-compile all @file{.el} files and copy the @file{*.elc} files into +the directory you specified in step 1. + +If you want to install the @file{*.el} files too, you can type +@samp{make install-el} to do so. + +If you only want to create the compiled elisp files, but don't want to +install them, you can type @samp{make} without parameters. + +@item +Edit the file @file{default.el} in your emacs lisp directory (usually +@file{/usr/gnu/lib/emacs/site-lisp} or something similar) and enter the +contents of the file @file{pcl-cvs-startup.el} into it. It contains a +couple of @code{auto-load}s that facilitates the use of pcl-cvs. + +@end enumerate + + +@node On-line manual installation, Typeset manual installation, Pcl-cvs installation, Installation +@comment node-name, next, previous, up + +@section Installation of the on-line manual. +@cindex Manual installation (on-line) +@cindex Installation of on-line manual +@cindex Generating the on-line manual +@cindex On-line manual (how to generate) +@cindex Info-file (how to generate) + +@enumerate +@item +Create the info file(s) @file{pcl-cvs.info*} from @file{pcl-cvs.texinfo} +by typing @samp{make info}. If you don't have the program +@samp{makeinfo} you can get it by anonymous ftp from +e.g. @samp{prep.ai.mit.edu} as @file{pub/gnu/texinfo-3.7.tar.gz} (there +might be a newer version there when you read this).@refill + +@item +Install the info file(s) @file{pcl-cvs.info*} into your standard +@file{info} directory. You should be able to do this by typing +@samp{make install-info}.@refill + +@item +Edit the file @file{dir} in the @file{info} directory and enter one line +to contain a pointer to the info file(s) @file{pcl-cvs.info*}. The line +can, for instance, look like this:@refill + +@example +* Pcl-cvs: (pcl-cvs). An Emacs front-end to CVS. +@end example +@end enumerate + + +@node Typeset manual installation, , On-line manual installation, Installation +@comment node-name, next, previous, up + +@section How to make typeset documentation from pcl-cvs.texinfo +@cindex Manual installation (typeset) +@cindex Installation of typeset manual +@cindex Printing a manual +@cindex TeX - generating a typeset manual +@cindex Generating a typeset manual + +If you have @TeX{} installed at your site, you can make a typeset manual +from @file{pcl-cvs.texinfo}. + +@enumerate +@item +Run @TeX{} by typing `@samp{make pcl-cvs.dvi}'. You will not get the +indices unless you have the @code{texindex} program. + +@item +Convert the resulting device independent file @file{pcl-cvs.dvi} to a +form which your printer can output and print it. If you have a +postscript printer there is a program, @code{dvi2ps}, which does. There +is also a program which comes together with @TeX{}, @code{dvips}, which +you can use. + +@end enumerate + + +@node About pcl-cvs, Getting started, Installation, Top +@comment node-name, next, previous, up + +@chapter About pcl-cvs +@cindex About pcl-cvs + +Pcl-cvs is a front-end to CVS versions 1.5 through 1.7 and newer; and +possibly verison 1.3 and 1.4A2. It integrates the most frequently used +CVS commands into an emacs interface. + +@menu +* Contributors:: Contributors to pcl-cvs. +* Archives:: Where can I get a copy of Pcl-Cvs? +@end menu + + +@node Contributors, Archives, About pcl-cvs, About pcl-cvs +@comment node-name, next, previous, up + +@section Contributors to pcl-cvs +@cindex Contributors +@cindex Authors + +Contributions to the package are welcome. I have limited time to work +on this project, but I will gladly add any code that you contribute to +me to this package (@pxref{Bugs}). + +The following persons have made contributions to pcl-cvs. + +@itemize @bullet +@item +Brian Berliner wrote CVS, together with some other contributors. +Without his work on CVS this package would be useless@dots{} + +@item +Per Cederqvist wrote most of the otherwise unattributed functions in +pcl-cvs as well as all documentation. + +@item +Inge Wallin (@samp{inge@@lysator.liu.se}) wrote the skeleton to +@file{pcl-cvs.texinfo}, and gave useful comments on it. He also wrote +the files @file{elib-node.el} and @file{compile-all.el}. The file +@file{cookie.el} was inspired by Inge.@refill + +@item +Linus Tolke (@samp{linus@@lysator.liu.se}) contributed useful comments +on both the functionality and the documentation.@refill + +@item +Jamie Zawinski (@samp{jwz@@lucid.com}) contributed +@file{pcl-cvs-lucid.el}. + +@item +Leif Lonnblad contributed RCVS support. (Since superceded by the new +remote CVS support.) + +@item +Jim Blandy (@samp{jimb@@cyclic.com}) contributed hooks to automatically +guess CVS log entries from ChangeLog contents; and initial support of +the new Cygnus / Cyclic remote CVS; as well as various sundry bug fixes +and cleanups. + +@item +Jim Kingdon (@samp{kingdon@@cyclic.com}) contributed lots of fixes to +the build and install procedure. + +@item +Greg A. Woods (@samp{woods@@planix.com}) contributed code to implement +the use of per-file diff buffers; and vendor join diffs with emerge and +ediff; as well as various an sundry bug fixes and cleanups. +@end itemize + +Apart from these, a lot of people have send me suggestions, ideas, +requests, bug reports and encouragement. Thanks a lot! Without your +there would be no new releases of pcl-cvs. + + +@node Archives, , Contributors, About pcl-cvs +@comment node-name, next, previous, up + +@section Where can I get pcl-cvs? +@cindex Sites +@cindex Archives +@cindex Ftp-sites +@cindex Getting pcl-cvs +@cindex Email archives + +The current release of pcl-cvs is included in CVS-1.7. + +The author's release of pcl-cvs can be fetched via anonymous ftp from +@code{ftp.lysator.liu.se}, (IP no. 130.236.254.1) in the directory +@code{pub/emacs}. If you don't live in Scandinavia you should probably +check with archie to see if there is a site closer to you that archives +pcl-cvs. + +New releases will be announced to appropriate newsgroups. If you send +your email address to me I will add you to my list of people to mail +when I make a new release. + + +@node Getting started, Buffer contents, About pcl-cvs, Top +@comment node-name, next, previous, up + +@chapter Getting started +@cindex Introduction +@cindex Example run + +This document assumes that you know what CVS is, and that you at least +knows the fundamental concepts of CVS. If that is not the case you +should read the man page for CVS. + +Pcl-cvs is only useful once you have checked out a module. So before +you invoke it you must have a copy of a module somewhere in the file +system. + +You invoke pcl-cvs by typing @kbd{M-x cvs-update RET}. If your emacs +responds with @samp{[No match]} your system administrator has not +installed pcl-cvs properly. Try @kbd{M-x load-library RET pcl-cvs RET}. +If that also fails - talk to your root. If it succeeds you might put +this line in your @file{.emacs} file so that you don't have to type the +@samp{load-library} command every time you wish to use pcl-cvs: + +@example +(autoload 'cvs-update "pcl-cvs" nil t) +@end example + +The function @code{cvs-update} will ask for a directory. The command +@samp{cvs update} will be run in that directory. (It should contain +files that have been checked out from a CVS archive.) The output from +@code{cvs} will be parsed and presented in a table in a buffer called +@samp{*cvs*}. It might look something like this: + +@example +PCL-CVS release 1.05-CVS-$Name: $. +@comment -release- + +In directory /users/ceder/FOO/test: + Updated bar + Updated file.txt + Modified ci namechange + Updated newer + +In directory /users/ceder/FOO/test/sub: + Modified ci ChangeLog +---------- End ----- +@end example + +In this example the two files (@file{bar}, @file{file.txt}, and +@file{newer}) that are marked with @samp{Updated} have been copied from +the CVS repository to @file{/users/ceder/FOO/test/} since someone else +have checked in newer versions of them. Two files (@file{namechange} +and @file{sub/ChangeLog}) have been modified locally, and needs to be +checked in. + +You can move the cursor up and down in the buffer with @kbd{C-n} and +@kbd{C-p} or @kbd{n} and @kbd{p}. If you press @kbd{c} on one of the +@samp{Modified} files that file will be checked in to the CVS +repository. @xref{Committing changes}. You can press @kbd{x} to get rid +of the "uninteresting" files that have only been @samp{Updated} (and +don't require any further action from you).@refill + +You can also easily get a @samp{diff} between your modified file and the +base version that you started from, and you can get the output from +@samp{cvs log} and @samp{cvs status} on the listed files simply by +pressing a key (@pxref{Getting info about files}). + + +@node Buffer contents, Commands, Getting started, Top +@comment node-name, next, previous, up + +@chapter Buffer contents +@cindex Buffer contents + +The display contains four columns. They contain, from left to right: + +@itemize @bullet +@item +An asterisk when the file is @dfn{marked} (@pxref{Selected +files}).@refill +@item +The status of the file. See @xref{File status}, for more +information.@refill +@item +A "need to be checked in"-marker (@samp{ci}). +@item +The file name. +@end itemize + +@menu +* File status:: The meaning of the second field. +* Selected files:: How selection works. +@end menu + + +@node File status, Selected files, Buffer contents, Buffer contents +@comment node-name, next, previous, up + +@section File status +@cindex File status +@cindex Updated (file status) +@cindex Patched (file status) +@cindex Modified (file status) +@cindex Merged (file status) +@cindex Conflict (file status) +@cindex Added (file status) +@cindex Removed (file status) +@cindex Unknown (file status) +@cindex Removed from repository (file status) +@cindex Removed from repository, changed by you (file status) +@cindex Removed by you, changed in repository (file status) +@cindex Move away @var{file} - it is in the way (file status) +@cindex This repository is missing!@dots{} (file status) + +The @samp{file status} field can have the following values: + +@table @samp + +@item Updated +The file was brought up to date with respect to the repository. This is +done for any file that exists in the repository but not in your source, +and for files that you haven't changed but are not the most recent +versions available in the repository.@refill + +@item Patched +The file was brought up to date with respect to a remote repository by +way of fetching and applying a patch to the file in your source. This +is done for any file that exists in a remote repository and in your +source; of which you haven't changed locally but is not the most recent +version available in the remote repository.@refill + +@item Modified +The file is modified in your working directory, and there was no +modification to the same file in the repository.@refill + +@item Merged +The file is modified in your working directory, and there were +modifications in the repository as well as in your copy, but they were +merged successfully, without conflict, in your working directory.@refill + +@item Conflict +A conflict was detected while trying to merge your changes to @var{file} +with changes from the source repository. @var{file} (the copy in your +working directory) is now the output of the @samp{rcsmerge} command on +the two versions; an unmodified copy of your file is also in your +working directory, with the name @file{.#@var{file}.@var{version}}, +where @var{version} is the RCS revision that your modified file started +from. @xref{Viewing differences}, for more details.@refill + +@item Added +The file has been added by you, but it still needs to be checked in to +the repository.@refill + +@item Removed +The file has been removed by you, but it needs to be checked in to the +repository. You can resurrect it by typing @kbd{a} (@pxref{Adding and +removing files}).@refill + +@item Unknown +A file that was detected in your directory, but that neither appears in +the repository, nor is present on the list of files that CVS should +ignore.@refill + +@end table + +There are also a few special cases, that rarely occur, which have longer +strings in the fields: + +@table @samp +@item Removed from repository +The file has been removed from your directory since someone has removed +it from the repository. (It is still present in the Attic directory, so +no permanent loss has occurred). This, unlike the other entries in this +table, is not an error condition.@refill + +@item Removed from repository, changed by you +You have modified a file that someone have removed from the repository. +You can correct this situation by removing the file manually (see +@pxref{Adding and removing files}).@refill + +@item Removed by you, changed in repository +You have removed a file, and before you committed the removal someone +committed a change to that file. You could use @kbd{a} to resurrect the +file (see @pxref{Adding and removing files}).@refill + +@item Move away @var{file} - it is in the way +For some reason CVS does not like the file @var{file}. Rename or remove +it.@refill + +@item This repository is missing! Remove this dir manually. +It is impossible to remove a directory in the CVS repository in a clean +way. Someone have tried to remove one, and CVS gets confused. Remove +your copy of the directory.@refill +@end table + + +@node Selected files, , File status, Buffer contents +@comment node-name, next, previous, up + +@section Selected files +@cindex Selected files +@cindex Marked files +@cindex File selection +@cindex Active files + +Many of the commands works on the current set of @dfn{selected} files. + +@itemize @bullet +@item +If there are any files that are marked they constitute the set of +selected files.@refill +@item +Otherwise, if the cursor points to a file, that file is the selected +file.@refill +@item +Otherwise, if the cursor points to a directory, all the files in that +directory that appears in the buffer are the selected files. +@end itemize + +This scheme might seem a little complicated, but once one get used to +it, it is quite powerful. + +@xref{Marking files} tells how you mark and unmark files. + + +@node Commands, Customization, Buffer contents, Top +@comment node-name, next, previous, up + +@chapter Commands + +@iftex +This chapter describes all the commands that you can use in pcl-cvs. +@end iftex +@ifinfo +The nodes in this menu contains explanations about all the commands that +you can use in pcl-cvs. They are grouped together by type. +@end ifinfo + +@menu +* Updating the directory:: Commands to update the local directory +* Movement commands:: How to move up and down in the buffer +* Marking files:: How to mark files that other commands + will later operate on. +* Committing changes:: Checking in your modifications to the + CVS repository. +* Editing files:: Loading files into Emacs. +* Getting info about files:: Display the log and status of files. +* Adding and removing files:: Adding and removing files +* Undoing changes:: Undoing changes +* Removing handled entries:: Uninteresting lines can easily be removed. +* Ignoring files:: Telling CVS to ignore generated files. +* Viewing differences:: Commands to @samp{diff} different versions. +* Invoking Ediff:: Running @samp{ediff} from @samp{*cvs*} buffer. +* Invoking Emerge:: Running @samp{emerge} from @samp{*cvs*} buffer. +* Reverting your buffers:: Reverting your buffers +* Miscellaneous commands:: Miscellaneous commands +@end menu + + +@node Updating the directory, Movement commands, Commands, Commands +@comment node-name, next, previous, up + +@section Updating the directory +@findex cvs-update +@findex cvs-mode-update-no-prompt +@findex cvs-delete-lock +@cindex Getting the *cvs* buffer +@kindex g - Rerun @samp{cvs update} + + +@table @kbd + +@item M-x cvs-update +Run a @samp{cvs update} command. You will be asked for the directory in +which the @samp{cvs update} will be run. The output will be parsed by +pcl-cvs, and the result printed in the @samp{*cvs*} buffer (see +@pxref{Buffer contents} for a description of the contents).@refill + +By default, @samp{cvs-update} will descend recursively into +subdirectories. You can avoid that behavior by giving a prefix +argument to it (e.g., by typing @kbd{C-u M-x cvs-update RET}).@refill + +All other commands in pcl-cvs requires that you have a @samp{*cvs*} +buffer. This is the command that you use to get one.@refill + +CVS uses lock files in the repository to ensure the integrity of the +data files in the repository. They might be left behind i.e. if a +workstation crashes in the middle of a CVS operation. CVS outputs a +message when it is waiting for a lock file to go away. Pcl-cvs will +show the same message in the *cvs* buffer, together with instructions +for deleting the lock files. You should normally not have to delete +them manually --- just wait a little while and the problem should fix +itself. But if the lock files doesn't disappear you can delete them +with @kbd{M-x cvs-delete-lock RET}.@refill + +@item g +This will run @samp{cvs update} again. It will always use the same +buffer that was used with the previous @samp{cvs update}. Give a prefix +argument to avoid descending into subdirectories. This runs the command +@samp{cvs-mode-update-no-prompt}.@refill + +@item G +This will run @samp{cvs update} and prompt for a new directory to +update. This runs the command @samp{cvs-update}.@refill + +@end table + + +@node Movement commands, Marking files, Updating the directory, Commands +@comment node-name, next, previous, up + +@section Movement Commands +@cindex Movement Commands +@findex cookie-next-cookie +@findex cookie-previous-cookie +@kindex SPC - Move down one file +@kindex C-n - Move down one file +@kindex n - Move down one file +@kindex C-p - Move up one file +@kindex p - Move up on file + +You can use most normal Emacs commands to move forward and backward in +the buffer. Some keys are rebound to functions that take advantage of +the fact that the buffer is a pcl-cvs buffer: + + +@table @kbd +@item SPC +@itemx C-n +@itemx n +These keys move the cursor one file forward, towards the end of the +buffer (@code{cookie-next-cookie}). + +@item C-p +@itemx p +These keys move one file backward, towards the beginning of the buffer +(@code{cookie-previous-cookie}). +@end table + + +@node Marking files, Committing changes, Movement commands, Commands +@comment node-name, next, previous, up + +@section Marking files +@cindex Selecting files (commands to mark files) +@cindex Marking files +@kindex m - marking a file +@kindex M - marking all files +@kindex u - unmark a file +@kindex ESC DEL - unmark all files +@kindex DEL - unmark previous file +@findex cvs-mode-mark +@findex cvs-mode-unmark +@findex cvs-mode-mark-all-files +@findex cvs-mode-unmark-all-files +@findex cvs-mode-unmark-up + +Pcl-cvs works on a set of @dfn{selected files} (@pxref{Selected files}). +You can mark and unmark files with these commands: + +@table @kbd +@item m +This marks the file that the cursor is positioned on. If the cursor is +positioned on a directory all files in that directory will be marked. +(@code{cvs-mode-mark}). + +@item u +Unmark the file that the cursor is positioned on. If the cursor is on a +directory, all files in that directory will be unmarked. +(@code{cvs-mode-unmark}).@refill + +@item M +Mark @emph{all} files in the buffer (@code{cvs-mode-mark-all-files}). + +@item @key{ESC} @key{DEL} +Unmark @emph{all} files (@code{cvs-mode-unmark-all-files}). + +@item @key{DEL} +Unmark the file on the previous line, and move point to that line +(@code{cvs-mode-unmark-up}). +@end table + + +@node Committing changes, Editing files, Marking files, Commands +@comment node-name, next, previous, up + +@section Committing changes +@cindex Committing changes +@cindex Ci +@findex cvs-mode-commit +@findex cvs-mode-changelog-commit +@kindex c - commit files +@kindex C - commit files with ChangeLog message +@vindex cvs-erase-input-buffer (variable) +@vindex cvs-auto-revert-after-commit (variable) +@cindex Commit buffer +@cindex Edit buffer +@cindex Erasing commit message +@cindex Reverting buffers after commit + +@table @kbd + +@item c +All files that have a "need to be checked in"-marker (@pxref{Buffer +contents}) can be checked in with the @kbd{c} command. It checks in all +selected files (@pxref{Selected files}) (except those who lack the +"ci"-marker - they are ignored). Pressing @kbd{c} causes +@code{cvs-mode-commit} to be run.@refill + +When you press @kbd{c} you will get a buffer called +@samp{*cvs-commit-message*}. Enter the log message for the file(s) in +it. When you are ready you should press @kbd{C-c C-c} to actually +commit the files (using @code{cvs-edit-done}). + +Normally the @samp{*cvs-commit-message*} buffer will retain the log +message from the previous commit, but if the variable +@code{cvs-erase-input-buffer} is set to a non-@code{nil} value the +buffer will be erased. Point and mark will always be located around the +entire buffer so that you can easily erase it with @kbd{C-w} +(@samp{kill-region}).@refill + +If you are editing the files in your emacs an automatic +@samp{revert-buffer} will be performed. (If the file contains +@samp{$@asis{Id}$} keywords @samp{cvs commit} will write a new file with +the new values substituted. The auto-revert makes sure that you get +them into your buffer). The revert will not occur if you have modified +your buffer, or if @samp{cvs-auto-revert-after-commit} is set to +@samp{nil}.@refill + +@item C +This is just like @samp{cvs-mode-commit}, except that it tries to +provide appropriate default log messages by looking at the +@samp{ChangeLog}s in the current directory. The idea is to write your +ChangeLog entries first, and then use this command to commit your +changes. Pressing @kbd{C} causes @code{cvs-mode-changelog-commit} to be +run.@refill + +To select default log text, pcl-cvs: +@itemize @minus +@item +finds the ChangeLogs for the files to be checked in; +@item +verifies that the top entry in the ChangeLog is on the current date and +by the current user; if not, no default text is provided; +@item +search the ChangeLog entry for paragraphs containing the names of the +files we're checking in; and finally +@item +uses those paragraphs as the default log text in the +@samp{*cvs-commit-message*} buffer. +@end itemize + +You can then commit the @samp{ChangeLog} file once per day without any +log message.@refill + +@end table + + +@node Editing files, Getting info about files, Committing changes, Commands +@comment node-name, next, previous, up + +@section Editing files +@cindex Editing files +@cindex Finding files +@cindex Loading files +@cindex Dired +@cindex Invoking dired +@findex cvs-mode-find-file +@findex cvs-mode-find-file-other-window +@findex cvs-mode-add-change-log-entry-other-window +@kindex f - find file or directory +@kindex o - find file in other window +@kindex A - add ChangeLog entry + +There are currently three commands that can be used to find a file (that +is, load it into a buffer and start editing it there). These commands +work on the line that the cursor is situated at. They ignore any marked +files. + +@table @kbd +@item f +Find the file that the cursor points to. Run @samp{dired} +@ifinfo +(@pxref{Dired,,,Emacs}) +@end ifinfo +if the cursor points to a directory (@code{cvs-mode-find-file}).@refill + +@item o +Like @kbd{f}, but use another window +(@code{cvs-mode-find-file-other-window}).@refill + +@item A +Invoke @samp{add-change-log-entry-other-window} to edit a +@samp{ChangeLog} file. The @samp{ChangeLog} will be found in the +directory of the file the cursor points to. +(@code{cvs-mode-add-change-log-entry-other-window}).@refill +@end table + + +@node Getting info about files, Adding and removing files, Editing files, Commands +@comment node-name, next, previous, up + +@section Getting info about files +@cindex Status (cvs command) +@cindex Log (RCS/cvs command) +@cindex Getting status +@kindex l - run @samp{cvs log} +@kindex s - run @samp{cvs status} +@findex cvs-mode-log +@findex cvs-mode-status + +Both of the following commands can be customized. +@xref{Customization}.@refill + +@table @kbd +@item l +Run @samp{cvs log} on all selected files, and show the result in a +temporary buffer (@code{cvs-mode-log}). + +@item s +Run @samp{cvs status} on all selected files, and show the result in a +temporary buffer (@code{cvs-mode-status}). +@end table + + +@node Adding and removing files, Undoing changes, Getting info about files, Commands +@comment node-name, next, previous, up + +@section Adding and removing files +@cindex Adding files +@cindex Removing files +@cindex Resurrecting files +@cindex Deleting files +@cindex Putting files under CVS control +@kindex a - add a file +@kindex r - remove a file +@findex cvs-mode-add +@findex cvs-mode-remove-file + +The following commands are available to make it easy to add and remove +files from the CVS repository. + +@table @kbd +@item a +Add all selected files. This command can be used on @samp{Unknown} +files (see @pxref{File status}). The status of the file will change to +@samp{Added}, and you will have to use @kbd{c} (@samp{cvs-mode-commit}, see +@pxref{Committing changes}) to really add the file to the +repository.@refill + +This command can also be used on @samp{Removed} files (before you commit +them) to resurrect them. + +Selected files that are neither @samp{Unknown} nor @samp{Removed} will +be ignored by this command. + +The command that is run is @code{cvs-mode-add}. + +@item r +This command removes the selected files (after prompting for +confirmation). The files are @samp{rm}ed from your directory and +(unless the status was @samp{Unknown}; @pxref{File status}) they will +also be @samp{cvs remove}d. If the files were @samp{Unknown} they will +disappear from the buffer. Otherwise their status will change to +@samp{Removed}, and you must use @kbd{c} (@samp{cvs-mode-commit}, +@pxref{Committing changes}) to commit the removal.@refill + +The command that is run is @code{cvs-mode-remove-file}. +@end table + + +@node Undoing changes, Removing handled entries, Adding and removing files, Commands +@comment node-name, next, previous, up + +@section Undoing changes +@cindex Undo changes +@cindex Flush changes +@kindex U - undo changes +@findex cvs-mode-undo-local-changes + +@table @kbd +@item U +If you have modified a file, and for some reason decide that you don't +want to keep the changes, you can undo them with this command. It works +by removing your working copy of the file and then getting the latest +version from the repository (@code{cvs-mode-undo-local-changes}. +@end table + + +@node Removing handled entries, Ignoring files, Undoing changes, Commands +@comment node-name, next, previous, up + +@section Removing handled entries +@cindex Expunging uninteresting entries +@cindex Uninteresting entries, getting rid of them +@cindex Getting rid of uninteresting lines +@cindex Removing uninteresting (processed) lines +@cindex Handled lines, removing them +@kindex x - remove processed entries +@kindex C-k - remove selected entries +@findex cvs-mode-remove-handled +@findex cvs-mode-acknowledge + +@table @kbd +@item x +This command allows you to remove all entries that you have processed. +More specifically, the lines for @samp{Updated} files (@pxref{File +status} and files that have been checked in (@pxref{Committing changes}) +are removed from the buffer. If a directory becomes empty the heading +for that directory is also removed. This makes it easier to get an +overview of what needs to be done. + +The command is called @code{cvs-mode-remove-handled}. If +@samp{cvs-auto-remove-handled} is set to non-@code{nil} this will +automatically be performed after every commit.@refill + +@item C-k +This command can be used for lines that @samp{cvs-mode-remove-handled} would +not delete, but that you want to delete (@code{cvs-mode-acknowledge}). +@end table + + +@node Ignoring files, Viewing differences, Removing handled entries, Commands +@comment node-name, next, previous, up + +@section Ignoring files +@kindex i - ignoring files +@findex cvs-mode-ignore + +@table @kbd +@item i +Arrange so that CVS will ignore the selected files. The file names are +added to the @file{.cvsignore} file in the corresponding directory. If +the @file{.cvsignore} doesn't exist it will be created. + +The @file{.cvsignore} file should normally be added to the repository, +but you could ignore it also if you like it better that way. + +This runs @code{cvs-mode-ignore}. +@end table + + +@node Viewing differences, Invoking Ediff, Ignoring files, Commands +@comment node-name, next, previous, up + +@section Viewing differences +@cindex Diff +@cindex Ediff +@cindex Invoking ediff +@cindex Conflicts, how to resolve them +@cindex Viewing differences +@kindex d - run @samp{cvs diff} +@kindex b - diff backup file +@findex cvs-mode-diff-cvs +@findex cvs-mode-diff-backup +@vindex cvs-diff-ignore-marks (variable) + +@table @kbd +@item d +Display a @samp{cvs diff} between the selected files and the RCS version +that they are based on. @xref{Customization} describes how you can send +flags to @samp{cvs diff}. If @var{cvs-diff-ignore-marks} is set to a +non-@code{nil} value or if a prefix argument is given (but not both) any +marked files will not be considered to be selected. +(@code{cvs-mode-diff-cvs}).@refill + +@item b +If CVS finds a conflict while merging two versions of a file (during a +@samp{cvs update}, @pxref{Updating the directory}) it will save the +original file in a file called @file{.#@var{FILE}.@var{VERSION}} where +@var{FILE} is the name of the file, and @var{VERSION} is the RCS version +number that your file was based on.@refill + +With the @kbd{b} command you can run a @samp{diff} on the files +@file{.#@var{FILE}.@var{VERSION}} and @file{@var{FILE}}. You can get a +context- or Unidiff by setting @samp{cvs-diff-flags} - +@pxref{Customization}. This command only works on files that have +status @samp{Conflict} or @samp{Merged}.@refill + +If @var{cvs-diff-ignore-marks} is set to a non-@code{nil} value or if a +prefix argument is given (but not both) any marked files will not be +considered to be selected. (@code{cvs-mode-diff-backup}).@refill +@end table + + +@node Invoking Ediff, Invoking Emerge, Viewing differences, Commands +@comment node-name, next, previous, up + +@section Running ediff +@cindex Ediff +@cindex Invoking ediff +@cindex Viewing differences +@cindex Conflicts, resolving +@cindex Resolving conflicts +@kindex e - invoke @samp{ediff} +@findex cvs-mode-ediff +@findex run-ediff-from-cvs-buffer +@findex cvs-old-ediff-interface + +@table @kbd +@item e +This command works +slightly different depending on the version of @samp{ediff} and the file +status.@refill + +With modern versions of @samp{ediff}, this command invokes +@samp{run-ediff-from-cvs-buffer} on one file.@refill + +@strong{Note:} When the file status is @samp{Merged} or @samp{Conflict}, +CVS has already performed a merge. The resulting file is not used in +any way if you use this command. If you use the @kbd{q} command inside +@samp{ediff} (to successfully terminate a merge) the file that CVS +created will be overwritten.@refill + +Older versions of @samp{ediff} use an interface similar to +@samp{emerge}. The function @samp{cvs-old-ediff-interface} is invoked +if the version of @samp{ediff} you have doesn't support +@samp{run-ediff-from-cvs-buffer}. These older versions do not support +merging of revisions.@refill + +@table @asis +@item @samp{Modified} +Run @samp{ediff-files} with your working file as file A, and the latest +revision in the repository as file B. + +@item @samp{Merged} +@itemx @samp{Conflict} +Run @samp{ediff-files3} with your working file (as it was prior to your +invocation of @samp{cvs-update}) as file A, the latest revision in the +repository as file B, and the revision that you based your local +modifications on as ancestor. + +@item @samp{Updated} +@itemx @samp{Patched} +Run @samp{ediff-files} with your working file as file A, and a given +revision in the repository as file B. You are prompted for the revision +to ediff against, and you may specify either a tag name or a numerical +revision number (@pxref{Getting info about files}). +@end table + +@end table + +@node Invoking Emerge, Reverting your buffers, Invoking Ediff, Commands +@comment node-name, next, previous, up + +@section Running emerge +@cindex Emerge +@cindex Ediff +@cindex Viewing differences +@cindex Invoking emerge +@cindex Conflicts, resolving +@cindex Resolving conflicts +@kindex E - invoke @samp{emerge} +@findex cvs-mode-emerge + +@table @kbd +@item E +Invoke @samp{emerge} on one file. This command works slightly different +depending on the file status. + +@table @asis +@item @samp{Modified} +Run @samp{emerge-files} with your working file as file A, and the latest +revision in the repository as file B. + +@item @samp{Merged} +@itemx @samp{Conflict} +Run @samp{emerge-files-with-ancestor} with your working file (as it was +prior to your invocation of @samp{cvs-update}) as file A, the latest +revision in the repository as file B, and the revision that you based +your local modifications on as ancestor. +@end table + +@strong{Note:} When the file status is @samp{Merged} or @samp{Conflict}, +CVS has already performed a merge. The resulting file is not used in +any way if you use this command. If you use the @kbd{q} command inside +@samp{emerge} (to successfully terminate the merge) the file that CVS +created will be overwritten. + +@end table + + +@node Reverting your buffers, Miscellaneous commands, Invoking Emerge, Commands +@comment node-name, next, previous, up + +@section Reverting your buffers +@findex cvs-mode-revert-updated-buffers +@kindex R - revert buffers +@cindex Syncing buffers +@cindex Reverting buffers + +@table @kbd +@item R +If you are editing (or just viewing) a file in a buffer, and that file +is changed by CVS during a @samp{cvs-update}, all you have to do is type +@kbd{R} in the *cvs* buffer to read in the new versions of the +files.@refill + +All files that are @samp{Updated}, @samp{Merged} or in @samp{Conflict} +are reverted from the disk. Any other files are ignored. Only files +that you were already editing are read.@refill + +An error is signalled if you have modified the buffer since it was last +changed. (@code{cvs-mode-revert-updated-buffers}).@refill +@end table + + +@node Miscellaneous commands, , Reverting your buffers, Commands +@comment node-name, next, previous, up + +@section Miscellaneous commands +@findex cvs-byte-compile-files +@cindex Recompiling elisp files +@cindex Byte compilation +@cindex Getting rid of lock files +@cindex Lock files +@kindex q - bury the *cvs* buffer +@findex bury-buffer + +@table @kbd +@item M-x cvs-byte-compile-files +Byte compile all selected files that end in .el. + +@item M-x cvs-delete-lock +This command can be used in any buffer, and deletes the lock files that +the *cvs* buffer informs you about. You should normally never have to +use this command since CVS tries very carefully to always remove the +lock files itself. + +You can only use this command when a message in the *cvs* buffer tells +you so. You should wait a while before using this command in case +someone else is running a cvs command. + +@item q +Bury the *cvs* buffer. (@code{bury-buffer}). + +@end table + + +@node Customization, Future enhancements, Commands, Top +@comment node-name, next, previous, up + +@chapter Customization +@vindex cvs-erase-input-buffer (variable) +@vindex cvs-inhibit-copyright-message (variable) +@vindex cvs-diff-flags (variable) +@vindex cvs-diff-ignore-marks (variable) +@vindex cvs-log-flags (variable) +@vindex cvs-status-flags (variable) +@vindex cvs-auto-remove-handled (variable) +@vindex cvs-update-prog-output-skip-regexp (variable) +@vindex cvs-cvsroot (variable) +@vindex TMPDIR (environment variable) +@vindex cvs-auto-revert-after-commit (variable) +@vindex cvs-commit-buffer-require-final-newline (variable) +@vindex cvs-sort-ignore-file (variable) +@cindex Inhibiting the Copyright message. +@cindex Copyright message, getting rid of it +@cindex Getting rid of the Copyright message. +@cindex Customization +@cindex Variables, list of all +@cindex Erasing the input buffer +@cindex Context diff, how to get +@cindex Unidiff, how to get +@cindex Automatically remove handled files +@cindex -u option in modules file +@cindex Modules file (-u option) +@cindex Update program (-u option in modules file) +@cindex Reverting buffers after commit +@cindex Require final newline +@cindex Automatically inserting newline +@cindex Commit message, inserting newline +@cindex Sorting the .cvsignore file +@cindex .cvsignore file, sorting +@cindex Automatically sorting .cvsignore + +If you have an idea about any customization that would be handy but +isn't present in this list, please tell me! @xref{Bugs} for info on how +to reach me.@refill + +@table @samp +@item cvs-erase-input-buffer +If set to anything else than @code{nil} the edit buffer will be erased +before you write the log message (@pxref{Committing changes}). + +@item cvs-inhibit-copyright-message +The copyright message that is displayed on startup can be annoying after +a while. Set this variable to @samp{t} if you want to get rid of it. +(But don't set this to @samp{t} in the system defaults file - new users +should see this message at least once). + +@item cvs-diff-flags +A list of strings to pass as arguments to the @samp{cvs diff} and +@samp{diff} programs. This is used by @samp{cvs-mode-diff-cvs} and +@samp{cvs-mode-diff-backup} (key @kbd{b}, @pxref{Viewing differences}). If +you prefer the Unidiff format you could add this line to your +@file{.emacs} file:@refill + +@example +(setq cvs-diff-flags '("-u")) +@end example + +@item cvs-diff-ignore-marks +If this variable is non-@code{nil} or if a prefix argument is given (but +not both) to @samp{cvs-mode-diff-cvs} or @samp{cvs-mode-diff-backup} +marked files are not considered selected. + +@item cvs-log-flags +List of strings to send to @samp{cvs log}. Used by @samp{cvs-mode-log} +(key @kbd{l}, @pxref{Getting info about files}). + +@item cvs-status-flags +List of strings to send to @samp{cvs status}. Used by @samp{cvs-mode-status} +(key @kbd{s}, @pxref{Getting info about files}). + +@item cvs-auto-remove-handled +If this variable is set to any non-@code{nil} value +@samp{cvs-mode-remove-handled} will be called every time you check in +files, after the check-in is ready. @xref{Removing handled +entries}.@refill + +@item cvs-auto-revert-after-commit +If this variable is set to any non-@samp{nil} value any buffers you have +that visit a file that is committed will be automatically reverted. +This variable is default @samp{t}. @xref{Committing changes}.@refill + +@item cvs-update-prog-output-skip-regexp +The @samp{-u} flag in the @file{modules} file can be used to run a command +whenever a @samp{cvs update} is performed (see cvs(5)). This regexp +is used to search for the last line in that output. It is normally set +to @samp{"$"}. That setting is only correct if the command outputs +nothing. Note that pcl-cvs will get very confused if the command +outputs @emph{anything} to @samp{stderr}. + +@item cvs-cvsroot +This variable can be set to override @samp{CVSROOT}. It should be a +string. If it is set then everytime a cvs command is run it will be +called as @samp{cvs -d @var{cvs-cvsroot}@dots{}} This can be useful if +your site has several repositories. + +@item TMPDIR +Pcl-cvs uses this @emph{environment variable} to decide where to put the +temporary files it needs. It defaults to @file{/tmp} if it is not set. + +@item cvs-commit-buffer-require-final-newline +When you enter a log message in the @samp{*cvs-commit-message*} buffer +pcl-cvs will normally automatically insert a trailing newline, unless +there already is one. This behavior can be controlled via +@samp{cvs-commit-buffer-require-final-newline}. If it is @samp{t} (the +default behavior), a newline will always be appended. If it is +@samp{nil}, newlines will never be appended. Any other value causes +pcl-cvs to ask the user whenever there is no trailing newline in the +commit message buffer. + +@item cvs-sort-ignore-file +If this variable is set to any non-@samp{nil} value the +@file{.cvsignore} will always be sorted whenever you use +@samp{cvs-mode-ignore} to add a file to it. This option is on by +default. + +@end table + + +@node Future enhancements, Bugs, Customization, Top +@comment node-name, next, previous, up + +@chapter Future enhancements +@cindex Enhancements + +Pcl-cvs is still under development and needs a number of enhancements to +be called complete. Below is my current wish-list for future releases +of pcl-cvs. Please, let me know which of these features you want most. +They are listed below in approximately the order that I currently think +I will implement them in. + +@itemize @bullet +@item +Rewritten parser code. There are many situations where pcl-cvs will +fail to recognize the output from CVS. The situation could be greatly +increased. + +@item +@samp{cvs-status}. This will run @samp{cvs status} in a directory and +produce a buffer that looks pretty much like the current *cvs* buffer. +That buffer will include information for all version-controlled files. +(There will be a simple keystroke to remove all "uninteresting" files, +that is, files that are "Up-to-date"). In this new buffer you will be +able to update a file, commit a file, et c. The big win with this is +that you will be able to watch the differences between your current +working file and the head revision in the repository before you update +the file, and you can then choose to update it or let it wait for a +while longer. + +@item +Log mode. When this mode is finished you will be able to move around +(using @kbd{n} and @kbd{p}) between the revisions of a file, mark two of +them, and run a diff between them. You will be able to hide branches +(similar to the way you can hide sub-paragraphs in outline-mode) and do +merges between revisions. Other ideas about this are welcome. + +@item +The current model for marks in the *cvs* buffer seems to be confusing. +I am considering to use the VM model instead, where marks are normally +inactive. To activate the mark, you issue a command like +@samp{cvs-mode-next-command-uses-marks}. I might implement a flag so +that you can use either version. Feedback on this before I start coding +it is very welcome. + +@item +It should be possible to run commands such as @samp{cvs log}, @samp{cvs +status} and @samp{cvs commit} directly from a buffer containing a file, +instead of having to @samp{cvs-update}. If the directory contains many +files the @samp{cvs-update} can take quite some time, especially on a +slow machine. I planed to put these kind of commands on the prefix +@kbd{C-c C-v}, but that turned out to be used by for instance c++-mode. +If you have any suggestions for a better prefix key, please let me know. + +@item +Increased robustness. For instance, you can not currently press +@kbd{C-g} when you are entering the description of a file that you are +adding without confusing pcl-cvs. + +@item +Support for multiple active *cvs* buffers. + +@item +Dired support. I have an experimental @file{dired-cvs.el} that works +together with CVS 1.2. Unfortunately I wrote it on top of a +non-standard @file{dired.el}, so it must be rewritten.@refill + +@item +An ability to send user-supplied options to all the cvs commands. + +@item +Pcl-cvs is not at all clever about what it should do when @samp{cvs +update} runs a program (due to the @samp{-u} option in the +@file{modules} file --- see @samp{cvs(5)}). The current release uses a +regexp to search for the end. At the very least that regexp should be +configured for different modules. Tell me if you have any idea about +what is the right thing to do. In a perfect world the program should +also be allowed to print to @samp{stderr} without causing pcl-cvs to +crash. +@end itemize + + +If you miss something in this wish-list, let me know! I don't promise +that I will write it, but I will at least try to coordinate the efforts +of making a good Emacs front end to CVS. See @xref{Bugs} for +information about how to reach me.@refill + +So far, I have written most of pcl-cvs in my all-to-rare spare time. If +you want pcl-cvs to be developed faster you can write a contract with +Signum Support to do the extension. You can reach Signum Support by +email to @samp{info@@signum.se} or via mail to Signum Support AB, Box +2044, S-580 02 Linkoping, Sweden. Phone: +46 (0) 13 - 21 46 00. Fax: ++46 (0) 13 - 21 47 00. + + +@node Bugs, COPYING, Future enhancements, Top +@comment node-name, next, previous, up + +@chapter Bugs (known and unknown) +@cindex Reporting bugs and ideas +@cindex Bugs, how to report them +@cindex Author, how to reach +@cindex Email to the author +@cindex Known bugs +@cindex Bugs, known +@cindex FAQ +@cindex Problems, list of common + +If you find a bug or misfeature, don't hesitate to tell me! Send email +to @samp{ceder@@lysator.liu.se}. + +If you have ideas for improvements, or if you have written some +extensions to this package, I would like to hear from you. I hope that +you find this package useful! + +Below is a partial list of currently known problems with pcl-cvs version +1.05. + +@table @asis +@item Commit causes Emacs to hang +Emacs waits for the @samp{cvs commit} command to finish before you can +do anything. If you start a background job from the loginfo file you +must take care that it closes @samp{stdout} and @samp{stderr} if you do +not want to wait for it. (You do that with @samp{background-command &>- +2&>- &} if you are starting @samp{background-command} from a +@samp{/bin/sh} shell script). + +Your emacs will also hang if there was a lock file in the repository. +In this case you can type @kbd{C-g} to get control over your emacs +again. + +@item Name clash in Emacs 19 +This is really a bug in Elib or the Emacs 19 distribution. Both Elib and +Emacs 19.6 through at least 19.10 contains a file named +@file{cookie.el}. One of the files will have to be renamed, and we are +currently negotiating about which of the files to rename. + +@item Commands while cvs-update is running +It is possible to type commands in the *cvs* buffer while the update is +running, but error messages is all that you will get. The error +messages should be better. + +@item Unexpected output from CVS +Unexpected output from CVS confuses pcl-cvs. It will currently create a +bug report that you can mail to me. It should do something more +civilized. +@end table + + +@node COPYING, Function and Variable Index, Bugs, Top +@comment node-name, next, previous, up + +@appendix GNU GENERAL PUBLIC LICENSE +@c @include gpl.texinfo + + +@node Function and Variable Index, Concept Index, COPYING, Top +@comment node-name, next, previous, up + +@unnumbered Function and Variable Index + +@printindex fn + + +@node Concept Index, Key Index, Function and Variable Index, Top +@comment node-name, next, previous, up + +@unnumbered Concept Index + +@printindex cp + + +@node Key Index, , Concept Index, Top +@comment node-name, next, previous, up + +@unnumbered Key Index + +@printindex ky + +@summarycontents +@contents +@bye |