mirror of
https://github.com/vitalif/viewvc-4intranet
synced 2019-04-16 04:14:59 +03:00
Compare commits
251 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
dd4b0785a4 | ||
![]() |
4f5800cec5 | ||
![]() |
e4cf54b49e | ||
![]() |
24070dcc6f | ||
![]() |
afe213e140 | ||
![]() |
0dd6919af9 | ||
![]() |
1b4543ed92 | ||
![]() |
26259d1fb1 | ||
![]() |
6941197684 | ||
![]() |
02b0870390 | ||
![]() |
715f4dfa5a | ||
![]() |
5e5ee35046 | ||
![]() |
77355718c6 | ||
![]() |
19ab90b4fb | ||
![]() |
7a3c8bf9c4 | ||
![]() |
d11dffb713 | ||
![]() |
16281c1db5 | ||
![]() |
0da8dec361 | ||
![]() |
1606e4e6dd | ||
![]() |
0393ef47f7 | ||
![]() |
3d9eb3c638 | ||
![]() |
6a0c6f2029 | ||
![]() |
b564bd020a | ||
![]() |
889f71f657 | ||
![]() |
84188fafbb | ||
![]() |
eff0ddeabe | ||
![]() |
d69802487f | ||
![]() |
0ff4aed795 | ||
![]() |
a4aa00b5f1 | ||
![]() |
cef439958f | ||
![]() |
175d6f0ac4 | ||
![]() |
27df7480c5 | ||
![]() |
05bec0ce73 | ||
![]() |
d7c84f3149 | ||
![]() |
a0ece2036e | ||
![]() |
fcec542cfb | ||
![]() |
37166f065c | ||
![]() |
983a2c298e | ||
![]() |
51988428f2 | ||
![]() |
9a8c28d886 | ||
![]() |
02db1bf262 | ||
![]() |
b048907b6d | ||
![]() |
5e497355a4 | ||
![]() |
45d1849883 | ||
![]() |
1e082e2982 | ||
![]() |
55a94583fa | ||
![]() |
cb41e82e6b | ||
![]() |
2b2f813262 | ||
![]() |
679153a77c | ||
![]() |
674a2ee494 | ||
![]() |
8930cd8736 | ||
![]() |
bef73fd21a | ||
![]() |
9fdfe05d24 | ||
![]() |
70b05c8a5e | ||
![]() |
2bc61a621d | ||
![]() |
eae9e0131f | ||
![]() |
28ecb8c8f4 | ||
![]() |
0642725543 | ||
![]() |
ad9a3d8c07 | ||
![]() |
7232487cf0 | ||
![]() |
2e9bbd9be6 | ||
![]() |
34b639106b | ||
![]() |
18667d725d | ||
![]() |
d01364d0bd | ||
![]() |
c80469ba5e | ||
![]() |
c8f661e626 | ||
![]() |
2376c9a7ef | ||
![]() |
2be790dcb3 | ||
![]() |
61c8b3d41f | ||
![]() |
341525d3e4 | ||
![]() |
808cf5265c | ||
![]() |
e824f93fb5 | ||
![]() |
9dcdc1a660 | ||
![]() |
9da51dbec2 | ||
![]() |
a0503097c0 | ||
![]() |
1e5d9dd974 | ||
![]() |
9322e4e59e | ||
![]() |
657b50834d | ||
![]() |
09c4ee378a | ||
![]() |
af4ba05019 | ||
![]() |
946e480778 | ||
![]() |
227fa91673 | ||
![]() |
c2d403b482 | ||
![]() |
248c7998c4 | ||
![]() |
d867b9001f | ||
![]() |
9639a02d25 | ||
![]() |
f5fa59bbd3 | ||
![]() |
5367f945e7 | ||
![]() |
9a797cdd53 | ||
![]() |
2af952b542 | ||
![]() |
86649f829b | ||
![]() |
b2b26e4a65 | ||
![]() |
ab0ac566fe | ||
![]() |
1c26250da7 | ||
![]() |
c230a38f9d | ||
![]() |
89a9e91a56 | ||
![]() |
773eb89f9b | ||
![]() |
7ae3a23f5f | ||
![]() |
127d12fd9c | ||
![]() |
d206d1d80b | ||
![]() |
fa424931e7 | ||
![]() |
bff90274b1 | ||
![]() |
45d2dec67b | ||
![]() |
f5c4bab1d0 | ||
![]() |
0b033dc738 | ||
![]() |
155679c36f | ||
![]() |
aaa04ac917 | ||
![]() |
260d00b24b | ||
![]() |
91f6f38ae4 | ||
![]() |
57850b9d84 | ||
![]() |
f3538a0136 | ||
![]() |
89282b3125 | ||
![]() |
10d0f30cfa | ||
![]() |
de7c322520 | ||
![]() |
fdabbe7687 | ||
![]() |
289e6514da | ||
![]() |
132f7e2342 | ||
![]() |
06b3adbbbf | ||
![]() |
77a8a74a05 | ||
![]() |
621adf6b35 | ||
![]() |
d89a788783 | ||
![]() |
2dade9e281 | ||
![]() |
3d81935a92 | ||
![]() |
9c1d4a954b | ||
![]() |
ff58af471c | ||
![]() |
9b25497fe9 | ||
![]() |
b01b1c3b1e | ||
![]() |
e3a16b2481 | ||
![]() |
457fa117d4 | ||
![]() |
9e55650c62 | ||
![]() |
50e4520d0f | ||
![]() |
1dbc6dcb57 | ||
![]() |
c870cc4f23 | ||
![]() |
7a0fcf1e9e | ||
![]() |
bf69d3d2aa | ||
![]() |
5526c78b4a | ||
![]() |
fe34dc73ad | ||
![]() |
c0859c9dd2 | ||
![]() |
ce17dc8180 | ||
![]() |
fcfada618a | ||
![]() |
fe17a6c8c6 | ||
![]() |
a8ace27847 | ||
![]() |
55052f1854 | ||
![]() |
8bb281930b | ||
![]() |
e31eb7859e | ||
![]() |
47b652e457 | ||
![]() |
44b8620adf | ||
![]() |
3db5f61f5e | ||
![]() |
ca3e52b05b | ||
![]() |
67fe0ffd7c | ||
![]() |
58f2c57c33 | ||
![]() |
0268b877e5 | ||
![]() |
620307a64c | ||
![]() |
e833b962b9 | ||
![]() |
f9953b7d87 | ||
![]() |
0d62e2c0c9 | ||
![]() |
cf456c5cfb | ||
![]() |
e9dee7ae87 | ||
![]() |
ec99cb429b | ||
![]() |
8093438d6b | ||
![]() |
a0135416b5 | ||
![]() |
27bcce39a8 | ||
![]() |
4e07d3a2ae | ||
![]() |
b8582e57c7 | ||
![]() |
5684cd97ad | ||
![]() |
56695b3f26 | ||
![]() |
352e8203cd | ||
![]() |
350db5250c | ||
![]() |
880690fc89 | ||
![]() |
a64dc83028 | ||
![]() |
d29fac8ba9 | ||
![]() |
6145ac2213 | ||
![]() |
32da3ae204 | ||
![]() |
cbadda8611 | ||
![]() |
451da3e26d | ||
![]() |
5130a7347f | ||
![]() |
2a83c75317 | ||
![]() |
8d1daf1787 | ||
![]() |
6c6aee893f | ||
![]() |
02f2243cc7 | ||
![]() |
8766cf00ca | ||
![]() |
ba039d5a8f | ||
![]() |
007ad51b2c | ||
![]() |
c3d12bb0c8 | ||
![]() |
d0de5e7ad0 | ||
![]() |
4a00a0bb4f | ||
![]() |
abb4a468fc | ||
![]() |
cc3807f964 | ||
![]() |
3bff6b7378 | ||
![]() |
fc6d80e2fb | ||
![]() |
425ae2e8ec | ||
![]() |
c2a29c83cd | ||
![]() |
799816c21f | ||
![]() |
e3dbdb4fcc | ||
![]() |
c28afba169 | ||
![]() |
d790f07d2c | ||
![]() |
edf252fb2f | ||
![]() |
6942dd6b4b | ||
![]() |
a75f32fc3c | ||
![]() |
e243a0e1ad | ||
![]() |
755e4e74c3 | ||
![]() |
a46b9dd3ba | ||
![]() |
ab987dc6fa | ||
![]() |
a6ac1d1892 | ||
![]() |
945cb91e38 | ||
![]() |
262f3569c7 | ||
![]() |
1b358fc88d | ||
![]() |
455faa1fe6 | ||
![]() |
01facd113b | ||
![]() |
67daa9e5e2 | ||
![]() |
03a5620947 | ||
![]() |
424521d40e | ||
![]() |
1eeb39f264 | ||
![]() |
7aa5c33589 | ||
![]() |
60236803e4 | ||
![]() |
a26cfff0ad | ||
![]() |
f0c34a715d | ||
![]() |
4cb1282b70 | ||
![]() |
f34424ca20 | ||
![]() |
bc6851ea6f | ||
![]() |
3b10a1763f | ||
![]() |
329d20633a | ||
![]() |
5251065e6d | ||
![]() |
3d6f71ce00 | ||
![]() |
1a69430064 | ||
![]() |
050ad1f8fe | ||
![]() |
3965ce9221 | ||
![]() |
12003f27b5 | ||
![]() |
b0b1b5603e | ||
![]() |
2b8cc9d34c | ||
![]() |
4ddb89e053 | ||
![]() |
c9e782c881 | ||
![]() |
7d8f036490 | ||
![]() |
02bad3fbe3 | ||
![]() |
892364776e | ||
![]() |
8996332950 | ||
![]() |
eb214de775 | ||
![]() |
5ca77ced3c | ||
![]() |
91d2ebfc64 | ||
![]() |
ee7abc077a | ||
![]() |
087db42096 | ||
![]() |
028b54de90 | ||
![]() |
cb4f496b99 | ||
![]() |
573293bbfc | ||
![]() |
e6080c8366 | ||
![]() |
346c91205b | ||
![]() |
3828e969de | ||
![]() |
1a821bda9e | ||
![]() |
ddd782a950 | ||
![]() |
f2dffceb6d | ||
![]() |
8810ab6c57 |
26
CHANGES
Normal file
26
CHANGES
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
Version 0.8 (released 10-Dec-2001)
|
||||||
|
|
||||||
|
* add EZT templating mechanism for generating output pages
|
||||||
|
* big update of cvs commit database
|
||||||
|
- updated MySQL support
|
||||||
|
- new CGI
|
||||||
|
- better database caching
|
||||||
|
- switch from old templates to new EZT templates (and integration
|
||||||
|
of look-and-feel)
|
||||||
|
* optional usage of CVSGraph is now builtin
|
||||||
|
* standalone server (for testing) is now provided
|
||||||
|
* shifted some options from viewcvs.conf to the templates
|
||||||
|
* the help at the top of the pages has been shifted to separate help
|
||||||
|
pages, so experienced users don't have to keep seeing it
|
||||||
|
* paths in viewcvs.conf don't require trailing slashes any more
|
||||||
|
* tweak the colorizing for Pascal and Fortran files
|
||||||
|
* fix file readability problem where the user had access via the
|
||||||
|
group, but the process' group did not match that group
|
||||||
|
* some Daylight Savings Time fixes in the CVS commit database
|
||||||
|
* fix tarball generation (the file name) for the root dir
|
||||||
|
* changed default human-readable-diff colors to "stoplight" metaphor
|
||||||
|
* web site and doc revamps
|
||||||
|
* fix the mime types on the download, view, etc links
|
||||||
|
* improved error response when the cvs root is missing
|
||||||
|
* don't try to process vhosts if the config section is not present
|
||||||
|
* various bug fixes and UI tweaks
|
219
INSTALL
219
INSTALL
@@ -1,25 +1,86 @@
|
|||||||
|
CONTENTS
|
||||||
|
--------
|
||||||
|
TO THE IMPATIENT
|
||||||
|
INSTALLING VIEWCVS
|
||||||
|
UPGRADING VIEWCVS
|
||||||
|
SQL CHECKIN DATABASE
|
||||||
|
ENSCRIPT CONFIGURATION
|
||||||
|
CVSGRAPH CONFIGURATION
|
||||||
|
IF YOU HAVE PROBLEMS...
|
||||||
|
|
||||||
|
|
||||||
|
TO THE IMPATIENT
|
||||||
|
----------------
|
||||||
|
Congratulations on getting this far. :-)
|
||||||
|
|
||||||
|
Prerequisites: Python 1.5 or later
|
||||||
|
(http://www.python.org/)
|
||||||
|
RCS, Revision Control System
|
||||||
|
(http://www.cs.purdue.edu/homes/trinkle/RCS/)
|
||||||
|
read-only, physical access to a CVS repository
|
||||||
|
(See http://www.cvshome.org/ for more information)
|
||||||
|
|
||||||
|
Optional: a web server capable of running CGI programs
|
||||||
|
(for example, Apache at http://httpd.apache.org/)
|
||||||
|
GNU-diff to replace broken diff implementations
|
||||||
|
(http://www.gnu.org/software/diffutils/diffutils.html)
|
||||||
|
MySQL to create and query a commit database
|
||||||
|
(http://www.mysql.com/)
|
||||||
|
(http://sourceforge.net/projects/mysql-python)
|
||||||
|
(and Python 1.5.2 or later)
|
||||||
|
Enscript to colorize code displayed from the CVS repository
|
||||||
|
(http://people.ssh.com/mtr/genscript/)
|
||||||
|
CvsGraph for a graphical representation of the CVS revisions
|
||||||
|
(http://www.akhphd.au.dk/~bertho/cvsgraph/)
|
||||||
|
|
||||||
|
GUI Operation:
|
||||||
|
|
||||||
|
If you just want to see what your CVS repository looks like with
|
||||||
|
ViewCVS, type "./standalone.py -g -r /PATH/TO/CVS/ROOT". This
|
||||||
|
will start a tiny webserver serving at http://localhost:7467/.
|
||||||
|
|
||||||
|
Standard operation:
|
||||||
|
|
||||||
|
To start installing right away (on UNIX): type "./viewcvs-install"
|
||||||
|
in the current directory and answer the prompts. When it
|
||||||
|
finishes, edit the file viewcvs.conf in the installation directory
|
||||||
|
to tell viewcvs the paths to your CVS repositories. Next,
|
||||||
|
configure your web server to run <INSTALL>/cgi/viewcvs.cgi, as
|
||||||
|
appropriate for your web server. The section `INSTALLING VIEWCVS'
|
||||||
|
below is still recommended reading.
|
||||||
|
|
||||||
|
|
||||||
INSTALLING VIEWCVS
|
INSTALLING VIEWCVS
|
||||||
------------------
|
------------------
|
||||||
|
|
||||||
1) To get viewcvs.cgi to work, make sure that you have Python 1.5
|
1) To get viewcvs.cgi to work, make sure that you have Python 1.5 or
|
||||||
installed and a webserver which is capable of executing cgi-scripts
|
greater installed and a webserver which is capable of executing
|
||||||
(either based on the .cgi extension, or by placing the script
|
CGI scripts (either based on the .cgi extension, or by placing the
|
||||||
within a specific directory).
|
script within a specific directory).
|
||||||
|
|
||||||
You need to have RCS installed. Specifically, "rlog", "rcsdiff",
|
You need to have RCS installed. Specifically, "rlog", "rcsdiff",
|
||||||
and "co". This script was tested against RedHat's rcs-5.7-10.rpm
|
and "co". This script was tested against RedHat's rcs-5.7-10.rpm
|
||||||
|
|
||||||
Note, that the viewcvs.cgi script needs to have READ-ONLY, physical
|
Note, that the viewcvs.cgi script needs to have READ-ONLY, physical
|
||||||
access to the CVS repository (or a copy of it). Therefore, rsh/ssh or
|
access to the CVS repository (or a copy of it). Therefore, rsh/ssh or
|
||||||
pserver access doesn't work yet.
|
pserver access to the repository will not work.
|
||||||
|
|
||||||
|
For the more human readable diff formats you need a modern diff utility.
|
||||||
|
If you are using Linux, this is no problem. But on commercial unices
|
||||||
|
you might want to install GNU-diff to be able to use unified or
|
||||||
|
side-by-side diffs.
|
||||||
|
|
||||||
|
If you want to use cvsgraph, you have to obtain and install this
|
||||||
|
separately. See below. This was tested with cvsgraph-1.1.2.
|
||||||
|
|
||||||
For the checkin database to work, you will need MySQL >= 3.22,
|
For the checkin database to work, you will need MySQL >= 3.22,
|
||||||
and the Python DBAPI 2.0 module, MySQLdb. This was tested with
|
and the Python DBAPI 2.0 module, MySQLdb. This was tested with
|
||||||
MySQLdb 1.12.
|
MySQLdb 0.9.1.
|
||||||
|
|
||||||
2) Installation is handled by the ./viewcvs-install script. Run this
|
2) Installation is handled by the ./viewcvs-install script. Run this
|
||||||
script and you will be prompted for a installation root path.
|
script and you will be prompted for a installation root path.
|
||||||
The default is /usr/local/viewcvs. The installer sets the install
|
The default is /usr/local/viewcvs-VERSION, where VERSION is
|
||||||
|
the version of this ViewCVS release. The installer sets the install
|
||||||
path in some of the files, and ViewCVS cannot be moved to a
|
path in some of the files, and ViewCVS cannot be moved to a
|
||||||
different path after the install.
|
different path after the install.
|
||||||
|
|
||||||
@@ -33,8 +94,8 @@ INSTALLING VIEWCVS
|
|||||||
a previous installation. It will always overwrite program files,
|
a previous installation. It will always overwrite program files,
|
||||||
however.
|
however.
|
||||||
|
|
||||||
3) Edit <install-root>viewcvs.conf for your specific configuration.
|
3) Edit <VIEWCVS_INSTALLATION_DIRECTORY>/viewcvs.conf for your specific
|
||||||
In particular, examine the following configuration options:
|
configuration. In particular, examine the following configuration options:
|
||||||
|
|
||||||
cvs_roots
|
cvs_roots
|
||||||
default_root
|
default_root
|
||||||
@@ -42,13 +103,20 @@ INSTALLING VIEWCVS
|
|||||||
mime_types_file
|
mime_types_file
|
||||||
|
|
||||||
There are some other options that are usually nice to change. See
|
There are some other options that are usually nice to change. See
|
||||||
viewcvs.conf for more information.
|
viewcvs.conf for more information. ViewCVS provides a working,
|
||||||
|
default look. However, if you want to customize the look of ViewCVS
|
||||||
|
then edit the files in <VIEWCVS_INSTALLATION_DIRECTORY>/templates.
|
||||||
|
You need knowledge about HTML to edit the templates.
|
||||||
|
|
||||||
4) The CGI programs are in <install-root>/cgi/. You can symlink to this
|
4) The CGI programs are in <VIEWCVS_INSTALLATION_DIRECTORY>/cgi/. You can
|
||||||
directory from somewhere in your published HTTP server path if your
|
symlink to this directory from somewhere in your published HTTP server
|
||||||
webserver is configured to follow symbolic links. You can also copy
|
path if your webserver is configured to follow symbolic links. You can
|
||||||
the installed <install-root>/cgi/*.cgi scripts after the install
|
also copy the installed <VIEWCVS_INSTALLATION_DIRECTORY>/cgi/*.cgi scripts
|
||||||
(unlike the other files in ViewCVS, the CGI scripts can be moved).
|
after the install (unlike the other files in ViewCVS, the CGI scripts can
|
||||||
|
be moved).
|
||||||
|
|
||||||
|
If you are using Apache, then the ScriptAlias directive is very
|
||||||
|
useful for pointing directly to the viwecvs.cgi script.
|
||||||
|
|
||||||
NOTE: for security reasons, it is not advisable to install ViewCVS
|
NOTE: for security reasons, it is not advisable to install ViewCVS
|
||||||
directly into your published HTTP directory tree (due to the MySQL
|
directly into your published HTTP directory tree (due to the MySQL
|
||||||
@@ -62,31 +130,40 @@ WARNING: ViewCVS has not been tested on web servers operating on the
|
|||||||
Win32 platform.
|
Win32 platform.
|
||||||
|
|
||||||
|
|
||||||
|
UPGRADING VIEWCVS
|
||||||
|
-----------------
|
||||||
|
|
||||||
SQL Checkin Database
|
Please read the file upgrading.html in the website subdirectory or
|
||||||
|
at <http://viewcvs.sourceforge.net/upgrading.html>.
|
||||||
|
|
||||||
|
|
||||||
|
SQL CHECKIN DATABASE
|
||||||
--------------------
|
--------------------
|
||||||
|
|
||||||
This feature is a clone of the Mozilla Project's Bonsai database. It
|
This feature is a clone of the Mozilla Project's Bonsai database. It
|
||||||
catalogs every commit in the CVS repository into a SQL database. In fact,
|
catalogs every commit in the CVS repository into a SQL database. In fact,
|
||||||
the databases are 100% compatible.
|
the databases are 100% compatible.
|
||||||
|
|
||||||
Various queries can be preformed on the database. After installing ViewCVS,
|
Various queries can be performed on the database. After installing ViewCVS,
|
||||||
there are some additional steps required to get the database working.
|
there are some additional steps required to get the database working.
|
||||||
|
|
||||||
1) You need MySQL >= 3.22, and the Python module MySQLdb >= 1.12 installed.
|
1) You need MySQL >= 3.22, and the Python module MySQLdb 0.9.0 installed.
|
||||||
|
Python 1.5.2 is REQUIRED by MySQLdb, therefore to use this part of
|
||||||
|
ViewCVS you must be using Python 1.5.2. Additionally you will need the
|
||||||
|
mxDateTime extension. I've tested with version 1.3.0
|
||||||
|
|
||||||
2) You need to create a MySQL user who has permission to create databases.
|
2) You need to create a MySQL user who has permission to create databases.
|
||||||
Optionally, you can create a second user with read-only access to the
|
Optionally, you can create a second user with read-only access to the
|
||||||
database.
|
database.
|
||||||
|
|
||||||
3) Run the <install-root>/make-database script. It will prompt you for
|
3) Run the <VIEWCVS_INSTALLATION_DIRECTORY>/make-database script. It will
|
||||||
your MySQL user, password, and the name of database you want to
|
prompt you for your MySQL user, password, and the name of database you
|
||||||
create. The database name defaults to "ViewCVS". This script creates
|
want to create. The database name defaults to "ViewCVS". This script
|
||||||
the database and sets up the empty tables. If you run this on a
|
creates the database and sets up the empty tables. If you run this on a
|
||||||
existing ViewCVS database, you will loose all your data!
|
existing ViewCVS database, you will lose all your data!
|
||||||
|
|
||||||
4) Edit your <install-root>/viewcvs.conf file. There is a [cvsdb]
|
4) Edit your <VIEWCVS_INSTALLATION_DIRECTORY>/viewcvs.conf file.
|
||||||
section. You will need to set:
|
There is a [cvsdb] section. You will need to set:
|
||||||
|
|
||||||
|
|
||||||
host = # MySQL database server host
|
host = # MySQL database server host
|
||||||
@@ -101,7 +178,7 @@ there are some additional steps required to get the database working.
|
|||||||
5) Two programs are provided for updating the checkin database,
|
5) Two programs are provided for updating the checkin database,
|
||||||
cvsdbadmin and loginfo-handler. They serve two different purposes.
|
cvsdbadmin and loginfo-handler. They serve two different purposes.
|
||||||
The cvsdbadmin program walks through your CVS repository and adds
|
The cvsdbadmin program walks through your CVS repository and adds
|
||||||
every commit in every file. This is commonly used for initalizing
|
every commit in every file. This is commonly used for initializing
|
||||||
the database from a repository which has been in use. The
|
the database from a repository which has been in use. The
|
||||||
loginfo-handler script is executed by the CVS server's CVSROOT/loginfo
|
loginfo-handler script is executed by the CVS server's CVSROOT/loginfo
|
||||||
system upon each commit. It makes real-time updates to the checkin
|
system upon each commit. It makes real-time updates to the checkin
|
||||||
@@ -111,31 +188,99 @@ there are some additional steps required to get the database working.
|
|||||||
invoke: "./cvsdbadmin rebuild /home/cvs". If you want to update
|
invoke: "./cvsdbadmin rebuild /home/cvs". If you want to update
|
||||||
the checkin database, invoke: "./cvsdbadmin update /home/cvs". The
|
the checkin database, invoke: "./cvsdbadmin update /home/cvs". The
|
||||||
update mode checks to see if a commit is already in the database,
|
update mode checks to see if a commit is already in the database,
|
||||||
and only adds it if it is abscent.
|
and only adds it if it is absent.
|
||||||
|
|
||||||
To get real-time updates, you'll want to checkout the CVSROOT module
|
To get real-time updates, you'll want to checkout the CVSROOT module
|
||||||
from your CVS repository and edit CVSROOT/loginfo. Add the line:
|
from your CVS repository and edit CVSROOT/loginfo. Add the line:
|
||||||
|
|
||||||
ALL (echo %{sVv}; cat) | <install-root>/loginfo-handler
|
ALL (echo %{sVv}; cat) | <VIEWCVS_INSTALLATION_DIRECTORY>/loginfo-handler
|
||||||
|
|
||||||
If you have other scripts invoked by CVSROOT/loginfo, you will want
|
If you have other scripts invoked by CVSROOT/loginfo, you will want
|
||||||
to make sure to change any running under the "DEFAULT" keyword to
|
to make sure to change any running under the "DEFAULT" keyword to
|
||||||
"ALL" like the loginfo handler, and probably carefully read the
|
"ALL" like the loginfo handler, and probably carefully read the
|
||||||
execution rules for CVSROOT/loginfo from the CVS manual.
|
execution rules for CVSROOT/loginfo from the CVS manual.
|
||||||
|
|
||||||
6) You may want to modify the HTML template files:
|
6) You may want to modify the HTML template file:
|
||||||
|
|
||||||
<install-root>/html-templates/queryformtemplate.html
|
<VIEWCVS_INSTALLATION_DIRECTORY>/templates/query.ezt
|
||||||
<install-root>/html-templates/querytemplate.html
|
|
||||||
|
|
||||||
They're used by the queryform.cgi and query.cgi scripts generate
|
This is used by the query.cgi script to generate part of its HTML output.
|
||||||
HTML output. At some point, viewcvs.cgi, query.cgi, and queryform.cgi
|
At some point the currently hardcoded table output will also vanish.
|
||||||
will use the same mechanism for HTML generation, but not yet.
|
|
||||||
|
|
||||||
7) You should be ready to go. Load up the queryform.cgi script and give
|
7) You should be ready to go. Load up the query.cgi script and give
|
||||||
it a try.
|
it a try.
|
||||||
|
|
||||||
|
|
||||||
|
ENSCRIPT CONFIGURATION
|
||||||
|
----------------------
|
||||||
|
|
||||||
|
Enscript is program that can colorize sourcecode of a lot of languages.
|
||||||
|
Linux distributions like for example SuSE Linux from at least 7.0
|
||||||
|
up to the recently released 7.3 already contain a precompiled and
|
||||||
|
configured enscript 1.6.2 package.
|
||||||
|
|
||||||
|
1) Download genscript from http://people.ssh.com/mtr/genscript/
|
||||||
|
|
||||||
|
2) Configure and compile per instructions with enscript.
|
||||||
|
(I 've not done this, since I'm using the precompiled package
|
||||||
|
delivered with SuSE Linux)
|
||||||
|
|
||||||
|
3) Set the 'use_enscript' option in viewcvs.conf to 1.
|
||||||
|
|
||||||
|
4) That's it!
|
||||||
|
|
||||||
|
5) If you want to colorize exotic languages, you might have to
|
||||||
|
patch 'lib/viewcvs.py' and add a new highlighting file to enscript.
|
||||||
|
I've done this for Modula-2 and submitted the file to the
|
||||||
|
enscript maintainer long ago. If interested in this patch for
|
||||||
|
enscript mailto:pefu@sourceforge.net
|
||||||
|
|
||||||
|
|
||||||
|
CVSGRAPH CONFIGURATION
|
||||||
|
----------------------
|
||||||
|
|
||||||
|
CvsGraph is a program that can display a clickable, graphical tree
|
||||||
|
of files in a CVS repository.
|
||||||
|
|
||||||
|
WARNING: Under certain circumstances (many revisions of a file
|
||||||
|
or many branches or both) cvsgraph can generate very huge images.
|
||||||
|
Especially on thin clients these images may crash the Web-Browser.
|
||||||
|
Currently there is no known way to avoid this behavior of cvsgraph.
|
||||||
|
So you have been warned!
|
||||||
|
|
||||||
|
Nevertheless cvsgraph can be quite helpful on repositories with
|
||||||
|
a reasonable number of revisions and branches.
|
||||||
|
|
||||||
|
1) Install viewcvs according to instructions in 'INSTALLING
|
||||||
|
VIEWCVS' section above. The installation directory is where
|
||||||
|
the 'viewcvs-install' script copied and configured the viewcvs
|
||||||
|
programs.
|
||||||
|
|
||||||
|
2) Download CvsGraph from http://www.akhphd.au.dk/~bertho/cvsgraph/
|
||||||
|
|
||||||
|
3) Configure and compile per instructions with CvsGraph. I had
|
||||||
|
problems with 'configure' finding the gd library. Had to create
|
||||||
|
a link from libgd.so to libgd.do.4.0.0. On Solaris you might
|
||||||
|
want to edit the link command line and add the option -R if
|
||||||
|
you have you libraries at non-standard location.
|
||||||
|
|
||||||
|
4) Place the 'cvsgraph' executable into a directory readable by the
|
||||||
|
userid running the web server. (default is '/usr/local/bin' if
|
||||||
|
you simply type 'make install' in the cvsgraph directory).
|
||||||
|
|
||||||
|
5) Check the setting of the 'cvsgraph_path' option in viewcvs.conf:
|
||||||
|
/usr/local/bin/ is most often NOT contained in $PATH of the
|
||||||
|
webserver process (e.g. Apache), so you will have to edit this.
|
||||||
|
Set the 'use_cvsgraph' option in viewcvs.conf to 1.
|
||||||
|
|
||||||
|
6) That's it!
|
||||||
|
|
||||||
|
7) There is a file <VIEWCVS_INSTALLATION_DIRECTORY>/cvsgraph.conf that
|
||||||
|
you may want to edit if desired to set color and font characteristics.
|
||||||
|
See the cvsgraph.conf documentation. No edits are required in
|
||||||
|
cvsgraph.conf for operation with viewcvs.
|
||||||
|
|
||||||
|
|
||||||
IF YOU HAVE PROBLEMS ...
|
IF YOU HAVE PROBLEMS ...
|
||||||
------------------------
|
------------------------
|
||||||
|
|
||||||
@@ -166,13 +311,13 @@ If you've trouble to make viewcvs.cgi work:
|
|||||||
CVS-Repository. The CGI-script often runs as the user 'nobody'
|
CVS-Repository. The CGI-script often runs as the user 'nobody'
|
||||||
or 'httpd' ..
|
or 'httpd' ..
|
||||||
|
|
||||||
o does viewcvs find your RCS utililties? (edit rcs_path)
|
o does viewcvs find your RCS utilities? (edit rcs_path)
|
||||||
|
|
||||||
=== If something else happens or you can't get it to work:
|
=== If something else happens or you can't get it to work:
|
||||||
|
|
||||||
o check the ViewCVS home page:
|
o check the ViewCVS home page:
|
||||||
|
|
||||||
http://www.lyra.org/viewcvs/
|
http://viewcvs.sourceforge.net/
|
||||||
|
|
||||||
o review the ViewCVS mailing list archive to see if somebody else had
|
o review the ViewCVS mailing list archive to see if somebody else had
|
||||||
the same problem, and it was solved:
|
the same problem, and it was solved:
|
||||||
|
3
README
Normal file
3
README
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
ViewCVS -- Viewing the content of CVS repositories with a Webbrowser.
|
||||||
|
|
||||||
|
Please read the file INSTALL for more information.
|
47
TODO
47
TODO
@@ -1,3 +1,13 @@
|
|||||||
|
PREFACE
|
||||||
|
-------
|
||||||
|
This file will go away soon after release 0.8. Please use the SourceForge
|
||||||
|
tracker to resubmit any of the items listed below, if you think, it is
|
||||||
|
still an issue:
|
||||||
|
http://sourceforge.net/tracker/?group_id=18760
|
||||||
|
Before reporting please check, whether someone else has already done this.
|
||||||
|
Working patches increase the chance to be included into the next release.
|
||||||
|
-- PeFu / October 2001
|
||||||
|
|
||||||
TODO ITEMS
|
TODO ITEMS
|
||||||
----------
|
----------
|
||||||
*) add Tamminen Eero's comments on how to make Linux directly execute
|
*) add Tamminen Eero's comments on how to make Linux directly execute
|
||||||
@@ -14,29 +24,30 @@ TODO ITEMS
|
|||||||
*) committing with a specific revision number:
|
*) committing with a specific revision number:
|
||||||
http://mailman.lyra.org/pipermail/viewcvs/2000q1/000008.html
|
http://mailman.lyra.org/pipermail/viewcvs/2000q1/000008.html
|
||||||
|
|
||||||
*) add an "allowed" modules. process the forbidden modules first. any
|
|
||||||
match will throw it out immediately. if an allowed is present, then
|
|
||||||
disallow any module unless it is present in the allowed list.
|
|
||||||
|
|
||||||
*) provide a virtual host capability. consider multiple hosts which
|
|
||||||
map a particular Location to the same directory (i.e. the same
|
|
||||||
ViewCVS script and config file). provide for a way to have global
|
|
||||||
options plus vhost-specific options.
|
|
||||||
|
|
||||||
*) add capability similar to cvs2cl.pl:
|
*) add capability similar to cvs2cl.pl:
|
||||||
http://mailman.lyra.org/pipermail/viewcvs/2000q2/000050.html
|
http://mailman.lyra.org/pipermail/viewcvs/2000q2/000050.html
|
||||||
suggestion from Chris Meyer <cmeyer@gatan.com>.
|
suggestion from Chris Meyer <cmeyer@gatan.com>.
|
||||||
|
|
||||||
|
*) add a tree view of the directory structure (and files?)
|
||||||
|
|
||||||
|
*) include a ConfigParser.py to help older Python installations
|
||||||
|
|
||||||
|
*) add a check for the rcs programs/paths to viewcvs-install. clarify the
|
||||||
|
dependency on RCS in the docs.
|
||||||
|
|
||||||
|
*) have a "check" mode that verifies binaries are available on rcs_path
|
||||||
|
|
||||||
|
-> alternately (probably?): use rcsparse rather than external tools
|
||||||
|
|
||||||
KNOWN BUGS
|
KNOWN BUGS
|
||||||
----------
|
----------
|
||||||
*) from Harri Pasanen, Feb 7: when hideattic is set, files added in a
|
*) time.timezone seems to not be available on some 1.5.2 installs.
|
||||||
branch are not visible.
|
I was unable to verify this. On RedHat and SuSE Linux this bug
|
||||||
|
is non existant.
|
||||||
|
|
||||||
*) from Dieter Deyke, Jan 12: if the CVS revisions differ by just a
|
*) With old repositories containing many branches, tags or thousands
|
||||||
keyword, then the diff output chokes.
|
or revisions, the cvsgraph feature becomes unusable (see INSTALL).
|
||||||
|
ViewCVS can't do much about this, but it might be possible to
|
||||||
*) no scroll bar in Netscape browser when you click "as text" on an
|
investigate the number of branches, tags and revision in advance
|
||||||
HTML document.
|
and disable the cvsgraph links, if the numbers exceed a certain
|
||||||
|
treshold.
|
||||||
*) time.timezone seems to not be available on some 1.5.2 installs
|
|
||||||
|
192
cgi/cvsgraph.conf.dist
Normal file
192
cgi/cvsgraph.conf.dist
Normal file
@@ -0,0 +1,192 @@
|
|||||||
|
# CvsGraph configuration
|
||||||
|
#
|
||||||
|
# - Empty lines and whitespace are ignored.
|
||||||
|
#
|
||||||
|
# - Comments start with '#' and everything until
|
||||||
|
# end of line is ignored.
|
||||||
|
#
|
||||||
|
# - Strings are C-style strings in which characters
|
||||||
|
# may be escaped with '\' and written in octal
|
||||||
|
# and hex escapes. Note that '\' must be escaped
|
||||||
|
# if it is to be entered as a character.
|
||||||
|
#
|
||||||
|
# - Some strings are expanded with printf like
|
||||||
|
# conversions which start with '%'. Not all
|
||||||
|
# are applicable at all times, in which case they
|
||||||
|
# will expand to noting.
|
||||||
|
# %c = cvsroot (with trailing '/')
|
||||||
|
# %C = cvsroot (*without* trailing '/')
|
||||||
|
# %m = module (with trailing '/')
|
||||||
|
# %M = module (*without* trailing '/')
|
||||||
|
# %f = filename without path
|
||||||
|
# %F = filename without path and with ",v" stripped
|
||||||
|
# %p = path part of filename (with trailing '/')
|
||||||
|
# %r = number of revisions
|
||||||
|
# %b = number of branches
|
||||||
|
# %% = '%'
|
||||||
|
# %R = the revision number (e.g. '1.2.4.4')
|
||||||
|
# %P = previous revision number
|
||||||
|
# %B = the branch number (e.g. '1.2.4')
|
||||||
|
# %d = date of revision
|
||||||
|
# %a = author of revision
|
||||||
|
# %s = state of revision
|
||||||
|
# %t = current tag of branch or revision
|
||||||
|
# %0..%9 = command-line argument -0 .. -9
|
||||||
|
# ViewCVS currently uses the following two command-line arguments to
|
||||||
|
# pass URL information to cvsgraph:
|
||||||
|
# -6 request.amp_query (the query preceeded with '&')
|
||||||
|
# -7 request.qmark_query (the query preceed with '?')
|
||||||
|
#
|
||||||
|
# - Numbers may be entered as octal, decimal or
|
||||||
|
# hex as in 0117, 79 and 0x4f respectively.
|
||||||
|
#
|
||||||
|
# - Fonts are numbered 0..4 (defined as in libgd)
|
||||||
|
# 0 = tiny
|
||||||
|
# 1 = small
|
||||||
|
# 2 = medium (bold)
|
||||||
|
# 3 = large
|
||||||
|
# 4 = giant
|
||||||
|
#
|
||||||
|
# - Colors are a string like html-type colors in
|
||||||
|
# the form "#rrggbb" with parts written in hex
|
||||||
|
# rr = red (00..ff)
|
||||||
|
# gg = green (00-ff)
|
||||||
|
# bb = blue (00-ff)
|
||||||
|
#
|
||||||
|
# - There are several reserved words besides of the
|
||||||
|
# feature-keywords. These additional reserved words
|
||||||
|
# expand to numerical values:
|
||||||
|
# * false = 0
|
||||||
|
# * true = 1
|
||||||
|
# * left = 0
|
||||||
|
# * center = 1
|
||||||
|
# * right = 2
|
||||||
|
# * gif = 0
|
||||||
|
# * png = 1
|
||||||
|
# * jpeg = 2
|
||||||
|
# * tiny = 0
|
||||||
|
# * small = 1
|
||||||
|
# * medium = 2
|
||||||
|
# * large = 3
|
||||||
|
# * giant = 4
|
||||||
|
|
||||||
|
# cvsroot <string>
|
||||||
|
# The *absolute* base directory where the
|
||||||
|
# CSV/RCS repository can be found
|
||||||
|
# cvsmodule <string>
|
||||||
|
#
|
||||||
|
cvsroot = "--unused--"; # unused with ViewCVS, will be overridden
|
||||||
|
cvsmodule = ""; # unused with ViewCVS -- please leave it blank
|
||||||
|
|
||||||
|
# color_bg <color>
|
||||||
|
# The background color of the image
|
||||||
|
color_bg = "#ffffff";
|
||||||
|
|
||||||
|
# date_format <string>
|
||||||
|
# The strftime(3) format string for date and time
|
||||||
|
date_format = "%d-%b-%Y %H:%M:%S";
|
||||||
|
|
||||||
|
box_shadow = true;
|
||||||
|
|
||||||
|
tag_font = medium;
|
||||||
|
tag_color = "#007000";
|
||||||
|
|
||||||
|
rev_font = giant;
|
||||||
|
rev_color = "#000000";
|
||||||
|
rev_bgcolor = "#f0f0f0";
|
||||||
|
rev_separator = 1;
|
||||||
|
rev_minline = 15;
|
||||||
|
rev_maxline = 30;
|
||||||
|
rev_lspace = 5;
|
||||||
|
rev_rspace = 5;
|
||||||
|
rev_tspace = 3;
|
||||||
|
rev_bspace = 3;
|
||||||
|
rev_text = "%d"; # or "%d\n%a, %s" for author and state too
|
||||||
|
rev_text_font = tiny;
|
||||||
|
rev_text_color = "#500020";
|
||||||
|
|
||||||
|
# branch_font <number>
|
||||||
|
# The font of the number and tags
|
||||||
|
# branch_color <color>
|
||||||
|
# All branch element's color
|
||||||
|
# branch_[lrtb]space <number>
|
||||||
|
# Interior spacing (margin)
|
||||||
|
# branch_margin <number>
|
||||||
|
# Exterior spacing
|
||||||
|
# branch_connect <number>
|
||||||
|
# Length of the vertical connector
|
||||||
|
branch_font = medium;
|
||||||
|
branch_color = "#0000c0";
|
||||||
|
branch_bgcolor = "#ffffc0";
|
||||||
|
branch_lspace = 5;
|
||||||
|
branch_rspace = 5;
|
||||||
|
branch_tspace = 3;
|
||||||
|
branch_bspace = 3;
|
||||||
|
branch_margin = 15;
|
||||||
|
branch_connect = 8;
|
||||||
|
|
||||||
|
# title <string>
|
||||||
|
# The title string is expanded (see above for details)
|
||||||
|
# title_[xy] <number>
|
||||||
|
# Postion of title
|
||||||
|
# title_font <number>
|
||||||
|
# The font
|
||||||
|
# title_align <number>
|
||||||
|
# 0 = left
|
||||||
|
# 1 = center
|
||||||
|
# 2 = right
|
||||||
|
# title_color <color>
|
||||||
|
title = "%C: %p%F\nRevisions: %r, Branches: %b";
|
||||||
|
title_x = 10;
|
||||||
|
title_y = 5;
|
||||||
|
title_font = small;
|
||||||
|
title_align = left;
|
||||||
|
title_color = "#800000";
|
||||||
|
|
||||||
|
# Margins of the image
|
||||||
|
# Note: the title is outside the margin
|
||||||
|
margin_top = 35;
|
||||||
|
margin_bottom = 10;
|
||||||
|
margin_left = 10;
|
||||||
|
margin_right = 10;
|
||||||
|
|
||||||
|
# Image format(s)
|
||||||
|
# image_type <number|{gif,jpeg,png}>
|
||||||
|
# gif (0) = Create gif image
|
||||||
|
# png (1) = Create png image
|
||||||
|
# jpeg (2) = Create jpeg image
|
||||||
|
# Image types are available if they can be found in
|
||||||
|
# the gd library. Newer versions of gd do not have
|
||||||
|
# gif anymore. CvsGraph will automatically generate
|
||||||
|
# png images instead.
|
||||||
|
# image_quality <number>
|
||||||
|
# The quality of a jpeg image (1..100)
|
||||||
|
image_type = png;
|
||||||
|
image_quality = 75;
|
||||||
|
|
||||||
|
# HTML ImageMap generation
|
||||||
|
# map_name <string>
|
||||||
|
# The name= attribute in <map name="mapname">...</map>
|
||||||
|
# map_branch_href <string>
|
||||||
|
# map_branch_alt <string>
|
||||||
|
# map_rev_href <string>
|
||||||
|
# map_rev_alt <string>
|
||||||
|
# map_diff_href <string>
|
||||||
|
# map_diff_alt <string>
|
||||||
|
# These are the href= and alt= attributes in the <area>
|
||||||
|
# tags of html. The strings are expanded (see above).
|
||||||
|
map_name = "MyMapName";
|
||||||
|
map_branch_href = "href=\"%m%F?only_with_tag=%t%8%6\"";
|
||||||
|
map_branch_alt = "alt=\"%0 %t (%B)\"";
|
||||||
|
# You might want to experiment with the following setting:
|
||||||
|
# 1. The default setting will take you to a ViewCVS generated page displaying
|
||||||
|
# that revision of the file, if you click into a revision box:
|
||||||
|
map_rev_href = "href=\"%m%F?rev=%R&content-type=text/vnd.viewcvs-markup%6\"";
|
||||||
|
# 2. This alternative setting will take you to the anchor representing this
|
||||||
|
# revision on a ViewCVS generated Log page for that file:
|
||||||
|
# map_rev_href = "href=\"%m%F%7#rev%R\"";
|
||||||
|
#
|
||||||
|
map_rev_alt = "alt=\"%1 %t (%R)\"";
|
||||||
|
map_diff_href = "href=\"%m%F.diff?r1=%P&r2=%R%8%6\"";
|
||||||
|
map_diff_alt = "alt=\"%2 %P <-> %R\"";
|
||||||
|
|
178
cgi/query.cgi
178
cgi/query.cgi
@@ -1,22 +1,24 @@
|
|||||||
#!/usr/bin/python
|
#!/usr/bin/python
|
||||||
# -*- Mode: python -*-
|
# -*-python-*-
|
||||||
#
|
#
|
||||||
# Copyright (C) 2000 The ViewCVS Group. All Rights Reserved.
|
# Copyright (C) 1999-2001 The ViewCVS Group. All Rights Reserved.
|
||||||
#
|
#
|
||||||
# By using this file, you agree to the terms and conditions set forth in
|
# By using this file, you agree to the terms and conditions set forth in
|
||||||
# the LICENSE.html file which can be found at the top level of the ViewCVS
|
# the LICENSE.html file which can be found at the top level of the ViewCVS
|
||||||
# distribution or at http://www.lyra.org/viewcvs/license-1.html.
|
# distribution or at http://viewcvs.sourceforge.net/license-1.html.
|
||||||
#
|
#
|
||||||
# Contact information:
|
# Contact information:
|
||||||
# Greg Stein, PO Box 760, Palo Alto, CA, 94302
|
# Greg Stein, PO Box 760, Palo Alto, CA, 94302
|
||||||
# gstein@lyra.org, http://www.lyra.org/viewcvs/
|
# gstein@lyra.org, http://viewcvs.sourceforge.net/
|
||||||
#
|
#
|
||||||
# -----------------------------------------------------------------------
|
# -----------------------------------------------------------------------
|
||||||
#
|
#
|
||||||
# CGI script to process and display queries to CVSdb
|
# query.cgi: View CVS commit database by web browser
|
||||||
#
|
#
|
||||||
# This script is part of the ViewCVS package. More information can be
|
# -----------------------------------------------------------------------
|
||||||
# found at http://www.lyra.org/viewcvs/.
|
#
|
||||||
|
# This is a teeny stub to launch the main ViewCVS app. It checks the load
|
||||||
|
# average, then loads the (precompiled) viewcvs.py file and runs it.
|
||||||
#
|
#
|
||||||
# -----------------------------------------------------------------------
|
# -----------------------------------------------------------------------
|
||||||
#
|
#
|
||||||
@@ -30,10 +32,12 @@
|
|||||||
#
|
#
|
||||||
|
|
||||||
LIBRARY_DIR = None
|
LIBRARY_DIR = None
|
||||||
CONF_PATHNAME = None
|
|
||||||
HTML_TEMPLATE_DIR = None
|
|
||||||
|
|
||||||
|
#########################################################################
|
||||||
|
#
|
||||||
# Adjust sys.path to include our library directory
|
# Adjust sys.path to include our library directory
|
||||||
|
#
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
if LIBRARY_DIR:
|
if LIBRARY_DIR:
|
||||||
@@ -43,157 +47,5 @@ else:
|
|||||||
|
|
||||||
#########################################################################
|
#########################################################################
|
||||||
|
|
||||||
import os, string, cgi, time, cvsdbapi
|
import query
|
||||||
|
query.run_cgi()
|
||||||
|
|
||||||
## tuple of alternating row colors
|
|
||||||
Colors = ("#ccccee", "#ffffff")
|
|
||||||
|
|
||||||
|
|
||||||
def HTMLHeader():
|
|
||||||
print "Content-type: text/html"
|
|
||||||
print
|
|
||||||
|
|
||||||
|
|
||||||
def FormToCheckinQuery(form):
|
|
||||||
query = cvsdbapi.CreateCheckinQuery()
|
|
||||||
|
|
||||||
if form.has_key("repository"):
|
|
||||||
temp = form["repository"].value
|
|
||||||
query.SetRepository(temp)
|
|
||||||
|
|
||||||
if form.has_key("branch"):
|
|
||||||
temp = form["branch"].value
|
|
||||||
query.SetBranch(temp)
|
|
||||||
|
|
||||||
if form.has_key("directory"):
|
|
||||||
temp = form["directory"].value
|
|
||||||
query.SetDirectory(temp)
|
|
||||||
|
|
||||||
if form.has_key("file"):
|
|
||||||
temp = form["file"].value
|
|
||||||
query.SetFile(temp)
|
|
||||||
|
|
||||||
if form.has_key("who"):
|
|
||||||
temp = form["who"].value
|
|
||||||
query.SetAuthor(temp)
|
|
||||||
|
|
||||||
if form.has_key("sortby"):
|
|
||||||
temp = form["sortby"].value
|
|
||||||
if temp == "date":
|
|
||||||
query.SetSortMethod(query.SORT_DATE)
|
|
||||||
elif temp == "author":
|
|
||||||
query.SetSortMethod(query.SORT_AUTHOR)
|
|
||||||
else:
|
|
||||||
query.SetSortMethod(query.SORT_FILE)
|
|
||||||
|
|
||||||
if form.has_key("date"):
|
|
||||||
temp = form["date"].value
|
|
||||||
if temp == "hours":
|
|
||||||
if form.has_key("hours"):
|
|
||||||
hours = string.atoi(form["hours"].value)
|
|
||||||
else:
|
|
||||||
hours = 2
|
|
||||||
query.SetFromDateHoursAgo(hours)
|
|
||||||
elif temp == "day":
|
|
||||||
query.SetFromDateDaysAgo(1)
|
|
||||||
elif temp == "week":
|
|
||||||
query.SetFromDateDaysAgo(7)
|
|
||||||
elif temp == "month":
|
|
||||||
query.SetFromDateDaysAgo(31)
|
|
||||||
|
|
||||||
return query
|
|
||||||
|
|
||||||
|
|
||||||
def PrintCommitRow(commit, color):
|
|
||||||
cTime = commit.GetTime()
|
|
||||||
if not cTime:
|
|
||||||
cTime = " ";
|
|
||||||
else:
|
|
||||||
cTime = time.strftime("%y/%m/%d %H:%M", time.localtime(cTime))
|
|
||||||
|
|
||||||
cAuthor = commit.GetAuthor()
|
|
||||||
if not cAuthor:
|
|
||||||
cAuthor = " ";
|
|
||||||
|
|
||||||
cFile = os.path.join(commit.GetDirectory(), commit.GetFile())
|
|
||||||
if not cFile:
|
|
||||||
cFile = " ";
|
|
||||||
|
|
||||||
cRevision = commit.GetRevision()
|
|
||||||
if not cRevision:
|
|
||||||
cRevision = " ";
|
|
||||||
|
|
||||||
cBranch = commit.GetBranch()
|
|
||||||
if not cBranch:
|
|
||||||
cBranch = " ";
|
|
||||||
|
|
||||||
cPlusMinus = '%d/%d' % (commit.GetPlusCount(), commit.GetMinusCount())
|
|
||||||
|
|
||||||
cDescription = commit.GetDescription()
|
|
||||||
if not cDescription:
|
|
||||||
cDescription = " ";
|
|
||||||
else:
|
|
||||||
cDescription = cgi.escape(cDescription)
|
|
||||||
cDescription = string.replace(cDescription, '\n', '<br>')
|
|
||||||
|
|
||||||
print '<tr bgcolor="%s"><td align=left valign=top>%s</td>\
|
|
||||||
<td align=left valign=top>%s</td>\
|
|
||||||
<td align=left valign=top>%s</td>\
|
|
||||||
<td align=left valign=top>%s</td>\
|
|
||||||
<td align=left valign=top>%s</td>\
|
|
||||||
<td aligh=left valign=top>%s</td>\
|
|
||||||
<td align=left valign=top>%s</td></tr>' % (
|
|
||||||
color, cTime, cAuthor, cFile, cRevision, cBranch,
|
|
||||||
cPlusMinus, cDescription)
|
|
||||||
|
|
||||||
|
|
||||||
def PrintCommitRows(commit_list):
|
|
||||||
color_index = 0
|
|
||||||
for commit in commit_list:
|
|
||||||
PrintCommitRow(commit, Colors[color_index])
|
|
||||||
color_index = (color_index + 1) % len(Colors)
|
|
||||||
|
|
||||||
|
|
||||||
g_iColorIndex = 0
|
|
||||||
def CommitCallback(commit):
|
|
||||||
global g_iColorIndex
|
|
||||||
PrintCommitRow(commit, Colors[g_iColorIndex])
|
|
||||||
g_iColorIndex = (g_iColorIndex + 1) % len(Colors)
|
|
||||||
|
|
||||||
|
|
||||||
def RunQuery(query):
|
|
||||||
query.SetCommitCB(CommitCallback)
|
|
||||||
db = cvsdbapi.ConnectDatabaseReadOnly()
|
|
||||||
db.RunQuery(query)
|
|
||||||
|
|
||||||
|
|
||||||
class HTMLTemplate:
|
|
||||||
def __init__(self, filename):
|
|
||||||
self.template = open(filename, 'r').read()
|
|
||||||
|
|
||||||
def Print1(self):
|
|
||||||
index = string.find(self.template, '<!-- INSERT QUERY ROWS -->')
|
|
||||||
print self.template[:index]
|
|
||||||
|
|
||||||
def Print2(self):
|
|
||||||
index = string.find(self.template, '<!-- INSERT QUERY ROWS -->')
|
|
||||||
print self.template[index:]
|
|
||||||
|
|
||||||
|
|
||||||
def Main():
|
|
||||||
HTMLHeader()
|
|
||||||
|
|
||||||
template_path = os.path.join(HTML_TEMPLATE_DIR, "querytemplate.html")
|
|
||||||
template = HTMLTemplate(template_path)
|
|
||||||
template.Print1()
|
|
||||||
|
|
||||||
form = cgi.FieldStorage()
|
|
||||||
query = FormToCheckinQuery(form)
|
|
||||||
RunQuery(query)
|
|
||||||
|
|
||||||
template.Print2()
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
Main()
|
|
||||||
|
@@ -1,54 +0,0 @@
|
|||||||
#!/usr/bin/python
|
|
||||||
# -*- Mode: python -*-
|
|
||||||
#
|
|
||||||
# Copyright (C) 2000 The ViewCVS Group. All Rights Reserved.
|
|
||||||
#
|
|
||||||
# By using this file, you agree to the terms and conditions set forth in
|
|
||||||
# the LICENSE.html file which can be found at the top level of the ViewCVS
|
|
||||||
# distribution or at http://www.lyra.org/viewcvs/license-1.html.
|
|
||||||
#
|
|
||||||
# Contact information:
|
|
||||||
# Greg Stein, PO Box 760, Palo Alto, CA, 94302
|
|
||||||
# gstein@lyra.org, http://www.lyra.org/viewcvs/
|
|
||||||
#
|
|
||||||
# -----------------------------------------------------------------------
|
|
||||||
#
|
|
||||||
|
|
||||||
#########################################################################
|
|
||||||
#
|
|
||||||
# INSTALL-TIME CONFIGURATION
|
|
||||||
#
|
|
||||||
# These values will be set during the installation process. During
|
|
||||||
# development, they will remain None.
|
|
||||||
#
|
|
||||||
|
|
||||||
LIBRARY_DIR = None
|
|
||||||
CONF_PATHNAME = None
|
|
||||||
HTML_TEMPLATE_DIR = None
|
|
||||||
|
|
||||||
# Adjust sys.path to include our library directory
|
|
||||||
import sys
|
|
||||||
|
|
||||||
if LIBRARY_DIR:
|
|
||||||
sys.path.insert(0, LIBRARY_DIR)
|
|
||||||
else:
|
|
||||||
sys.path[:0] = ['../lib'] # any other places to look?
|
|
||||||
|
|
||||||
#########################################################################
|
|
||||||
|
|
||||||
import os, string
|
|
||||||
|
|
||||||
|
|
||||||
def HTMLHeader():
|
|
||||||
print "Content-type: text/html"
|
|
||||||
print
|
|
||||||
|
|
||||||
|
|
||||||
def Main():
|
|
||||||
HTMLHeader()
|
|
||||||
template_path = os.path.join(HTML_TEMPLATE_DIR, "queryformtemplate.html")
|
|
||||||
print open(template_path, "r").read()
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
Main()
|
|
2206
cgi/viewcvs.cgi
2206
cgi/viewcvs.cgi
File diff suppressed because it is too large
Load Diff
@@ -3,7 +3,7 @@
|
|||||||
# Configuration file for ViewCVS
|
# Configuration file for ViewCVS
|
||||||
#
|
#
|
||||||
# Information on ViewCVS is located at the following web site:
|
# Information on ViewCVS is located at the following web site:
|
||||||
# http://www.lyra.org/viewcvs/
|
# http://viewcvs.sourceforge.net/
|
||||||
#
|
#
|
||||||
#---------------------------------------------------------------------------
|
#---------------------------------------------------------------------------
|
||||||
|
|
||||||
@@ -22,13 +22,10 @@
|
|||||||
#
|
#
|
||||||
# address
|
# address
|
||||||
# main_title
|
# main_title
|
||||||
# logo
|
|
||||||
# forbidden
|
# forbidden
|
||||||
#
|
#
|
||||||
# long_intro
|
|
||||||
# repository_info
|
|
||||||
#
|
|
||||||
# use_enscript
|
# use_enscript
|
||||||
|
# use_cvsgraph
|
||||||
#
|
#
|
||||||
# For Python source colorization:
|
# For Python source colorization:
|
||||||
#
|
#
|
||||||
@@ -38,6 +35,9 @@
|
|||||||
#
|
#
|
||||||
# icons
|
# icons
|
||||||
#
|
#
|
||||||
|
# Also, review the .ezt templates in the templates/ directory to adjust them
|
||||||
|
# for your particular site.
|
||||||
|
#
|
||||||
|
|
||||||
#
|
#
|
||||||
# FORMAT INFORMATION
|
# FORMAT INFORMATION
|
||||||
@@ -73,7 +73,6 @@ cvs_roots =
|
|||||||
default_root = Development
|
default_root = Development
|
||||||
|
|
||||||
# uncomment if the RCS binaries are not on the standard path
|
# uncomment if the RCS binaries are not on the standard path
|
||||||
# NOTE: the trailing slash is required!
|
|
||||||
#rcs_path = /usr/bin/
|
#rcs_path = /usr/bin/
|
||||||
|
|
||||||
#
|
#
|
||||||
@@ -86,8 +85,9 @@ default_root = Development
|
|||||||
# For example, you can use the mime.types from apache here:
|
# For example, you can use the mime.types from apache here:
|
||||||
#mime_types_file = /usr/local/apache/conf/mime.types
|
#mime_types_file = /usr/local/apache/conf/mime.types
|
||||||
|
|
||||||
# This address is shown in the footer. It should contain the CVS maintainer.
|
# This address is shown in the footer of the generated pages.
|
||||||
address = <a href="mailto:gstein@lyra.org">gstein@lyra.org</a>
|
# It must be replaced with the address of the local CVS maintainer.
|
||||||
|
address = <a href="mailto:cvs-admin@insert.your.domain.here">No CVS admin address has been configured</a>
|
||||||
|
|
||||||
# this title is used on the main entry page
|
# this title is used on the main entry page
|
||||||
main_title = CVS Repository
|
main_title = CVS Repository
|
||||||
@@ -136,6 +136,21 @@ forbidden =
|
|||||||
# forbidden = !xml, x*, !*
|
# forbidden = !xml, x*, !*
|
||||||
#
|
#
|
||||||
|
|
||||||
|
#---------------------------------------------------------------------------
|
||||||
|
[templates]
|
||||||
|
|
||||||
|
#
|
||||||
|
# The templates are specified relative to the configuration file. Absolute
|
||||||
|
# paths may be used, if you want to keep these elsewhere.
|
||||||
|
#
|
||||||
|
|
||||||
|
directory = templates/directory.ezt
|
||||||
|
log = templates/log.ezt
|
||||||
|
# There is also a new style table based alternative template available.
|
||||||
|
# You might want to try it out:
|
||||||
|
# log = templates/log_table.ezt
|
||||||
|
query = templates/query.ezt
|
||||||
|
|
||||||
#---------------------------------------------------------------------------
|
#---------------------------------------------------------------------------
|
||||||
[cvsdb]
|
[cvsdb]
|
||||||
|
|
||||||
@@ -145,6 +160,7 @@ forbidden =
|
|||||||
#passwd =
|
#passwd =
|
||||||
#readonly_user =
|
#readonly_user =
|
||||||
#readonly_passwd =
|
#readonly_passwd =
|
||||||
|
#row_limit = 1000
|
||||||
|
|
||||||
#---------------------------------------------------------------------------
|
#---------------------------------------------------------------------------
|
||||||
[images]
|
[images]
|
||||||
@@ -152,9 +168,6 @@ forbidden =
|
|||||||
# All images are defined with three values: URL, WIDTH, HEIGHT
|
# All images are defined with three values: URL, WIDTH, HEIGHT
|
||||||
#
|
#
|
||||||
|
|
||||||
# this logo will appear on the page
|
|
||||||
logo = /icons/apache_pb.gif, 259, 32
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# these icons represent a back-pointer, a directory (folder), and a file.
|
# these icons represent a back-pointer, a directory (folder), and a file.
|
||||||
# they are normally available in a standard Apache distribution, along
|
# they are normally available in a standard Apache distribution, along
|
||||||
@@ -170,26 +183,25 @@ file_icon = /icons/small/text.gif, 16, 16
|
|||||||
# background color of log entry in markup
|
# background color of log entry in markup
|
||||||
markup_log = #ffffff
|
markup_log = #ffffff
|
||||||
|
|
||||||
# color of change-section headings in a diff
|
# The following six colors are used together:
|
||||||
|
# color of change-section headings in a diff (default turquoise)
|
||||||
diff_heading = #99cccc
|
diff_heading = #99cccc
|
||||||
|
|
||||||
# color of "empty" lines
|
# color of "empty" lines (default light gray)
|
||||||
diff_empty = #cccccc
|
diff_empty = #cccccc
|
||||||
|
|
||||||
# removed lines
|
# removed lines (default light red)
|
||||||
diff_remove = #ff9999
|
diff_remove = #ffaaaa
|
||||||
|
|
||||||
# changed lines
|
# changed lines (default light yellow)
|
||||||
diff_change = #99ff99
|
diff_change = #ffff77
|
||||||
|
|
||||||
# added lines
|
# added lines (default light green)
|
||||||
diff_add = #ccccff
|
diff_add = #aaffaa
|
||||||
|
|
||||||
# empty lines in a change block (one part smaller than the other)
|
# empty lines in a change block (if one part smaller is than the other;
|
||||||
diff_dark_change = #99cc99
|
# default is a greyish yellow: the color should match the hue of 'diff_change')
|
||||||
|
diff_dark_change = #eeee77
|
||||||
# even/odd row colors
|
|
||||||
even_odd = #ccccee, #ffffff
|
|
||||||
|
|
||||||
# navigation header (in diff screen, file view, annotation, etc)
|
# navigation header (in diff screen, file view, annotation, etc)
|
||||||
nav_header = #9999ee
|
nav_header = #9999ee
|
||||||
@@ -203,79 +215,6 @@ background = #ffffff
|
|||||||
# color of alternate background (diffs, file view, annotations, etc)
|
# color of alternate background (diffs, file view, annotations, etc)
|
||||||
alt_background = #eeeeee
|
alt_background = #eeeeee
|
||||||
|
|
||||||
# table header colors (normal and the sorted-by column)
|
|
||||||
column_header_normal = #cccccc
|
|
||||||
column_header_sorted = #88ff88
|
|
||||||
|
|
||||||
# Uncomment the following line for colored borders in tables
|
|
||||||
#table_border = #999999
|
|
||||||
|
|
||||||
#---------------------------------------------------------------------------
|
|
||||||
[text]
|
|
||||||
|
|
||||||
#
|
|
||||||
# WARNING: if you continue the text onto multiple lines, then make SURE that
|
|
||||||
# you indent the continuations.
|
|
||||||
#
|
|
||||||
|
|
||||||
short_intro =
|
|
||||||
<p>
|
|
||||||
Click on a directory to enter that directory. Click on a file to display
|
|
||||||
its revision history and to get a chance to display diffs between
|
|
||||||
revisions.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
long_intro =
|
|
||||||
<p>
|
|
||||||
This is a WWW interface for CVS Repositories.
|
|
||||||
You can browse the file hierarchy by picking directories
|
|
||||||
(which have slashes after them, <i>e.g.</i>, <b>src/</b>).
|
|
||||||
If you pick a file, you will see the revision history
|
|
||||||
for that file.
|
|
||||||
Selecting a revision number will download that revision of
|
|
||||||
the file. There is a link at each revision to display
|
|
||||||
diffs between that revision and the previous one, and
|
|
||||||
a form at the bottom of the page that allows you to
|
|
||||||
display diffs between arbitrary revisions.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
This script
|
|
||||||
(<a href="http://www.lyra.org/viewcvs/">ViewCVS</a>)
|
|
||||||
has been written by Greg Stein
|
|
||||||
<<a href="mailto:gstein@lyra.org">gstein@lyra.org</a>>
|
|
||||||
based on the
|
|
||||||
<a href="http://linux.fh-heilbronn.de/~zeller/cgi/cvsweb.cgi">cvsweb</a>
|
|
||||||
script by Henner Zeller
|
|
||||||
<<a href="mailto:zeller@think.de">zeller@think.de</a>>;
|
|
||||||
it is covered by the
|
|
||||||
<a href="http://www.opensource.org/licenses/bsd-license.html">BSD
|
|
||||||
Licence</a>.
|
|
||||||
If you would like to use this CGI script on your own web server and
|
|
||||||
CVS tree, see Greg's
|
|
||||||
<a href="http://www.lyra.org/viewcvs/">ViewCVS distribution
|
|
||||||
site</a>.
|
|
||||||
Please send any suggestions, comments, etc. to
|
|
||||||
<a href="mailto:gstein@lyra.org">Greg Stein</a>.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
doc_info =
|
|
||||||
<h3>CVS Documentation</h3>
|
|
||||||
<blockquote>
|
|
||||||
<p>
|
|
||||||
<a href="http://www.loria.fr/~molli/cvs/doc/cvs_toc.html">CVS
|
|
||||||
User's Guide</a><br>
|
|
||||||
<a href="http://www.arc.unm.edu/~rsahu/cvs.html">CVS Tutorial</a><br>
|
|
||||||
<a href="http://cellworks.washington.edu/pub/docs/cvs/tutorial/cvs_tutorial_1.html">Another CVS tutorial</a><br>
|
|
||||||
<a href="http://www.csc.calpoly.edu/~dbutler/tutorials/winter96/cvs/">Yet
|
|
||||||
another CVS tutorial (a little old, but nice)</a><br>
|
|
||||||
<a href="http://www.cs.utah.edu/dept/old/texinfo/cvs/FAQ.txt">An old but
|
|
||||||
very useful FAQ about CVS</a>
|
|
||||||
</p>
|
|
||||||
</blockquote>
|
|
||||||
|
|
||||||
repository_info =
|
|
||||||
<!-- insert repository access instructions here -->
|
|
||||||
|
|
||||||
#---------------------------------------------------------------------------
|
#---------------------------------------------------------------------------
|
||||||
[options]
|
[options]
|
||||||
### DOC
|
### DOC
|
||||||
@@ -304,7 +243,7 @@ log_sort = date
|
|||||||
# u Unified diff
|
# u Unified diff
|
||||||
# c Context diff
|
# c Context diff
|
||||||
# s Side by side
|
# s Side by side
|
||||||
# H Long human readable
|
# l Long human readable (more context)
|
||||||
diff_format = h
|
diff_format = h
|
||||||
|
|
||||||
# hide_cvsroot: Don't show the CVSROOT directory
|
# hide_cvsroot: Don't show the CVSROOT directory
|
||||||
@@ -342,9 +281,6 @@ hr_ignore_white = 1
|
|||||||
hr_ignore_keyword_subst = 1
|
hr_ignore_keyword_subst = 1
|
||||||
|
|
||||||
# allow annotation of files.
|
# allow annotation of files.
|
||||||
# NOTE: this requires rw-access to the CVSROOT/history file, and rw-access to
|
|
||||||
# the subdirectory to place the lock... so you maybe don't want it
|
|
||||||
# WARNING: this is not yet implemented!!
|
|
||||||
allow_annotate = 1
|
allow_annotate = 1
|
||||||
|
|
||||||
# allow pretty-printed version of files
|
# allow pretty-printed version of files
|
||||||
@@ -363,11 +299,6 @@ use_java_script = 1
|
|||||||
# open Download-Links in another window
|
# open Download-Links in another window
|
||||||
open_extern_window = 1
|
open_extern_window = 1
|
||||||
|
|
||||||
# The size of this extern window; this size option needs use_java_script
|
|
||||||
# to be defined
|
|
||||||
extern_window_width = 600
|
|
||||||
extern_window_height = 440
|
|
||||||
|
|
||||||
# If you have files which automatically refers to other files
|
# If you have files which automatically refers to other files
|
||||||
# (such as HTML) then this allows you to browse the checked
|
# (such as HTML) then this allows you to browse the checked
|
||||||
# out files as if outside CVS.
|
# out files as if outside CVS.
|
||||||
@@ -383,6 +314,15 @@ checkout_magic = 1
|
|||||||
# Enable this if you like the feature, but don't rely on correct results.
|
# Enable this if you like the feature, but don't rely on correct results.
|
||||||
show_subdir_lastmod = 0
|
show_subdir_lastmod = 0
|
||||||
|
|
||||||
|
# The next flag defines the meaning of clicking on either a filename or
|
||||||
|
# the revision number of that file in the directory view. if 0 then
|
||||||
|
# the traditional behavior applies: clicking on the name takes you to the
|
||||||
|
# CVS log page, where clicking on the revision number displays that revision.
|
||||||
|
# If the flag is set to 1 then both columns will be swapped and the meaning
|
||||||
|
# of clicking is also exchanged. This should be more intuitive to new users.
|
||||||
|
# The classic setting is default:
|
||||||
|
flip_links_in_dirview = 0
|
||||||
|
|
||||||
# show a portion of the most recent log entry in directory listings
|
# show a portion of the most recent log entry in directory listings
|
||||||
show_logs = 1
|
show_logs = 1
|
||||||
|
|
||||||
@@ -392,7 +332,6 @@ show_log_in_markup = 1
|
|||||||
# == Configuration defaults ==
|
# == Configuration defaults ==
|
||||||
# Defaults for configuration variables that shouldn't need
|
# Defaults for configuration variables that shouldn't need
|
||||||
# to be configured..
|
# to be configured..
|
||||||
allow_version_select = 1
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# If you want to use Marc-Andrew Lemburg's py2html (and Just van Rossum's
|
# If you want to use Marc-Andrew Lemburg's py2html (and Just van Rossum's
|
||||||
@@ -408,20 +347,14 @@ py2html_path = .
|
|||||||
# shown in the directory view
|
# shown in the directory view
|
||||||
short_log_len = 80
|
short_log_len = 80
|
||||||
|
|
||||||
table_padding = 2
|
|
||||||
|
|
||||||
diff_font_face = Helvetica,Arial
|
diff_font_face = Helvetica,Arial
|
||||||
diff_font_size = -1
|
diff_font_size = -1
|
||||||
|
|
||||||
# the width of the textinput in the request-diff-form
|
|
||||||
input_text_size = 12
|
|
||||||
|
|
||||||
# should we use 'enscript' for syntax coloring?
|
# should we use 'enscript' for syntax coloring?
|
||||||
use_enscript = 0
|
use_enscript = 0
|
||||||
|
|
||||||
#
|
#
|
||||||
# if the enscript program is not on the path, set this value
|
# if the enscript program is not on the path, set this value
|
||||||
# Note: there should be a trailing slash
|
|
||||||
#
|
#
|
||||||
enscript_path =
|
enscript_path =
|
||||||
# enscript_path = /usr/bin/
|
# enscript_path = /usr/bin/
|
||||||
@@ -434,6 +367,31 @@ enscript_path =
|
|||||||
disable_enscript_lang =
|
disable_enscript_lang =
|
||||||
# disable_enscript_lang = perl, cpp
|
# disable_enscript_lang = perl, cpp
|
||||||
|
|
||||||
|
#
|
||||||
|
# ViewCVS can generate tarball from a repository on the fly.
|
||||||
|
#
|
||||||
|
allow_tar = 0
|
||||||
|
# allow_tar = 1
|
||||||
|
|
||||||
|
#
|
||||||
|
# Use CvsGraph. See http://www.akhphd.au.dk/~bertho/cvsgraph/ for
|
||||||
|
# documentation and download.
|
||||||
|
#
|
||||||
|
use_cvsgraph = 0
|
||||||
|
# use_cvsgraph = 1
|
||||||
|
|
||||||
|
#
|
||||||
|
# if the cvsgraph program is not on the path, set this value
|
||||||
|
#
|
||||||
|
cvsgraph_path =
|
||||||
|
# cvsgraph_path = /usr/local/bin/
|
||||||
|
|
||||||
|
#
|
||||||
|
# Location of the customized cvsgraph configuration file.
|
||||||
|
# You will need an absolute pathname here:
|
||||||
|
#
|
||||||
|
cvsgraph_conf = <VIEWCVS_INSTALL_DIRECTORY>/cvsgraph.conf
|
||||||
|
|
||||||
#---------------------------------------------------------------------------
|
#---------------------------------------------------------------------------
|
||||||
[vhosts]
|
[vhosts]
|
||||||
### DOC
|
### DOC
|
||||||
|
@@ -1,81 +0,0 @@
|
|||||||
<html>
|
|
||||||
<head><title>CVSdb Query Form</title></head>
|
|
||||||
<body bgcolor="#ffffff">
|
|
||||||
|
|
||||||
<h1>CVSdb Query Form for GNOME CVS Repository</h1>
|
|
||||||
|
|
||||||
<form method=get action='query.cgi'>
|
|
||||||
<table border cellpading=8 cellspacing=0>
|
|
||||||
|
|
||||||
<tr>
|
|
||||||
<td align=right>Repository:</td>
|
|
||||||
<td><input type=text name=repository size=25></td>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
<tr>
|
|
||||||
<td align=right>Branch:</td>
|
|
||||||
<td><input type=text name=branch size=25></td>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
<tr>
|
|
||||||
<td align=right>Directory:</td>
|
|
||||||
<td><input type=text name=directory value='' size=45></td>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
<tr>
|
|
||||||
<td align=right>File:</td>
|
|
||||||
<td><input type=text name=file value='' size=45></td>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
<tr>
|
|
||||||
<td align=right>Who:</td>
|
|
||||||
<td><input type=text name=who value='' size=45></td>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
<tr>
|
|
||||||
<td align=right>Sort By:</td>
|
|
||||||
<td>
|
|
||||||
<select name=sortby>
|
|
||||||
<option value="date">Date</option>
|
|
||||||
<option value="author">Author</option>
|
|
||||||
<option value="file">File</option>
|
|
||||||
</select>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
<tr>
|
|
||||||
<td align=right valign=top><br>Date:</td>
|
|
||||||
<td colspan=2>
|
|
||||||
<table BORDER=0 CELLSPACING=0 CELLPADDING=0>
|
|
||||||
<tr>
|
|
||||||
<td><input type=radio name=date checked value=hours></td>
|
|
||||||
<td>
|
|
||||||
In the last <input type=text name=hours value=2 size=4> hours
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td><input type=radio name=date value=day></td>
|
|
||||||
<td>In the last day</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td><input type=radio name=date value=week></td>
|
|
||||||
<td>In the last week</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td><input type=radio name=date value=month></td>
|
|
||||||
<td>In the last month</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td><input type=radio name=date value=all></td>
|
|
||||||
<td>Since the beginning of time</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td colspan=2><input type=submit value='Run Query'></td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
</html>
|
|
@@ -1,30 +0,0 @@
|
|||||||
<html>
|
|
||||||
<head><title>CVSdb Query</title></head>
|
|
||||||
<body bgcolor="#ffffff">
|
|
||||||
|
|
||||||
<h1>CVSdb Query</h1>
|
|
||||||
|
|
||||||
<table width="100%" border=0 cellspacing=0 cellpadding=2>
|
|
||||||
<tr bgcolor="#88ff88">
|
|
||||||
<th align=left valign=top>Date</th>
|
|
||||||
<th align=left valign=top>Author</th>
|
|
||||||
<th align=left valign=top>File</th>
|
|
||||||
<th align=left valign=top>Revision</th>
|
|
||||||
<th align=left valign=top>Branch</th>
|
|
||||||
<th align=left valign=top>+/-</th>
|
|
||||||
<th align=left valign=top>Description</th>
|
|
||||||
</tr>
|
|
||||||
<!-- INSERT QUERY ROWS -->
|
|
||||||
<tr bgcolor="#88ff88">
|
|
||||||
<th align=left valign=top> </th>
|
|
||||||
<th align=left valign=top> </th>
|
|
||||||
<th align=left valign=top> </th>
|
|
||||||
<th align=left valign=top> </th>
|
|
||||||
<th align=left valign=top> </th>
|
|
||||||
<th align=left valign=top> </th>
|
|
||||||
<th align=left valign=top> </th>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
|
|
||||||
</body>
|
|
||||||
</html>
|
|
199
lib/apache_icons.py
Normal file
199
lib/apache_icons.py
Normal file
@@ -0,0 +1,199 @@
|
|||||||
|
#! /usr/bin/env python
|
||||||
|
# This file was automatically generated! DO NOT EDIT!
|
||||||
|
# Howto regenerate: see ../tools/bin2inline.py
|
||||||
|
# $Id$
|
||||||
|
# You have been warned. But if you want to edit, go ahead using your
|
||||||
|
# favorite editor :-)
|
||||||
|
## vim:ts=4:et:nowrap
|
||||||
|
# [Emacs: -*- python -*-]
|
||||||
|
|
||||||
|
_ap_icons = {
|
||||||
|
"/icons/apache_pb.gif" :
|
||||||
|
'GIF89a\003\001 \000\367\000\000\377\377\377'
|
||||||
|
'\316\316\316\245\245\245\204\204\204ssskkkZ'
|
||||||
|
'ZZ!\030\030\377B\030\3771\000\275R\020\336\255'
|
||||||
|
'\204\357\234B\377\204\000\377\316\030\377\316\000\316\316\306'
|
||||||
|
'\275\275\3061\000\377c\000\377\234\000\377\357\000\377\347'
|
||||||
|
'J\357\336{\336\326\245\316\377\000\234\357J\214\377\000'
|
||||||
|
'c\347\204\234\357Rc\377\000\030\000\000\000\000\000\000'
|
||||||
|
'\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000'
|
||||||
|
'\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000'
|
||||||
|
'\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000'
|
||||||
|
'\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000'
|
||||||
|
'\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000'
|
||||||
|
'\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000'
|
||||||
|
'\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000'
|
||||||
|
'\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000'
|
||||||
|
'\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000'
|
||||||
|
'\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000'
|
||||||
|
'\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000'
|
||||||
|
'\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000'
|
||||||
|
'\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000'
|
||||||
|
'\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000'
|
||||||
|
'\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000'
|
||||||
|
'\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000'
|
||||||
|
'\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000'
|
||||||
|
'\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000'
|
||||||
|
'\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000'
|
||||||
|
'\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000'
|
||||||
|
'\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000'
|
||||||
|
'\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000'
|
||||||
|
'\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000'
|
||||||
|
'\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000'
|
||||||
|
'\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000'
|
||||||
|
'\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000'
|
||||||
|
'\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000'
|
||||||
|
'\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000'
|
||||||
|
'\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000'
|
||||||
|
'\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000'
|
||||||
|
'\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000'
|
||||||
|
'\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000'
|
||||||
|
'\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000'
|
||||||
|
'\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000'
|
||||||
|
'\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000'
|
||||||
|
'\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000'
|
||||||
|
'\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000'
|
||||||
|
'\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000'
|
||||||
|
'\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000'
|
||||||
|
'\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000'
|
||||||
|
'\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000'
|
||||||
|
'\000\000\000\000\000\000\000\000\000\000\000\000\000!\371\004'
|
||||||
|
'\001\000\000\001\000,\000\000\000\000\003\001 \000G\010'
|
||||||
|
'\377\000\003\010\034H\260\240\301\203\010\023*\\\310\260'
|
||||||
|
'\241\303\207\020#J\234H\261\242E\206\021\016\034\030'
|
||||||
|
'0\260\300\200\001\002\002l0\260!\303\006\017(Q'
|
||||||
|
'&X\331\300A\204\201\007\376\3753\020\363_\004\231'
|
||||||
|
'\007h\376;\020 \246\001\0032\011\370\024\2603\302'
|
||||||
|
'\200\231=e\006%03\002P\002\001p\352\374W'
|
||||||
|
'\020\350L\253\004\254\336\264y\261\253\327\257`\303\026'
|
||||||
|
'|Iv\203\331\012\033*P\2400a\302?\011J'
|
||||||
|
'\343\276\225@w\002\333\265\0252P\3100!\203I'
|
||||||
|
'\277\005P\376;\231\022\001\002\001._\212]\314\270'
|
||||||
|
'\261\343\307b#Dh0\000A\203\225\011R\012N'
|
||||||
|
'\371\317\203\001\017f\375\206\366+\332d^\275\245\323'
|
||||||
|
"f\030\360\240u\353\006\017\032\314\374'\200\243\300\013"
|
||||||
|
"u'Hh\333\226\356\356\335\272%\\0xa\355"
|
||||||
|
'\332\341\022/\220^\316\274y\006\015\0065{X\330'
|
||||||
|
'A3\206\202\0300\257d\270@{\202\203\014\032\210'
|
||||||
|
'\377o\000\371\253\200\363\347\313/\214\220\236 \373\220'
|
||||||
|
'\352\343\313\237\357\360\345y\271o\347\266]\233!o'
|
||||||
|
'\337\275\031L\225\222\001\010(PSQ\002\035\305S'
|
||||||
|
'T\264\011$\300\201P\305\264\240@L)\025\222S'
|
||||||
|
'rU\025\327F\016\312\244\030} \2068_\004\010'
|
||||||
|
'\030\260Rg\240\225d\332r\024T`\327ZnY'
|
||||||
|
'\205\237\\p\375\366\033[\025\2505\300^\033\000\230'
|
||||||
|
'\300\000-\271\324P\004\272\265\025\200\005\277M\360\341'
|
||||||
|
'A\305\031G\301BN"\207\335r\303Y\260\334u'
|
||||||
|
'\011I\267\220\006\326a\347\035w_\032\024\336x"'
|
||||||
|
'\226i\346\231h\246\251\346\232l\266\351\246A\017.'
|
||||||
|
'9\320y\005(\346\200\000\015\340\331PM\002X\305'
|
||||||
|
'\221Q\006\014`\337O/\031\000\225S\006\220E\200'
|
||||||
|
'\240\024\032 \200S\360\011@\000|\002E@@V'
|
||||||
|
'2i\370\317\000\216\006\320g\242\236\032\372\346\250e'
|
||||||
|
'\032U\023P \355\367_\217*J\247RM\037n'
|
||||||
|
'\377\005U\205\222)\365\023N\001\034E\033zIM'
|
||||||
|
'\010\324\001\350=*S\242Vi\352\351\260\001\310('
|
||||||
|
"'\251\314\252\367\350O\004\024\020\000_\301\3218\227"
|
||||||
|
'\001\022\030p\227\213\250M0\022a\011\240\310\222x'
|
||||||
|
'\206!\260\254|[Q\332\354\272"\032\225\222Y\241'
|
||||||
|
'\251\325\237\213l\3015c~\276\365\266\037\275x\241'
|
||||||
|
'6\322_&\201\326\300\271\023\345;\201\005\0155\351'
|
||||||
|
'\344\302\014\033\207%B\0324\367pB\030\270j\261'
|
||||||
|
'\253\034\030\224\035f\035\200\251\035x\343\205,\362\310'
|
||||||
|
'\013<\206\247\211\232\015\206\222\212\244\261\314\234qj'
|
||||||
|
'\3355\301On\331;#P\015\030\020\333k\2559'
|
||||||
|
" \244\306ENP\020\222ENL\220\302\015'\275"
|
||||||
|
'\226\321\312\221&\345m\313e|\220\226\025m\354\335'
|
||||||
|
'\325Xc\006\362\310"\227<\252d\001HfT\003'
|
||||||
|
'\033!p\000\002)\036\340\301\000\033\014\000\332\333\360'
|
||||||
|
'\232\205\322\001q\033\345\300\000w\252{d\276F\037'
|
||||||
|
'\377\204Ap\022 <\020\322O7\304\\\337~\037'
|
||||||
|
'>\020\007\0277\216R\307^~\274Pw\222\0274'
|
||||||
|
'&\327\230\213\007\001\273\234w\356\371\347\240\207.\372'
|
||||||
|
'\350\244\227\3169\301a\203M\321\201\310\212X,A'
|
||||||
|
'\257\233.;D\246\352-)\243hk\226\200e\003'
|
||||||
|
'#T\323\261\377L*\227\260\233\022e\323\257\311*'
|
||||||
|
'\005lRV\361t\340\001/U\030\227\246\312oe'
|
||||||
|
'\000\363\263go\320\000\032\011\012@\001\005@\205Z'
|
||||||
|
'\005\004\2304RJ,\355~\030\353\300\032?\253L'
|
||||||
|
'\222\342$!\374\025j\224)\237\317\356d?\2602'
|
||||||
|
'q\024\273@\257\363\311V|\242\275\002\036DF\267'
|
||||||
|
'\252\213\223\374\223\232\317x@)\0360\314\300jB'
|
||||||
|
'A\000\302OW\327\263\236\244\234\267\2239\015\353S'
|
||||||
|
'I\311\020\354\360\023\022\343]\317\200(\014[\001\200'
|
||||||
|
'b\000\217\330L)\276\221\000\214\214\343\257\275\020\000'
|
||||||
|
'\\\036X\211\002,\203\030\325%\204=\014yTW'
|
||||||
|
'\377X\230B\024\212\215=/\351Vo^\010\303|'
|
||||||
|
'\311\320.}\351V\3344\343\300\0346\240\\z\203'
|
||||||
|
'\314\005rR\304.V*l\271\353\221_\3622\303'
|
||||||
|
'\266\334k.7z"\031\333\242\032\322\334\020\\\231'
|
||||||
|
')\014\352(R\227\302%Dix\274\000\342\012r'
|
||||||
|
'\001\013\370Qp\015\201\000\007\030\347\270\224p\000q'
|
||||||
|
'\030\350\200";\3405\205Pn%\215$\010\003&'
|
||||||
|
'I\311JZ\262\222\221\264\310d\334\366.\227\235\246'
|
||||||
|
'E0b"~jd\243\375\340H-1\243@\001'
|
||||||
|
'\344V\030\004\260\315\\\022\311\315o\000\231\020\244Q'
|
||||||
|
'\340\002\270\314\245.\011\227\020\347\370e\217\003\221\316'
|
||||||
|
' \207IL\016h\240:)\331\034A\254\346\001\310'
|
||||||
|
'92L\226\013\331\002\246I\315jZ\2231w\273'
|
||||||
|
'L\034W\026\232\264\250\34649B\245]\012\000\027'
|
||||||
|
'\031\235Q&\300\341\015o\332&\236\330\300\346g\010'
|
||||||
|
'!\022p\006B\264\335\240\316\226\273\314g\2244F'
|
||||||
|
'\363\245\301])K\232Y\010\006\016\211\310\254\031\364'
|
||||||
|
"j[\023\317%'\231I\365\214\315D+\331\014J"
|
||||||
|
'H\302\315n.\3474\375\361\313^\310\330\242\276\304'
|
||||||
|
'\314\0003\273U\\~b\000\361\300S \177K\222'
|
||||||
|
':y\223$\304\331\022\217\306\321#\037}IS\251'
|
||||||
|
'\025\204j\011\341\200"\017\2511\357\024\223\003\326|'
|
||||||
|
'\244\326\304$\315\240.@\231i\222Lm\\\351\235'
|
||||||
|
'\034\272\252G\025e\245\001\376B\030\222\250\250\000\355'
|
||||||
|
'\204\215k|\366!\334\000G\217\030\010k\000\302J'
|
||||||
|
'\326\013,\261p\274\214H\323\374\002V\262\2725\254'
|
||||||
|
'\034X\016t\010"\314\237\016\363\230\232A*J\241'
|
||||||
|
'\231\020\241~\207\250\343\241\244Q\257\351E\332\351r'
|
||||||
|
'\216\3569\354@0\240K`\372M\227\020\031\3500'
|
||||||
|
"'&Y\273ZV\257\003\201@5\035+\020\315V"
|
||||||
|
'\363 \203\015-5\013K\332\322\232\366\264\250=\010'
|
||||||
|
'@\002\004\004\000;',
|
||||||
|
|
||||||
|
"/icons/small/back.gif" :
|
||||||
|
'GIF89a\020\000\020\000\242\377\000!!!'
|
||||||
|
'111ZZZ\204\204\204\214\214\214\300\300\300\000'
|
||||||
|
'\000\000\000\000\000!\371\004\001\000\000\005\000,\000\000'
|
||||||
|
'\000\000\020\000\020\000\000\003FX\272\334\015\300I`'
|
||||||
|
'L\224\213V\213\213\320\025 t\304f\020]Qn'
|
||||||
|
'(i\266\303\240\310\312Z\215D,\010\255\022\230\236'
|
||||||
|
'\20000Z\010L\203\241\320a"*\035 \321\260'
|
||||||
|
'\267\260\235\204E\307q3\310J\010`p#\001\000'
|
||||||
|
';',
|
||||||
|
|
||||||
|
"/icons/small/dir.gif" :
|
||||||
|
'GIF89a\020\000\020\000\242\377\000\377\336\255'
|
||||||
|
'\377\334\256\300\300\300\270\240}WK;+%\035\000'
|
||||||
|
'\000\000\000\000\000!\371\004\001\000\000\002\000,\000\000'
|
||||||
|
'\000\000\020\000\020\000\000\003I(b\246\376\316\2241'
|
||||||
|
'\230\262\320\2002\253\031\001v\001$GQA\025\221'
|
||||||
|
'\254y6#\333v\360"\337_\255\335dE\350\274'
|
||||||
|
'\236\341\267\012\372t\204\001\357\370`$e\314\314P'
|
||||||
|
'\0118\026 \227\251\365\212]\014\277\234n\204\021\026'
|
||||||
|
'$\000\000;',
|
||||||
|
|
||||||
|
"/icons/small/text.gif" :
|
||||||
|
'GIF89a\020\000\020\000\242\377\000\377\377\377'
|
||||||
|
'999kkk\214\214\214\300\300\300\316\316\316\347'
|
||||||
|
'\347\347\000\000\000!\371\004\001\000\000\004\000,\000\000'
|
||||||
|
'\000\000\020\000\020\000\000\003EH\272\334\276\343\025@'
|
||||||
|
"k1\261\315J'\326\\W\024D\246LA\232\002"
|
||||||
|
'j\300xFlP\212\360\262me\330\013,\307\024'
|
||||||
|
'\336\011\327Z\011\011\276\337\354\210*\032oJ\031\200'
|
||||||
|
'I,No!\316\261$\350z\275\256E\002\000;',
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def serve_icon(pathname, fp):
|
||||||
|
if _ap_icons.has_key(pathname):
|
||||||
|
fp.write(_ap_icons[pathname])
|
||||||
|
else:
|
||||||
|
raise OSError # icon not found
|
||||||
|
|
299
lib/blame.py
299
lib/blame.py
@@ -1,16 +1,16 @@
|
|||||||
#!/usr/local/bin/python
|
#!/usr/local/bin/python
|
||||||
# -*-python-*-
|
# -*-python-*-
|
||||||
#
|
#
|
||||||
# Copyright (C) 2000 The ViewCVS Group. All Rights Reserved.
|
# Copyright (C) 2000-2001 The ViewCVS Group. All Rights Reserved.
|
||||||
# Copyright (C) 2000 Curt Hagenlocher <curt@hagenlocher.org>
|
# Copyright (C) 2000 Curt Hagenlocher <curt@hagenlocher.org>
|
||||||
#
|
#
|
||||||
# By using this file, you agree to the terms and conditions set forth in
|
# By using this file, you agree to the terms and conditions set forth in
|
||||||
# the LICENSE.html file which can be found at the top level of the ViewCVS
|
# the LICENSE.html file which can be found at the top level of the ViewCVS
|
||||||
# distribution or at http://www.lyra.org/viewcvs/license-1.html.
|
# distribution or at http://viewcvs.sourceforge.net/license-1.html.
|
||||||
#
|
#
|
||||||
# Contact information:
|
# Contact information:
|
||||||
# Greg Stein, PO Box 760, Palo Alto, CA, 94302
|
# Greg Stein, PO Box 760, Palo Alto, CA, 94302
|
||||||
# gstein@lyra.org, http://www.lyra.org/viewcvs/
|
# gstein@lyra.org, http://viewcvs.sourceforge.net/
|
||||||
#
|
#
|
||||||
# -----------------------------------------------------------------------
|
# -----------------------------------------------------------------------
|
||||||
#
|
#
|
||||||
@@ -21,7 +21,7 @@
|
|||||||
#
|
#
|
||||||
# This software is being maintained as part of the ViewCVS project.
|
# This software is being maintained as part of the ViewCVS project.
|
||||||
# Information is available at:
|
# Information is available at:
|
||||||
# http://www.lyra.org/viewcvs/
|
# http://viewcvs.sourceforge.net/
|
||||||
#
|
#
|
||||||
# This file is based on the cvsblame.pl portion of the Bonsai CVS tool,
|
# This file is based on the cvsblame.pl portion of the Bonsai CVS tool,
|
||||||
# developed by Steve Lamm for Netscape Communications Corporation. More
|
# developed by Steve Lamm for Netscape Communications Corporation. More
|
||||||
@@ -40,17 +40,10 @@ import re
|
|||||||
import time
|
import time
|
||||||
import math
|
import math
|
||||||
import cgi
|
import cgi
|
||||||
|
import rcsparse
|
||||||
|
|
||||||
path_sep = os.path.normpath('/')[-1]
|
class CVSParser(rcsparse.Sink):
|
||||||
|
|
||||||
class CVSParser:
|
|
||||||
# Precompiled regular expressions
|
# Precompiled regular expressions
|
||||||
nonws_token = re.compile('^([^;@][^;\\s]*)\\s*')
|
|
||||||
semic_token = re.compile('^;\\s*')
|
|
||||||
rcsen_token = re.compile('^@([^@]*)')
|
|
||||||
undo_escape = re.compile('@@')
|
|
||||||
single_at = re.compile('([^@]|^)@([^@]|$)')
|
|
||||||
rcs_tree = re.compile('^\\d')
|
|
||||||
trunk_rev = re.compile('^[0-9]+\\.[0-9]+$')
|
trunk_rev = re.compile('^[0-9]+\\.[0-9]+$')
|
||||||
last_branch = re.compile('(.*)\\.[0-9]+')
|
last_branch = re.compile('(.*)\\.[0-9]+')
|
||||||
is_branch = re.compile('(.*)\\.0\\.([0-9]+)')
|
is_branch = re.compile('(.*)\\.0\\.([0-9]+)')
|
||||||
@@ -63,9 +56,6 @@ class CVSParser:
|
|||||||
self.Reset()
|
self.Reset()
|
||||||
|
|
||||||
def Reset(self):
|
def Reset(self):
|
||||||
self.line_buffer = ''
|
|
||||||
self.rcsfile = None
|
|
||||||
self.debug = 0
|
|
||||||
self.last_revision = {}
|
self.last_revision = {}
|
||||||
self.prev_revision = {}
|
self.prev_revision = {}
|
||||||
self.revision_date = {}
|
self.revision_date = {}
|
||||||
@@ -73,7 +63,6 @@ class CVSParser:
|
|||||||
self.revision_branches = {}
|
self.revision_branches = {}
|
||||||
self.next_delta = {}
|
self.next_delta = {}
|
||||||
self.prev_delta = {}
|
self.prev_delta = {}
|
||||||
self.feof = 0
|
|
||||||
self.tag_revision = {}
|
self.tag_revision = {}
|
||||||
self.revision_symbolic_name = {}
|
self.revision_symbolic_name = {}
|
||||||
self.timestamp = {}
|
self.timestamp = {}
|
||||||
@@ -85,69 +74,6 @@ class CVSParser:
|
|||||||
self.lines_added = {}
|
self.lines_added = {}
|
||||||
self.lines_removed = {}
|
self.lines_removed = {}
|
||||||
|
|
||||||
# Get the next token from the RCS file
|
|
||||||
def get_token(self):
|
|
||||||
# Erase all-whitespace lines
|
|
||||||
while len(self.line_buffer) == 0:
|
|
||||||
self.line_buffer = self.rcsfile.readline()
|
|
||||||
if self.line_buffer == '':
|
|
||||||
raise RuntimeError, 'EOF'
|
|
||||||
self.line_buffer = string.lstrip(self.line_buffer)
|
|
||||||
|
|
||||||
# A string of non-whitespace characters is a token
|
|
||||||
match = self.nonws_token.match(self.line_buffer)
|
|
||||||
if match:
|
|
||||||
self.line_buffer = self.nonws_token.sub('', self.line_buffer)
|
|
||||||
return match.group(1)
|
|
||||||
|
|
||||||
# ...and so is a single semicolon
|
|
||||||
if self.semic_token.match(self.line_buffer):
|
|
||||||
self.line_buffer = self.semic_token.sub('', self.line_buffer)
|
|
||||||
return ';'
|
|
||||||
|
|
||||||
# ...or an RCS-encoded string that starts with an @ character
|
|
||||||
match = self.rcsen_token.match(self.line_buffer)
|
|
||||||
self.line_buffer = self.rcsen_token.sub('', self.line_buffer)
|
|
||||||
token = match.group(1)
|
|
||||||
|
|
||||||
# Detect single @ character used to close RCS-encoded string
|
|
||||||
while string.find(self.line_buffer, '@') < 0 or not self.single_at.search(self.line_buffer):
|
|
||||||
token = token + self.line_buffer
|
|
||||||
self.line_buffer = self.rcsfile.readline()
|
|
||||||
if self.line_buffer == '':
|
|
||||||
raise RuntimeError, 'EOF'
|
|
||||||
|
|
||||||
# Retain the remainder of the line after the terminating @ character
|
|
||||||
i = string.rindex(self.line_buffer, '@')
|
|
||||||
token = token + self.line_buffer[:i]
|
|
||||||
self.line_buffer = self.line_buffer[i+1:]
|
|
||||||
|
|
||||||
# Undo escape-coding of @ characters.
|
|
||||||
token = self.undo_escape.sub('@', token)
|
|
||||||
|
|
||||||
# Digest any extra blank lines
|
|
||||||
while len(self.line_buffer) == 0 or self.line_buffer == '\n':
|
|
||||||
self.line_buffer = self.rcsfile.readline()
|
|
||||||
if self.line_buffer == '':
|
|
||||||
self.feof = 1
|
|
||||||
break
|
|
||||||
|
|
||||||
if token[-1:] == '\n':
|
|
||||||
token = token[:-1]
|
|
||||||
|
|
||||||
return token
|
|
||||||
|
|
||||||
# Try to match the next token from the input buffer
|
|
||||||
def match_token(self, match):
|
|
||||||
token = self.get_token()
|
|
||||||
if token != match:
|
|
||||||
raise RuntimeError, ('Unexpected parsing error in RCS file.\n' +
|
|
||||||
'Expected token: %s, but saw: %s' % (match, token))
|
|
||||||
|
|
||||||
# Push RCS token back into the input buffer.
|
|
||||||
def unget_token(self, token):
|
|
||||||
self.line_buffer = token + " " + self.line_buffer
|
|
||||||
|
|
||||||
# Map a tag to a numerical revision number. The tag can be a symbolic
|
# Map a tag to a numerical revision number. The tag can be a symbolic
|
||||||
# branch tag, a symbolic revision tag, or an ordinary numerical
|
# branch tag, a symbolic revision tag, or an ordinary numerical
|
||||||
# revision number.
|
# revision number.
|
||||||
@@ -182,6 +108,13 @@ class CVSParser:
|
|||||||
|
|
||||||
return ancestors
|
return ancestors
|
||||||
|
|
||||||
|
# Split deltatext specified by rev to each line.
|
||||||
|
def deltatext_split(self, rev):
|
||||||
|
lines = string.split(self.revision_deltatext[rev], '\n')
|
||||||
|
if lines[-1] == '':
|
||||||
|
del lines[-1]
|
||||||
|
return lines
|
||||||
|
|
||||||
# Extract the given revision from the digested RCS file.
|
# Extract the given revision from the digested RCS file.
|
||||||
# (Essentially the equivalent of cvs up -rXXX)
|
# (Essentially the equivalent of cvs up -rXXX)
|
||||||
def extract_revision(self, revision):
|
def extract_revision(self, revision):
|
||||||
@@ -195,12 +128,12 @@ class CVSParser:
|
|||||||
path.reverse()
|
path.reverse()
|
||||||
path = path[1:] # Get rid of head revision
|
path = path[1:] # Get rid of head revision
|
||||||
|
|
||||||
text = string.split(self.revision_deltatext[self.head_revision], '\n')
|
text = self.deltatext_split(self.head_revision)
|
||||||
|
|
||||||
# Iterate, applying deltas to previous revision
|
# Iterate, applying deltas to previous revision
|
||||||
for revision in path:
|
for revision in path:
|
||||||
adjust = 0
|
adjust = 0
|
||||||
diffs = string.split(self.revision_deltatext[revision], '\n')
|
diffs = self.deltatext_split(revision)
|
||||||
self.lines_added[revision] = 0
|
self.lines_added[revision] = 0
|
||||||
self.lines_removed[revision] = 0
|
self.lines_removed[revision] = 0
|
||||||
lines_added_now = 0
|
lines_added_now = 0
|
||||||
@@ -235,49 +168,25 @@ class CVSParser:
|
|||||||
self.lines_removed[revision] = self.lines_removed[revision] + lines_removed_now
|
self.lines_removed[revision] = self.lines_removed[revision] + lines_removed_now
|
||||||
return text
|
return text
|
||||||
|
|
||||||
def parse_rcs_admin(self):
|
def set_head_revision(self, revision):
|
||||||
while 1:
|
self.head_revision = revision
|
||||||
# Read initial token at beginning of line
|
|
||||||
token = self.get_token()
|
|
||||||
|
|
||||||
# We're done once we reach the description of the RCS tree
|
def set_principal_branch(self, branch_name):
|
||||||
if self.rcs_tree.match(token):
|
self.principal_branch = branch_name
|
||||||
self.unget_token(token)
|
|
||||||
return
|
|
||||||
|
|
||||||
# print "token:", token
|
def define_tag(self, name, revision):
|
||||||
|
|
||||||
if token == "head":
|
|
||||||
self.head_revision = self.get_token()
|
|
||||||
self.get_token() # Eat semicolon
|
|
||||||
elif token == "branch":
|
|
||||||
self.principal_branch = self.get_token()
|
|
||||||
self.get_token() # Eat semicolon
|
|
||||||
elif token == "symbols":
|
|
||||||
# Create an associate array that maps from tag name to
|
# Create an associate array that maps from tag name to
|
||||||
# revision number and vice-versa.
|
# revision number and vice-versa.
|
||||||
while 1:
|
self.tag_revision[name] = revision
|
||||||
tag = self.get_token()
|
|
||||||
if tag == ';':
|
|
||||||
break
|
|
||||||
(tag_name, tag_rev) = string.split(tag, ':')
|
|
||||||
self.tag_revision[tag_name] = tag_rev
|
|
||||||
self.revision_symbolic_name[tag_rev] = tag_name
|
|
||||||
elif token == "comment":
|
|
||||||
self.file_description = self.get_token()
|
|
||||||
self.get_token() # Eat semicolon
|
|
||||||
|
|
||||||
# Ignore all these other fields - We don't care about them.
|
### actually, this is a bit bogus... a rev can have multiple names
|
||||||
elif token in ("locks", "strict", "expand", "access"):
|
self.revision_symbolic_name[revision] = name
|
||||||
while 1:
|
|
||||||
tag = self.get_token()
|
|
||||||
if tag == ';':
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
pass
|
|
||||||
# warn("Unexpected RCS token: $token\n")
|
|
||||||
|
|
||||||
raise RuntimeError, "Unexpected EOF";
|
def set_comment(self, comment):
|
||||||
|
self.file_description = comment
|
||||||
|
|
||||||
|
def set_description(self, description):
|
||||||
|
self.rcs_file_description = description
|
||||||
|
|
||||||
# Construct dicts that represent the topology of the RCS tree
|
# Construct dicts that represent the topology of the RCS tree
|
||||||
# and other arrays that contain info about individual revisions.
|
# and other arrays that contain info about individual revisions.
|
||||||
@@ -301,34 +210,14 @@ class CVSParser:
|
|||||||
# Also creates self.last_revision, keyed by a branch revision number, which
|
# Also creates self.last_revision, keyed by a branch revision number, which
|
||||||
# indicates the latest revision on a given branch,
|
# indicates the latest revision on a given branch,
|
||||||
# e.g. self.last_revision{"1.2.8"} == 1.2.8.5
|
# e.g. self.last_revision{"1.2.8"} == 1.2.8.5
|
||||||
|
def define_revision(self, revision, timestamp, author, state,
|
||||||
def parse_rcs_tree(self):
|
branches, next):
|
||||||
while 1:
|
|
||||||
revision = self.get_token()
|
|
||||||
|
|
||||||
# End of RCS tree description ?
|
|
||||||
if revision == 'desc':
|
|
||||||
self.unget_token(revision)
|
|
||||||
return
|
|
||||||
|
|
||||||
is_trunk_revision = self.trunk_rev.match(revision) is not None
|
|
||||||
|
|
||||||
self.tag_revision[revision] = revision
|
self.tag_revision[revision] = revision
|
||||||
branch = self.last_branch.match(revision).group(1)
|
branch = self.last_branch.match(revision).group(1)
|
||||||
self.last_revision[branch] = revision
|
self.last_revision[branch] = revision
|
||||||
|
|
||||||
# Parse date
|
#self.revision_date[revision] = date
|
||||||
self.match_token('date')
|
self.timestamp[revision] = timestamp
|
||||||
date = self.get_token()
|
|
||||||
self.revision_date[revision] = date
|
|
||||||
self.match_token(';')
|
|
||||||
|
|
||||||
# Convert date into timestamp
|
|
||||||
date_fields = string.split(date, '.') + ['0', '0', '0']
|
|
||||||
date_fields = map(string.atoi, date_fields)
|
|
||||||
if date_fields[0] < 100:
|
|
||||||
date_fields[0] = date_fields[0] + 1900
|
|
||||||
self.timestamp[revision] = time.mktime(date_fields)
|
|
||||||
|
|
||||||
# Pretty print the date string
|
# Pretty print the date string
|
||||||
ltime = time.localtime(self.timestamp[revision])
|
ltime = time.localtime(self.timestamp[revision])
|
||||||
@@ -336,57 +225,33 @@ class CVSParser:
|
|||||||
self.revision_ctime[revision] = formatted_date
|
self.revision_ctime[revision] = formatted_date
|
||||||
|
|
||||||
# Save age
|
# Save age
|
||||||
self.revision_age[revision] = (
|
self.revision_age[revision] = ((time.time() - self.timestamp[revision])
|
||||||
(time.time() - self.timestamp[revision]) / self.SECONDS_PER_DAY)
|
/ self.SECONDS_PER_DAY)
|
||||||
|
|
||||||
# Parse author
|
# save author
|
||||||
self.match_token('author')
|
|
||||||
author = self.get_token()
|
|
||||||
self.revision_author[revision] = author
|
self.revision_author[revision] = author
|
||||||
self.match_token(';')
|
|
||||||
|
|
||||||
# Parse state
|
# ignore the state
|
||||||
self.match_token('state')
|
|
||||||
while self.get_token() != ';':
|
|
||||||
pass
|
|
||||||
|
|
||||||
# Parse branches
|
# process the branch information
|
||||||
self.match_token('branches')
|
branch_text = ''
|
||||||
branches = ''
|
for branch in branches:
|
||||||
while 1:
|
self.prev_revision[branch] = revision
|
||||||
token = self.get_token()
|
self.next_delta[revision] = branch
|
||||||
if token == ';':
|
self.prev_delta[branch] = revision
|
||||||
break
|
branch_text = branch_text + branch + ''
|
||||||
self.prev_revision[token] = revision
|
self.revision_branches[revision] = branch_text
|
||||||
self.prev_delta[token] = revision
|
|
||||||
branches = branches + token + ' '
|
|
||||||
self.revision_branches[revision] = branches
|
|
||||||
|
|
||||||
# Parse revision of next delta in chain
|
# process the "next revision" information
|
||||||
self.match_token('next')
|
if next:
|
||||||
next = ''
|
|
||||||
token = self.get_token()
|
|
||||||
if token != ';':
|
|
||||||
next = token
|
|
||||||
self.get_token() # Eat semicolon
|
|
||||||
self.next_delta[revision] = next
|
self.next_delta[revision] = next
|
||||||
self.prev_delta[next] = revision
|
self.prev_delta[next] = revision
|
||||||
|
is_trunk_revision = self.trunk_rev.match(revision) is not None
|
||||||
if is_trunk_revision:
|
if is_trunk_revision:
|
||||||
self.prev_revision[revision] = next
|
self.prev_revision[revision] = next
|
||||||
else:
|
else:
|
||||||
self.prev_revision[next] = revision
|
self.prev_revision[next] = revision
|
||||||
|
|
||||||
if self.debug >= 3:
|
|
||||||
print "<pre>revision =", revision
|
|
||||||
print "date = ", date
|
|
||||||
print "author = ", author
|
|
||||||
print "branches = ", branches
|
|
||||||
print "next = ", next + "</pre>\n"
|
|
||||||
|
|
||||||
def parse_rcs_description(self):
|
|
||||||
self.match_token('desc')
|
|
||||||
self.rcs_file_description = self.get_token()
|
|
||||||
|
|
||||||
# Construct associative arrays containing info about individual revisions.
|
# Construct associative arrays containing info about individual revisions.
|
||||||
#
|
#
|
||||||
# The following associative arrays are created, keyed by revision number:
|
# The following associative arrays are created, keyed by revision number:
|
||||||
@@ -398,36 +263,9 @@ class CVSParser:
|
|||||||
# revision if this revision is on the trunk or
|
# revision if this revision is on the trunk or
|
||||||
# relative to its immediate predecessor if this
|
# relative to its immediate predecessor if this
|
||||||
# revision is on a branch.
|
# revision is on a branch.
|
||||||
def parse_rcs_deltatext(self):
|
def set_revision_info(self, revision, log, text):
|
||||||
while not self.feof:
|
self.revision_log[revision] = log
|
||||||
revision = self.get_token()
|
self.revision_deltatext[revision] = text
|
||||||
if self.debug >= 3:
|
|
||||||
print "Reading delta for revision:", revision
|
|
||||||
self.match_token('log')
|
|
||||||
self.revision_log[revision] = self.get_token()
|
|
||||||
self.match_token('text')
|
|
||||||
self.revision_deltatext[revision] = self.get_token()
|
|
||||||
|
|
||||||
def parse_rcs_file(self):
|
|
||||||
if self.debug >= 2:
|
|
||||||
print "Reading RCS admin..."
|
|
||||||
self.parse_rcs_admin()
|
|
||||||
if self.debug >= 2:
|
|
||||||
print "Reading RCS revision tree topology..."
|
|
||||||
self.parse_rcs_tree()
|
|
||||||
|
|
||||||
if self.debug >= 3:
|
|
||||||
print "<pre>Keys:\n"
|
|
||||||
for i in self.tag_revision.keys():
|
|
||||||
print "yoyuo %s: %s" % (i, self.tag_revision[i])
|
|
||||||
print "</pre>"
|
|
||||||
|
|
||||||
self.parse_rcs_description()
|
|
||||||
if self.debug >= 2:
|
|
||||||
print "Reading RCS revision deltas..."
|
|
||||||
self.parse_rcs_deltatext()
|
|
||||||
if self.debug >= 2:
|
|
||||||
print "Done reading RCS file..."
|
|
||||||
|
|
||||||
def parse_cvs_file(self, rcs_pathname, opt_rev = None, opt_m_timestamp = None):
|
def parse_cvs_file(self, rcs_pathname, opt_rev = None, opt_m_timestamp = None):
|
||||||
# Args in: opt_rev - requested revision
|
# Args in: opt_rev - requested revision
|
||||||
@@ -438,13 +276,13 @@ class CVSParser:
|
|||||||
|
|
||||||
# CheckHidden(rcs_pathname);
|
# CheckHidden(rcs_pathname);
|
||||||
try:
|
try:
|
||||||
self.rcsfile = open(rcs_pathname, 'r')
|
rcsfile = open(rcs_pathname, 'r')
|
||||||
except:
|
except:
|
||||||
raise RuntimeError, ('error: %s appeared to be under CVS control, ' +
|
raise RuntimeError, ('error: %s appeared to be under CVS control, ' +
|
||||||
'but the RCS file is inaccessible.') % rcs_pathname
|
'but the RCS file is inaccessible.') % rcs_pathname
|
||||||
|
|
||||||
self.parse_rcs_file()
|
rcsparse.Parser().parse(rcsfile, self)
|
||||||
self.rcsfile.close()
|
rcsfile.close()
|
||||||
|
|
||||||
if opt_rev in [None, '', 'HEAD']:
|
if opt_rev in [None, '', 'HEAD']:
|
||||||
# Explicitly specified topmost revision in tree
|
# Explicitly specified topmost revision in tree
|
||||||
@@ -470,14 +308,14 @@ class CVSParser:
|
|||||||
# first revision.
|
# first revision.
|
||||||
line_count = 0
|
line_count = 0
|
||||||
if self.revision_deltatext.get(self.head_revision):
|
if self.revision_deltatext.get(self.head_revision):
|
||||||
tmp_array = string.split(self.revision_deltatext[self.head_revision], '\n')
|
tmp_array = self.deltatext_split(self.head_revision)
|
||||||
line_count = len(tmp_array)
|
line_count = len(tmp_array)
|
||||||
|
|
||||||
skip = 0
|
skip = 0
|
||||||
|
|
||||||
rev = self.prev_revision.get(self.head_revision)
|
rev = self.prev_revision.get(self.head_revision)
|
||||||
while rev:
|
while rev:
|
||||||
diffs = string.split(self.revision_deltatext[rev], '\n')
|
diffs = self.deltatext_split(rev)
|
||||||
for command in diffs:
|
for command in diffs:
|
||||||
dmatch = self.d_command.match(command)
|
dmatch = self.d_command.match(command)
|
||||||
amatch = self.a_command.match(command)
|
amatch = self.a_command.match(command)
|
||||||
@@ -522,7 +360,7 @@ class CVSParser:
|
|||||||
is_trunk_revision = self.trunk_rev.match(revision) is not None
|
is_trunk_revision = self.trunk_rev.match(revision) is not None
|
||||||
|
|
||||||
if is_trunk_revision:
|
if is_trunk_revision:
|
||||||
diffs = string.split(self.revision_deltatext[last_revision], '\n')
|
diffs = self.deltatext_split(last_revision)
|
||||||
|
|
||||||
# Revisions on the trunk specify deltas that transform a
|
# Revisions on the trunk specify deltas that transform a
|
||||||
# revision into an earlier revision, so invert the translation
|
# revision into an earlier revision, so invert the translation
|
||||||
@@ -555,7 +393,7 @@ class CVSParser:
|
|||||||
# the trunk. They specify deltas that transform a revision
|
# the trunk. They specify deltas that transform a revision
|
||||||
# into a later revision.
|
# into a later revision.
|
||||||
adjust = 0
|
adjust = 0
|
||||||
diffs = string.split(self.revision_deltatext[revision], '\n')
|
diffs = self.deltatext_split(revision)
|
||||||
for command in diffs:
|
for command in diffs:
|
||||||
if skip > 0:
|
if skip > 0:
|
||||||
skip = skip - 1
|
skip = skip - 1
|
||||||
@@ -589,21 +427,24 @@ class CVSParser:
|
|||||||
re_includes = re.compile('\\#(\\s*)include(\\s*)"(.*?)"')
|
re_includes = re.compile('\\#(\\s*)include(\\s*)"(.*?)"')
|
||||||
re_filename = re.compile('(.*[\\\\/])?(.+)')
|
re_filename = re.compile('(.*[\\\\/])?(.+)')
|
||||||
|
|
||||||
def link_includes(text, root, rcs_path):
|
def link_includes(text, root, rcs_path, sticky = None):
|
||||||
match = re_includes.match(text)
|
match = re_includes.match(text)
|
||||||
if match:
|
if match:
|
||||||
incfile = match.group(3)
|
incfile = match.group(3)
|
||||||
for rel_path in ('', 'Attic', '..'):
|
for rel_path in ('', 'Attic', '..'):
|
||||||
trial_root = os.path.join(rcs_path, rel_path)
|
trial_root = os.path.join(rcs_path, rel_path)
|
||||||
file = os.path.normpath('%s%s%s%s%s,v' % (root, path_sep, trial_root, path_sep, incfile))
|
file = os.path.join(root, trial_root)
|
||||||
|
file = os.path.normpath(os.path.join(file, incfile + ',v'))
|
||||||
if os.access(file, os.F_OK):
|
if os.access(file, os.F_OK):
|
||||||
|
url = os.path.join(rel_path, incfile)
|
||||||
|
if sticky:
|
||||||
|
url = url + '?' + sticky
|
||||||
return '#%sinclude%s"<a href="%s">%s</a>"' % \
|
return '#%sinclude%s"<a href="%s">%s</a>"' % \
|
||||||
(match.group(1), match.group(2),
|
(match.group(1), match.group(2), url, incfile)
|
||||||
os.path.join(rel_path, incfile), incfile)
|
|
||||||
return text
|
return text
|
||||||
|
|
||||||
def make_html(root, rcs_path, opt_rev = None):
|
def make_html(root, rcs_path, opt_rev = None, sticky = None):
|
||||||
filename = root + path_sep + rcs_path
|
filename = os.path.join(root, rcs_path)
|
||||||
parser = CVSParser()
|
parser = CVSParser()
|
||||||
revision = parser.parse_cvs_file(filename, opt_rev)
|
revision = parser.parse_cvs_file(filename, opt_rev)
|
||||||
count = len(parser.revision_map)
|
count = len(parser.revision_map)
|
||||||
@@ -649,7 +490,7 @@ def make_html(root, rcs_path, opt_rev = None):
|
|||||||
|
|
||||||
# Add a link to traverse to included files
|
# Add a link to traverse to included files
|
||||||
if 1: # opt_includes
|
if 1: # opt_includes
|
||||||
thisline = link_includes(thisline, root, file_head)
|
thisline = link_includes(thisline, root, file_head, sticky)
|
||||||
|
|
||||||
output = ''
|
output = ''
|
||||||
|
|
||||||
@@ -685,9 +526,11 @@ def make_html(root, rcs_path, opt_rev = None):
|
|||||||
|
|
||||||
if parser.prev_revision.get(revision):
|
if parser.prev_revision.get(revision):
|
||||||
fname = file_tail[:-2] # strip the ",v"
|
fname = file_tail[:-2] # strip the ",v"
|
||||||
### need the sticky options! need cvsroot if not-default
|
url = '%s.diff?r1=%s&r2=%s' % \
|
||||||
output = output + ' <a href="%s.diff?r1=%s&r2=%s"' % \
|
|
||||||
(fname, parser.prev_revision[revision], revision)
|
(fname, parser.prev_revision[revision], revision)
|
||||||
|
if sticky:
|
||||||
|
url = url + '&' + sticky
|
||||||
|
output = output + ' <a href="%s"' % (url, )
|
||||||
if 0: # use_layers
|
if 0: # use_layers
|
||||||
output = output + " onmouseover='return log(event,\"%s\",\"%s\");'" % (
|
output = output + " onmouseover='return log(event,\"%s\",\"%s\");'" % (
|
||||||
parser.prev_revision[revision], revision)
|
parser.prev_revision[revision], revision)
|
||||||
|
160
lib/commit.py
160
lib/commit.py
@@ -1,160 +0,0 @@
|
|||||||
# -*- Mode: python -*-
|
|
||||||
#
|
|
||||||
# Copyright (C) 2000 The ViewCVS Group. All Rights Reserved.
|
|
||||||
#
|
|
||||||
# By using this file, you agree to the terms and conditions set forth in
|
|
||||||
# the LICENSE.html file which can be found at the top level of the ViewCVS
|
|
||||||
# distribution or at http://www.lyra.org/viewcvs/license-1.html.
|
|
||||||
#
|
|
||||||
# Contact information:
|
|
||||||
# Greg Stein, PO Box 760, Palo Alto, CA, 94302
|
|
||||||
# gstein@lyra.org, http://www.lyra.org/viewcvs/
|
|
||||||
#
|
|
||||||
# -----------------------------------------------------------------------
|
|
||||||
#
|
|
||||||
|
|
||||||
import os
|
|
||||||
|
|
||||||
## the Commit class holds data on one commit, the representation is as
|
|
||||||
## close as possible to how it should be committed and retrieved to the
|
|
||||||
## database engine
|
|
||||||
|
|
||||||
class Commit:
|
|
||||||
|
|
||||||
## static constants for type of commit
|
|
||||||
CHANGE = 0
|
|
||||||
ADD = 1
|
|
||||||
REMOVE = 2
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
self.__directory = ''
|
|
||||||
self.__file = ''
|
|
||||||
self.__repository = ''
|
|
||||||
self.__revision = ''
|
|
||||||
self.__author = ''
|
|
||||||
self.__branch = ''
|
|
||||||
self.__pluscount = ''
|
|
||||||
self.__minuscount = ''
|
|
||||||
self.__description = ''
|
|
||||||
self.__gmt_time = 0.0
|
|
||||||
self.__type = Commit.CHANGE
|
|
||||||
|
|
||||||
def SetRepository(self, repository):
|
|
||||||
## clean up repository path; make sure it doesn't end with a
|
|
||||||
## path seperator
|
|
||||||
while repository[-1] == os.sep:
|
|
||||||
repository = repository[:-1]
|
|
||||||
|
|
||||||
self.__repository = repository
|
|
||||||
|
|
||||||
def GetRepository(self):
|
|
||||||
return self.__repository
|
|
||||||
|
|
||||||
def SetDirectory(self, dir):
|
|
||||||
## clean up directory path; make sure it doesn't begin
|
|
||||||
## or end with a path seperator
|
|
||||||
while dir[0] == os.sep:
|
|
||||||
dir = dir[1:]
|
|
||||||
while dir[-1] == os.sep:
|
|
||||||
dir = dir[:-1]
|
|
||||||
|
|
||||||
self.__directory = dir
|
|
||||||
|
|
||||||
def GetDirectory(self):
|
|
||||||
return self.__directory
|
|
||||||
|
|
||||||
def SetFile(self, file):
|
|
||||||
## clean up filename; make sure it doesn't begin
|
|
||||||
## or end with a path seperator
|
|
||||||
while file[0] == os.sep:
|
|
||||||
file = file[1:]
|
|
||||||
while file[-1] == os.sep:
|
|
||||||
file = file[:-1]
|
|
||||||
|
|
||||||
self.__file = file
|
|
||||||
|
|
||||||
def GetFile(self):
|
|
||||||
return self.__file
|
|
||||||
|
|
||||||
def SetRevision(self, revision):
|
|
||||||
self.__revision = revision
|
|
||||||
|
|
||||||
def GetRevision(self):
|
|
||||||
return self.__revision
|
|
||||||
|
|
||||||
def SetTime(self, gmt_time):
|
|
||||||
self.__gmt_time = float(gmt_time)
|
|
||||||
|
|
||||||
def GetTime(self):
|
|
||||||
return self.__gmt_time
|
|
||||||
|
|
||||||
def SetAuthor(self, author):
|
|
||||||
self.__author = author
|
|
||||||
|
|
||||||
def GetAuthor(self):
|
|
||||||
return self.__author
|
|
||||||
|
|
||||||
def SetBranch(self, branch):
|
|
||||||
if not branch:
|
|
||||||
self.__branch = ''
|
|
||||||
else:
|
|
||||||
self.__branch = branch
|
|
||||||
|
|
||||||
def GetBranch(self):
|
|
||||||
return self.__branch
|
|
||||||
|
|
||||||
def SetPlusCount(self, pluscount):
|
|
||||||
self.__pluscount = pluscount
|
|
||||||
|
|
||||||
def GetPlusCount(self):
|
|
||||||
return self.__pluscount
|
|
||||||
|
|
||||||
def SetMinusCount(self, minuscount):
|
|
||||||
self.__minuscount = minuscount
|
|
||||||
|
|
||||||
def GetMinusCount(self):
|
|
||||||
return self.__minuscount
|
|
||||||
|
|
||||||
def SetDescription(self, description):
|
|
||||||
self.__description = description
|
|
||||||
|
|
||||||
def GetDescription(self):
|
|
||||||
return self.__description
|
|
||||||
|
|
||||||
def SetTypeChange(self):
|
|
||||||
self.__type = Commit.CHANGE
|
|
||||||
|
|
||||||
def SetTypeAdd(self):
|
|
||||||
self.__type = Commit.ADD
|
|
||||||
|
|
||||||
def SetTypeRemove(self):
|
|
||||||
self.__type = Commit.REMOVE
|
|
||||||
|
|
||||||
def GetType(self):
|
|
||||||
return self.__type
|
|
||||||
|
|
||||||
def GetTypeString(self):
|
|
||||||
if self.__type == Commit.CHANGE:
|
|
||||||
return 'Change'
|
|
||||||
elif self.__type == Commit.ADD:
|
|
||||||
return 'Add'
|
|
||||||
elif self.__type == Commit.REMOVE:
|
|
||||||
return 'Remove'
|
|
||||||
|
|
||||||
|
|
||||||
## entrypoints
|
|
||||||
|
|
||||||
def CreateCommit():
|
|
||||||
return Commit()
|
|
||||||
|
|
||||||
|
|
||||||
def PrintCommit(commit):
|
|
||||||
print os.path.join(commit.GetDirectory(), commit.GetFile()),\
|
|
||||||
commit.GetRevision(),\
|
|
||||||
commit.GetAuthor()
|
|
||||||
|
|
||||||
if commit.GetBranch():
|
|
||||||
print commit.GetBranch()
|
|
||||||
print commit.GetDescription()
|
|
||||||
print
|
|
||||||
|
|
@@ -1,14 +1,14 @@
|
|||||||
# -*- Mode: python -*-
|
# -*- Mode: python -*-
|
||||||
#
|
#
|
||||||
# Copyright (C) 2000 The ViewCVS Group. All Rights Reserved.
|
# Copyright (C) 2000-2001 The ViewCVS Group. All Rights Reserved.
|
||||||
#
|
#
|
||||||
# By using this file, you agree to the terms and conditions set forth in
|
# By using this file, you agree to the terms and conditions set forth in
|
||||||
# the LICENSE.html file which can be found at the top level of the ViewCVS
|
# the LICENSE.html file which can be found at the top level of the ViewCVS
|
||||||
# distribution or at http://www.lyra.org/viewcvs/license-1.html.
|
# distribution or at http://viewcvs.sourceforge.net/license-1.html.
|
||||||
#
|
#
|
||||||
# Contact information:
|
# Contact information:
|
||||||
# Greg Stein, PO Box 760, Palo Alto, CA, 94302
|
# Greg Stein, PO Box 760, Palo Alto, CA, 94302
|
||||||
# gstein@lyra.org, http://www.lyra.org/viewcvs/
|
# gstein@lyra.org, http://viewcvs.sourceforge.net/
|
||||||
#
|
#
|
||||||
# -----------------------------------------------------------------------
|
# -----------------------------------------------------------------------
|
||||||
#
|
#
|
||||||
@@ -38,21 +38,22 @@ except AttributeError:
|
|||||||
keyvalue = [ ]
|
keyvalue = [ ]
|
||||||
for key, value in dict.items():
|
for key, value in dict.items():
|
||||||
keyvalue.append(quote(key) + '=' + quote(str(value)))
|
keyvalue.append(quote(key) + '=' + quote(str(value)))
|
||||||
return '?' + string.join(keyvalue, '&')
|
return string.join(keyvalue, '&')
|
||||||
|
|
||||||
#
|
#
|
||||||
# time.strptime() is new to Python 1.5.2
|
# time.strptime() is new to Python 1.5.2
|
||||||
#
|
#
|
||||||
if hasattr(time, 'strptime'):
|
if hasattr(time, 'strptime'):
|
||||||
def cvs_strptime(timestr):
|
def cvs_strptime(timestr):
|
||||||
|
'Parse a CVS-style date/time value.'
|
||||||
return time.strptime(timestr, '%Y/%m/%d %H:%M:%S')
|
return time.strptime(timestr, '%Y/%m/%d %H:%M:%S')
|
||||||
else:
|
else:
|
||||||
_re_rev_date = re.compile('([0-9]{4})/([0-9][0-9])/([0-9][0-9]) '
|
_re_rev_date = re.compile('([0-9]{4})/([0-9][0-9])/([0-9][0-9]) '
|
||||||
'([0-9][0-9]):([0-9][0-9]):([0-9][0-9])')
|
'([0-9][0-9]):([0-9][0-9]):([0-9][0-9])')
|
||||||
def cvs_strptime(timestr):
|
def cvs_strptime(timestr):
|
||||||
|
'Parse a CVS-style date/time value.'
|
||||||
matches = _re_rev_date.match(timestr).groups()
|
matches = _re_rev_date.match(timestr).groups()
|
||||||
return tuple(map(int, matches)) + (0, 1, -1)
|
return tuple(map(int, matches)) + (0, 1, 0)
|
||||||
cvs_strptime.__doc__ = 'Parse a CVS-style date/time value.'
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# os.makedirs() is new to Python 1.5.2
|
# os.makedirs() is new to Python 1.5.2
|
||||||
|
119
lib/config.py
119
lib/config.py
@@ -1,14 +1,14 @@
|
|||||||
# -*- Mode: python -*-
|
# -*- Mode: python -*-
|
||||||
#
|
#
|
||||||
# Copyright (C) 2000 The ViewCVS Group. All Rights Reserved.
|
# Copyright (C) 2000-2001 The ViewCVS Group. All Rights Reserved.
|
||||||
#
|
#
|
||||||
# By using this file, you agree to the terms and conditions set forth in
|
# By using this file, you agree to the terms and conditions set forth in
|
||||||
# the LICENSE.html file which can be found at the top level of the ViewCVS
|
# the LICENSE.html file which can be found at the top level of the ViewCVS
|
||||||
# distribution or at http://www.lyra.org/viewcvs/license-1.html.
|
# distribution or at http://viewcvs.sourceforge.net/license-1.html.
|
||||||
#
|
#
|
||||||
# Contact information:
|
# Contact information:
|
||||||
# Greg Stein, PO Box 760, Palo Alto, CA, 94302
|
# Greg Stein, PO Box 760, Palo Alto, CA, 94302
|
||||||
# gstein@lyra.org, http://www.lyra.org/viewcvs/
|
# gstein@lyra.org, http://viewcvs.sourceforge.net/
|
||||||
#
|
#
|
||||||
# -----------------------------------------------------------------------
|
# -----------------------------------------------------------------------
|
||||||
#
|
#
|
||||||
@@ -30,7 +30,7 @@ import fnmatch
|
|||||||
#
|
#
|
||||||
# There are three forms of configuration:
|
# There are three forms of configuration:
|
||||||
#
|
#
|
||||||
# 1) copy viewcvs.conf.dist to viewcvs.conf and edit
|
# 1) edit the viewcvs.conf created by the viewcvs-install(er)
|
||||||
# 2) as (1), but delete all unchanged entries from viewcvs.conf
|
# 2) as (1), but delete all unchanged entries from viewcvs.conf
|
||||||
# 3) do not use viewcvs.conf and just edit the defaults in this file
|
# 3) do not use viewcvs.conf and just edit the defaults in this file
|
||||||
#
|
#
|
||||||
@@ -41,9 +41,8 @@ import fnmatch
|
|||||||
#########################################################################
|
#########################################################################
|
||||||
|
|
||||||
class Config:
|
class Config:
|
||||||
_sections = ('general', 'images', 'options', 'colors', 'text', 'cvsdb')
|
_sections = ('general', 'images', 'options', 'colors', 'cvsdb', 'templates')
|
||||||
_force_multi_value = ('cvs_roots', 'forbidden', 'even_odd',
|
_force_multi_value = ('cvs_roots', 'forbidden', 'disable_enscript_lang')
|
||||||
'disable_enscript_lang')
|
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
for section in self._sections:
|
for section in self._sections:
|
||||||
@@ -59,7 +58,7 @@ class Config:
|
|||||||
if parser.has_section(section):
|
if parser.has_section(section):
|
||||||
self._process_section(parser, section, section)
|
self._process_section(parser, section, section)
|
||||||
|
|
||||||
if vhost:
|
if vhost and parser.has_section('vhosts'):
|
||||||
self._process_vhost(parser, vhost)
|
self._process_vhost(parser, vhost)
|
||||||
|
|
||||||
def _process_section(self, parser, section, subcfg_name):
|
def _process_section(self, parser, section, subcfg_name):
|
||||||
@@ -118,10 +117,14 @@ class Config:
|
|||||||
self.general.default_root = "Development"
|
self.general.default_root = "Development"
|
||||||
self.general.rcs_path = ''
|
self.general.rcs_path = ''
|
||||||
self.general.mime_types_file = ''
|
self.general.mime_types_file = ''
|
||||||
self.general.address = '<a href="mailto:gstein@lyra.org">gstein@lyra.org</a>'
|
self.general.address = '<a href="mailto:user@insert.your.domain.here">No CVS admin address has been configured</a>'
|
||||||
self.general.main_title = 'CVS Repository'
|
self.general.main_title = 'CVS Repository'
|
||||||
self.general.forbidden = ()
|
self.general.forbidden = ()
|
||||||
|
|
||||||
|
self.templates.directory = 'templates/directory.ezt'
|
||||||
|
self.templates.log = 'templates/log.ezt'
|
||||||
|
self.templates.query = 'templates/query.ezt'
|
||||||
|
|
||||||
self.cvsdb.enabled = 0
|
self.cvsdb.enabled = 0
|
||||||
self.cvsdb.host = ''
|
self.cvsdb.host = ''
|
||||||
self.cvsdb.database_name = ''
|
self.cvsdb.database_name = ''
|
||||||
@@ -129,8 +132,8 @@ class Config:
|
|||||||
self.cvsdb.passwd = ''
|
self.cvsdb.passwd = ''
|
||||||
self.cvsdb.readonly_user = ''
|
self.cvsdb.readonly_user = ''
|
||||||
self.cvsdb.readonly_passwd = ''
|
self.cvsdb.readonly_passwd = ''
|
||||||
|
self.cvsdb.row_limit = 1000
|
||||||
|
|
||||||
self.images.logo = "/icons/apache_pb.gif", 259, 32
|
|
||||||
self.images.back_icon = "/icons/small/back.gif", 16, 16
|
self.images.back_icon = "/icons/small/back.gif", 16, 16
|
||||||
self.images.dir_icon = "/icons/small/dir.gif", 16, 16
|
self.images.dir_icon = "/icons/small/dir.gif", 16, 16
|
||||||
self.images.file_icon = "/icons/small/text.gif", 16, 16
|
self.images.file_icon = "/icons/small/text.gif", 16, 16
|
||||||
@@ -139,12 +142,11 @@ class Config:
|
|||||||
|
|
||||||
self.colors.diff_heading = "#99cccc"
|
self.colors.diff_heading = "#99cccc"
|
||||||
self.colors.diff_empty = "#cccccc"
|
self.colors.diff_empty = "#cccccc"
|
||||||
self.colors.diff_remove = "#ff9999"
|
# trafic light methaphor:
|
||||||
self.colors.diff_change = "#99ff99"
|
self.colors.diff_remove = "#ffaaaa" # red
|
||||||
self.colors.diff_add = "#ccccff"
|
self.colors.diff_change = "#ffff77" # yellow/orange
|
||||||
self.colors.diff_dark_change = "#99cc99"
|
self.colors.diff_add = "#aaffaa" # green
|
||||||
|
self.colors.diff_dark_change = "#eeee77" # meets hue of diff_change
|
||||||
self.colors.even_odd = ("#ccccee", "#ffffff")
|
|
||||||
|
|
||||||
self.colors.nav_header = "#9999ee"
|
self.colors.nav_header = "#9999ee"
|
||||||
|
|
||||||
@@ -152,11 +154,6 @@ class Config:
|
|||||||
self.colors.background = "#ffffff"
|
self.colors.background = "#ffffff"
|
||||||
self.colors.alt_background = "#eeeeee"
|
self.colors.alt_background = "#eeeeee"
|
||||||
|
|
||||||
self.colors.column_header_normal = "#cccccc"
|
|
||||||
self.colors.column_header_sorted = "#88ff88"
|
|
||||||
|
|
||||||
self.colors.table_border = None # no border
|
|
||||||
|
|
||||||
self.options.sort_by = 'file'
|
self.options.sort_by = 'file'
|
||||||
self.options.hide_attic = 1
|
self.options.hide_attic = 1
|
||||||
self.options.log_sort = 'date'
|
self.options.log_sort = 'date'
|
||||||
@@ -168,88 +165,27 @@ class Config:
|
|||||||
self.options.hr_funout = 1
|
self.options.hr_funout = 1
|
||||||
self.options.hr_ignore_white = 1
|
self.options.hr_ignore_white = 1
|
||||||
self.options.hr_ignore_keyword_subst = 1
|
self.options.hr_ignore_keyword_subst = 1
|
||||||
self.options.allow_annotate = 0 ### doesn't work yet!
|
self.options.allow_annotate = 1
|
||||||
self.options.allow_markup = 1
|
self.options.allow_markup = 1
|
||||||
self.options.allow_compress = 1
|
self.options.allow_compress = 1
|
||||||
self.options.use_java_script = 1
|
self.options.use_java_script = 1
|
||||||
self.options.open_extern_window = 1
|
self.options.open_extern_window = 1
|
||||||
self.options.extern_window_width = 600
|
|
||||||
self.options.extern_window_height = 440
|
|
||||||
self.options.checkout_magic = 1
|
self.options.checkout_magic = 1
|
||||||
self.options.show_subdir_lastmod = 0
|
self.options.show_subdir_lastmod = 0
|
||||||
|
self.options.flip_links_in_dirview = 0
|
||||||
self.options.show_logs = 1
|
self.options.show_logs = 1
|
||||||
self.options.show_log_in_markup = 1
|
self.options.show_log_in_markup = 1
|
||||||
self.options.allow_version_select = 1
|
|
||||||
self.options.py2html_path = '.'
|
self.options.py2html_path = '.'
|
||||||
self.options.short_log_len = 80
|
self.options.short_log_len = 80
|
||||||
self.options.table_padding = 2
|
|
||||||
self.options.diff_font_face = 'Helvetica,Arial'
|
self.options.diff_font_face = 'Helvetica,Arial'
|
||||||
self.options.diff_font_size = -1
|
self.options.diff_font_size = -1
|
||||||
self.options.input_text_size = 12
|
|
||||||
self.options.use_enscript = 0
|
self.options.use_enscript = 0
|
||||||
self.options.enscript_path = ''
|
self.options.enscript_path = ''
|
||||||
self.options.disable_enscript_lang = ()
|
self.options.disable_enscript_lang = ()
|
||||||
|
self.options.allow_tar = 0
|
||||||
self.text.long_intro = """\
|
self.options.use_cvsgraph = 0
|
||||||
<p>
|
self.options.cvsgraph_path = ''
|
||||||
This is a WWW interface for CVS Repositories.
|
self.options.cvsgraph_conf = "<VIEWCVS_INSTALL_DIRECTORY>/cvsgraph.conf"
|
||||||
You can browse the file hierarchy by picking directories
|
|
||||||
(which have slashes after them, <i>e.g.</i>, <b>src/</b>).
|
|
||||||
If you pick a file, you will see the revision history
|
|
||||||
for that file.
|
|
||||||
Selecting a revision number will download that revision of
|
|
||||||
the file. There is a link at each revision to display
|
|
||||||
diffs between that revision and the previous one, and
|
|
||||||
a form at the bottom of the page that allows you to
|
|
||||||
display diffs between arbitrary revisions.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
This script
|
|
||||||
(<a href="http://www.lyra.org/viewcvs/">ViewCVS</a>)
|
|
||||||
has been written by Greg Stein
|
|
||||||
<<a href="mailto:gstein@lyra.org">gstein@lyra.org</a>>
|
|
||||||
based on the
|
|
||||||
<a href="http://linux.fh-heilbronn.de/~zeller/cgi/cvsweb.cgi">cvsweb</a>
|
|
||||||
script by Henner Zeller
|
|
||||||
<<a href="mailto:zeller@think.de">zeller@think.de</a>>;
|
|
||||||
it is covered by the
|
|
||||||
<a href="http://www.opensource.org/licenses/bsd-license.html">BSD-License</a>.
|
|
||||||
If you would like to use this CGI script on your own web server and
|
|
||||||
CVS tree, see Greg's
|
|
||||||
<a href="http://www.lyra.org/viewcvs/">ViewCVS distribution
|
|
||||||
site</a>.
|
|
||||||
Please send any suggestions, comments, etc. to
|
|
||||||
<a href="mailto:gstein@lyra.org">Greg Stein</a>.
|
|
||||||
</p>
|
|
||||||
"""
|
|
||||||
# ' stupid emacs...
|
|
||||||
|
|
||||||
self.text.doc_info = """
|
|
||||||
<h3>CVS Documentation</h3>
|
|
||||||
<blockquote>
|
|
||||||
<p>
|
|
||||||
<a href="http://www.loria.fr/~molli/cvs/doc/cvs_toc.html">CVS
|
|
||||||
User's Guide</a><br>
|
|
||||||
<a href="http://www.arc.unm.edu/~rsahu/cvs.html">CVS Tutorial</a><br>
|
|
||||||
<a href="http://cellworks.washington.edu/pub/docs/cvs/tutorial/cvs_tutorial_1.html">Another CVS tutorial</a><br>
|
|
||||||
<a href="http://www.csc.calpoly.edu/~dbutler/tutorials/winter96/cvs/">Yet another CVS tutorial (a little old, but nice)</a><br>
|
|
||||||
<a href="http://www.cs.utah.edu/dept/old/texinfo/cvs/FAQ.txt">An old but very useful FAQ about CVS</a>
|
|
||||||
</p>
|
|
||||||
</blockquote>
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Fill in stuff on (say) anonymous pserver access here. For example, what
|
|
||||||
# access mechanism, login, path, etc should be used.
|
|
||||||
self.text.repository_info = """
|
|
||||||
<!-- insert repository access instructions here -->
|
|
||||||
"""
|
|
||||||
|
|
||||||
self.text.short_intro = """\
|
|
||||||
<p>
|
|
||||||
Click on a directory to enter that directory. Click on a file to display
|
|
||||||
its revision history and to get a chance to display diffs between revisions.
|
|
||||||
</p>
|
|
||||||
"""
|
|
||||||
|
|
||||||
def is_forbidden(self, module):
|
def is_forbidden(self, module):
|
||||||
if not module:
|
if not module:
|
||||||
@@ -272,3 +208,10 @@ class _sub_config:
|
|||||||
return '<img src="%s" alt="%s" border=0 width=%s height=%s>' % \
|
return '<img src="%s" alt="%s" border=0 width=%s height=%s>' % \
|
||||||
(path, text, width, height)
|
(path, text, width, height)
|
||||||
return text
|
return text
|
||||||
|
|
||||||
|
if not hasattr(sys, 'hexversion'):
|
||||||
|
# Python 1.5 or 1.5.1. fix the syntax for ConfigParser options.
|
||||||
|
import regex
|
||||||
|
ConfigParser.option_cre = regex.compile('^\([-A-Za-z0-9._]+\)\(:\|['
|
||||||
|
+ string.whitespace
|
||||||
|
+ ']*=\)\(.*\)$')
|
||||||
|
753
lib/cvsdb.py
Normal file
753
lib/cvsdb.py
Normal file
@@ -0,0 +1,753 @@
|
|||||||
|
# -*- Mode: python -*-
|
||||||
|
#
|
||||||
|
# Copyright (C) 2000 The ViewCVS Group. All Rights Reserved.
|
||||||
|
#
|
||||||
|
# By using this file, you agree to the terms and conditions set forth in
|
||||||
|
# the LICENSE.html file which can be found at the top level of the ViewCVS
|
||||||
|
# distribution or at http://www.lyra.org/viewcvs/license-1.html.
|
||||||
|
#
|
||||||
|
# Contact information:
|
||||||
|
# Greg Stein, PO Box 760, Palo Alto, CA, 94302
|
||||||
|
# gstein@lyra.org, http://www.lyra.org/viewcvs/
|
||||||
|
#
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
#
|
||||||
|
|
||||||
|
#########################################################################
|
||||||
|
#
|
||||||
|
# INSTALL-TIME CONFIGURATION
|
||||||
|
#
|
||||||
|
# These values will be set during the installation process. During
|
||||||
|
# development, they will remain None.
|
||||||
|
#
|
||||||
|
|
||||||
|
CONF_PATHNAME = None
|
||||||
|
|
||||||
|
#########################################################################
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import string
|
||||||
|
import time
|
||||||
|
|
||||||
|
import config
|
||||||
|
import dbi
|
||||||
|
import rlog
|
||||||
|
|
||||||
|
|
||||||
|
## load configuration file, the data is used globally here
|
||||||
|
if CONF_PATHNAME:
|
||||||
|
_cfg_pathname = CONF_PATHNAME
|
||||||
|
else:
|
||||||
|
# developer assistance: running from a CVS working copy
|
||||||
|
_cfg_pathname = os.path.join(os.path.dirname(__file__), os.pardir, 'cgi',
|
||||||
|
'viewcvs.conf')
|
||||||
|
cfg = config.Config()
|
||||||
|
cfg.set_defaults()
|
||||||
|
cfg.load_config(_cfg_pathname)
|
||||||
|
|
||||||
|
## error
|
||||||
|
error = "cvsdb error"
|
||||||
|
|
||||||
|
## cached (active) database connections
|
||||||
|
gCheckinDatabase = None
|
||||||
|
gCheckinDatabaseReadOnly = None
|
||||||
|
|
||||||
|
|
||||||
|
## CheckinDatabase provides all interfaces needed to the SQL database
|
||||||
|
## back-end; it needs to be subclassed, and have its "Connect" method
|
||||||
|
## defined to actually be complete; it should run well off of any DBI 2.0
|
||||||
|
## complient database interface
|
||||||
|
|
||||||
|
class CheckinDatabase:
|
||||||
|
def __init__(self, host, user, passwd, database):
|
||||||
|
self._host = host
|
||||||
|
self._user = user
|
||||||
|
self._passwd = passwd
|
||||||
|
self._database = database
|
||||||
|
|
||||||
|
## database lookup caches
|
||||||
|
self._get_cache = {}
|
||||||
|
self._get_id_cache = {}
|
||||||
|
self._desc_id_cache = {}
|
||||||
|
|
||||||
|
def Connect(self):
|
||||||
|
self.db = dbi.connect(
|
||||||
|
self._host, self._user, self._passwd, self._database)
|
||||||
|
|
||||||
|
def sql_get_id(self, table, column, value, auto_set):
|
||||||
|
sql = "SELECT id FROM %s WHERE %s=%%s" % (table, column)
|
||||||
|
sql_args = (value, )
|
||||||
|
|
||||||
|
cursor = self.db.cursor()
|
||||||
|
cursor.execute(sql, sql_args)
|
||||||
|
try:
|
||||||
|
(id, ) = cursor.fetchone()
|
||||||
|
except TypeError:
|
||||||
|
if not auto_set:
|
||||||
|
return None
|
||||||
|
else:
|
||||||
|
return str(int(id))
|
||||||
|
|
||||||
|
## insert the new identifier
|
||||||
|
sql = "INSERT INTO %s(%s) VALUES(%%s)" % (table, column)
|
||||||
|
sql_args = (value, )
|
||||||
|
cursor.execute(sql, sql_args)
|
||||||
|
|
||||||
|
return self.sql_get_id(table, column, value, 0)
|
||||||
|
|
||||||
|
def get_id(self, table, column, value, auto_set):
|
||||||
|
## attempt to retrieve from cache
|
||||||
|
try:
|
||||||
|
return self._get_id_cache[table][column][value]
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
id = self.sql_get_id(table, column, value, auto_set)
|
||||||
|
if id == None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
## add to cache
|
||||||
|
try:
|
||||||
|
temp = self._get_id_cache[table]
|
||||||
|
except KeyError:
|
||||||
|
temp = self._get_id_cache[table] = {}
|
||||||
|
|
||||||
|
try:
|
||||||
|
temp2 = temp[column]
|
||||||
|
except KeyError:
|
||||||
|
temp2 = temp[column] = {}
|
||||||
|
|
||||||
|
temp2[value] = id
|
||||||
|
return id
|
||||||
|
|
||||||
|
def sql_get(self, table, column, id):
|
||||||
|
sql = "SELECT %s FROM %s WHERE id=%%s" % (column, table)
|
||||||
|
sql_args = (id, )
|
||||||
|
|
||||||
|
cursor = self.db.cursor()
|
||||||
|
cursor.execute(sql, sql_args)
|
||||||
|
try:
|
||||||
|
(value, ) = cursor.fetchone()
|
||||||
|
except TypeError:
|
||||||
|
return None
|
||||||
|
|
||||||
|
return value
|
||||||
|
|
||||||
|
def get(self, table, column, id):
|
||||||
|
## attempt to retrieve from cache
|
||||||
|
try:
|
||||||
|
return self._get_cache[table][column][id]
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
value = self.sql_get(table, column, id)
|
||||||
|
if value == None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
## add to cache
|
||||||
|
try:
|
||||||
|
temp = self._get_cache[table]
|
||||||
|
except KeyError:
|
||||||
|
temp = self._get_cache[table] = {}
|
||||||
|
|
||||||
|
try:
|
||||||
|
temp2 = temp[column]
|
||||||
|
except KeyError:
|
||||||
|
temp2 = temp[column] = {}
|
||||||
|
|
||||||
|
temp2[id] = value
|
||||||
|
return value
|
||||||
|
|
||||||
|
def get_list(self, table, field_index):
|
||||||
|
sql = "SELECT * FROM %s" % (table)
|
||||||
|
cursor = self.db.cursor()
|
||||||
|
cursor.execute(sql)
|
||||||
|
|
||||||
|
list = []
|
||||||
|
while 1:
|
||||||
|
row = cursor.fetchone()
|
||||||
|
if row == None:
|
||||||
|
break
|
||||||
|
list.append(row[field_index])
|
||||||
|
|
||||||
|
return list
|
||||||
|
|
||||||
|
def GetBranchID(self, branch, auto_set = 1):
|
||||||
|
return self.get_id("branches", "branch", branch, auto_set)
|
||||||
|
|
||||||
|
def GetBranch(self, id):
|
||||||
|
return self.get("branches", "branch", id)
|
||||||
|
|
||||||
|
def GetDirectoryID(self, dir, auto_set = 1):
|
||||||
|
return self.get_id("dirs", "dir", dir, auto_set)
|
||||||
|
|
||||||
|
def GetDirectory(self, id):
|
||||||
|
return self.get("dirs", "dir", id)
|
||||||
|
|
||||||
|
def GetFileID(self, file, auto_set = 1):
|
||||||
|
return self.get_id("files", "file", file, auto_set)
|
||||||
|
|
||||||
|
def GetFile(self, id):
|
||||||
|
return self.get("files", "file", id)
|
||||||
|
|
||||||
|
def GetAuthorID(self, author, auto_set = 1):
|
||||||
|
return self.get_id("people", "who", author, auto_set)
|
||||||
|
|
||||||
|
def GetAuthor(self, id):
|
||||||
|
return self.get("people", "who", id)
|
||||||
|
|
||||||
|
def GetRepositoryID(self, repository, auto_set = 1):
|
||||||
|
return self.get_id("repositories", "repository", repository, auto_set)
|
||||||
|
|
||||||
|
def GetRepository(self, id):
|
||||||
|
return self.get("repositories", "repository", id)
|
||||||
|
|
||||||
|
def SQLGetDescriptionID(self, description, auto_set = 1):
|
||||||
|
## lame string hash, blame Netscape -JMP
|
||||||
|
hash = len(description)
|
||||||
|
|
||||||
|
sql = "SELECT id FROM descs WHERE hash=%s AND description=%s"
|
||||||
|
sql_args = (hash, description)
|
||||||
|
|
||||||
|
cursor = self.db.cursor()
|
||||||
|
cursor.execute(sql, sql_args)
|
||||||
|
try:
|
||||||
|
(id, ) = cursor.fetchone()
|
||||||
|
except TypeError:
|
||||||
|
if not auto_set:
|
||||||
|
return None
|
||||||
|
else:
|
||||||
|
return str(int(id))
|
||||||
|
|
||||||
|
sql = "INSERT INTO descs (hash,description) values (%s,%s)"
|
||||||
|
sql_args = (hash, description)
|
||||||
|
cursor.execute(sql, sql_args)
|
||||||
|
|
||||||
|
return self.GetDescriptionID(description, 0)
|
||||||
|
|
||||||
|
def GetDescriptionID(self, description, auto_set = 1):
|
||||||
|
## attempt to retrieve from cache
|
||||||
|
hash = len(description)
|
||||||
|
try:
|
||||||
|
return self._desc_id_cache[hash][description]
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
id = self.SQLGetDescriptionID(description, auto_set)
|
||||||
|
if id == None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
## add to cache
|
||||||
|
try:
|
||||||
|
temp = self._desc_id_cache[hash]
|
||||||
|
except KeyError:
|
||||||
|
temp = self._desc_id_cache[hash] = {}
|
||||||
|
|
||||||
|
temp[description] = id
|
||||||
|
return id
|
||||||
|
|
||||||
|
def GetDescription(self, id):
|
||||||
|
return self.get("descs", "description", id)
|
||||||
|
|
||||||
|
def GetRepositoryList(self):
|
||||||
|
return self.get_list("repositories", 1)
|
||||||
|
|
||||||
|
def GetBranchList(self):
|
||||||
|
return self.get_list("branches", 1)
|
||||||
|
|
||||||
|
def GetAuthorList(self):
|
||||||
|
return self.get_list("people", 1)
|
||||||
|
|
||||||
|
def AddCommitList(self, commit_list):
|
||||||
|
for commit in commit_list:
|
||||||
|
self.AddCommit(commit)
|
||||||
|
|
||||||
|
def AddCommit(self, commit):
|
||||||
|
## MORE TIME HELL: the MySQLdb module doesn't construct times
|
||||||
|
## correctly when created with TimestampFromTicks -- it doesn't
|
||||||
|
## account for daylight savings time, so we use Python's time
|
||||||
|
## module to do the conversion
|
||||||
|
temp = time.localtime(commit.GetTime())
|
||||||
|
ci_when = dbi.Timestamp(
|
||||||
|
temp[0], temp[1], temp[2], temp[3], temp[4], temp[5])
|
||||||
|
|
||||||
|
ci_type = commit.GetTypeString()
|
||||||
|
who_id = self.GetAuthorID(commit.GetAuthor())
|
||||||
|
repository_id = self.GetRepositoryID(commit.GetRepository())
|
||||||
|
directory_id = self.GetDirectoryID(commit.GetDirectory())
|
||||||
|
file_id = self.GetFileID(commit.GetFile())
|
||||||
|
revision = commit.GetRevision()
|
||||||
|
sticky_tag = "NULL"
|
||||||
|
branch_id = self.GetBranchID(commit.GetBranch())
|
||||||
|
plus_count = commit.GetPlusCount()
|
||||||
|
minus_count = commit.GetMinusCount()
|
||||||
|
description_id = self.GetDescriptionID(commit.GetDescription())
|
||||||
|
|
||||||
|
sql = "REPLACE INTO checkins"\
|
||||||
|
" (type,ci_when,whoid,repositoryid,dirid,fileid,revision,"\
|
||||||
|
" stickytag,branchid,addedlines,removedlines,descid)"\
|
||||||
|
"VALUES(%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)"
|
||||||
|
sql_args = (ci_type, ci_when, who_id, repository_id,
|
||||||
|
directory_id, file_id, revision, sticky_tag, branch_id,
|
||||||
|
plus_count, minus_count, description_id)
|
||||||
|
|
||||||
|
cursor = self.db.cursor()
|
||||||
|
cursor.execute(sql, sql_args)
|
||||||
|
|
||||||
|
def SQLQueryListString(self, sqlString, query_entry_list):
|
||||||
|
sqlList = []
|
||||||
|
|
||||||
|
for query_entry in query_entry_list:
|
||||||
|
## figure out the correct match type
|
||||||
|
if query_entry.match == "exact":
|
||||||
|
match = "="
|
||||||
|
elif query_entry.match == "like":
|
||||||
|
match = " LIKE "
|
||||||
|
elif query_entry.match == "regex":
|
||||||
|
match = " REGEXP "
|
||||||
|
|
||||||
|
sqlList.append(sqlString % (match, query_entry.data))
|
||||||
|
|
||||||
|
return "(%s)" % (string.join(sqlList, " OR "))
|
||||||
|
|
||||||
|
def CreateSQLQueryString(self, query):
|
||||||
|
tableList = ["checkins"]
|
||||||
|
condList = []
|
||||||
|
|
||||||
|
## XXX: this is to exclude .ver files -- RN specific hack --JMP
|
||||||
|
tableList.append("files")
|
||||||
|
temp = "(checkins.fileid=files.id AND files.file NOT LIKE \"%.ver\")"
|
||||||
|
condList.append(temp)
|
||||||
|
## XXX
|
||||||
|
|
||||||
|
if len(query.repository_list):
|
||||||
|
tableList.append("repositories")
|
||||||
|
|
||||||
|
sql = "(checkins.repositoryid=repositories.id AND "\
|
||||||
|
"repositories.repository%s\"%s\")"
|
||||||
|
temp = self.SQLQueryListString(sql, query.repository_list)
|
||||||
|
condList.append(temp)
|
||||||
|
|
||||||
|
if len(query.branch_list):
|
||||||
|
tableList.append("branches")
|
||||||
|
|
||||||
|
sql = "(checkins.branchid=branches.id AND "\
|
||||||
|
"branches.branch%s\"%s\")"
|
||||||
|
temp = self.SQLQueryListString(sql, query.branch_list)
|
||||||
|
condList.append(temp)
|
||||||
|
|
||||||
|
if len(query.directory_list):
|
||||||
|
tableList.append("dirs")
|
||||||
|
|
||||||
|
sql = "(checkins.dirid=dirs.id AND dirs.dir%s\"%s\")"
|
||||||
|
temp = self.SQLQueryListString(sql, query.directory_list)
|
||||||
|
condList.append(temp)
|
||||||
|
|
||||||
|
if len(query.file_list):
|
||||||
|
tableList.append("files")
|
||||||
|
|
||||||
|
sql = "(checkins.fileid=files.id AND files.file%s\"%s\")"
|
||||||
|
temp = self.SQLQueryListString(sql, query.file_list)
|
||||||
|
condList.append(temp)
|
||||||
|
|
||||||
|
if len(query.author_list):
|
||||||
|
tableList.append("people")
|
||||||
|
|
||||||
|
sql = "(checkins.whoid=people.id AND people.who%s\"%s\")"
|
||||||
|
temp = self.SQLQueryListString(sql, query.author_list)
|
||||||
|
condList.append(temp)
|
||||||
|
|
||||||
|
if query.from_date:
|
||||||
|
temp = "(checkins.ci_when>=\"%s\")" % (str(query.from_date))
|
||||||
|
condList.append(temp)
|
||||||
|
|
||||||
|
if query.to_date:
|
||||||
|
temp = "(checkins.ci_when<=\"%s\")" % (str(query.to_date))
|
||||||
|
condList.append(temp)
|
||||||
|
|
||||||
|
if query.sort == "date":
|
||||||
|
order_by = "ORDER BY checkins.ci_when DESC"
|
||||||
|
elif query.sort == "author":
|
||||||
|
order_by = "ORDER BY checkins.whoid"
|
||||||
|
elif query.sort == "file":
|
||||||
|
order_by = "ORDER BY checkins.fileid"
|
||||||
|
|
||||||
|
## exclude duplicates from the table list
|
||||||
|
for table in tableList[:]:
|
||||||
|
while tableList.count(table) > 1:
|
||||||
|
tableList.remove(table)
|
||||||
|
|
||||||
|
tables = string.join(tableList, ",")
|
||||||
|
conditions = string.join(condList, " AND ")
|
||||||
|
|
||||||
|
## limit the number of rows requested or we could really slam
|
||||||
|
## a server with a large database
|
||||||
|
limit = ""
|
||||||
|
if cfg.cvsdb.row_limit:
|
||||||
|
limit = "LIMIT %s" % (str(cfg.cvsdb.row_limit))
|
||||||
|
|
||||||
|
sql = "SELECT checkins.* FROM %s WHERE %s %s %s" % (
|
||||||
|
tables, conditions, order_by, limit)
|
||||||
|
|
||||||
|
return sql
|
||||||
|
|
||||||
|
def RunQuery(self, query):
|
||||||
|
sql = self.CreateSQLQueryString(query)
|
||||||
|
cursor = self.db.cursor()
|
||||||
|
cursor.execute(sql)
|
||||||
|
|
||||||
|
while 1:
|
||||||
|
row = cursor.fetchone()
|
||||||
|
if not row:
|
||||||
|
break
|
||||||
|
|
||||||
|
(dbType, dbCI_When, dbAuthorID, dbRepositoryID, dbDirID,
|
||||||
|
dbFileID, dbRevision, dbStickyTag, dbBranchID, dbAddedLines,
|
||||||
|
dbRemovedLines, dbDescID) = row
|
||||||
|
|
||||||
|
commit = CreateCommit()
|
||||||
|
|
||||||
|
## TIME, TIME, TIME is all fucked up; dateobject.gmticks()
|
||||||
|
## is broken, dateobject.ticks() returns somthing like
|
||||||
|
## GMT ticks, except it forgets about daylight savings
|
||||||
|
## time -- we handle it ourself in the following painful way
|
||||||
|
gmt_time = time.mktime(
|
||||||
|
(dbCI_When.year, dbCI_When.month, dbCI_When.day,
|
||||||
|
dbCI_When.hour, dbCI_When.minute, dbCI_When.second,
|
||||||
|
0, 0, dbCI_When.dst))
|
||||||
|
|
||||||
|
commit.SetTime(gmt_time)
|
||||||
|
|
||||||
|
commit.SetFile(self.GetFile(dbFileID))
|
||||||
|
commit.SetDirectory(self.GetDirectory(dbDirID))
|
||||||
|
commit.SetRevision(dbRevision)
|
||||||
|
commit.SetRepository(self.GetRepository(dbRepositoryID))
|
||||||
|
commit.SetAuthor(self.GetAuthor(dbAuthorID))
|
||||||
|
commit.SetBranch(self.GetBranch(dbBranchID))
|
||||||
|
commit.SetPlusCount(dbAddedLines)
|
||||||
|
commit.SetMinusCount(dbRemovedLines)
|
||||||
|
commit.SetDescription(self.GetDescription(dbDescID))
|
||||||
|
|
||||||
|
query.AddCommit(commit)
|
||||||
|
|
||||||
|
def CheckCommit(self, commit):
|
||||||
|
repository_id = self.GetRepositoryID(commit.GetRepository(), 0)
|
||||||
|
if repository_id == None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
dir_id = self.GetDirectoryID(commit.GetDirectory(), 0)
|
||||||
|
if dir_id == None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
file_id = self.GetFileID(commit.GetFile(), 0)
|
||||||
|
if file_id == None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
sql = "SELECT * FROM checkins WHERE "\
|
||||||
|
" repositoryid=%s AND dirid=%s AND fileid=%s AND revision=%s"
|
||||||
|
sql_args = (repository_id, dir_id, file_id, commit.GetRevision())
|
||||||
|
|
||||||
|
cursor = self.db.cursor()
|
||||||
|
cursor.execute(sql, sql_args)
|
||||||
|
try:
|
||||||
|
(ci_type, ci_when, who_id, repository_id,
|
||||||
|
dir_id, file_id, revision, sticky_tag, branch_id,
|
||||||
|
plus_count, minus_count, description_id) = cursor.fetchone()
|
||||||
|
except TypeError:
|
||||||
|
return None
|
||||||
|
|
||||||
|
return commit
|
||||||
|
|
||||||
|
## the Commit class holds data on one commit, the representation is as
|
||||||
|
## close as possible to how it should be committed and retrieved to the
|
||||||
|
## database engine
|
||||||
|
class Commit:
|
||||||
|
## static constants for type of commit
|
||||||
|
CHANGE = 0
|
||||||
|
ADD = 1
|
||||||
|
REMOVE = 2
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.__directory = ''
|
||||||
|
self.__file = ''
|
||||||
|
self.__repository = ''
|
||||||
|
self.__revision = ''
|
||||||
|
self.__author = ''
|
||||||
|
self.__branch = ''
|
||||||
|
self.__pluscount = ''
|
||||||
|
self.__minuscount = ''
|
||||||
|
self.__description = ''
|
||||||
|
self.__gmt_time = 0.0
|
||||||
|
self.__type = Commit.CHANGE
|
||||||
|
|
||||||
|
def SetRepository(self, repository):
|
||||||
|
## clean up repository path; make sure it doesn't end with a
|
||||||
|
## path seperator
|
||||||
|
while len(repository) and repository[-1] == os.sep:
|
||||||
|
repository = repository[:-1]
|
||||||
|
|
||||||
|
self.__repository = repository
|
||||||
|
|
||||||
|
def GetRepository(self):
|
||||||
|
return self.__repository
|
||||||
|
|
||||||
|
def SetDirectory(self, dir):
|
||||||
|
## clean up directory path; make sure it doesn't begin
|
||||||
|
## or end with a path seperator
|
||||||
|
while len(dir) and dir[0] == os.sep:
|
||||||
|
dir = dir[1:]
|
||||||
|
while len(dir) and dir[-1] == os.sep:
|
||||||
|
dir = dir[:-1]
|
||||||
|
|
||||||
|
self.__directory = dir
|
||||||
|
|
||||||
|
def GetDirectory(self):
|
||||||
|
return self.__directory
|
||||||
|
|
||||||
|
def SetFile(self, file):
|
||||||
|
## clean up filename; make sure it doesn't begin
|
||||||
|
## or end with a path seperator
|
||||||
|
while len(file) and file[0] == os.sep:
|
||||||
|
file = file[1:]
|
||||||
|
while len(file) and file[-1] == os.sep:
|
||||||
|
file = file[:-1]
|
||||||
|
|
||||||
|
self.__file = file
|
||||||
|
|
||||||
|
def GetFile(self):
|
||||||
|
return self.__file
|
||||||
|
|
||||||
|
def SetRevision(self, revision):
|
||||||
|
self.__revision = revision
|
||||||
|
|
||||||
|
def GetRevision(self):
|
||||||
|
return self.__revision
|
||||||
|
|
||||||
|
def SetTime(self, gmt_time):
|
||||||
|
self.__gmt_time = float(gmt_time)
|
||||||
|
|
||||||
|
def GetTime(self):
|
||||||
|
return self.__gmt_time
|
||||||
|
|
||||||
|
def SetAuthor(self, author):
|
||||||
|
self.__author = author
|
||||||
|
|
||||||
|
def GetAuthor(self):
|
||||||
|
return self.__author
|
||||||
|
|
||||||
|
def SetBranch(self, branch):
|
||||||
|
if not branch:
|
||||||
|
self.__branch = ''
|
||||||
|
else:
|
||||||
|
self.__branch = branch
|
||||||
|
|
||||||
|
def GetBranch(self):
|
||||||
|
return self.__branch
|
||||||
|
|
||||||
|
def SetPlusCount(self, pluscount):
|
||||||
|
self.__pluscount = pluscount
|
||||||
|
|
||||||
|
def GetPlusCount(self):
|
||||||
|
return self.__pluscount
|
||||||
|
|
||||||
|
def SetMinusCount(self, minuscount):
|
||||||
|
self.__minuscount = minuscount
|
||||||
|
|
||||||
|
def GetMinusCount(self):
|
||||||
|
return self.__minuscount
|
||||||
|
|
||||||
|
def SetDescription(self, description):
|
||||||
|
self.__description = description
|
||||||
|
|
||||||
|
def GetDescription(self):
|
||||||
|
return self.__description
|
||||||
|
|
||||||
|
def SetTypeChange(self):
|
||||||
|
self.__type = Commit.CHANGE
|
||||||
|
|
||||||
|
def SetTypeAdd(self):
|
||||||
|
self.__type = Commit.ADD
|
||||||
|
|
||||||
|
def SetTypeRemove(self):
|
||||||
|
self.__type = Commit.REMOVE
|
||||||
|
|
||||||
|
def GetType(self):
|
||||||
|
return self.__type
|
||||||
|
|
||||||
|
def GetTypeString(self):
|
||||||
|
if self.__type == Commit.CHANGE:
|
||||||
|
return 'Change'
|
||||||
|
elif self.__type == Commit.ADD:
|
||||||
|
return 'Add'
|
||||||
|
elif self.__type == Commit.REMOVE:
|
||||||
|
return 'Remove'
|
||||||
|
|
||||||
|
## QueryEntry holds data on one match-type in the SQL database
|
||||||
|
## match is: "exact", "like", or "regex"
|
||||||
|
class QueryEntry:
|
||||||
|
def __init__(self, data, match):
|
||||||
|
self.data = data
|
||||||
|
self.match = match
|
||||||
|
|
||||||
|
## CheckinDatabaseQueryData is a object which contains the search parameters
|
||||||
|
## for a query to the CheckinDatabase
|
||||||
|
class CheckinDatabaseQuery:
|
||||||
|
def __init__(self):
|
||||||
|
## sorting
|
||||||
|
self.sort = "date"
|
||||||
|
|
||||||
|
## repository to query
|
||||||
|
self.repository_list = []
|
||||||
|
self.branch_list = []
|
||||||
|
self.directory_list = []
|
||||||
|
self.file_list = []
|
||||||
|
self.author_list = []
|
||||||
|
|
||||||
|
## date range in DBI 2.0 timedate objects
|
||||||
|
self.from_date = None
|
||||||
|
self.to_date = None
|
||||||
|
|
||||||
|
## list of commits -- filled in by CVS query
|
||||||
|
self.commit_list = []
|
||||||
|
|
||||||
|
## commit_cb provides a callback for commits as they
|
||||||
|
## are added
|
||||||
|
self.commit_cb = None
|
||||||
|
|
||||||
|
def SetRepository(self, repository, match = "exact"):
|
||||||
|
self.repository_list.append(QueryEntry(repository, match))
|
||||||
|
|
||||||
|
def SetBranch(self, branch, match = "exact"):
|
||||||
|
self.branch_list.append(QueryEntry(branch, match))
|
||||||
|
|
||||||
|
def SetDirectory(self, directory, match = "exact"):
|
||||||
|
self.directory_list.append(QueryEntry(directory, match))
|
||||||
|
|
||||||
|
def SetFile(self, file, match = "exact"):
|
||||||
|
self.file_list.append(QueryEntry(file, match))
|
||||||
|
|
||||||
|
def SetAuthor(self, author, match = "exact"):
|
||||||
|
self.author_list.append(QueryEntry(author, match))
|
||||||
|
|
||||||
|
def SetSortMethod(self, sort):
|
||||||
|
self.sort = sort
|
||||||
|
|
||||||
|
def SetFromDateObject(self, ticks):
|
||||||
|
self.from_date = dbi.TimestampFromTicks(ticks)
|
||||||
|
|
||||||
|
def SetToDateObject(self, ticks):
|
||||||
|
self.to_date = dbi.TimestampFromTicks(ticks)
|
||||||
|
|
||||||
|
def SetFromDateHoursAgo(self, hours_ago):
|
||||||
|
ticks = time.time() - (3600 * hours_ago)
|
||||||
|
self.from_date = dbi.TimestampFromTicks(ticks)
|
||||||
|
|
||||||
|
def SetFromDateDaysAgo(self, days_ago):
|
||||||
|
ticks = time.time() - (86400 * days_ago)
|
||||||
|
self.from_date = dbi.TimestampFromTicks(ticks)
|
||||||
|
|
||||||
|
def SetToDateDaysAgo(self, days_ago):
|
||||||
|
ticks = time.time() - (86400 * days_ago)
|
||||||
|
self.to_date = dbi.TimestampFromTicks(ticks)
|
||||||
|
|
||||||
|
def AddCommit(self, commit):
|
||||||
|
self.commit_list.append(commit)
|
||||||
|
if self.commit_cb:
|
||||||
|
self.commit_cb(commit)
|
||||||
|
|
||||||
|
def SetCommitCB(self, callback):
|
||||||
|
self.commit_cb = callback
|
||||||
|
|
||||||
|
|
||||||
|
##
|
||||||
|
## entrypoints
|
||||||
|
##
|
||||||
|
def CreateCheckinDatabase(host, user, passwd, database):
|
||||||
|
return CheckinDatabase(host, user, passwd, database)
|
||||||
|
|
||||||
|
def CreateCommit():
|
||||||
|
return Commit()
|
||||||
|
|
||||||
|
def CreateCheckinQuery():
|
||||||
|
return CheckinDatabaseQuery()
|
||||||
|
|
||||||
|
def ConnectDatabaseReadOnly():
|
||||||
|
global gCheckinDatabaseReadOnly
|
||||||
|
|
||||||
|
if gCheckinDatabaseReadOnly:
|
||||||
|
return gCheckinDatabaseReadOnly
|
||||||
|
|
||||||
|
gCheckinDatabaseReadOnly = CreateCheckinDatabase(
|
||||||
|
cfg.cvsdb.host,
|
||||||
|
cfg.cvsdb.readonly_user,
|
||||||
|
cfg.cvsdb.readonly_passwd,
|
||||||
|
cfg.cvsdb.database_name)
|
||||||
|
|
||||||
|
gCheckinDatabaseReadOnly.Connect()
|
||||||
|
return gCheckinDatabaseReadOnly
|
||||||
|
|
||||||
|
def ConnectDatabase():
|
||||||
|
global gCheckinDatabase
|
||||||
|
|
||||||
|
gCheckinDatabase = CreateCheckinDatabase(
|
||||||
|
cfg.cvsdb.host,
|
||||||
|
cfg.cvsdb.user,
|
||||||
|
cfg.cvsdb.passwd,
|
||||||
|
cfg.cvsdb.database_name)
|
||||||
|
|
||||||
|
gCheckinDatabase.Connect()
|
||||||
|
return gCheckinDatabase
|
||||||
|
|
||||||
|
def RLogDataToCommitList(repository, rlog_data):
|
||||||
|
commit_list = []
|
||||||
|
|
||||||
|
## the filename in rlog_data contains the entire path of the
|
||||||
|
## repository; we strip that out here
|
||||||
|
temp = rlog_data.filename[len(repository):]
|
||||||
|
directory, file = os.path.split(temp)
|
||||||
|
|
||||||
|
for rlog_entry in rlog_data.rlog_entry_list:
|
||||||
|
commit = CreateCommit()
|
||||||
|
commit.SetRepository(repository)
|
||||||
|
commit.SetDirectory(directory)
|
||||||
|
commit.SetFile(file)
|
||||||
|
commit.SetRevision(rlog_entry.revision)
|
||||||
|
commit.SetAuthor(rlog_entry.author)
|
||||||
|
commit.SetDescription(rlog_entry.description)
|
||||||
|
commit.SetTime(rlog_entry.time)
|
||||||
|
commit.SetPlusCount(rlog_entry.pluscount)
|
||||||
|
commit.SetMinusCount(rlog_entry.minuscount)
|
||||||
|
commit.SetBranch(rlog_data.LookupBranch(rlog_entry))
|
||||||
|
|
||||||
|
if rlog_entry.type == rlog_entry.CHANGE:
|
||||||
|
commit.SetTypeChange()
|
||||||
|
elif rlog_entry.type == rlog_entry.ADD:
|
||||||
|
commit.SetTypeAdd()
|
||||||
|
elif rlog_entry.type == rlog_entry.REMOVE:
|
||||||
|
commit.SetTypeRemove()
|
||||||
|
|
||||||
|
commit_list.append(commit)
|
||||||
|
|
||||||
|
return commit_list
|
||||||
|
|
||||||
|
def GetCommitListFromRCSFile(repository, filename):
|
||||||
|
try:
|
||||||
|
rlog_data = rlog.GetRLogData(cfg, filename)
|
||||||
|
except rlog.error, e:
|
||||||
|
raise error, e
|
||||||
|
|
||||||
|
commit_list = RLogDataToCommitList(repository, rlog_data)
|
||||||
|
return commit_list
|
||||||
|
|
||||||
|
def GetUnrecordedCommitList(repository, filename):
|
||||||
|
commit_list = GetCommitListFromRCSFile(repository, filename)
|
||||||
|
db = ConnectDatabase()
|
||||||
|
|
||||||
|
unrecorded_commit_list = []
|
||||||
|
for commit in commit_list:
|
||||||
|
result = db.CheckCommit(commit)
|
||||||
|
if not result:
|
||||||
|
unrecorded_commit_list.append(commit)
|
||||||
|
|
||||||
|
return unrecorded_commit_list
|
136
lib/cvsdbapi.py
136
lib/cvsdbapi.py
@@ -1,136 +0,0 @@
|
|||||||
# -*- Mode: python -*-
|
|
||||||
#
|
|
||||||
# Copyright (C) 2000 The ViewCVS Group. All Rights Reserved.
|
|
||||||
#
|
|
||||||
# By using this file, you agree to the terms and conditions set forth in
|
|
||||||
# the LICENSE.html file which can be found at the top level of the ViewCVS
|
|
||||||
# distribution or at http://www.lyra.org/viewcvs/license-1.html.
|
|
||||||
#
|
|
||||||
# Contact information:
|
|
||||||
# Greg Stein, PO Box 760, Palo Alto, CA, 94302
|
|
||||||
# gstein@lyra.org, http://www.lyra.org/viewcvs/
|
|
||||||
#
|
|
||||||
# -----------------------------------------------------------------------
|
|
||||||
#
|
|
||||||
|
|
||||||
#########################################################################
|
|
||||||
#
|
|
||||||
# INSTALL-TIME CONFIGURATION
|
|
||||||
#
|
|
||||||
# These values will be set during the installation process. During
|
|
||||||
# development, they will remain None.
|
|
||||||
#
|
|
||||||
|
|
||||||
CONF_PATHNAME = None
|
|
||||||
|
|
||||||
#########################################################################
|
|
||||||
|
|
||||||
import os, database, rlog, commit, config
|
|
||||||
|
|
||||||
## error
|
|
||||||
error = 'cvsdbapi error'
|
|
||||||
|
|
||||||
## database
|
|
||||||
CreateCheckinDatabase = database.CreateCheckinDatabase
|
|
||||||
CreateCheckinQuery = database.CreateCheckinQuery
|
|
||||||
|
|
||||||
## rlog
|
|
||||||
GetRLogData = rlog.GetRLogData
|
|
||||||
|
|
||||||
## commit
|
|
||||||
CreateCommit = commit.CreateCommit
|
|
||||||
PrintCommit = commit.PrintCommit
|
|
||||||
|
|
||||||
## cached (active) database connections
|
|
||||||
gCheckinDatabase = None
|
|
||||||
gCheckinDatabaseReadOnly = None
|
|
||||||
|
|
||||||
## load configuration file, the data is used globally here
|
|
||||||
cfg = config.Config()
|
|
||||||
cfg.set_defaults()
|
|
||||||
cfg.load_config(CONF_PATHNAME)
|
|
||||||
|
|
||||||
|
|
||||||
def ConnectDatabaseReadOnly():
|
|
||||||
global gCheckinDatabaseReadOnly
|
|
||||||
|
|
||||||
if gCheckinDatabaseReadOnly:
|
|
||||||
return gCheckinDatabaseReadOnly
|
|
||||||
|
|
||||||
gCheckinDatabaseReadOnly = database.CreateCheckinDatabase(
|
|
||||||
cfg.cvsdb.host,
|
|
||||||
cfg.cvsdb.readonly_user,
|
|
||||||
cfg.cvsdb.readonly_passwd,
|
|
||||||
cfg.cvsdb.database_name)
|
|
||||||
|
|
||||||
gCheckinDatabaseReadOnly.Connect()
|
|
||||||
return gCheckinDatabaseReadOnly
|
|
||||||
|
|
||||||
|
|
||||||
def ConnectDatabase():
|
|
||||||
global gCheckinDatabase
|
|
||||||
|
|
||||||
gCheckinDatabase = database.CreateCheckinDatabase(
|
|
||||||
cfg.cvsdb.host,
|
|
||||||
cfg.cvsdb.user,
|
|
||||||
cfg.cvsdb.passwd,
|
|
||||||
cfg.cvsdb.database_name)
|
|
||||||
|
|
||||||
gCheckinDatabase.Connect()
|
|
||||||
return gCheckinDatabase
|
|
||||||
|
|
||||||
|
|
||||||
def RLogDataToCommitList(repository, rlog_data):
|
|
||||||
commit_list = []
|
|
||||||
|
|
||||||
## the filename in rlog_data contains the entire path of the
|
|
||||||
## repository; we strip that out here
|
|
||||||
temp = rlog_data.filename[len(repository):]
|
|
||||||
directory, file = os.path.split(temp)
|
|
||||||
|
|
||||||
for rlog_entry in rlog_data.rlog_entry_list:
|
|
||||||
commit = CreateCommit()
|
|
||||||
commit.SetRepository(repository)
|
|
||||||
commit.SetDirectory(directory)
|
|
||||||
commit.SetFile(file)
|
|
||||||
commit.SetRevision(rlog_entry.revision)
|
|
||||||
commit.SetAuthor(rlog_entry.author)
|
|
||||||
commit.SetDescription(rlog_entry.description)
|
|
||||||
commit.SetTime(rlog_entry.time)
|
|
||||||
commit.SetPlusCount(rlog_entry.pluscount)
|
|
||||||
commit.SetMinusCount(rlog_entry.minuscount)
|
|
||||||
commit.SetBranch(rlog_data.LookupBranch(rlog_entry))
|
|
||||||
|
|
||||||
if rlog_entry.type == rlog_entry.CHANGE:
|
|
||||||
commit.SetTypeChange()
|
|
||||||
elif rlog_entry.type == rlog_entry.ADD:
|
|
||||||
commit.SetTypeAdd()
|
|
||||||
elif rlog_entry.type == rlog_entry.REMOVE:
|
|
||||||
commit.SetTypeRemove()
|
|
||||||
|
|
||||||
commit_list.append(commit)
|
|
||||||
|
|
||||||
return commit_list
|
|
||||||
|
|
||||||
|
|
||||||
def GetCommitListFromRCSFile(repository, filename):
|
|
||||||
try:
|
|
||||||
rlog_data = GetRLogData(filename)
|
|
||||||
except rlog.error, e:
|
|
||||||
raise error, e
|
|
||||||
|
|
||||||
commit_list = RLogDataToCommitList(repository, rlog_data)
|
|
||||||
return commit_list
|
|
||||||
|
|
||||||
|
|
||||||
def GetUnrecordedCommitList(repository, filename):
|
|
||||||
commit_list = GetCommitListFromRCSFile(repository, filename)
|
|
||||||
db = ConnectDatabase()
|
|
||||||
|
|
||||||
unrecorded_commit_list = []
|
|
||||||
for commit in commit_list:
|
|
||||||
result = db.CheckCommit(commit)
|
|
||||||
if not result:
|
|
||||||
unrecorded_commit_list.append(commit)
|
|
||||||
|
|
||||||
return unrecorded_commit_list
|
|
476
lib/database.py
476
lib/database.py
@@ -1,476 +0,0 @@
|
|||||||
# -*- Mode: python -*-
|
|
||||||
#
|
|
||||||
# Copyright (C) 2000 The ViewCVS Group. All Rights Reserved.
|
|
||||||
#
|
|
||||||
# By using this file, you agree to the terms and conditions set forth in
|
|
||||||
# the LICENSE.html file which can be found at the top level of the ViewCVS
|
|
||||||
# distribution or at http://www.lyra.org/viewcvs/license-1.html.
|
|
||||||
#
|
|
||||||
# Contact information:
|
|
||||||
# Greg Stein, PO Box 760, Palo Alto, CA, 94302
|
|
||||||
# gstein@lyra.org, http://www.lyra.org/viewcvs/
|
|
||||||
#
|
|
||||||
# -----------------------------------------------------------------------
|
|
||||||
#
|
|
||||||
|
|
||||||
import os, sys, string, time
|
|
||||||
|
|
||||||
## imports from the database API; we re-assign the namespace here so it
|
|
||||||
## is easier to switch databases
|
|
||||||
import MySQLdb
|
|
||||||
DBI = MySQLdb
|
|
||||||
|
|
||||||
from commit import CreateCommit, PrintCommit
|
|
||||||
|
|
||||||
|
|
||||||
## base strings used in SQL querries, these should be static members
|
|
||||||
## of the CheckinDatabase class
|
|
||||||
|
|
||||||
sqlBase = 'SELECT checkins.type, checkins.ci_when,checkins. whoid, checkins.repositoryid, checkins.dirid, checkins.fileid, checkins.revision, checkins.stickytag, checkins.branchid, checkins.addedlines, checkins.removedlines, checkins.descid FROM %s WHERE %s %s'
|
|
||||||
sqlRepository = '(checkins.repositoryid=repositories.id AND repositories.repository="%s")'
|
|
||||||
sqlBranch = '(checkins.branchid=branches.id AND branches.branch="%s")'
|
|
||||||
sqlDirectory = '(checkins.dirid=dirs.id AND dirs.dir LIKE "%s%%")'
|
|
||||||
sqlFile = '(checkins.fileid=files.id AND files.file="%s")'
|
|
||||||
sqlFromDate ='(checkins.ci_when>="%s")'
|
|
||||||
sqlToDate = '(checkins.ci_when<="%s")'
|
|
||||||
sqlAuthor = '(checkins.whoid=people.id AND people.who="%s")'
|
|
||||||
sqlSortByDate = 'ORDER BY checkins.ci_when DESC'
|
|
||||||
sqlSortByAuthor = 'ORDER BY checkins.whoid'
|
|
||||||
sqlSortByFile = 'ORDER BY checkins.fileid'
|
|
||||||
sqlExcludeVersionFiles = '(checkins.fileid=files.id AND files.file NOT LIKE "%%.ver")'
|
|
||||||
sqlCheckCommit = 'SELECT * FROM checkins WHERE checkins.repositoryid=%s AND checkins.dirid=%s AND checkins.fileid=%s AND checkins.revision=%s'
|
|
||||||
|
|
||||||
## CheckinDatabase provides all interfaces needed to the SQL database
|
|
||||||
## back-end; it needs to be subclassed, and have its "Connect" method
|
|
||||||
## defined to actually be complete; it should run well off of any DBI 2.0
|
|
||||||
## complient database interface
|
|
||||||
|
|
||||||
class CheckinDatabase:
|
|
||||||
def __init__(self, host, user, passwd, database):
|
|
||||||
self.dbHost = host
|
|
||||||
self.dbUser = user
|
|
||||||
self.dbPasswd = passwd
|
|
||||||
self.dbDatabase = database
|
|
||||||
|
|
||||||
## cache Value lookups
|
|
||||||
self.dbGetCache = {}
|
|
||||||
self.dbGetIDCache = {}
|
|
||||||
self.dbDescriptionIDCache = {}
|
|
||||||
|
|
||||||
def Connect(self):
|
|
||||||
self.dbConn = self.SQLConnect()
|
|
||||||
|
|
||||||
def SQLGetID(self, table, field, identifier, auto_set):
|
|
||||||
sql = 'SELECT id FROM %s x WHERE x.%s="%s"' % (
|
|
||||||
table, field, identifier)
|
|
||||||
|
|
||||||
cursor = self.dbConn.cursor()
|
|
||||||
cursor.execute(sql)
|
|
||||||
row = cursor.fetchone()
|
|
||||||
if row:
|
|
||||||
return row[0]
|
|
||||||
|
|
||||||
if not auto_set:
|
|
||||||
return None
|
|
||||||
|
|
||||||
## insert the new identifier
|
|
||||||
sql = 'INSERT INTO %s (%s) VALUES ("%s")' % (table, field, identifier)
|
|
||||||
cursor.execute(sql)
|
|
||||||
return self.SQLGetID(table, field, identifier, 0)
|
|
||||||
|
|
||||||
def GetID(self, table, field, identifier, auto_set):
|
|
||||||
## attempt to retrieve from cache
|
|
||||||
try:
|
|
||||||
return self.dbGetIDCache[table][field][identifier]
|
|
||||||
except KeyError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
id = self.SQLGetID(table, field, identifier, auto_set)
|
|
||||||
if not id:
|
|
||||||
return id
|
|
||||||
|
|
||||||
## add to cache
|
|
||||||
if not self.dbGetIDCache.has_key(table):
|
|
||||||
self.dbGetIDCache[table] = {}
|
|
||||||
if not self.dbGetIDCache[table].has_key(field):
|
|
||||||
self.dbGetIDCache[table][field] = {}
|
|
||||||
self.dbGetIDCache[table][field][identifier] = id
|
|
||||||
return id
|
|
||||||
|
|
||||||
def SQLGet(self, table, field, id):
|
|
||||||
sql = 'SELECT %s FROM %s x WHERE x.id="%s"' % (field, table, id)
|
|
||||||
|
|
||||||
cursor = self.dbConn.cursor()
|
|
||||||
cursor.execute(sql)
|
|
||||||
row = cursor.fetchone()
|
|
||||||
if not row:
|
|
||||||
return None
|
|
||||||
return row[0]
|
|
||||||
|
|
||||||
def Get(self, table, field, id):
|
|
||||||
## attempt to retrieve from cache
|
|
||||||
try:
|
|
||||||
return self.dbGetCache[table][field][id]
|
|
||||||
except KeyError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
value = self.SQLGet(table, field, id)
|
|
||||||
if not value:
|
|
||||||
return None
|
|
||||||
|
|
||||||
## add to cache
|
|
||||||
if not self.dbGetCache.has_key(table):
|
|
||||||
self.dbGetCache[table] = {}
|
|
||||||
if not self.dbGetCache[table].has_key(field):
|
|
||||||
self.dbGetCache[table][field] = {}
|
|
||||||
self.dbGetCache[table][field][id] = value
|
|
||||||
return value
|
|
||||||
|
|
||||||
def GetBranchID(self, branch, auto_set = 1):
|
|
||||||
return self.GetID('branches', 'branch', branch, auto_set)
|
|
||||||
|
|
||||||
def GetBranch(self, id):
|
|
||||||
return self.Get('branches', 'branch', id)
|
|
||||||
|
|
||||||
def GetDirectoryID(self, dir, auto_set = 1):
|
|
||||||
return self.GetID('dirs', 'dir', dir, auto_set)
|
|
||||||
|
|
||||||
def GetDirectory(self, id):
|
|
||||||
return self.Get('dirs', 'dir', id)
|
|
||||||
|
|
||||||
def GetFileID(self, file, auto_set = 1):
|
|
||||||
return self.GetID('files', 'file', file, auto_set)
|
|
||||||
|
|
||||||
def GetFile(self, id):
|
|
||||||
return self.Get('files', 'file', id)
|
|
||||||
|
|
||||||
def GetAuthorID(self, author, auto_set = 1):
|
|
||||||
return self.GetID('people', 'who', author, auto_set)
|
|
||||||
|
|
||||||
def GetAuthor(self, id):
|
|
||||||
return self.Get('people', 'who', id)
|
|
||||||
|
|
||||||
def GetRepositoryID(self, repository, auto_set = 1):
|
|
||||||
return self.GetID('repositories', 'repository', repository, auto_set)
|
|
||||||
|
|
||||||
def GetRepository(self, id):
|
|
||||||
return self.Get('repositories', 'repository', id)
|
|
||||||
|
|
||||||
def SQLGetDescriptionID(self, description, auto_set = 1):
|
|
||||||
## lame string hash, blame Netscape -JMP
|
|
||||||
hash = len(description)
|
|
||||||
|
|
||||||
cursor = self.dbConn.cursor()
|
|
||||||
cursor.execute(
|
|
||||||
'SELECT id FROM descs WHERE hash=%s and description=%s',
|
|
||||||
(hash, description))
|
|
||||||
|
|
||||||
row = cursor.fetchone()
|
|
||||||
if row:
|
|
||||||
return row[0]
|
|
||||||
|
|
||||||
if not auto_set:
|
|
||||||
return None
|
|
||||||
|
|
||||||
cursor = self.dbConn.cursor()
|
|
||||||
cursor.execute(
|
|
||||||
'INSERT INTO descs (hash, description) values (%s, %s)',
|
|
||||||
(hash, description))
|
|
||||||
|
|
||||||
return self.GetDescriptionID(description, 0)
|
|
||||||
|
|
||||||
def GetDescriptionID(self, description, auto_set = 1):
|
|
||||||
## lame string hash, blame Netscape -JMP
|
|
||||||
hash = len(description)
|
|
||||||
|
|
||||||
## attempt to retrieve from cache
|
|
||||||
try:
|
|
||||||
return self.dbDescriptionIDCache[hash][description]
|
|
||||||
except KeyError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
id = self.SQLGetDescriptionID(description, auto_set)
|
|
||||||
if not id:
|
|
||||||
return id
|
|
||||||
|
|
||||||
## add to cache
|
|
||||||
if not self.dbDescriptionIDCache.has_key(hash):
|
|
||||||
self.dbDescriptionIDCache[hash] = {}
|
|
||||||
self.dbDescriptionIDCache[hash][description] = id
|
|
||||||
return id
|
|
||||||
|
|
||||||
def GetDescription(self, id):
|
|
||||||
return self.Get('descs', 'description', id)
|
|
||||||
|
|
||||||
def GetList(self, table, field_index):
|
|
||||||
sql = 'SELECT * FROM %s' % (table)
|
|
||||||
|
|
||||||
cursor = self.dbConn.cursor()
|
|
||||||
cursor.execute(sql)
|
|
||||||
|
|
||||||
list = []
|
|
||||||
while 1:
|
|
||||||
row = cursor.fetchone()
|
|
||||||
if not row:
|
|
||||||
break
|
|
||||||
list.append(row[field_index])
|
|
||||||
|
|
||||||
return list
|
|
||||||
|
|
||||||
def GetRepositoryList(self):
|
|
||||||
return self.GetList('repositories', 1)
|
|
||||||
|
|
||||||
def GetBranchList(self):
|
|
||||||
return self.GetList('branches', 1)
|
|
||||||
|
|
||||||
def GetAuthorList(self):
|
|
||||||
return self.GetList('people', 1)
|
|
||||||
|
|
||||||
def AddCommitList(self, commit_list):
|
|
||||||
for commit in commit_list:
|
|
||||||
self.AddCommit(commit)
|
|
||||||
|
|
||||||
def AddCommit(self, commit):
|
|
||||||
dbType = commit.GetTypeString()
|
|
||||||
|
|
||||||
## MORE TIME HELL: the MySQLdb module doesn't construct times
|
|
||||||
## correctly when created with TimestampFromTicks -- it doesn't
|
|
||||||
## account for daylight savings time, so we use Python's time
|
|
||||||
## module to do the conversion
|
|
||||||
temp = time.localtime(commit.GetTime())
|
|
||||||
|
|
||||||
dbCI_When = DBI.Timestamp(
|
|
||||||
temp[0], temp[1], temp[2], temp[3], temp[4], temp[5])
|
|
||||||
|
|
||||||
dbWhoID = self.GetAuthorID(commit.GetAuthor())
|
|
||||||
dbRepositoryID = self.GetRepositoryID(commit.GetRepository())
|
|
||||||
dbDirectoryID = self.GetDirectoryID(commit.GetDirectory())
|
|
||||||
dbFileID = self.GetFileID(commit.GetFile())
|
|
||||||
dbRevision = commit.GetRevision()
|
|
||||||
dbStickyTag = 'NULL'
|
|
||||||
dbBranchID = self.GetBranchID(commit.GetBranch())
|
|
||||||
dbPlusCount = commit.GetPlusCount()
|
|
||||||
dbMinusCount = commit.GetMinusCount()
|
|
||||||
dbDescriptionID = self.GetDescriptionID(commit.GetDescription())
|
|
||||||
|
|
||||||
sql = 'REPLACE INTO checkins(type, ci_when, whoid, repositoryid, dirid, fileid, revision, stickytag, branchid, addedlines, removedlines, descid) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)'
|
|
||||||
sqlArguments = (
|
|
||||||
dbType, dbCI_When, dbWhoID, dbRepositoryID, dbDirectoryID,
|
|
||||||
dbFileID, dbRevision, dbStickyTag, dbBranchID, dbPlusCount,
|
|
||||||
dbMinusCount, dbDescriptionID)
|
|
||||||
|
|
||||||
cursor = self.dbConn.cursor()
|
|
||||||
cursor.execute(sql, sqlArguments)
|
|
||||||
|
|
||||||
def CreateSQLQueryString(self, query):
|
|
||||||
tableList = ['checkins']
|
|
||||||
condList = []
|
|
||||||
|
|
||||||
tableList.append('files')
|
|
||||||
condList.append(sqlExcludeVersionFiles)
|
|
||||||
|
|
||||||
if query.repository:
|
|
||||||
tableList.append('repositories')
|
|
||||||
condList.append(sqlRepository % (query.repository))
|
|
||||||
|
|
||||||
if query.branch:
|
|
||||||
tableList.append('branches')
|
|
||||||
condList.append(sqlBranch % (query.branch))
|
|
||||||
|
|
||||||
if query.from_date:
|
|
||||||
condList.append(sqlFromDate % (str(query.from_date)))
|
|
||||||
|
|
||||||
if query.to_date:
|
|
||||||
condList.append(sqlToDate % (str(query.to_date)))
|
|
||||||
|
|
||||||
if query.author:
|
|
||||||
tableList.append('people')
|
|
||||||
condList.append(sqlAuthor % (query.author))
|
|
||||||
|
|
||||||
if query.directory:
|
|
||||||
tableList.append('dirs')
|
|
||||||
condList.append(sqlDirectory % (query.directory))
|
|
||||||
|
|
||||||
if query.file:
|
|
||||||
#tableList.append('files')
|
|
||||||
condList.append(sqlFile % (query.file))
|
|
||||||
|
|
||||||
if query.sort == query.SORT_DATE:
|
|
||||||
order_by = sqlSortByDate
|
|
||||||
elif query.sort == query.SORT_AUTHOR:
|
|
||||||
order_by = sqlSortByAuthor
|
|
||||||
elif query.sort == query.SORT_FILE:
|
|
||||||
order_by = sqlSortByFile
|
|
||||||
|
|
||||||
sql = sqlBase % (
|
|
||||||
string.join(tableList, ', '),
|
|
||||||
string.join(condList, ' AND '),
|
|
||||||
order_by)
|
|
||||||
|
|
||||||
return sql
|
|
||||||
|
|
||||||
def RunQuery(self, query):
|
|
||||||
sql = self.CreateSQLQueryString(query)
|
|
||||||
cursor = self.dbConn.cursor()
|
|
||||||
cursor.execute(sql)
|
|
||||||
|
|
||||||
while 1:
|
|
||||||
row = cursor.fetchone()
|
|
||||||
if not row:
|
|
||||||
break
|
|
||||||
|
|
||||||
(dbType, dbCI_When, dbAuthorID, dbRepositoryID, dbDirID,
|
|
||||||
dbFileID, dbRevision, dbStickyTag, dbBranchID, dbAddedLines,
|
|
||||||
dbRemovedLines, dbDescID) = row
|
|
||||||
|
|
||||||
commit = CreateCommit()
|
|
||||||
|
|
||||||
## TIME, TIME, TIME is all fucked up; dateobject.gmticks()
|
|
||||||
## is broken, dateobject.ticks() returns somthing like
|
|
||||||
## GMT ticks, except it forgets about daylight savings
|
|
||||||
## time -- we handle it ourself in the following painful way
|
|
||||||
gmt_time = time.mktime(
|
|
||||||
(dbCI_When.year, dbCI_When.month, dbCI_When.day,
|
|
||||||
dbCI_When.hour, dbCI_When.minute, dbCI_When.second,
|
|
||||||
0, 0, dbCI_When.dst))
|
|
||||||
|
|
||||||
commit.SetTime(gmt_time)
|
|
||||||
|
|
||||||
commit.SetFile(self.GetFile(dbFileID))
|
|
||||||
commit.SetDirectory(self.GetDirectory(dbDirID))
|
|
||||||
commit.SetRevision(dbRevision)
|
|
||||||
commit.SetRepository(self.GetRepository(dbRepositoryID))
|
|
||||||
commit.SetAuthor(self.GetAuthor(dbAuthorID))
|
|
||||||
commit.SetBranch(self.GetBranch(dbBranchID))
|
|
||||||
commit.SetPlusCount(dbAddedLines)
|
|
||||||
commit.SetMinusCount(dbRemovedLines)
|
|
||||||
commit.SetDescription(self.GetDescription(dbDescID))
|
|
||||||
|
|
||||||
query.AddCommit(commit)
|
|
||||||
|
|
||||||
def CheckCommit(self, commit):
|
|
||||||
dbRepositoryID = self.GetRepositoryID(commit.GetRepository(), 0)
|
|
||||||
if dbRepositoryID == None:
|
|
||||||
return None
|
|
||||||
|
|
||||||
dbDirID = self.GetDirectoryID(commit.GetDirectory(), 0)
|
|
||||||
if dbDirID == None:
|
|
||||||
return None
|
|
||||||
|
|
||||||
dbFileID = self.GetFileID(commit.GetFile(), 0)
|
|
||||||
if dbFileID == None:
|
|
||||||
return None
|
|
||||||
|
|
||||||
sqlArguments = (dbRepositoryID, dbDirID, dbFileID,
|
|
||||||
commit.GetRevision())
|
|
||||||
|
|
||||||
cursor = self.dbConn.cursor()
|
|
||||||
cursor.execute(sqlCheckCommit, sqlArguments)
|
|
||||||
row = cursor.fetchone()
|
|
||||||
if not row:
|
|
||||||
return None
|
|
||||||
|
|
||||||
return commit
|
|
||||||
|
|
||||||
|
|
||||||
class MySQLCheckinDatabase(CheckinDatabase):
|
|
||||||
def SQLConnect(self):
|
|
||||||
return MySQLdb.connect(
|
|
||||||
host = self.dbHost,
|
|
||||||
user = self.dbUser,
|
|
||||||
passwd = self.dbPasswd,
|
|
||||||
db = self.dbDatabase)
|
|
||||||
|
|
||||||
|
|
||||||
## CheckinDatabaseQueryData is a object which contains the search parameters
|
|
||||||
## for a query to the CheckinDatabase
|
|
||||||
|
|
||||||
class CheckinDatabaseQuery:
|
|
||||||
|
|
||||||
SORT_DATE = 0
|
|
||||||
SORT_AUTHOR = 1
|
|
||||||
SORT_FILE = 2
|
|
||||||
SORT_CHANGESIZE = 3
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
## repository to query
|
|
||||||
self.repository = None
|
|
||||||
|
|
||||||
## branch
|
|
||||||
self.branch = None
|
|
||||||
|
|
||||||
## directory to seach
|
|
||||||
self.directory = None
|
|
||||||
|
|
||||||
## file to search for
|
|
||||||
self.file = None
|
|
||||||
|
|
||||||
## sorting method
|
|
||||||
self.sort = CheckinDatabaseQuery.SORT_DATE;
|
|
||||||
|
|
||||||
## author to search for
|
|
||||||
self.author = None
|
|
||||||
|
|
||||||
## date range in DBI 2.0 timedate objects
|
|
||||||
self.from_date = None
|
|
||||||
self.to_date = None
|
|
||||||
|
|
||||||
## list of commits -- filled in by CVS query
|
|
||||||
self.commit_list = []
|
|
||||||
|
|
||||||
## commit_cb provides a callback for commits as they
|
|
||||||
## are added
|
|
||||||
self.commit_cb = None
|
|
||||||
|
|
||||||
def SetRepository(self, repository):
|
|
||||||
self.repository = repository
|
|
||||||
|
|
||||||
def SetBranch(self, branch):
|
|
||||||
self.branch = branch
|
|
||||||
|
|
||||||
def SetDirectory(self, directory):
|
|
||||||
self.directory = directory
|
|
||||||
|
|
||||||
def SetFile(self, file):
|
|
||||||
self.file = file
|
|
||||||
|
|
||||||
def SetSortMethod(self, sort):
|
|
||||||
self.sort = sort
|
|
||||||
|
|
||||||
def SetAuthor(self, author):
|
|
||||||
self.author = author
|
|
||||||
|
|
||||||
def SetFromDateObject(self, date):
|
|
||||||
self.from_date = date
|
|
||||||
|
|
||||||
def SetToDateObject(self, date):
|
|
||||||
self.to_date = date
|
|
||||||
|
|
||||||
def SetFromDateHoursAgo(self, hours_ago):
|
|
||||||
ticks = time.time() - (3600 * hours_ago)
|
|
||||||
self.from_date = DBI.TimestampFromTicks(ticks)
|
|
||||||
|
|
||||||
def SetFromDateDaysAgo(self, days_ago):
|
|
||||||
ticks = time.time() - (86400 * days_ago)
|
|
||||||
self.from_date = DBI.TimestampFromTicks(ticks)
|
|
||||||
|
|
||||||
def SetToDateDaysAgo(self, days_ago):
|
|
||||||
ticks = time.time() - (86400 * days_ago)
|
|
||||||
self.to_date = DBI.TimestampFromTicks(ticks)
|
|
||||||
|
|
||||||
def AddCommit(self, commit):
|
|
||||||
self.commit_list.append(commit)
|
|
||||||
if self.commit_cb:
|
|
||||||
self.commit_cb(commit)
|
|
||||||
|
|
||||||
def SetCommitCB(self, callback):
|
|
||||||
self.commit_cb = callback
|
|
||||||
|
|
||||||
|
|
||||||
## entrypoints
|
|
||||||
|
|
||||||
def CreateCheckinDatabase(host, user, passwd, database):
|
|
||||||
return MySQLCheckinDatabase(host, user, passwd, database)
|
|
||||||
|
|
||||||
def CreateCheckinQuery():
|
|
||||||
return CheckinDatabaseQuery()
|
|
@@ -1,14 +1,14 @@
|
|||||||
# -*- Mode: python -*-
|
# -*- Mode: python -*-
|
||||||
#
|
#
|
||||||
# Copyright (C) 2000 The ViewCVS Group. All Rights Reserved.
|
# Copyright (C) 2000-2001 The ViewCVS Group. All Rights Reserved.
|
||||||
#
|
#
|
||||||
# By using this file, you agree to the terms and conditions set forth in
|
# By using this file, you agree to the terms and conditions set forth in
|
||||||
# the LICENSE.html file which can be found at the top level of the ViewCVS
|
# the LICENSE.html file which can be found at the top level of the ViewCVS
|
||||||
# distribution or at http://www.lyra.org/viewcvs/license-1.html.
|
# distribution or at http://viewcvs.sourceforge.net/license-1.html.
|
||||||
#
|
#
|
||||||
# Contact information:
|
# Contact information:
|
||||||
# Greg Stein, PO Box 760, Palo Alto, CA, 94302
|
# Greg Stein, PO Box 760, Palo Alto, CA, 94302
|
||||||
# gstein@lyra.org, http://www.lyra.org/viewcvs/
|
# gstein@lyra.org, http://viewcvs.sourceforge.net/
|
||||||
#
|
#
|
||||||
# -----------------------------------------------------------------------
|
# -----------------------------------------------------------------------
|
||||||
#
|
#
|
||||||
|
39
lib/debug.py
Normal file
39
lib/debug.py
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
# -*- Mode: python -*-
|
||||||
|
#
|
||||||
|
# Copyright (C) 2000-2001 The ViewCVS Group. All Rights Reserved.
|
||||||
|
#
|
||||||
|
# By using this file, you agree to the terms and conditions set forth in
|
||||||
|
# the LICENSE.html file which can be found at the top level of the ViewCVS
|
||||||
|
# distribution or at http://viewcvs.sourceforge.net/license-1.html.
|
||||||
|
#
|
||||||
|
# Contact information:
|
||||||
|
# Greg Stein, PO Box 760, Palo Alto, CA, 94302
|
||||||
|
# gstein@lyra.org, http://viewcvs.sourceforge.net/
|
||||||
|
#
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
#
|
||||||
|
|
||||||
|
if 0:
|
||||||
|
|
||||||
|
import time
|
||||||
|
|
||||||
|
_stack = [ ]
|
||||||
|
_times = { }
|
||||||
|
|
||||||
|
def t_start():
|
||||||
|
_stack.append(time.time())
|
||||||
|
|
||||||
|
def t_end(which):
|
||||||
|
t = time.time() - _stack.pop()
|
||||||
|
if _times.has_key(which):
|
||||||
|
_times[which] = _times[which] + t
|
||||||
|
else:
|
||||||
|
_times[which] = t
|
||||||
|
|
||||||
|
def dump():
|
||||||
|
for name, value in _times.items():
|
||||||
|
print '%s: %.6f<br>' % (name, value)
|
||||||
|
|
||||||
|
else:
|
||||||
|
|
||||||
|
t_start = t_end = dump = lambda *args: None
|
432
lib/ezt.py
Normal file
432
lib/ezt.py
Normal file
@@ -0,0 +1,432 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
"""ezt.py -- easy templating
|
||||||
|
|
||||||
|
ezt templates are very similar to standard HTML files. But additionaly
|
||||||
|
they contain directives sprinkled in between. With these directives
|
||||||
|
it possible to generate the dynamic content from the ezt templates.
|
||||||
|
|
||||||
|
These directives are enclosed in square brackets. If you are a
|
||||||
|
C-programmer, you might be familar with the #ifdef directives of the
|
||||||
|
C preprocessor 'cpp'. ezt provides a similar concept for HTML. Additionally
|
||||||
|
EZT has a 'for' directive, which allows to iterate (repeat) certain
|
||||||
|
subsections of the template according to sequence of data items
|
||||||
|
provided by the application.
|
||||||
|
|
||||||
|
The HTML rendering is performed by the method generate() of the Template
|
||||||
|
class. Building template instances can either be done using external
|
||||||
|
EZT files (convention: use the suffix .ezt for such files):
|
||||||
|
|
||||||
|
>>> template = Template("../templates/log.ezt")
|
||||||
|
|
||||||
|
or by calling the parse() method of a template instance directly with
|
||||||
|
a EZT template string:
|
||||||
|
|
||||||
|
>>> template = Template()
|
||||||
|
>>> template.parse('''<html><head>
|
||||||
|
... <title>[title_string]</title></head>
|
||||||
|
... <body><h1>[title_string]</h1>
|
||||||
|
... [for a_sequence] <p>[a_sequence]</p>
|
||||||
|
... [end] <hr>
|
||||||
|
... The [person] is [if-any state]in[else]out[end].
|
||||||
|
... </body>
|
||||||
|
... </html>
|
||||||
|
... ''')
|
||||||
|
|
||||||
|
The application should build a dictionary 'data' and pass it together
|
||||||
|
with the output fileobject to the templates generate method:
|
||||||
|
|
||||||
|
>>> data = {'title_string' : "A Dummy Page",
|
||||||
|
... 'a_sequence' : ['list item 1', 'list item 2', 'another element'],
|
||||||
|
... 'person': "doctor",
|
||||||
|
... 'state' : None }
|
||||||
|
>>> import sys
|
||||||
|
>>> template.generate(sys.stdout, data)
|
||||||
|
<html><head>
|
||||||
|
<title>A Dummy Page</title></head>
|
||||||
|
<body><h1>A Dummy Page</h1>
|
||||||
|
<p>list item 1</p>
|
||||||
|
<p>list item 2</p>
|
||||||
|
<p>another element</p>
|
||||||
|
<hr>
|
||||||
|
The doctor is out.
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
||||||
|
Directives
|
||||||
|
==========
|
||||||
|
|
||||||
|
Several directives allow the use of dotted qualified names refering to objects
|
||||||
|
or attributes of objects contained in the data dictionary given to the
|
||||||
|
.generate() method.
|
||||||
|
|
||||||
|
Simple directives
|
||||||
|
-----------------
|
||||||
|
|
||||||
|
[QUAL_NAME]
|
||||||
|
|
||||||
|
This directive is simply replaced by the value of identifier from the data
|
||||||
|
dictionary. QUAL_NAME might be a dotted qualified name refering to some
|
||||||
|
instance attribute of objects contained in the dats dictionary.
|
||||||
|
Numbers are converted to string though.
|
||||||
|
|
||||||
|
[include "filename"] or [include QUAL_NAME]
|
||||||
|
|
||||||
|
This directive is replaced by content of the named include file.
|
||||||
|
|
||||||
|
Block directives
|
||||||
|
----------------
|
||||||
|
|
||||||
|
[for QUAL_NAME] ... [end]
|
||||||
|
|
||||||
|
The text within the [for ...] directive and the corresponding [end]
|
||||||
|
is repeated for each element in the sequence referred to by the qualified
|
||||||
|
name in the for directive. Within the for block this identifiers now
|
||||||
|
refers to the actual item indexed by this loop iteration.
|
||||||
|
|
||||||
|
[if-any QUAL_NAME] ... [else] ... [end]
|
||||||
|
|
||||||
|
Test if the value QUAL_NAME is not None or an empty string or list.
|
||||||
|
The [else] clause is optional. CAUTION: Numeric values are converted to string,
|
||||||
|
so if QUAL_NAME refers to a numeric value 0, the then-clause is
|
||||||
|
substituted!
|
||||||
|
|
||||||
|
[if-index odd] ... [else] ... [end]
|
||||||
|
[if-index even] ... [else] ... [end]
|
||||||
|
[if-index first] ... [else] ... [end]
|
||||||
|
[if-index last] ... [else] ... [end]
|
||||||
|
[if-index NUMBER] ... [else] ... [end]
|
||||||
|
|
||||||
|
These five directives work similar to [if-any], but are only useful
|
||||||
|
within a [for ...]-block (see above). The odd/even directives are
|
||||||
|
for example useful to choose different background colors for adjacent rows
|
||||||
|
in a table. Similar the first/last directives might be used to
|
||||||
|
remove certain parts (for example "Diff to previous" doesn't make sense,
|
||||||
|
if there is no previous).
|
||||||
|
|
||||||
|
[is QUAL_NAME STRING] ... [else] ... [end]
|
||||||
|
[is QUAL_NAME QUAL_NAME] ... [else] ... [end]
|
||||||
|
|
||||||
|
The [is ...] directive is similar to the other conditional directives
|
||||||
|
above. But it allows to compare two value references or a value reference
|
||||||
|
with some constant string.
|
||||||
|
|
||||||
|
"""
|
||||||
|
#
|
||||||
|
# Copyright (C) 2001 Greg Stein. All Rights Reserved.
|
||||||
|
#
|
||||||
|
# Redistribution and use in source and binary forms, with or without
|
||||||
|
# modification, are permitted provided that the following conditions are
|
||||||
|
# met:
|
||||||
|
#
|
||||||
|
# * Redistributions of source code must retain the above copyright
|
||||||
|
# notice, this list of conditions and the following disclaimer.
|
||||||
|
#
|
||||||
|
# * Redistributions in binary form must reproduce the above copyright
|
||||||
|
# notice, this list of conditions and the following disclaimer in the
|
||||||
|
# documentation and/or other materials provided with the distribution.
|
||||||
|
#
|
||||||
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
|
||||||
|
# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
|
||||||
|
# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||||
|
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE
|
||||||
|
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||||
|
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||||
|
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||||
|
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||||
|
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||||
|
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||||
|
# POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
#
|
||||||
|
#
|
||||||
|
# This software is maintained by Greg and is available at:
|
||||||
|
# http://viewcvs.sourceforge.net/
|
||||||
|
# it is also used by the following projects:
|
||||||
|
# http://edna.sourceforge.net/
|
||||||
|
#
|
||||||
|
|
||||||
|
import string
|
||||||
|
import re
|
||||||
|
from types import StringType, IntType, FloatType
|
||||||
|
|
||||||
|
#
|
||||||
|
# This regular expression matches three alternatives:
|
||||||
|
# expr: DIRECTIVE | BRACKET | COMMENT
|
||||||
|
# DIRECTIVE: '[' ('-' | '.' | ' ' | '"' | '/' | alphanum)+ ']
|
||||||
|
# BRACKET: '[[]'
|
||||||
|
# COMMENT: '[#' not-rbracket* ']'
|
||||||
|
#
|
||||||
|
# When used with the split() method, the return value will be composed of
|
||||||
|
# non-matching text and the two paren groups (DIRECTIVE and BRACKET). Since
|
||||||
|
# the COMMENT matches are not placed into a group, they are considered a
|
||||||
|
# "splitting" value and simply dropped.
|
||||||
|
#
|
||||||
|
_re_parse = re.compile('(\[[-\w."/ ]+\])|(\[\[\])|\[#[^\]]*\]')
|
||||||
|
|
||||||
|
# block commands and their argument counts
|
||||||
|
_block_cmd_specs = { 'if-any':1, 'if-index':2, 'for':1, 'is':2 }
|
||||||
|
_block_cmds = _block_cmd_specs.keys()
|
||||||
|
|
||||||
|
# two regular expresssions for compressing whitespace. the first is used to
|
||||||
|
# compress any whitespace including a newline into a single newline. the
|
||||||
|
# second regex is used to compress runs of whitespace into a single space.
|
||||||
|
_re_newline = re.compile('[ \t\r\f\v]*\n\\s*')
|
||||||
|
_re_whitespace = re.compile(r'\s\s+')
|
||||||
|
|
||||||
|
class Template:
|
||||||
|
|
||||||
|
def __init__(self, fname=None):
|
||||||
|
if fname:
|
||||||
|
self.parse_file(fname)
|
||||||
|
|
||||||
|
def parse_file(self, fname):
|
||||||
|
"""fname -> a string object with pathname of file containg an EZT template.
|
||||||
|
"""
|
||||||
|
self.program = self._parse_file(fname)
|
||||||
|
|
||||||
|
def parse(self, text):
|
||||||
|
"""text -> a string object containing the HTML template.
|
||||||
|
|
||||||
|
parse the template program into: (TEXT DIRECTIVE BRACKET)* TEXT
|
||||||
|
DIRECTIVE will be '[directive]' or None
|
||||||
|
BRACKET will be '[[]' or None
|
||||||
|
"""
|
||||||
|
self.program = self._parse(text)
|
||||||
|
|
||||||
|
def generate(self, fp, data):
|
||||||
|
ctx = _context()
|
||||||
|
ctx.data = data
|
||||||
|
ctx.for_index = { }
|
||||||
|
self._execute(self.program, fp, ctx)
|
||||||
|
|
||||||
|
def _parse_file(self, fname, for_names=None):
|
||||||
|
return self._parse(open(fname, "rt").read(), for_names)
|
||||||
|
|
||||||
|
def _parse(self, text, for_names=None):
|
||||||
|
"""text -> string object containing the HTML template.
|
||||||
|
|
||||||
|
This is a private helper function doing the real work for method parse.
|
||||||
|
It returns the parsed template as a 'program'. This program is a sequence
|
||||||
|
made out of strings or (function, argument) 2-tuples.
|
||||||
|
|
||||||
|
Note: comment directives [# ...] are automatically dropped by _re_parse.
|
||||||
|
"""
|
||||||
|
|
||||||
|
parts = _re_parse.split(text)
|
||||||
|
|
||||||
|
program = [ ]
|
||||||
|
stack = [ ]
|
||||||
|
if not for_names:
|
||||||
|
for_names = [ ]
|
||||||
|
|
||||||
|
for i in range(len(parts)):
|
||||||
|
piece = parts[i]
|
||||||
|
which = i % 3 # discriminate between: TEXT DIRECTIVE BRACKET
|
||||||
|
if which == 0:
|
||||||
|
# TEXT. append if non-empty.
|
||||||
|
if piece:
|
||||||
|
piece = _re_whitespace.sub(' ', _re_newline.sub('\n', piece))
|
||||||
|
program.append(piece)
|
||||||
|
elif which == 2:
|
||||||
|
# BRACKET directive. append '[' if present.
|
||||||
|
if piece:
|
||||||
|
program.append('[')
|
||||||
|
elif piece:
|
||||||
|
# DIRECTIVE is present.
|
||||||
|
args = string.split(piece[1:-1])
|
||||||
|
cmd = args[0]
|
||||||
|
if cmd == 'else':
|
||||||
|
if len(args) > 1:
|
||||||
|
raise ArgCountSyntaxError()
|
||||||
|
### check: don't allow for 'for' cmd
|
||||||
|
idx = stack[-1][1]
|
||||||
|
true_section = program[idx:]
|
||||||
|
del program[idx:]
|
||||||
|
stack[-1][3] = true_section
|
||||||
|
elif cmd == 'end':
|
||||||
|
if len(args) > 1:
|
||||||
|
raise ArgCountSyntaxError()
|
||||||
|
# note: true-section may be None
|
||||||
|
cmd, idx, args, true_section = stack.pop()
|
||||||
|
else_section = program[idx:]
|
||||||
|
func = getattr(self, '_cmd_' + re.sub('-', '_', cmd))
|
||||||
|
program[idx:] = [ (func, (args, true_section, else_section)) ]
|
||||||
|
if cmd == 'for':
|
||||||
|
for_names.pop()
|
||||||
|
elif cmd in _block_cmds:
|
||||||
|
if len(args) > _block_cmd_specs[cmd] + 1:
|
||||||
|
raise ArgCountSyntaxError()
|
||||||
|
### this assumes arg1 is always a ref
|
||||||
|
args[1] = _prepare_ref(args[1], for_names)
|
||||||
|
|
||||||
|
# handle arg2 for the 'is' command
|
||||||
|
if cmd == 'is':
|
||||||
|
if args[2][0] == '"':
|
||||||
|
# strip the quotes
|
||||||
|
args[2] = args[2][1:-1]
|
||||||
|
else:
|
||||||
|
args[2] = _prepare_ref(args[2], for_names)
|
||||||
|
elif cmd == 'for':
|
||||||
|
for_names.append(args[1][0])
|
||||||
|
|
||||||
|
# remember the cmd, current pos, args, and a section placeholder
|
||||||
|
stack.append([cmd, len(program), args[1:], None])
|
||||||
|
elif cmd == 'include':
|
||||||
|
if len(args) != 2:
|
||||||
|
raise ArgCountSyntaxError()
|
||||||
|
if args[1][0] in ('"', "'"):
|
||||||
|
include_filename = args[1][1:-1]
|
||||||
|
program.extend(self._parse_file(include_filename, for_names))
|
||||||
|
else:
|
||||||
|
program.append((self._cmd_include, _prepare_ref(args[1], for_names)))
|
||||||
|
else:
|
||||||
|
# implied PRINT command
|
||||||
|
if len(args) > 1:
|
||||||
|
raise ArgCountSyntaxError()
|
||||||
|
program.append((self._cmd_print, _prepare_ref(args[0], for_names)))
|
||||||
|
|
||||||
|
return program
|
||||||
|
|
||||||
|
def _execute(self, program, fp, ctx):
|
||||||
|
"""This private helper function takes a 'program' sequence as created
|
||||||
|
by the method '_parse' and executes it step by step. strings are written
|
||||||
|
to the file object 'fp' and functions are called.
|
||||||
|
"""
|
||||||
|
for step in program:
|
||||||
|
if isinstance(step, StringType):
|
||||||
|
fp.write(step)
|
||||||
|
else:
|
||||||
|
step[0](step[1], fp, ctx)
|
||||||
|
|
||||||
|
def _cmd_print(self, valref, fp, ctx):
|
||||||
|
### type check the value
|
||||||
|
fp.write(_get_value(valref, ctx))
|
||||||
|
|
||||||
|
def _cmd_include(self, valref, fp, ctx):
|
||||||
|
self._execute(self._parse_file(_get_value(valref, ctx)), fp, ctx)
|
||||||
|
|
||||||
|
def _cmd_if_any(self, args, fp, ctx):
|
||||||
|
"If the value is a non-empty string or non-empty list, then T else F."
|
||||||
|
((valref,), t_section, f_section) = args
|
||||||
|
value = _get_value(valref, ctx)
|
||||||
|
self._do_if(value, t_section, f_section, fp, ctx)
|
||||||
|
|
||||||
|
def _cmd_if_index(self, args, fp, ctx):
|
||||||
|
((valref, value), t_section, f_section) = args
|
||||||
|
list, idx = ctx.for_index[valref[0]]
|
||||||
|
if value == 'even':
|
||||||
|
value = idx % 2 == 0
|
||||||
|
elif value == 'odd':
|
||||||
|
value = idx % 2 == 1
|
||||||
|
elif value == 'first':
|
||||||
|
value = idx == 0
|
||||||
|
elif value == 'last':
|
||||||
|
value = idx == len(list)-1
|
||||||
|
else:
|
||||||
|
value = idx == int(value)
|
||||||
|
self._do_if(value, t_section, f_section, fp, ctx)
|
||||||
|
|
||||||
|
def _cmd_is(self, args, fp, ctx):
|
||||||
|
((valref, value), t_section, f_section) = args
|
||||||
|
if not isinstance(value, StringType):
|
||||||
|
value = _get_value(value, ctx)
|
||||||
|
value = string.lower(_get_value(valref, ctx)) == string.lower(value)
|
||||||
|
self._do_if(value, t_section, f_section, fp, ctx)
|
||||||
|
|
||||||
|
def _do_if(self, value, t_section, f_section, fp, ctx):
|
||||||
|
if t_section is None:
|
||||||
|
t_section = f_section
|
||||||
|
f_section = None
|
||||||
|
if value:
|
||||||
|
section = t_section
|
||||||
|
else:
|
||||||
|
section = f_section
|
||||||
|
if section is not None:
|
||||||
|
self._execute(section, fp, ctx)
|
||||||
|
|
||||||
|
def _cmd_for(self, args, fp, ctx):
|
||||||
|
((valref,), unused, section) = args
|
||||||
|
list = _get_value(valref, ctx)
|
||||||
|
if isinstance(list, StringType):
|
||||||
|
raise NeedSequenceError()
|
||||||
|
refname = valref[0]
|
||||||
|
ctx.for_index[refname] = [ list, 0 ]
|
||||||
|
for i in range(len(list)):
|
||||||
|
ctx.for_index[refname][1] = i
|
||||||
|
self._execute(section, fp, ctx)
|
||||||
|
del ctx.for_index[refname]
|
||||||
|
|
||||||
|
|
||||||
|
def _prepare_ref(refname, for_names):
|
||||||
|
"""refname -> a string containing a dotted identifier. example:"foo.bar.bang"
|
||||||
|
for_names -> a list of active for sequences.
|
||||||
|
|
||||||
|
Returns a `value reference', a 3-Tupel made out of (refname, start, rest),
|
||||||
|
for fast access later.
|
||||||
|
"""
|
||||||
|
parts = string.split(refname, '.')
|
||||||
|
start = parts[0]
|
||||||
|
rest = parts[1:]
|
||||||
|
while rest and (start in for_names):
|
||||||
|
# check if the next part is also a "for name"
|
||||||
|
name = start + '.' + rest[0]
|
||||||
|
if name in for_names:
|
||||||
|
start = name
|
||||||
|
del rest[0]
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
return refname, start, rest
|
||||||
|
|
||||||
|
def _get_value((refname, start, rest), ctx):
|
||||||
|
"""(refname, start, rest) -> a prepared `value reference' (see above).
|
||||||
|
ctx -> an execution context instance.
|
||||||
|
|
||||||
|
Does a name space lookup within the template name space. Active
|
||||||
|
for blocks take precedence over data dictionary members with the
|
||||||
|
same name.
|
||||||
|
"""
|
||||||
|
if ctx.for_index.has_key(start):
|
||||||
|
list, idx = ctx.for_index[start]
|
||||||
|
ob = list[idx]
|
||||||
|
elif ctx.data.has_key(start):
|
||||||
|
ob = ctx.data[start]
|
||||||
|
else:
|
||||||
|
raise UnknownReference(refname)
|
||||||
|
|
||||||
|
# walk the rest of the dotted reference
|
||||||
|
for attr in rest:
|
||||||
|
try:
|
||||||
|
ob = getattr(ob, attr)
|
||||||
|
except AttributeError:
|
||||||
|
raise UnknownReference(refname)
|
||||||
|
|
||||||
|
# make sure we return a string instead of some various Python types
|
||||||
|
if isinstance(ob, IntType) or isinstance(ob, FloatType):
|
||||||
|
return str(ob)
|
||||||
|
if ob is None:
|
||||||
|
return ''
|
||||||
|
|
||||||
|
# string or a sequence
|
||||||
|
return ob
|
||||||
|
|
||||||
|
class _context:
|
||||||
|
"""A container for the execution context"""
|
||||||
|
|
||||||
|
class ArgCountSyntaxError(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class UnknownReference(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class NeedSequenceError(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
# --- standard test environment ---
|
||||||
|
def _test(argv):
|
||||||
|
import doctest, ezt
|
||||||
|
verbose = "-v" in argv
|
||||||
|
return doctest.testmod(ezt, verbose=verbose)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
# invoke unit test for this module:
|
||||||
|
import sys
|
||||||
|
sys.exit(_test(sys.argv)[0])
|
110
lib/popen.py
110
lib/popen.py
@@ -1,13 +1,13 @@
|
|||||||
#
|
#
|
||||||
# Copyright (C) 2000 The ViewCVS Group. All Rights Reserved.
|
# Copyright (C) 2000-2001 The ViewCVS Group. All Rights Reserved.
|
||||||
#
|
#
|
||||||
# By using this file, you agree to the terms and conditions set forth in
|
# By using this file, you agree to the terms and conditions set forth in
|
||||||
# the LICENSE.html file which can be found at the top level of the ViewCVS
|
# the LICENSE.html file which can be found at the top level of the ViewCVS
|
||||||
# distribution or at http://www.lyra.org/viewcvs/license-1.html.
|
# distribution or at http://viewcvs.sourceforge.net/license-1.html.
|
||||||
#
|
#
|
||||||
# Contact information:
|
# Contact information:
|
||||||
# Greg Stein, PO Box 760, Palo Alto, CA, 94302
|
# Greg Stein, PO Box 760, Palo Alto, CA, 94302
|
||||||
# gstein@lyra.org, http://www.lyra.org/viewcvs/
|
# gstein@lyra.org, http://viewcvs.sourceforge.net/
|
||||||
#
|
#
|
||||||
# -----------------------------------------------------------------------
|
# -----------------------------------------------------------------------
|
||||||
#
|
#
|
||||||
@@ -22,8 +22,13 @@
|
|||||||
#
|
#
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
def popen(cmd, args, mode, capture_err=1):
|
def popen(cmd, args, mode, capture_err=1):
|
||||||
|
# flush the stdio buffers since we are about to change the FD under them
|
||||||
|
sys.stdout.flush()
|
||||||
|
sys.stderr.flush()
|
||||||
|
|
||||||
r, w = os.pipe()
|
r, w = os.pipe()
|
||||||
pid = os.fork()
|
pid = os.fork()
|
||||||
if pid:
|
if pid:
|
||||||
@@ -45,6 +50,7 @@ def popen(cmd, args, mode, capture_err=1):
|
|||||||
# hook stdout/stderr to the "write" channel
|
# hook stdout/stderr to the "write" channel
|
||||||
os.dup2(w, 1)
|
os.dup2(w, 1)
|
||||||
# "close" stdin; the child shouldn't use it
|
# "close" stdin; the child shouldn't use it
|
||||||
|
### this isn't quite right... we may want the child to read from stdin
|
||||||
os.dup2(null, 0)
|
os.dup2(null, 0)
|
||||||
# what to do with errors?
|
# what to do with errors?
|
||||||
if capture_err:
|
if capture_err:
|
||||||
@@ -55,6 +61,7 @@ def popen(cmd, args, mode, capture_err=1):
|
|||||||
# hook stdin to the "read" channel
|
# hook stdin to the "read" channel
|
||||||
os.dup2(r, 0)
|
os.dup2(r, 0)
|
||||||
# "close" stdout/stderr; the child shouldn't use them
|
# "close" stdout/stderr; the child shouldn't use them
|
||||||
|
### this isn't quite right... we may want the child to write to these
|
||||||
os.dup2(null, 1)
|
os.dup2(null, 1)
|
||||||
os.dup2(null, 2)
|
os.dup2(null, 2)
|
||||||
|
|
||||||
@@ -64,11 +71,94 @@ def popen(cmd, args, mode, capture_err=1):
|
|||||||
os.close(w)
|
os.close(w)
|
||||||
|
|
||||||
# the stdin/stdout/stderr are all set up. exec the target
|
# the stdin/stdout/stderr are all set up. exec the target
|
||||||
|
try:
|
||||||
os.execvp(cmd, (cmd,) + tuple(args))
|
os.execvp(cmd, (cmd,) + tuple(args))
|
||||||
|
except:
|
||||||
|
# aid debugging, if the os.execvp above fails for some reason:
|
||||||
|
import string
|
||||||
|
print "<h2>exec failed:</h2><pre>", cmd, string.join(args), "</pre>"
|
||||||
|
raise
|
||||||
|
|
||||||
# crap. shouldn't be here.
|
# crap. shouldn't be here.
|
||||||
sys.exit(127)
|
sys.exit(127)
|
||||||
|
|
||||||
|
def pipe_cmds(cmds):
|
||||||
|
# flush the stdio buffers since we are about to change the FD under them
|
||||||
|
sys.stdout.flush()
|
||||||
|
sys.stderr.flush()
|
||||||
|
|
||||||
|
prev_r, parent_w = os.pipe()
|
||||||
|
|
||||||
|
null = os.open('/dev/null', os.O_RDWR)
|
||||||
|
|
||||||
|
for cmd in cmds[:-1]:
|
||||||
|
r, w = os.pipe()
|
||||||
|
pid = os.fork()
|
||||||
|
if not pid:
|
||||||
|
# in the child
|
||||||
|
|
||||||
|
# hook up stdin to the "read" channel
|
||||||
|
os.dup2(prev_r, 0)
|
||||||
|
|
||||||
|
# hook up stdout to the output channel
|
||||||
|
os.dup2(w, 1)
|
||||||
|
|
||||||
|
# toss errors
|
||||||
|
os.dup2(null, 2)
|
||||||
|
|
||||||
|
# close these extra descriptors
|
||||||
|
os.close(prev_r)
|
||||||
|
os.close(parent_w)
|
||||||
|
os.close(null)
|
||||||
|
os.close(r)
|
||||||
|
os.close(w)
|
||||||
|
|
||||||
|
# time to run the command
|
||||||
|
try:
|
||||||
|
os.execvp(cmd[0], cmd)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
sys.exit(127)
|
||||||
|
|
||||||
|
# in the parent
|
||||||
|
|
||||||
|
# we don't need these any more
|
||||||
|
os.close(prev_r)
|
||||||
|
os.close(w)
|
||||||
|
|
||||||
|
# the read channel of this pipe will feed into to the next command
|
||||||
|
prev_r = r
|
||||||
|
|
||||||
|
# no longer needed
|
||||||
|
os.close(null)
|
||||||
|
|
||||||
|
# done with most of the commands. set up the last command to write to stdout
|
||||||
|
pid = os.fork()
|
||||||
|
if not pid:
|
||||||
|
# in the child (the last command)
|
||||||
|
|
||||||
|
# hook up stdin to the "read" channel
|
||||||
|
os.dup2(prev_r, 0)
|
||||||
|
|
||||||
|
# close these extra descriptors
|
||||||
|
os.close(prev_r)
|
||||||
|
os.close(parent_w)
|
||||||
|
|
||||||
|
# run the last command
|
||||||
|
try:
|
||||||
|
os.execvp(cmds[-1][0], cmds[-1])
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
sys.exit(127)
|
||||||
|
|
||||||
|
# not needed any more
|
||||||
|
os.close(prev_r)
|
||||||
|
|
||||||
|
# write into the first pipe, wait on the final process
|
||||||
|
return _pipe(os.fdopen(parent_w, 'w'), pid)
|
||||||
|
|
||||||
|
|
||||||
class _pipe:
|
class _pipe:
|
||||||
"Wrapper for a file which can wait() on a child process at close time."
|
"Wrapper for a file which can wait() on a child process at close time."
|
||||||
@@ -77,10 +167,20 @@ class _pipe:
|
|||||||
self.file = file
|
self.file = file
|
||||||
self.child_pid = child_pid
|
self.child_pid = child_pid
|
||||||
|
|
||||||
def close(self):
|
def eof(self):
|
||||||
|
pid, status = os.waitpid(self.child_pid, os.WNOHANG)
|
||||||
|
if pid:
|
||||||
self.file.close()
|
self.file.close()
|
||||||
self.file = None
|
self.file = None
|
||||||
return os.waitpid(self.child_pid, 0)[1] or None
|
return status
|
||||||
|
return None
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
if self.file:
|
||||||
|
self.file.close()
|
||||||
|
self.file = None
|
||||||
|
return os.waitpid(self.child_pid, 0)[1]
|
||||||
|
return None
|
||||||
|
|
||||||
def __getattr__(self, name):
|
def __getattr__(self, name):
|
||||||
return getattr(self.file, name)
|
return getattr(self.file, name)
|
||||||
|
497
lib/query.py
497
lib/query.py
@@ -1,3 +1,4 @@
|
|||||||
|
#!/usr/bin/python
|
||||||
# -*- Mode: python -*-
|
# -*- Mode: python -*-
|
||||||
#
|
#
|
||||||
# Copyright (C) 2000 The ViewCVS Group. All Rights Reserved.
|
# Copyright (C) 2000 The ViewCVS Group. All Rights Reserved.
|
||||||
@@ -12,91 +13,419 @@
|
|||||||
#
|
#
|
||||||
# -----------------------------------------------------------------------
|
# -----------------------------------------------------------------------
|
||||||
#
|
#
|
||||||
|
# CGI script to process and display queries to CVSdb
|
||||||
|
#
|
||||||
|
# This script is part of the ViewCVS package. More information can be
|
||||||
|
# found at http://www.lyra.org/viewcvs/.
|
||||||
|
#
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
#
|
||||||
|
|
||||||
|
#########################################################################
|
||||||
|
#
|
||||||
|
# INSTALL-TIME CONFIGURATION
|
||||||
|
#
|
||||||
|
# These values will be set during the installation process. During
|
||||||
|
# development, they will remain None.
|
||||||
|
#
|
||||||
|
|
||||||
|
CONF_PATHNAME = None
|
||||||
|
|
||||||
|
#########################################################################
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import string
|
||||||
|
import cgi
|
||||||
import time
|
import time
|
||||||
import dbi
|
|
||||||
|
import cvsdb
|
||||||
|
import viewcvs
|
||||||
|
import ezt
|
||||||
|
|
||||||
|
class FormData:
|
||||||
|
def __init__(self, form):
|
||||||
|
self.valid = 0
|
||||||
|
|
||||||
|
self.repository = ""
|
||||||
|
self.branch = ""
|
||||||
|
self.directory = ""
|
||||||
|
self.file = ""
|
||||||
|
self.who = ""
|
||||||
|
self.sortby = ""
|
||||||
|
self.date = ""
|
||||||
|
self.hours = 0
|
||||||
|
|
||||||
|
self.decode_thyself(form)
|
||||||
|
|
||||||
|
def decode_thyself(self, form):
|
||||||
|
try:
|
||||||
|
self.repository = string.strip(form["repository"].value)
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
except TypeError:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
self.valid = 1
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.branch = string.strip(form["branch"].value)
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
except TypeError:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
self.valid = 1
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.directory = string.strip(form["directory"].value)
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
except TypeError:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
self.valid = 1
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.file = string.strip(form["file"].value)
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
except TypeError:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
self.valid = 1
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.who = string.strip(form["who"].value)
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
except TypeError:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
self.valid = 1
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.sortby = string.strip(form["sortby"].value)
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
except TypeError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.date = string.strip(form["date"].value)
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
except TypeError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.hours = int(form["hours"].value)
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
except TypeError:
|
||||||
|
pass
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
self.valid = 1
|
||||||
|
|
||||||
|
## returns a tuple-list (mod-str, string)
|
||||||
|
def listparse_string(str):
|
||||||
|
return_list = []
|
||||||
|
|
||||||
|
cmd = ""
|
||||||
|
temp = ""
|
||||||
|
escaped = 0
|
||||||
|
state = "eat leading whitespace"
|
||||||
|
|
||||||
|
for c in str:
|
||||||
|
## handle escaped charactors
|
||||||
|
if not escaped and c == "\\":
|
||||||
|
escaped = 1
|
||||||
|
continue
|
||||||
|
|
||||||
|
## strip leading white space
|
||||||
|
if state == "eat leading whitespace":
|
||||||
|
if c in string.whitespace:
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
state = "get command or data"
|
||||||
|
|
||||||
|
## parse to '"' or ","
|
||||||
|
if state == "get command or data":
|
||||||
|
|
||||||
|
## just add escaped charactors
|
||||||
|
if escaped:
|
||||||
|
escaped = 0
|
||||||
|
temp = temp + c
|
||||||
|
continue
|
||||||
|
|
||||||
|
## the data is in quotes after the command
|
||||||
|
elif c == "\"":
|
||||||
|
cmd = temp
|
||||||
|
temp = ""
|
||||||
|
state = "get quoted data"
|
||||||
|
continue
|
||||||
|
|
||||||
|
## this tells us there was no quoted data, therefore no
|
||||||
|
## command; add the command and start over
|
||||||
|
elif c == ",":
|
||||||
|
## strip ending whitespace on un-quoted data
|
||||||
|
temp = string.rstrip(temp)
|
||||||
|
return_list.append( ("", temp) )
|
||||||
|
temp = ""
|
||||||
|
state = "eat leading whitespace"
|
||||||
|
continue
|
||||||
|
|
||||||
|
## record the data
|
||||||
|
else:
|
||||||
|
temp = temp + c
|
||||||
|
continue
|
||||||
|
|
||||||
|
## parse until ending '"'
|
||||||
|
if state == "get quoted data":
|
||||||
|
|
||||||
|
## just add escaped charactors
|
||||||
|
if escaped:
|
||||||
|
escaped = 0
|
||||||
|
temp = temp + c
|
||||||
|
continue
|
||||||
|
|
||||||
|
## look for ending '"'
|
||||||
|
elif c == "\"":
|
||||||
|
return_list.append( (cmd, temp) )
|
||||||
|
cmd = ""
|
||||||
|
temp = ""
|
||||||
|
state = "eat comma after quotes"
|
||||||
|
continue
|
||||||
|
|
||||||
|
## record the data
|
||||||
|
else:
|
||||||
|
temp = temp + c
|
||||||
|
continue
|
||||||
|
|
||||||
|
## parse until ","
|
||||||
|
if state == "eat comma after quotes":
|
||||||
|
if c in string.whitespace:
|
||||||
|
continue
|
||||||
|
|
||||||
|
elif c == ",":
|
||||||
|
state = "eat leading whitespace"
|
||||||
|
continue
|
||||||
|
|
||||||
|
else:
|
||||||
|
print "format error"
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
if cmd or temp:
|
||||||
|
return_list.append((cmd, temp))
|
||||||
|
|
||||||
|
return return_list
|
||||||
|
|
||||||
|
def decode_command(cmd):
|
||||||
|
if cmd == "r":
|
||||||
|
return "regex"
|
||||||
|
elif cmd == "l":
|
||||||
|
return "like"
|
||||||
|
else:
|
||||||
|
return "exact"
|
||||||
|
|
||||||
|
def form_to_cvsdb_query(form_data):
|
||||||
|
query = cvsdb.CreateCheckinQuery()
|
||||||
|
|
||||||
|
if form_data.repository:
|
||||||
|
for cmd, str in listparse_string(form_data.repository):
|
||||||
|
cmd = decode_command(cmd)
|
||||||
|
query.SetRepository(str, cmd)
|
||||||
|
|
||||||
|
if form_data.branch:
|
||||||
|
for cmd, str in listparse_string(form_data.branch):
|
||||||
|
cmd = decode_command(cmd)
|
||||||
|
query.SetBranch(str, cmd)
|
||||||
|
|
||||||
|
if form_data.directory:
|
||||||
|
for cmd, str in listparse_string(form_data.directory):
|
||||||
|
cmd = decode_command(cmd)
|
||||||
|
query.SetDirectory(str, cmd)
|
||||||
|
|
||||||
|
if form_data.file:
|
||||||
|
for cmd, str in listparse_string(form_data.file):
|
||||||
|
cmd = decode_command(cmd)
|
||||||
|
query.SetFile(str, cmd)
|
||||||
|
|
||||||
|
if form_data.who:
|
||||||
|
for cmd, str in listparse_string(form_data.who):
|
||||||
|
cmd = decode_command(cmd)
|
||||||
|
query.SetAuthor(str, cmd)
|
||||||
|
|
||||||
|
if form_data.sortby == "author":
|
||||||
|
query.SetSortMethod("author")
|
||||||
|
elif form_data.sortby == "file":
|
||||||
|
query.SetSortMethod("file")
|
||||||
|
else:
|
||||||
|
query.SetSortMethod("date")
|
||||||
|
|
||||||
|
if form_data.date:
|
||||||
|
if form_data.date == "hours" and form_data.hours:
|
||||||
|
query.SetFromDateHoursAgo(form_data.hours)
|
||||||
|
elif form_data.date == "day":
|
||||||
|
query.SetFromDateDaysAgo(1)
|
||||||
|
elif form_data.date == "week":
|
||||||
|
query.SetFromDateDaysAgo(7)
|
||||||
|
elif form_data.date == "month":
|
||||||
|
query.SetFromDateDaysAgo(31)
|
||||||
|
|
||||||
|
return query
|
||||||
|
|
||||||
|
def cvsroot_name_from_path(cvsroot):
|
||||||
|
## we need to resolve the cvsroot path from the database
|
||||||
|
## to the name given to it in the viewcvs.conf file
|
||||||
|
for key, value in cfg.general.cvs_roots.items():
|
||||||
|
if value == cvsroot:
|
||||||
|
return key
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
def build_commit(desc, files):
|
||||||
|
ob = _item(num_files=len(files), files=[])
|
||||||
|
|
||||||
|
if desc:
|
||||||
|
ob.desc = string.replace(cgi.escape(desc), '\n', '<br>')
|
||||||
|
else:
|
||||||
|
ob.desc = ' '
|
||||||
|
|
||||||
|
for commit in files:
|
||||||
|
ctime = commit.GetTime()
|
||||||
|
if not ctime:
|
||||||
|
ctime = " ";
|
||||||
|
else:
|
||||||
|
ctime = time.strftime("%y/%m/%d %H:%M", time.localtime(ctime))
|
||||||
|
|
||||||
|
## make the file link
|
||||||
|
file = os.path.join(commit.GetDirectory(), commit.GetFile())
|
||||||
|
file_full_path = os.path.join(commit.GetRepository(), file)
|
||||||
|
|
||||||
|
## if we couldn't find the cvsroot path configured in the
|
||||||
|
## viewcvs.conf file, then don't make the link
|
||||||
|
cvsroot_name = cvsroot_name_from_path(commit.GetRepository())
|
||||||
|
if cvsroot_name:
|
||||||
|
flink = '<a href="viewcvs.cgi/%s?cvsroot=%s">%s</a>' \
|
||||||
|
% (file, cvsroot_name, file_full_path)
|
||||||
|
else:
|
||||||
|
flink = file_full_path
|
||||||
|
|
||||||
|
ob.files.append(_item(date=ctime,
|
||||||
|
author=commit.GetAuthor(),
|
||||||
|
link=flink,
|
||||||
|
rev=commit.GetRevision(),
|
||||||
|
branch=commit.GetBranch(),
|
||||||
|
plus=int(commit.GetPlusCount()),
|
||||||
|
minus=int(commit.GetMinusCount()),
|
||||||
|
))
|
||||||
|
|
||||||
|
return ob
|
||||||
|
|
||||||
|
def run_query(form_data):
|
||||||
|
query = form_to_cvsdb_query(form_data)
|
||||||
|
db = cvsdb.ConnectDatabaseReadOnly()
|
||||||
|
db.RunQuery(query)
|
||||||
|
|
||||||
|
if not query.commit_list:
|
||||||
|
return [ ]
|
||||||
|
|
||||||
|
commits = [ ]
|
||||||
|
files = [ ]
|
||||||
|
|
||||||
|
current_desc = query.commit_list[0].GetDescription()
|
||||||
|
for commit in query.commit_list:
|
||||||
|
desc = commit.GetDescription()
|
||||||
|
if current_desc == desc:
|
||||||
|
files.append(commit)
|
||||||
|
continue
|
||||||
|
|
||||||
|
commits.append(build_commit(current_desc, files))
|
||||||
|
|
||||||
|
files = [ commit ]
|
||||||
|
current_desc = desc
|
||||||
|
|
||||||
|
## add the last file group to the commit list
|
||||||
|
commits.append(build_commit(current_desc, files))
|
||||||
|
|
||||||
|
return commits
|
||||||
|
|
||||||
|
def handle_config():
|
||||||
|
viewcvs.handle_config()
|
||||||
|
global cfg
|
||||||
|
cfg = viewcvs.cfg
|
||||||
|
|
||||||
|
def main():
|
||||||
|
handle_config()
|
||||||
|
|
||||||
|
form = cgi.FieldStorage()
|
||||||
|
form_data = FormData(form)
|
||||||
|
|
||||||
|
if form_data.valid:
|
||||||
|
commits = run_query(form_data)
|
||||||
|
query = None
|
||||||
|
else:
|
||||||
|
commits = [ ]
|
||||||
|
query = 'skipped'
|
||||||
|
|
||||||
|
data = {
|
||||||
|
'cfg' : cfg,
|
||||||
|
'address' : cfg.general.address,
|
||||||
|
'vsn' : viewcvs.__version__,
|
||||||
|
|
||||||
|
'repository' : cgi.escape(form_data.repository, 1),
|
||||||
|
'branch' : cgi.escape(form_data.branch, 1),
|
||||||
|
'directory' : cgi.escape(form_data.directory, 1),
|
||||||
|
'file' : cgi.escape(form_data.file, 1),
|
||||||
|
'who' : cgi.escape(form_data.who, 1),
|
||||||
|
|
||||||
|
'sortby' : form_data.sortby,
|
||||||
|
'date' : form_data.date,
|
||||||
|
|
||||||
|
'query' : query,
|
||||||
|
'commits' : commits,
|
||||||
|
'num_commits' : len(commits),
|
||||||
|
}
|
||||||
|
|
||||||
|
if form_data.hours:
|
||||||
|
data['hours'] = form_data.hours
|
||||||
|
else:
|
||||||
|
data['hours'] = 2
|
||||||
|
|
||||||
|
template = ezt.Template()
|
||||||
|
template.parse_file(os.path.join(viewcvs.g_install_dir,
|
||||||
|
cfg.templates.query))
|
||||||
|
|
||||||
|
viewcvs.http_header()
|
||||||
|
|
||||||
|
# generate the page
|
||||||
|
template.generate(sys.stdout, data)
|
||||||
|
|
||||||
|
def run_cgi():
|
||||||
|
|
||||||
|
### be nice to share all this logic with viewcvs.run_cgi
|
||||||
|
|
||||||
|
try:
|
||||||
|
main()
|
||||||
|
except SystemExit, e:
|
||||||
|
# don't catch SystemExit (caused by sys.exit()). propagate the exit code
|
||||||
|
sys.exit(e[0])
|
||||||
|
except:
|
||||||
|
info = sys.exc_info()
|
||||||
|
viewcvs.http_header()
|
||||||
|
print '<html><head><title>Python Exception Occurred</title></head>'
|
||||||
|
print '<body bgcolor=white><h1>Python Exception Occurred</h1>'
|
||||||
|
import traceback
|
||||||
|
lines = apply(traceback.format_exception, info)
|
||||||
|
print '<pre>'
|
||||||
|
print cgi.escape(string.join(lines, ''))
|
||||||
|
print '</pre>'
|
||||||
|
viewcvs.html_footer()
|
||||||
|
|
||||||
|
|
||||||
## QueryEntry holds data on one match-type in the SQL database
|
class _item:
|
||||||
## match is: "exact", "like", or "regex"
|
def __init__(self, **kw):
|
||||||
|
vars(self).update(kw)
|
||||||
class QueryEntry:
|
|
||||||
def __init__(self, data, match):
|
|
||||||
self.data = data
|
|
||||||
self.match = match
|
|
||||||
|
|
||||||
|
|
||||||
## CheckinDatabaseQueryData is a object which contains the search parameters
|
|
||||||
## for a query to the CheckinDatabase
|
|
||||||
|
|
||||||
class CheckinDatabaseQuery:
|
|
||||||
def __init__(self):
|
|
||||||
## sorting
|
|
||||||
self.sort = "date"
|
|
||||||
|
|
||||||
## repository to query
|
|
||||||
self.repository_list = []
|
|
||||||
self.branch_list = []
|
|
||||||
self.directory_list = []
|
|
||||||
self.file_list = []
|
|
||||||
self.author_list = []
|
|
||||||
|
|
||||||
## date range in DBI 2.0 timedate objects
|
|
||||||
self.from_date = None
|
|
||||||
self.to_date = None
|
|
||||||
|
|
||||||
## list of commits -- filled in by CVS query
|
|
||||||
self.commit_list = []
|
|
||||||
|
|
||||||
## commit_cb provides a callback for commits as they
|
|
||||||
## are added
|
|
||||||
self.commit_cb = None
|
|
||||||
|
|
||||||
def SetRepository(self, repository, match = "exact"):
|
|
||||||
self.repository_list.append(QueryEntry(repository, match))
|
|
||||||
|
|
||||||
def SetBranch(self, branch, match = "exact"):
|
|
||||||
self.branch_list.append(QueryEntry(branch, match))
|
|
||||||
|
|
||||||
def SetDirectory(self, directory, match = "exact"):
|
|
||||||
self.directory_list.append(QueryEntry(directory, match))
|
|
||||||
|
|
||||||
def SetFile(self, file, match = "exact"):
|
|
||||||
self.file_list.append(QueryEntry(file, match))
|
|
||||||
|
|
||||||
def SetAuthor(self, author, match = "exact"):
|
|
||||||
self.author_list.append(QueryEntry(author, match))
|
|
||||||
|
|
||||||
def SetSortMethod(self, sort):
|
|
||||||
self.sort = sort
|
|
||||||
|
|
||||||
def SetFromDateObject(self, ticks):
|
|
||||||
self.from_date = dbi.TimestampFromTicks(ticks)
|
|
||||||
|
|
||||||
def SetToDateObject(self, ticks):
|
|
||||||
self.to_date = dbi.TimestampFromTicks(ticks)
|
|
||||||
|
|
||||||
def SetFromDateHoursAgo(self, hours_ago):
|
|
||||||
ticks = time.time() - (3600 * hours_ago)
|
|
||||||
self.from_date = dbi.TimestampFromTicks(ticks)
|
|
||||||
|
|
||||||
def SetFromDateDaysAgo(self, days_ago):
|
|
||||||
ticks = time.time() - (86400 * days_ago)
|
|
||||||
self.from_date = dbi.TimestampFromTicks(ticks)
|
|
||||||
|
|
||||||
def SetToDateDaysAgo(self, days_ago):
|
|
||||||
ticks = time.time() - (86400 * days_ago)
|
|
||||||
self.to_date = dbi.TimestampFromTicks(ticks)
|
|
||||||
|
|
||||||
def AddCommit(self, commit):
|
|
||||||
self.commit_list.append(commit)
|
|
||||||
if self.commit_cb:
|
|
||||||
self.commit_cb(commit)
|
|
||||||
|
|
||||||
def SetCommitCB(self, callback):
|
|
||||||
self.commit_cb = callback
|
|
||||||
|
|
||||||
|
|
||||||
## entrypoints
|
|
||||||
def CreateCheckinQuery():
|
|
||||||
return CheckinDatabaseQuery()
|
|
||||||
|
441
lib/rcsparse.py
Normal file
441
lib/rcsparse.py
Normal file
@@ -0,0 +1,441 @@
|
|||||||
|
#
|
||||||
|
# Copyright (C) 2000-2001 The ViewCVS Group. All Rights Reserved.
|
||||||
|
#
|
||||||
|
# By using this file, you agree to the terms and conditions set forth in
|
||||||
|
# the LICENSE.html file which can be found at the top level of the ViewCVS
|
||||||
|
# distribution or at http://viewcvs.sourceforge.net/license-1.html.
|
||||||
|
#
|
||||||
|
# Contact information:
|
||||||
|
# Greg Stein, PO Box 760, Palo Alto, CA, 94302
|
||||||
|
# gstein@lyra.org, http://viewcvs.sourceforge.net/
|
||||||
|
#
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
#
|
||||||
|
# This software is being maintained as part of the ViewCVS project.
|
||||||
|
# Information is available at:
|
||||||
|
# http://viewcvs.sourceforge.net/
|
||||||
|
#
|
||||||
|
# This file was originally based on portions of the blame.py script by
|
||||||
|
# Curt Hagenlocher.
|
||||||
|
#
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
|
||||||
|
import string
|
||||||
|
import time
|
||||||
|
|
||||||
|
|
||||||
|
class _TokenStream:
|
||||||
|
token_term = string.whitespace + ';'
|
||||||
|
|
||||||
|
# the algorithm is about the same speed for any CHUNK_SIZE chosen.
|
||||||
|
# grab a good-sized chunk, but not too large to overwhelm memory.
|
||||||
|
CHUNK_SIZE = 100000
|
||||||
|
|
||||||
|
# CHUNK_SIZE = 5 # for debugging, make the function grind...
|
||||||
|
|
||||||
|
def __init__(self, file):
|
||||||
|
self.rcsfile = file
|
||||||
|
self.idx = 0
|
||||||
|
self.buf = self.rcsfile.read(self.CHUNK_SIZE)
|
||||||
|
if self.buf == '':
|
||||||
|
raise RuntimeError, 'EOF'
|
||||||
|
|
||||||
|
def get(self):
|
||||||
|
"Get the next token from the RCS file."
|
||||||
|
|
||||||
|
# Note: we can afford to loop within Python, examining individual
|
||||||
|
# characters. For the whitespace and tokens, the number of iterations
|
||||||
|
# is typically quite small. Thus, a simple iterative loop will beat
|
||||||
|
# out more complex solutions.
|
||||||
|
|
||||||
|
buf = self.buf
|
||||||
|
idx = self.idx
|
||||||
|
|
||||||
|
while 1:
|
||||||
|
if idx == len(buf):
|
||||||
|
buf = self.rcsfile.read(self.CHUNK_SIZE)
|
||||||
|
if buf == '':
|
||||||
|
# signal EOF by returning None as the token
|
||||||
|
del self.buf # so we fail if get() is called again
|
||||||
|
return None
|
||||||
|
idx = 0
|
||||||
|
|
||||||
|
if buf[idx] not in string.whitespace:
|
||||||
|
break
|
||||||
|
|
||||||
|
idx = idx + 1
|
||||||
|
|
||||||
|
if buf[idx] == ';':
|
||||||
|
self.buf = buf
|
||||||
|
self.idx = idx + 1
|
||||||
|
return ';'
|
||||||
|
|
||||||
|
if buf[idx] != '@':
|
||||||
|
end = idx + 1
|
||||||
|
token = ''
|
||||||
|
while 1:
|
||||||
|
# find token characters in the current buffer
|
||||||
|
while end < len(buf) and buf[end] not in self.token_term:
|
||||||
|
end = end + 1
|
||||||
|
token = token + buf[idx:end]
|
||||||
|
|
||||||
|
if end < len(buf):
|
||||||
|
# we stopped before the end, so we have a full token
|
||||||
|
idx = end
|
||||||
|
break
|
||||||
|
|
||||||
|
# we stopped at the end of the buffer, so we may have a partial token
|
||||||
|
buf = self.rcsfile.read(self.CHUNK_SIZE)
|
||||||
|
idx = end = 0
|
||||||
|
|
||||||
|
self.buf = buf
|
||||||
|
self.idx = idx
|
||||||
|
return token
|
||||||
|
|
||||||
|
# a "string" which starts with the "@" character. we'll skip it when we
|
||||||
|
# search for content.
|
||||||
|
idx = idx + 1
|
||||||
|
|
||||||
|
chunks = [ ]
|
||||||
|
|
||||||
|
while 1:
|
||||||
|
if idx == len(buf):
|
||||||
|
idx = 0
|
||||||
|
buf = self.rcsfile.read(self.CHUNK_SIZE)
|
||||||
|
if buf == '':
|
||||||
|
raise RuntimeError, 'EOF'
|
||||||
|
i = string.find(buf, '@', idx)
|
||||||
|
if i == -1:
|
||||||
|
chunks.append(buf[idx:])
|
||||||
|
idx = len(buf)
|
||||||
|
continue
|
||||||
|
if i == len(buf) - 1:
|
||||||
|
chunks.append(buf[idx:i])
|
||||||
|
idx = 0
|
||||||
|
buf = '@' + self.rcsfile.read(self.CHUNK_SIZE)
|
||||||
|
if buf == '@':
|
||||||
|
raise RuntimeError, 'EOF'
|
||||||
|
continue
|
||||||
|
if buf[i + 1] == '@':
|
||||||
|
chunks.append(buf[idx:i+1])
|
||||||
|
idx = i + 2
|
||||||
|
continue
|
||||||
|
|
||||||
|
chunks.append(buf[idx:i])
|
||||||
|
|
||||||
|
self.buf = buf
|
||||||
|
self.idx = i + 1
|
||||||
|
|
||||||
|
return string.join(chunks, '')
|
||||||
|
|
||||||
|
# _get = get
|
||||||
|
# def get(self):
|
||||||
|
token = self._get()
|
||||||
|
print 'T:', `token`
|
||||||
|
return token
|
||||||
|
|
||||||
|
def match(self, match):
|
||||||
|
"Try to match the next token from the input buffer."
|
||||||
|
|
||||||
|
token = self.get()
|
||||||
|
if token != match:
|
||||||
|
raise RuntimeError, ('Unexpected parsing error in RCS file.\n' +
|
||||||
|
'Expected token: %s, but saw: %s' % (match, token))
|
||||||
|
|
||||||
|
def unget(self, token):
|
||||||
|
"Put this token back, for the next get() to return."
|
||||||
|
|
||||||
|
# Override the class' .get method with a function which clears the
|
||||||
|
# overridden method then returns the pushed token. Since this function
|
||||||
|
# will not be looked up via the class mechanism, it should be a "normal"
|
||||||
|
# function, meaning it won't have "self" automatically inserted.
|
||||||
|
# Therefore, we need to pass both self and the token thru via defaults.
|
||||||
|
|
||||||
|
# note: we don't put this into the input buffer because it may have been
|
||||||
|
# @-unescaped already.
|
||||||
|
|
||||||
|
def give_it_back(self=self, token=token):
|
||||||
|
del self.get
|
||||||
|
return token
|
||||||
|
|
||||||
|
self.get = give_it_back
|
||||||
|
|
||||||
|
class Parser:
|
||||||
|
|
||||||
|
def parse_rcs_admin(self):
|
||||||
|
while 1:
|
||||||
|
# Read initial token at beginning of line
|
||||||
|
token = self.ts.get()
|
||||||
|
|
||||||
|
# We're done once we reach the description of the RCS tree
|
||||||
|
if token[0] in string.digits:
|
||||||
|
self.ts.unget(token)
|
||||||
|
return
|
||||||
|
|
||||||
|
if token == "head":
|
||||||
|
self.sink.set_head_revision(self.ts.get())
|
||||||
|
self.ts.match(';')
|
||||||
|
elif token == "branch":
|
||||||
|
self.sink.set_principal_branch(self.ts.get())
|
||||||
|
self.ts.match(';')
|
||||||
|
elif token == "symbols":
|
||||||
|
while 1:
|
||||||
|
tag = self.ts.get()
|
||||||
|
if tag == ';':
|
||||||
|
break
|
||||||
|
(tag_name, tag_rev) = string.split(tag, ':')
|
||||||
|
self.sink.define_tag(tag_name, tag_rev)
|
||||||
|
elif token == "comment":
|
||||||
|
self.sink.set_comment(self.ts.get())
|
||||||
|
self.ts.match(';')
|
||||||
|
|
||||||
|
# Ignore all these other fields - We don't care about them. Also chews
|
||||||
|
# up "newphrase".
|
||||||
|
elif token in ("locks", "strict", "expand", "access"):
|
||||||
|
while 1:
|
||||||
|
tag = self.ts.get()
|
||||||
|
if tag == ';':
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
pass
|
||||||
|
# warn("Unexpected RCS token: $token\n")
|
||||||
|
|
||||||
|
raise RuntimeError, "Unexpected EOF";
|
||||||
|
|
||||||
|
def parse_rcs_tree(self):
|
||||||
|
while 1:
|
||||||
|
revision = self.ts.get()
|
||||||
|
|
||||||
|
# End of RCS tree description ?
|
||||||
|
if revision == 'desc':
|
||||||
|
self.ts.unget(revision)
|
||||||
|
return
|
||||||
|
|
||||||
|
# Parse date
|
||||||
|
self.ts.match('date')
|
||||||
|
date = self.ts.get()
|
||||||
|
self.ts.match(';')
|
||||||
|
|
||||||
|
# Convert date into timestamp
|
||||||
|
date_fields = string.split(date, '.') + ['0', '0', '0']
|
||||||
|
date_fields = map(string.atoi, date_fields)
|
||||||
|
if date_fields[0] < 100:
|
||||||
|
date_fields[0] = date_fields[0] + 1900
|
||||||
|
timestamp = time.mktime(tuple(date_fields))
|
||||||
|
|
||||||
|
# Parse author
|
||||||
|
self.ts.match('author')
|
||||||
|
author = self.ts.get()
|
||||||
|
self.ts.match(';')
|
||||||
|
|
||||||
|
# Parse state
|
||||||
|
self.ts.match('state')
|
||||||
|
state = ''
|
||||||
|
while 1:
|
||||||
|
token = self.ts.get()
|
||||||
|
if token == ';':
|
||||||
|
break
|
||||||
|
state = state + token + ' '
|
||||||
|
state = state[:-1] # toss the trailing space
|
||||||
|
|
||||||
|
# Parse branches
|
||||||
|
self.ts.match('branches')
|
||||||
|
branches = [ ]
|
||||||
|
while 1:
|
||||||
|
token = self.ts.get()
|
||||||
|
if token == ';':
|
||||||
|
break
|
||||||
|
branches.append(token)
|
||||||
|
|
||||||
|
# Parse revision of next delta in chain
|
||||||
|
self.ts.match('next')
|
||||||
|
next = self.ts.get()
|
||||||
|
if next == ';':
|
||||||
|
next = None
|
||||||
|
else:
|
||||||
|
self.ts.match(';')
|
||||||
|
|
||||||
|
# there are some files with extra tags in them. for example:
|
||||||
|
# owner 640;
|
||||||
|
# group 15;
|
||||||
|
# permissions 644;
|
||||||
|
# hardlinks @configure.in@;
|
||||||
|
# this is "newphrase" in RCSFILE(5). we just want to skip over these.
|
||||||
|
while 1:
|
||||||
|
token = self.ts.get()
|
||||||
|
if token == 'desc' or token[0] in string.digits:
|
||||||
|
self.ts.unget(token)
|
||||||
|
break
|
||||||
|
# consume everything up to the semicolon
|
||||||
|
while self.ts.get() != ';':
|
||||||
|
pass
|
||||||
|
|
||||||
|
self.sink.define_revision(revision, timestamp, author, state, branches,
|
||||||
|
next)
|
||||||
|
|
||||||
|
def parse_rcs_description(self):
|
||||||
|
self.ts.match('desc')
|
||||||
|
self.sink.set_description(self.ts.get())
|
||||||
|
|
||||||
|
def parse_rcs_deltatext(self):
|
||||||
|
while 1:
|
||||||
|
revision = self.ts.get()
|
||||||
|
if revision is None:
|
||||||
|
# EOF
|
||||||
|
break
|
||||||
|
self.ts.match('log')
|
||||||
|
log = self.ts.get()
|
||||||
|
### need to add code to chew up "newphrase"
|
||||||
|
self.ts.match('text')
|
||||||
|
text = self.ts.get()
|
||||||
|
self.sink.set_revision_info(revision, log, text)
|
||||||
|
|
||||||
|
def parse(self, file, sink):
|
||||||
|
self.ts = _TokenStream(file)
|
||||||
|
self.sink = sink
|
||||||
|
|
||||||
|
self.parse_rcs_admin()
|
||||||
|
self.parse_rcs_tree()
|
||||||
|
|
||||||
|
# many sinks want to know when the tree has been completed so they can
|
||||||
|
# do some work to prep for the arrival of the deltatext
|
||||||
|
self.sink.tree_completed()
|
||||||
|
|
||||||
|
self.parse_rcs_description()
|
||||||
|
self.parse_rcs_deltatext()
|
||||||
|
|
||||||
|
# easiest for us to tell the sink it is done, rather than worry about
|
||||||
|
# higher level software doing it.
|
||||||
|
self.sink.parse_completed()
|
||||||
|
|
||||||
|
self.ts = self.sink = None
|
||||||
|
|
||||||
|
|
||||||
|
class Sink:
|
||||||
|
def set_head_revision(self, revision):
|
||||||
|
pass
|
||||||
|
def set_principal_branch(self, branch_name):
|
||||||
|
pass
|
||||||
|
def define_tag(self, name, revision):
|
||||||
|
pass
|
||||||
|
def set_comment(self, comment):
|
||||||
|
pass
|
||||||
|
def set_description(self, description):
|
||||||
|
pass
|
||||||
|
def define_revision(self, revision, timestamp, author, state,
|
||||||
|
branches, next):
|
||||||
|
pass
|
||||||
|
def set_revision_info(self, revision, log, text):
|
||||||
|
pass
|
||||||
|
def tree_completed(self):
|
||||||
|
pass
|
||||||
|
def parse_completed(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------------
|
||||||
|
#
|
||||||
|
# TESTING AND DEBUGGING TOOLS
|
||||||
|
#
|
||||||
|
|
||||||
|
class DebugSink:
|
||||||
|
def set_head_revision(self, revision):
|
||||||
|
print 'head:', revision
|
||||||
|
|
||||||
|
def set_principal_branch(self, branch_name):
|
||||||
|
print 'branch:', branch_name
|
||||||
|
|
||||||
|
def define_tag(self, name, revision):
|
||||||
|
print 'tag:', name, '=', revision
|
||||||
|
|
||||||
|
def set_comment(self, comment):
|
||||||
|
print 'comment:', comment
|
||||||
|
|
||||||
|
def set_description(self, description):
|
||||||
|
print 'description:', description
|
||||||
|
|
||||||
|
def define_revision(self, revision, timestamp, author, state,
|
||||||
|
branches, next):
|
||||||
|
print 'revision:', revision
|
||||||
|
print ' timestamp:', timestamp
|
||||||
|
print ' author:', author
|
||||||
|
print ' state:', state
|
||||||
|
print ' branches:', branches
|
||||||
|
print ' next:', next
|
||||||
|
|
||||||
|
def set_revision_info(self, revision, log, text):
|
||||||
|
print 'revision:', revision
|
||||||
|
print ' log:', log
|
||||||
|
print ' text:', text[:100], '...'
|
||||||
|
|
||||||
|
class DumpSink:
|
||||||
|
"""Dump all the parse information directly to stdout.
|
||||||
|
|
||||||
|
The output is relatively unformatted and untagged. It is intended as a
|
||||||
|
raw dump of the data in the RCS file. A copy can be saved, then changes
|
||||||
|
made to the parsing engine, then a comparison of the new output against
|
||||||
|
the old output.
|
||||||
|
"""
|
||||||
|
def __init__(self):
|
||||||
|
global sha
|
||||||
|
import sha
|
||||||
|
|
||||||
|
def set_head_revision(self, revision):
|
||||||
|
print revision
|
||||||
|
|
||||||
|
def set_principal_branch(self, branch_name):
|
||||||
|
print branch_name
|
||||||
|
|
||||||
|
def define_tag(self, name, revision):
|
||||||
|
print name, revision
|
||||||
|
|
||||||
|
def set_comment(self, comment):
|
||||||
|
print comment
|
||||||
|
|
||||||
|
def set_description(self, description):
|
||||||
|
print description
|
||||||
|
|
||||||
|
def define_revision(self, revision, timestamp, author, state,
|
||||||
|
branches, next):
|
||||||
|
print revision, timestamp, author, state, branches, next
|
||||||
|
|
||||||
|
def set_revision_info(self, revision, log, text):
|
||||||
|
print revision, sha.new(log).hexdigest(), sha.new(text).hexdigest()
|
||||||
|
|
||||||
|
def tree_completed(self):
|
||||||
|
print 'tree_completed'
|
||||||
|
|
||||||
|
def parse_completed(self):
|
||||||
|
print 'parse_completed'
|
||||||
|
|
||||||
|
def dump_file(fname):
|
||||||
|
Parser().parse(open(fname), DumpSink())
|
||||||
|
|
||||||
|
def time_file(fname):
|
||||||
|
import time
|
||||||
|
p = Parser().parse
|
||||||
|
f = open(fname)
|
||||||
|
s = Sink()
|
||||||
|
t = time.time()
|
||||||
|
p(f, s)
|
||||||
|
t = time.time() - t
|
||||||
|
print t
|
||||||
|
|
||||||
|
def _usage():
|
||||||
|
print 'This is normally a module for importing, but it has a couple'
|
||||||
|
print 'features for testing as an executable script.'
|
||||||
|
print 'USAGE: %s COMMAND filename,v' % sys.argv[0]
|
||||||
|
print ' where COMMAND is one of:'
|
||||||
|
print ' dump: filename is "dumped" to stdout'
|
||||||
|
print ' time: filename is parsed with the time written to stdout'
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
import sys
|
||||||
|
if len(sys.argv) != 3:
|
||||||
|
usage()
|
||||||
|
if sys.argv[1] == 'dump':
|
||||||
|
dump_file(sys.argv[2])
|
||||||
|
elif sys.argv[1] == 'time':
|
||||||
|
time_file(sys.argv[2])
|
||||||
|
else:
|
||||||
|
usage()
|
43
lib/rlog.py
43
lib/rlog.py
@@ -1,36 +1,22 @@
|
|||||||
# -*- Mode: python -*-
|
# -*- Mode: python -*-
|
||||||
#
|
#
|
||||||
# Copyright (C) 2000 The ViewCVS Group. All Rights Reserved.
|
# Copyright (C) 2000-2001 The ViewCVS Group. All Rights Reserved.
|
||||||
#
|
#
|
||||||
# By using this file, you agree to the terms and conditions set forth in
|
# By using this file, you agree to the terms and conditions set forth in
|
||||||
# the LICENSE.html file which can be found at the top level of the ViewCVS
|
# the LICENSE.html file which can be found at the top level of the ViewCVS
|
||||||
# distribution or at http://www.lyra.org/viewcvs/license-1.html.
|
# distribution or at http://viewcvs.sourceforge.net/license-1.html.
|
||||||
#
|
#
|
||||||
# Contact information:
|
# Contact information:
|
||||||
# Greg Stein, PO Box 760, Palo Alto, CA, 94302
|
# Greg Stein, PO Box 760, Palo Alto, CA, 94302
|
||||||
# gstein@lyra.org, http://www.lyra.org/viewcvs/
|
# gstein@lyra.org, http://viewcvs.sourceforge.net/
|
||||||
#
|
#
|
||||||
# -----------------------------------------------------------------------
|
# -----------------------------------------------------------------------
|
||||||
#
|
#
|
||||||
|
|
||||||
#########################################################################
|
import os
|
||||||
#
|
import string
|
||||||
# INSTALL-TIME CONFIGURATION
|
import re
|
||||||
#
|
import time
|
||||||
# These values will be set during the installation process. During
|
|
||||||
# development, they will remain None.
|
|
||||||
#
|
|
||||||
|
|
||||||
CONF_PATHNAME = None
|
|
||||||
|
|
||||||
#########################################################################
|
|
||||||
|
|
||||||
import os, sys, string, re, time, config
|
|
||||||
|
|
||||||
## load configuration file, the data is used globally here
|
|
||||||
cfg = config.Config()
|
|
||||||
cfg.set_defaults()
|
|
||||||
cfg.load_config(CONF_PATHNAME)
|
|
||||||
|
|
||||||
|
|
||||||
## RLogOutputParser uses the output of rlog to build a list of Commit
|
## RLogOutputParser uses the output of rlog to build a list of Commit
|
||||||
@@ -94,7 +80,7 @@ class RLogEntry:
|
|||||||
class RLog:
|
class RLog:
|
||||||
"Provides a alternative file-like interface for running 'rlog'."
|
"Provides a alternative file-like interface for running 'rlog'."
|
||||||
|
|
||||||
def __init__(self, filename, revision, date):
|
def __init__(self, cfg, filename, revision, date):
|
||||||
self.filename = self.fix_filename(filename)
|
self.filename = self.fix_filename(filename)
|
||||||
self.checkout_filename = self.create_checkout_filename(self.filename)
|
self.checkout_filename = self.create_checkout_filename(self.filename)
|
||||||
self.revision = revision
|
self.revision = revision
|
||||||
@@ -245,7 +231,9 @@ class RLogOutputParser:
|
|||||||
def parse_one_rlog_entry(self):
|
def parse_one_rlog_entry(self):
|
||||||
## revision line/first line
|
## revision line/first line
|
||||||
line = self.rlog.readline()
|
line = self.rlog.readline()
|
||||||
if not line:
|
# Since FreeBSD's rlog outputs extra "---...---\n" before
|
||||||
|
# "===...===\n", _rlog_end may be occured here.
|
||||||
|
if not line or line == _rlog_end:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
## revision
|
## revision
|
||||||
@@ -322,7 +310,10 @@ class RLogOutputParser:
|
|||||||
gmt_time = \
|
gmt_time = \
|
||||||
time.mktime((year, month, day, hour, minute, second, 0, 0, -1))
|
time.mktime((year, month, day, hour, minute, second, 0, 0, -1))
|
||||||
|
|
||||||
if time.daylight:
|
if time.localtime(gmt_time)[8] == 1:
|
||||||
|
# dst time active?
|
||||||
|
# XXX: This is still wrong in those both nights,
|
||||||
|
# where the switch between DST and normal time occurs.
|
||||||
gmt_time = gmt_time - time.altzone
|
gmt_time = gmt_time - time.altzone
|
||||||
else:
|
else:
|
||||||
gmt_time = gmt_time - time.timezone
|
gmt_time = gmt_time - time.timezone
|
||||||
@@ -342,7 +333,7 @@ class RLogOutputParser:
|
|||||||
|
|
||||||
## entrypoints
|
## entrypoints
|
||||||
|
|
||||||
def GetRLogData(path, revision = '', date = ''):
|
def GetRLogData(cfg, path, revision = '', date = ''):
|
||||||
rlog = RLog(path, revision, date)
|
rlog = RLog(cfg, path, revision, date)
|
||||||
rlog_parser = RLogOutputParser(rlog)
|
rlog_parser = RLogOutputParser(rlog)
|
||||||
return rlog_parser.rlog_data
|
return rlog_parser.rlog_data
|
||||||
|
2614
lib/viewcvs.py
Normal file
2614
lib/viewcvs.py
Normal file
File diff suppressed because it is too large
Load Diff
507
standalone.py
Executable file
507
standalone.py
Executable file
@@ -0,0 +1,507 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# $Id$
|
||||||
|
# vim:sw=4:ts=4:et:nowrap
|
||||||
|
# [Emacs: -*- python -*-]
|
||||||
|
#
|
||||||
|
# Copyright (C) 1999-2001 The ViewCVS Group. All Rights Reserved.
|
||||||
|
#
|
||||||
|
# By using this file, you agree to the terms and conditions set forth in
|
||||||
|
# the LICENSE.html file which can be found at the top level of the ViewCVS
|
||||||
|
# distribution or at http://viewcvs.sourceforge.net/license-1.html.
|
||||||
|
#
|
||||||
|
# Contact information:
|
||||||
|
# This file: Peter Funk, Oldenburger Str.86, 27777 Ganderkesee, Germany
|
||||||
|
# ViewCVS project: Greg Stein, PO Box 760, Palo Alto, CA, 94302
|
||||||
|
# gstein@lyra.org, http://viewcvs.sourceforge.net/
|
||||||
|
#
|
||||||
|
# Note: this module is designed to deploy instantly and run under any
|
||||||
|
# version of Python from 1.5 and up. That's why some 2.0 features
|
||||||
|
# (like string methods) are conspicuously avoided.
|
||||||
|
|
||||||
|
# XXX Security issues?
|
||||||
|
|
||||||
|
"""Run "standalone.py -p <port>" to start an HTTP server on a given port
|
||||||
|
on the local machine to generate ViewCVS web pages.
|
||||||
|
"""
|
||||||
|
|
||||||
|
__author__ = "Peter Funk <pf@artcom-gmbh.de>"
|
||||||
|
__date__ = "11 November 2001"
|
||||||
|
__version__ = "$Revision$"
|
||||||
|
__credits__ = """Guido van Rossum, for an excellent programming language.
|
||||||
|
Greg Stein, for writing ViewCVS in the first place.
|
||||||
|
Ka-Ping Yee, for the GUI code and the framework stolen from pydoc.py.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# INSTALL-TIME CONFIGURATION
|
||||||
|
#
|
||||||
|
# This value will be set during the installation process. During
|
||||||
|
# development, it will remain None.
|
||||||
|
#
|
||||||
|
|
||||||
|
LIBRARY_DIR = None
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
import string
|
||||||
|
import urllib
|
||||||
|
import socket
|
||||||
|
import select
|
||||||
|
import BaseHTTPServer
|
||||||
|
|
||||||
|
class Options:
|
||||||
|
port = 7467 # default TCP/IP port used for the server
|
||||||
|
start_gui = 0 # No GUI unless requested.
|
||||||
|
repository = None # use default repository specified in config
|
||||||
|
|
||||||
|
# --- web browser interface: ----------------------------------------------
|
||||||
|
|
||||||
|
def serve(port, callback=None):
|
||||||
|
"""start a HTTP server on the given port. call 'callback' when the
|
||||||
|
server is ready to serve"""
|
||||||
|
|
||||||
|
class ViewCVS_Handler(BaseHTTPServer.BaseHTTPRequestHandler):
|
||||||
|
|
||||||
|
def do_GET(self):
|
||||||
|
"""Serve a GET request."""
|
||||||
|
if not self.path or self.path == "/":
|
||||||
|
self.redirect()
|
||||||
|
elif self.is_viewcvs():
|
||||||
|
self.run_viewcvs()
|
||||||
|
elif self.path[:7] == "/icons/":
|
||||||
|
# XXX icon type should not be hardcoded to GIF:
|
||||||
|
self.send_response(200)
|
||||||
|
self.send_header("Content-type", "image/gif")
|
||||||
|
self.end_headers()
|
||||||
|
apache_icons.serve_icon(self.path, self.wfile)
|
||||||
|
else:
|
||||||
|
self.send_error(404)
|
||||||
|
|
||||||
|
def do_POST(self):
|
||||||
|
"""Serve a POST request."""
|
||||||
|
if self.is_viewcvs():
|
||||||
|
self.run_viewcvs()
|
||||||
|
else:
|
||||||
|
self.send_error(501, "Can only POST to viewcvs")
|
||||||
|
|
||||||
|
def is_viewcvs(self):
|
||||||
|
"""Check whether self.path matches the hardcoded ScriptAlias
|
||||||
|
/viewcvs"""
|
||||||
|
if self.path[:8] == "/viewcvs":
|
||||||
|
return 1
|
||||||
|
return 0
|
||||||
|
|
||||||
|
def redirect(self):
|
||||||
|
"""redirect the browser to the viewcvs URL"""
|
||||||
|
self.send_response(301, "moved (redirection follows)")
|
||||||
|
self.send_header("Content-type", "text/html")
|
||||||
|
self.send_header("Location", self.server.url + 'viewcvs/')
|
||||||
|
self.end_headers()
|
||||||
|
self.wfile.write("""<html>
|
||||||
|
<head>
|
||||||
|
<meta http-equiv="refresh" content="1; URL=%s">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Redirection to <a href="%s">ViewCVS</a></h1>
|
||||||
|
Wait a second. You will be automatically redirected to <b>ViewCVS</b>.
|
||||||
|
If this doesn't work, please click on the link above.
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
""" % tuple([self.server.url + "viewcvs/"]*2))
|
||||||
|
|
||||||
|
def run_viewcvs(self):
|
||||||
|
"""This is a quick and dirty cut'n'rape from Pythons
|
||||||
|
standard library module CGIHTTPServer."""
|
||||||
|
assert self.path[:8] == "/viewcvs"
|
||||||
|
viewcvs_url, rest = self.server.url[:-1]+"/viewcvs", self.path[8:]
|
||||||
|
i = string.rfind(rest, '?')
|
||||||
|
if i >= 0:
|
||||||
|
rest, query = rest[:i], rest[i+1:]
|
||||||
|
else:
|
||||||
|
query = ''
|
||||||
|
i = string.find(rest, '/')
|
||||||
|
if i >= 0:
|
||||||
|
script, rest = rest[:i], rest[i:]
|
||||||
|
else:
|
||||||
|
script, rest = rest, ''
|
||||||
|
scriptname = viewcvs_url + script
|
||||||
|
# sys.stderr.write("Debug: '"+scriptname+"' '"+rest+"' '"+query+"'\n")
|
||||||
|
env = os.environ
|
||||||
|
# Since we're going to modify the env in the parent, provide empty
|
||||||
|
# values to override previously set values
|
||||||
|
for k in ('QUERY_STRING', 'REMOTE_HOST', 'CONTENT_LENGTH',
|
||||||
|
'HTTP_USER_AGENT', 'HTTP_COOKIE'):
|
||||||
|
if env.has_key(k):
|
||||||
|
env[k] = ""
|
||||||
|
# XXX Much of the following could be prepared ahead of time!
|
||||||
|
env['SERVER_SOFTWARE'] = self.version_string()
|
||||||
|
env['SERVER_NAME'] = self.server.server_name
|
||||||
|
env['GATEWAY_INTERFACE'] = 'CGI/1.1'
|
||||||
|
env['SERVER_PROTOCOL'] = self.protocol_version
|
||||||
|
env['SERVER_PORT'] = str(self.server.server_port)
|
||||||
|
env['REQUEST_METHOD'] = self.command
|
||||||
|
uqrest = urllib.unquote(rest)
|
||||||
|
env['PATH_INFO'] = uqrest
|
||||||
|
env['SCRIPT_NAME'] = scriptname
|
||||||
|
if query:
|
||||||
|
env['QUERY_STRING'] = query
|
||||||
|
host = self.address_string()
|
||||||
|
if host != self.client_address[0]:
|
||||||
|
env['REMOTE_HOST'] = host
|
||||||
|
env['REMOTE_ADDR'] = self.client_address[0]
|
||||||
|
# AUTH_TYPE
|
||||||
|
# REMOTE_USER
|
||||||
|
# REMOTE_IDENT
|
||||||
|
if self.headers.typeheader is None:
|
||||||
|
env['CONTENT_TYPE'] = self.headers.type
|
||||||
|
else:
|
||||||
|
env['CONTENT_TYPE'] = self.headers.typeheader
|
||||||
|
length = self.headers.getheader('content-length')
|
||||||
|
if length:
|
||||||
|
env['CONTENT_LENGTH'] = length
|
||||||
|
accept = []
|
||||||
|
for line in self.headers.getallmatchingheaders('accept'):
|
||||||
|
if line[:1] in string.whitespace:
|
||||||
|
accept.append(string.strip(line))
|
||||||
|
else:
|
||||||
|
accept = accept + string.split(line[7:], ',')
|
||||||
|
env['HTTP_ACCEPT'] = string.joinfields(accept, ',')
|
||||||
|
ua = self.headers.getheader('user-agent')
|
||||||
|
if ua:
|
||||||
|
env['HTTP_USER_AGENT'] = ua
|
||||||
|
# XXX Other HTTP_* headers
|
||||||
|
decoded_query = string.replace(query, '+', ' ')
|
||||||
|
|
||||||
|
self.send_response(200)
|
||||||
|
self.send_header("Content-type", "text/html")
|
||||||
|
self.end_headers()
|
||||||
|
|
||||||
|
# Preserve state, because we execute script in current process:
|
||||||
|
save_argv = sys.argv
|
||||||
|
save_stdin = sys.stdin
|
||||||
|
save_stdout = sys.stdout
|
||||||
|
save_stderr = sys.stderr
|
||||||
|
# For external tools like enscript we also need to redirect
|
||||||
|
# the real stdout file descriptor:
|
||||||
|
save_realstdout = os.dup(1)
|
||||||
|
try:
|
||||||
|
try:
|
||||||
|
sys.stdout = self.wfile
|
||||||
|
os.close(1)
|
||||||
|
assert os.dup(self.wfile.fileno()) == 1
|
||||||
|
sys.stdin = self.rfile
|
||||||
|
viewcvs.run_cgi()
|
||||||
|
finally:
|
||||||
|
sys.argv = save_argv
|
||||||
|
sys.stdin = save_stdin
|
||||||
|
sys.stdout.flush()
|
||||||
|
os.close(1)
|
||||||
|
assert os.dup(save_realstdout) == 1
|
||||||
|
sys.stdout = save_stdout
|
||||||
|
sys.stderr = save_stderr
|
||||||
|
except SystemExit, status:
|
||||||
|
self.log_error("ViewCVS exit status %s", str(status))
|
||||||
|
else:
|
||||||
|
self.log_error("ViewCVS exited ok")
|
||||||
|
|
||||||
|
class ViewCVS_Server(BaseHTTPServer.HTTPServer):
|
||||||
|
def __init__(self, port, callback):
|
||||||
|
host = (sys.platform == 'mac') and '127.0.0.1' or 'localhost'
|
||||||
|
self.address = ('', port)
|
||||||
|
self.url = 'http://%s:%d/' % (host, port)
|
||||||
|
self.callback = callback
|
||||||
|
BaseHTTPServer.HTTPServer.__init__(self, self.address,
|
||||||
|
self.handler)
|
||||||
|
|
||||||
|
def serve_until_quit(self):
|
||||||
|
self.quit = 0
|
||||||
|
while not self.quit:
|
||||||
|
rd, wr, ex = select.select([self.socket.fileno()], [], [], 1)
|
||||||
|
if rd:
|
||||||
|
self.handle_request()
|
||||||
|
|
||||||
|
def server_activate(self):
|
||||||
|
BaseHTTPServer.HTTPServer.server_activate(self)
|
||||||
|
if self.callback:
|
||||||
|
self.callback(self)
|
||||||
|
|
||||||
|
def server_bind(self):
|
||||||
|
# set SO_REUSEADDR (if available on this platform)
|
||||||
|
if hasattr(socket, 'SOL_SOCKET') \
|
||||||
|
and hasattr(socket, 'SO_REUSEADDR'):
|
||||||
|
self.socket.setsockopt(socket.SOL_SOCKET,
|
||||||
|
socket.SO_REUSEADDR, 1)
|
||||||
|
BaseHTTPServer.HTTPServer.server_bind(self)
|
||||||
|
|
||||||
|
ViewCVS_Server.handler = ViewCVS_Handler
|
||||||
|
|
||||||
|
try:
|
||||||
|
# XXX Move this code out of this function.
|
||||||
|
# Early loading of configuration here. Used to
|
||||||
|
# allow tinkering with some configuration settings:
|
||||||
|
viewcvs.handle_config()
|
||||||
|
if options.repository:
|
||||||
|
if viewcvs.cfg.general.cvs_roots.has_key("Development"):
|
||||||
|
viewcvs.cfg.general.cvs_roots["Development"] = options.repository
|
||||||
|
else:
|
||||||
|
sys.stderr.write("*** No default ViewCVS configuration. Edit viewcvs.conf\n")
|
||||||
|
raise KeyboardInterrupt # Hack!
|
||||||
|
elif viewcvs.cfg.general.cvs_roots.has_key("Development") and \
|
||||||
|
not os.path.isdir(viewcvs.cfg.general.cvs_roots["Development"]):
|
||||||
|
sys.stderr.write("*** No repository found. Please use the -r option.\n")
|
||||||
|
sys.stderr.write(" Use --help for more info.\n")
|
||||||
|
raise KeyboardInterrupt # Hack!
|
||||||
|
os.close(0) # To avoid problems with shell job control
|
||||||
|
ViewCVS_Server(port, callback).serve_until_quit()
|
||||||
|
except (KeyboardInterrupt, select.error):
|
||||||
|
pass
|
||||||
|
print 'server stopped'
|
||||||
|
|
||||||
|
# --- graphical interface: --------------------------------------------------
|
||||||
|
|
||||||
|
def gui(port):
|
||||||
|
"""Graphical interface (starts web server and pops up a control window)."""
|
||||||
|
class GUI:
|
||||||
|
def __init__(self, window, port):
|
||||||
|
self.window = window
|
||||||
|
self.server = None
|
||||||
|
self.scanner = None
|
||||||
|
|
||||||
|
import Tkinter
|
||||||
|
self.server_frm = Tkinter.Frame(window)
|
||||||
|
self.title_lbl = Tkinter.Label(self.server_frm,
|
||||||
|
text='Starting server...\n ')
|
||||||
|
self.open_btn = Tkinter.Button(self.server_frm,
|
||||||
|
text='open browser', command=self.open, state='disabled')
|
||||||
|
self.quit_btn = Tkinter.Button(self.server_frm,
|
||||||
|
text='quit serving', command=self.quit, state='disabled')
|
||||||
|
|
||||||
|
|
||||||
|
self.window.title('ViewCVS standalone')
|
||||||
|
self.window.protocol('WM_DELETE_WINDOW', self.quit)
|
||||||
|
self.title_lbl.pack(side='top', fill='x')
|
||||||
|
self.open_btn.pack(side='left', fill='x', expand=1)
|
||||||
|
self.quit_btn.pack(side='right', fill='x', expand=1)
|
||||||
|
|
||||||
|
# Early loading of configuration here. Used to
|
||||||
|
# allow tinkering with configuration settings through the gui:
|
||||||
|
viewcvs.handle_config()
|
||||||
|
if not LIBRARY_DIR:
|
||||||
|
viewcvs.cfg.options.cvsgraph_conf = "../cgi/cvsgraph.conf.dist"
|
||||||
|
|
||||||
|
self.options_frm = Tkinter.Frame(window)
|
||||||
|
|
||||||
|
# cvsgraph toggle:
|
||||||
|
self.cvsgraph_ivar = Tkinter.IntVar()
|
||||||
|
self.cvsgraph_ivar.set(viewcvs.cfg.options.use_cvsgraph)
|
||||||
|
self.cvsgraph_toggle = Tkinter.Checkbutton(self.options_frm,
|
||||||
|
text="enable cvsgraph (needs binary)", var=self.cvsgraph_ivar,
|
||||||
|
command=self.toggle_use_cvsgraph)
|
||||||
|
self.cvsgraph_toggle.pack(side='top', anchor='w')
|
||||||
|
|
||||||
|
# enscript toggle:
|
||||||
|
self.enscript_ivar = Tkinter.IntVar()
|
||||||
|
self.enscript_ivar.set(viewcvs.cfg.options.use_enscript)
|
||||||
|
self.enscript_toggle = Tkinter.Checkbutton(self.options_frm,
|
||||||
|
text="enable enscript (needs binary)", var=self.enscript_ivar,
|
||||||
|
command=self.toggle_use_enscript)
|
||||||
|
self.enscript_toggle.pack(side='top', anchor='w')
|
||||||
|
|
||||||
|
# show_subdir_lastmod toggle:
|
||||||
|
self.subdirmod_ivar = Tkinter.IntVar()
|
||||||
|
self.subdirmod_ivar.set(viewcvs.cfg.options.show_subdir_lastmod)
|
||||||
|
self.subdirmod_toggle = Tkinter.Checkbutton(self.options_frm,
|
||||||
|
text="show subdir last mod (dir view)", var=self.subdirmod_ivar,
|
||||||
|
command=self.toggle_subdirmod)
|
||||||
|
self.subdirmod_toggle.pack(side='top', anchor='w')
|
||||||
|
|
||||||
|
# flip_links_in_dirview toggle:
|
||||||
|
self.fliplinks_ivar = Tkinter.IntVar()
|
||||||
|
self.fliplinks_ivar.set(viewcvs.cfg.options.flip_links_in_dirview)
|
||||||
|
self.fliplinks_toggle = Tkinter.Checkbutton(self.options_frm,
|
||||||
|
text="flip file/rev columns (dir view)", var=self.fliplinks_ivar,
|
||||||
|
command=self.toggle_fliplinks)
|
||||||
|
self.fliplinks_toggle.pack(side='top', anchor='w')
|
||||||
|
|
||||||
|
# directory view template:
|
||||||
|
self.dirtemplate_lbl = Tkinter.Label(self.options_frm,
|
||||||
|
text='Chooose HTML Template for the Directory pages:')
|
||||||
|
self.dirtemplate_lbl.pack(side='top', anchor='w')
|
||||||
|
self.dirtemplate_svar = Tkinter.StringVar()
|
||||||
|
self.dirtemplate_svar.set(viewcvs.cfg.templates.directory)
|
||||||
|
self.dirtemplate_entry = Tkinter.Entry(self.options_frm,
|
||||||
|
width = 40, textvariable=self.dirtemplate_svar)
|
||||||
|
self.dirtemplate_entry.bind('<Return>', self.set_templates_directory)
|
||||||
|
self.dirtemplate_entry.pack(side='top', anchor='w')
|
||||||
|
self.templates_dir = Tkinter.Radiobutton(self.options_frm,
|
||||||
|
text="directory.ezt", value="templates/directory.ezt",
|
||||||
|
var=self.dirtemplate_svar, command=self.set_templates_directory)
|
||||||
|
self.templates_dir.pack(side='top', anchor='w')
|
||||||
|
|
||||||
|
# log view template:
|
||||||
|
self.logtemplate_lbl = Tkinter.Label(self.options_frm,
|
||||||
|
text='Chooose HTML Template for the Log pages:')
|
||||||
|
self.logtemplate_lbl.pack(side='top', anchor='w')
|
||||||
|
self.logtemplate_svar = Tkinter.StringVar()
|
||||||
|
self.logtemplate_svar.set(viewcvs.cfg.templates.log)
|
||||||
|
self.logtemplate_entry = Tkinter.Entry(self.options_frm,
|
||||||
|
width = 40, textvariable=self.logtemplate_svar)
|
||||||
|
self.logtemplate_entry.bind('<Return>', self.set_templates_log)
|
||||||
|
self.logtemplate_entry.pack(side='top', anchor='w')
|
||||||
|
self.templates_log = Tkinter.Radiobutton(self.options_frm,
|
||||||
|
text="log.ezt", value="templates/log.ezt",
|
||||||
|
var=self.logtemplate_svar, command=self.set_templates_log)
|
||||||
|
self.templates_log.pack(side='top', anchor='w')
|
||||||
|
self.templates_log_table = Tkinter.Radiobutton(self.options_frm,
|
||||||
|
text="log_table.ezt", value="templates/log_table.ezt",
|
||||||
|
var=self.logtemplate_svar, command=self.set_templates_log)
|
||||||
|
self.templates_log_table.pack(side='top', anchor='w')
|
||||||
|
|
||||||
|
# query view template:
|
||||||
|
self.querytemplate_lbl = Tkinter.Label(self.options_frm,
|
||||||
|
text='Template for the database query page:')
|
||||||
|
self.querytemplate_lbl.pack(side='top', anchor='w')
|
||||||
|
self.querytemplate_svar = Tkinter.StringVar()
|
||||||
|
self.querytemplate_svar.set(viewcvs.cfg.templates.query)
|
||||||
|
self.querytemplate_entry = Tkinter.Entry(self.options_frm,
|
||||||
|
width = 40, textvariable=self.querytemplate_svar)
|
||||||
|
self.querytemplate_entry.bind('<Return>', self.set_templates_query)
|
||||||
|
self.querytemplate_entry.pack(side='top', anchor='w')
|
||||||
|
self.templates_query = Tkinter.Radiobutton(self.options_frm,
|
||||||
|
text="query.ezt", value="templates/query.ezt",
|
||||||
|
var=self.querytemplate_svar, command=self.set_templates_query)
|
||||||
|
self.templates_query.pack(side='top', anchor='w')
|
||||||
|
|
||||||
|
# pack and set window manager hints:
|
||||||
|
self.server_frm.pack(side='top', fill='x')
|
||||||
|
self.options_frm.pack(side='top', fill='x')
|
||||||
|
|
||||||
|
self.window.update()
|
||||||
|
self.minwidth = self.window.winfo_width()
|
||||||
|
self.minheight = self.window.winfo_height()
|
||||||
|
self.expanded = 0
|
||||||
|
self.window.wm_geometry('%dx%d' % (self.minwidth, self.minheight))
|
||||||
|
self.window.wm_minsize(self.minwidth, self.minheight)
|
||||||
|
|
||||||
|
import threading
|
||||||
|
threading.Thread(target=serve, args=(port, self.ready)).start()
|
||||||
|
|
||||||
|
def toggle_use_cvsgraph(self, event=None):
|
||||||
|
viewcvs.cfg.options.use_cvsgraph = self.cvsgraph_ivar.get()
|
||||||
|
|
||||||
|
def toggle_use_enscript(self, event=None):
|
||||||
|
viewcvs.cfg.options.use_enscript = self.enscript_ivar.get()
|
||||||
|
|
||||||
|
def toggle_subdirmod(self, event=None):
|
||||||
|
viewcvs.cfg.options.show_subdir_lastmod = self.subdirmod_ivar.get()
|
||||||
|
|
||||||
|
def toggle_fliplinks(self, event=None):
|
||||||
|
viewcvs.cfg.options.flip_links_in_dirview = self.fliplinks_ivar.get()
|
||||||
|
|
||||||
|
def set_templates_log(self, event=None):
|
||||||
|
viewcvs.cfg.templates.log = self.logtemplate_svar.get()
|
||||||
|
|
||||||
|
def set_templates_directory(self, event=None):
|
||||||
|
viewcvs.cfg.templates.directory = self.dirtemplate_svar.get()
|
||||||
|
|
||||||
|
def set_templates_query(self, event=None):
|
||||||
|
viewcvs.cfg.templates.query = self.querytemplate_svar.get()
|
||||||
|
|
||||||
|
def ready(self, server):
|
||||||
|
"""used as callback parameter to the serve() function"""
|
||||||
|
self.server = server
|
||||||
|
self.title_lbl.config(
|
||||||
|
text='ViewCVS standalone server at\n' + server.url)
|
||||||
|
self.open_btn.config(state='normal')
|
||||||
|
self.quit_btn.config(state='normal')
|
||||||
|
|
||||||
|
def open(self, event=None, url=None):
|
||||||
|
"""opens a browser window on the local machine"""
|
||||||
|
url = url or self.server.url
|
||||||
|
try:
|
||||||
|
import webbrowser
|
||||||
|
webbrowser.open(url)
|
||||||
|
except ImportError: # pre-webbrowser.py compatibility
|
||||||
|
if sys.platform == 'win32':
|
||||||
|
os.system('start "%s"' % url)
|
||||||
|
elif sys.platform == 'mac':
|
||||||
|
try:
|
||||||
|
import ic
|
||||||
|
ic.launchurl(url)
|
||||||
|
except ImportError: pass
|
||||||
|
else:
|
||||||
|
rc = os.system('netscape -remote "openURL(%s)" &' % url)
|
||||||
|
if rc: os.system('netscape "%s" &' % url)
|
||||||
|
|
||||||
|
def quit(self, event=None):
|
||||||
|
if self.server:
|
||||||
|
self.server.quit = 1
|
||||||
|
self.window.quit()
|
||||||
|
|
||||||
|
import Tkinter
|
||||||
|
try:
|
||||||
|
gui = GUI(Tkinter.Tk(), port)
|
||||||
|
Tkinter.mainloop()
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# --- command-line interface: ----------------------------------------------
|
||||||
|
|
||||||
|
def cli(argv):
|
||||||
|
"""Command-line interface (looks at argv to decide what to do)."""
|
||||||
|
import getopt
|
||||||
|
class BadUsage(Exception): pass
|
||||||
|
|
||||||
|
try:
|
||||||
|
opts, args = getopt.getopt(argv[1:], 'gp:r:',
|
||||||
|
['gui', 'port=', 'repository='])
|
||||||
|
for opt, val in opts:
|
||||||
|
if opt in ('-g', '--gui'):
|
||||||
|
options.start_gui = 1
|
||||||
|
elif opt in ('-r', '--repository'):
|
||||||
|
options.repository = val
|
||||||
|
elif opt in ('-p', '--port'):
|
||||||
|
try:
|
||||||
|
options.port = int(val)
|
||||||
|
except ValueError:
|
||||||
|
raise BadUsage
|
||||||
|
if options.start_gui:
|
||||||
|
gui(options.port)
|
||||||
|
return
|
||||||
|
elif options.port:
|
||||||
|
def ready(server):
|
||||||
|
print 'server ready at %s' % server.url
|
||||||
|
serve(options.port, ready)
|
||||||
|
return
|
||||||
|
raise BadUsage
|
||||||
|
except (getopt.error, BadUsage):
|
||||||
|
cmd = sys.argv[0]
|
||||||
|
port = options.port
|
||||||
|
print """ViewCVS standalone - a simple standalone HTTP-Server
|
||||||
|
|
||||||
|
Usage: %(cmd)s [ <options> ]
|
||||||
|
|
||||||
|
Available Options:
|
||||||
|
-p <port> or --port=<port>
|
||||||
|
Start an HTTP server on the given port on the local machine.
|
||||||
|
Default port is %(port)d.
|
||||||
|
|
||||||
|
-r <path> or --repository=<path>
|
||||||
|
Specify another path for the default CVS repository "Development".
|
||||||
|
If you don't have your repository at /home/cvsroot you will need to
|
||||||
|
use this option or you have to install first and edit viewcvs.conf.
|
||||||
|
|
||||||
|
-g or --gui
|
||||||
|
Pop up a graphical interface for serving and testing ViewCVS.
|
||||||
|
|
||||||
|
""" % locals()
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
if LIBRARY_DIR:
|
||||||
|
sys.path.insert(0, LIBRARY_DIR)
|
||||||
|
else:
|
||||||
|
sys.path[:0] = ['lib']
|
||||||
|
os.chdir('lib')
|
||||||
|
import viewcvs
|
||||||
|
import apache_icons
|
||||||
|
options = Options()
|
||||||
|
cli(sys.argv)
|
236
templates/directory.ezt
Normal file
236
templates/directory.ezt
Normal file
@@ -0,0 +1,236 @@
|
|||||||
|
<!doctype html public "-//W3C//DTD HTML 4.0 Transitional//EN"
|
||||||
|
"http://www.w3.org/TR/REC-html40/loose.dtd">
|
||||||
|
<html><head>
|
||||||
|
<!-- ViewCVS -- http://viewcvs.sourceforge.net/
|
||||||
|
by Greg Stein -- mailto:gstein@lyra.org
|
||||||
|
-->
|
||||||
|
<title>[if-any where][where][else][cfg.general.main_title][end]</title>
|
||||||
|
</head>
|
||||||
|
<body text="#000000" bgcolor="#ffffff">
|
||||||
|
<table width="100%" border=0 cellspacing=0 cellpadding=0>
|
||||||
|
<tr>
|
||||||
|
<td rowspan=2><h1>[if-any where][where][else][cfg.general.main_title][end]</h1></td>
|
||||||
|
<td align=right><img src="/icons/apache_pb.gif" alt="(logo)" border=0
|
||||||
|
width=259 height=32></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td align=right><h3><b><a target="_blank"
|
||||||
|
href="[request.script_name]/*docroot*/help_[if-any where]dir[else]root[end]view.html">Help</a></b></h3></td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
[if-any where][else]
|
||||||
|
<!-- you may insert repository access instructions here -->
|
||||||
|
|
||||||
|
[if-any roots]
|
||||||
|
<h3>Project Root</h3>
|
||||||
|
<form method=GET action="./">
|
||||||
|
<select name=cvsroot onchange="submit()">
|
||||||
|
[for roots]
|
||||||
|
[is roots current_root]
|
||||||
|
<option selected>[roots]</option>
|
||||||
|
[else]
|
||||||
|
<option>[roots]</option>
|
||||||
|
[end]
|
||||||
|
[end]
|
||||||
|
</select>
|
||||||
|
<input type=submit value="Go">
|
||||||
|
</form>
|
||||||
|
[end]
|
||||||
|
[end]
|
||||||
|
|
||||||
|
<p><a name="dirlist"></a></p>
|
||||||
|
|
||||||
|
[if-any where]
|
||||||
|
<p>Current directory: <b>[nav_path]</b></p>
|
||||||
|
[if-any view_tag]
|
||||||
|
<p>Current tag: <b>[view_tag]</b></p>
|
||||||
|
[end]
|
||||||
|
[end]
|
||||||
|
|
||||||
|
<p></p><hr noshade>
|
||||||
|
|
||||||
|
[# if you want a colored border around the table of directory
|
||||||
|
information, then add this additional table tag:
|
||||||
|
|
||||||
|
<table border=0 cellpadding=0 width="100%"><tr>
|
||||||
|
<td bgcolor="#999999">
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
<table width="100%" border=0 cellspacing=1 cellpadding=2>
|
||||||
|
<tr>
|
||||||
|
[for headers]
|
||||||
|
[is headers.which sortby]
|
||||||
|
<th align=left bgcolor="#88ff88" colspan=[headers.colspan]>[headers.title]</th>
|
||||||
|
[else]
|
||||||
|
<th align=left bgcolor="#cccccc" colspan=[headers.colspan]>
|
||||||
|
<a href="[headers.href]">[headers.title]</a>
|
||||||
|
</th>
|
||||||
|
[end]
|
||||||
|
[end]
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
[for rows]
|
||||||
|
<tr bgcolor="[if-index rows even]#ffffff[else]#ccccee[end]">
|
||||||
|
[is rows.type "unreadable"]
|
||||||
|
[is rev_in_front "1"]<td width="0%"> </td>[end]
|
||||||
|
<td><a name="[rows.anchor]">[rows.name]</a></td>
|
||||||
|
<td colspan=[rows.span]><i>this entry is unreadable</i></td>
|
||||||
|
[else]
|
||||||
|
[is rows.type "dir"]
|
||||||
|
[is rev_in_front "1"]<td width="0%"> </td>[end]
|
||||||
|
<td><a name="[rows.anchor]" href="[rows.href]">
|
||||||
|
<img src="/icons/small/dir.gif" alt="(dir)" border=0 width=16 height=16>
|
||||||
|
[rows.name]
|
||||||
|
</a>
|
||||||
|
[if-any rows.hide_attic_href]
|
||||||
|
<a href="[rows.hide_attic_href]">[[]Don't hide]</a>
|
||||||
|
[end]
|
||||||
|
</td>
|
||||||
|
[is rows.cvs "error"]
|
||||||
|
<td colspan=[rows.span]><i>CVS information is unreadable</i></td>
|
||||||
|
[else]
|
||||||
|
[is rows.cvs "none"]
|
||||||
|
[for rows.cols]<td> </td>[end]
|
||||||
|
[else]
|
||||||
|
[if-any rows.graph_href]
|
||||||
|
<td width="1%"> </td>
|
||||||
|
[end]
|
||||||
|
[is cfg.options.flip_links_in_dirview "0"]<td> </td>[end]
|
||||||
|
<td> [rows.time]</td>
|
||||||
|
[if-any rows.author]
|
||||||
|
<td> [rows.author]</td>
|
||||||
|
[end]
|
||||||
|
[if-any rows.show_log]
|
||||||
|
<td> [rows.log_file]/[rows.log_rev]<br>
|
||||||
|
<font size="-1">[rows.log]</font></td>
|
||||||
|
[end]
|
||||||
|
[end]
|
||||||
|
[end]
|
||||||
|
|
||||||
|
[else]
|
||||||
|
[is rows.cvs "error"]
|
||||||
|
[is rev_in_front "1"]<td> </td>[end]
|
||||||
|
<td><a name="[rows.anchor]">[rows.name]</a></td>
|
||||||
|
<td colspan=[rows.span]><i>CVS information is unreadable</i></td>
|
||||||
|
[else]
|
||||||
|
[is cfg.options.flip_links_in_dirview "1"]
|
||||||
|
<td><a name="[rows.anchor]" href="[rows.href]"><b>[rows.rev]</b></a>
|
||||||
|
</td>
|
||||||
|
[end]
|
||||||
|
<td><a name="[rows.anchor]"
|
||||||
|
[is cfg.options.flip_links_in_dirview "0"]
|
||||||
|
href="[rows.href]">
|
||||||
|
[else]
|
||||||
|
href="[rows.rev_href]&content-type=text/vnd.viewcvs-markup">
|
||||||
|
[# to display the revision in a separate window, you could use:
|
||||||
|
|
||||||
|
href="{rows.rev_href}" target="cvs_checkout"
|
||||||
|
onClick="window.open('{rows.rev_href}', 'cvs_checkout',
|
||||||
|
'resizeable=1,scrollbars=1')">
|
||||||
|
|
||||||
|
(substitute brackets for the braces)
|
||||||
|
] [end]<img src="/icons/small/text.gif" alt="(file)" border=0
|
||||||
|
width=16 height=16>
|
||||||
|
[rows.name]
|
||||||
|
</a>
|
||||||
|
[rows.attic]
|
||||||
|
</td>
|
||||||
|
[if-any rows.graph_href]
|
||||||
|
<td width="1%"><a href="[rows.graph_href]"><img
|
||||||
|
src="[request.script_name]/*docroot*/images/cvsgraph_16x16.png"
|
||||||
|
alt="(graph)" width=16 height=16 border=0>
|
||||||
|
</a></td>
|
||||||
|
[end]
|
||||||
|
[is cfg.options.flip_links_in_dirview "0"]
|
||||||
|
<td>
|
||||||
|
<a href="[rows.rev_href]&content-type=text/vnd.viewcvs-markup">
|
||||||
|
<b>[rows.rev]</b>
|
||||||
|
</a>
|
||||||
|
[# to display the revision in a separate window, you could use:
|
||||||
|
|
||||||
|
<a href="{rows.rev_href}" target="cvs_checkout"
|
||||||
|
onClick="window.open('{rows.rev_href}', 'cvs_checkout',
|
||||||
|
'resizeable=1,scrollbars=1')"><b>{rows.rev}</b></a>
|
||||||
|
|
||||||
|
(substitute brackets for the braces)
|
||||||
|
]
|
||||||
|
</td>
|
||||||
|
[end]
|
||||||
|
<td> [rows.time]</td>
|
||||||
|
[if-any rows.author]
|
||||||
|
<td> [rows.author]</td>
|
||||||
|
[end]
|
||||||
|
[if-any rows.show_log]
|
||||||
|
<td> [rows.log]</td>
|
||||||
|
[end]
|
||||||
|
[end]
|
||||||
|
[end]
|
||||||
|
[end]
|
||||||
|
</tr>
|
||||||
|
[end]
|
||||||
|
|
||||||
|
</table>
|
||||||
|
|
||||||
|
[# if you are using a table border (see above), then add:
|
||||||
|
|
||||||
|
</td></tr></table>
|
||||||
|
]
|
||||||
|
|
||||||
|
[if-any no_match]
|
||||||
|
<p><b>NOTE:</b> There are [num_files] files, but none match the
|
||||||
|
current tag ([view_tag])
|
||||||
|
[end]
|
||||||
|
|
||||||
|
[if-any unreadable]
|
||||||
|
<hr size=1 noshade>
|
||||||
|
<b>NOTE:</b> One or more files were unreadable. The files in the CVS
|
||||||
|
repository should be readable by the web server process. Please
|
||||||
|
report this condition to the administrator of this CVS repository.
|
||||||
|
[end]
|
||||||
|
|
||||||
|
[if-any has_tags]
|
||||||
|
<hr size=1 noshade>
|
||||||
|
<form method=GET action="./">
|
||||||
|
[for params]
|
||||||
|
<input type=hidden name="[params.name]" value="[params.value]">
|
||||||
|
[end]
|
||||||
|
|
||||||
|
Show only files with tag:
|
||||||
|
<select name=only_with_tag onchange="submit()">
|
||||||
|
[if-any branch_tags]
|
||||||
|
<option value="">- Branches -</option>
|
||||||
|
[for branch_tags]
|
||||||
|
[is branch_tags view_tag]
|
||||||
|
<option selected>[branch_tags]</option>
|
||||||
|
[else]
|
||||||
|
<option>[branch_tags]</option>
|
||||||
|
[end]
|
||||||
|
[end]
|
||||||
|
[end]
|
||||||
|
<option value="">- Non-branch tags -</option>
|
||||||
|
[for plain_tags]
|
||||||
|
[is plain_tags view_tag]
|
||||||
|
<option selected>[plain_tags]</option>
|
||||||
|
[else]
|
||||||
|
<option>[plain_tags]</option>
|
||||||
|
[end]
|
||||||
|
[end]
|
||||||
|
</select>
|
||||||
|
<input type=submit value="Go">
|
||||||
|
</form>
|
||||||
|
[end]
|
||||||
|
|
||||||
|
[# if you want to disable tarball generation remove the following: ]
|
||||||
|
[if-any tarball_href]
|
||||||
|
<a href="[tarball_href]">Download tarball</a>
|
||||||
|
[end]
|
||||||
|
|
||||||
|
<hr noshade>
|
||||||
|
<table width="100%" border=0 cellpadding=0 cellspacing=0><tr>
|
||||||
|
<td align=left><address>[address]</address></td>
|
||||||
|
<td align=right>
|
||||||
|
Powered by<br><a href="http://viewcvs.sourceforge.net/">ViewCVS [vsn]</a>
|
||||||
|
</td></tr></table>
|
||||||
|
</body></html>
|
248
templates/log.ezt
Normal file
248
templates/log.ezt
Normal file
@@ -0,0 +1,248 @@
|
|||||||
|
<!doctype html public "-//W3C//DTD HTML 4.0 Transitional//EN"
|
||||||
|
"http://www.w3.org/TR/REC-html40/loose.dtd">
|
||||||
|
<html><head>
|
||||||
|
<!-- ViewCVS -- http://viewcvs.sourceforge.net/
|
||||||
|
by Greg Stein -- mailto:gstein@lyra.org
|
||||||
|
-->
|
||||||
|
<title>CVS log for [where]</title>
|
||||||
|
</head>
|
||||||
|
<body text="#000000" bgcolor="#ffffff">
|
||||||
|
<table width="100%" border=0 cellspacing=0 cellpadding=0>
|
||||||
|
<tr>
|
||||||
|
<td><h1>CVS log for [where]</h1></td>
|
||||||
|
<td align=right><img src="/icons/apache_pb.gif" alt="(logo)" border=0
|
||||||
|
width=259 height=32></td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<a href="[back_url]"><img src="/icons/small/back.gif" alt="(back)" border=0
|
||||||
|
width=16 height=16></a>
|
||||||
|
<b>Up to [nav_path]</b><p>
|
||||||
|
<a href="#diff">Request diff between arbitrary revisions</a>
|
||||||
|
[if-any graph_href]
|
||||||
|
/ <a href="[graph_href]">Display revisions graphically</a>
|
||||||
|
[end]
|
||||||
|
<hr noshade>
|
||||||
|
|
||||||
|
[if-any branch]
|
||||||
|
Default branch: [branch]
|
||||||
|
<br>
|
||||||
|
Bookmark a link to:
|
||||||
|
[if-any viewable]
|
||||||
|
<a href="[head_href]"><b>HEAD</b></a>
|
||||||
|
/
|
||||||
|
(<a href="[head_abs_href]" target="cvs_checkout"
|
||||||
|
onClick="window.open('about:blank', 'cvs_checkout',
|
||||||
|
'resizeable=1,scrollbars=1[is mime_type "text/html"],status,toolbar[end]')"
|
||||||
|
><b>download</b></a>)
|
||||||
|
[else]
|
||||||
|
<a href="[head_href]" target="cvs_checkout"
|
||||||
|
onClick="window.open('about:blank', 'cvs_checkout',
|
||||||
|
'resizeable=1,scrollbars=1[is mime_type "text/html"],status,toolbar[end]')"
|
||||||
|
><b>HEAD</b></a>
|
||||||
|
[end]
|
||||||
|
[else]
|
||||||
|
No default branch
|
||||||
|
[end]
|
||||||
|
<br>
|
||||||
|
|
||||||
|
[if-any view_tag]
|
||||||
|
Current tag: [view_tag] <br>
|
||||||
|
[end]
|
||||||
|
|
||||||
|
[for entries]
|
||||||
|
<hr size=1 noshade>
|
||||||
|
|
||||||
|
[is entries.state "dead"]
|
||||||
|
Revision <b>[entries.rev]</b>
|
||||||
|
[else]
|
||||||
|
<a name="rev[entries.rev]"></a>
|
||||||
|
[for entries.tag_names]<a name="[entries.tag_names]"></a>
|
||||||
|
[end]
|
||||||
|
[for entries.branch_names]<a name="[entries.branch_names]"></a>
|
||||||
|
[end]
|
||||||
|
|
||||||
|
Revision
|
||||||
|
[if-any viewable]
|
||||||
|
<a href="[entries.href]" target="cvs_checkout"
|
||||||
|
onClick="window.open('about:blank', 'cvs_checkout',
|
||||||
|
'resizeable=1,scrollbars=1[is mime_type "text/html"],status,toolbar[end]')"
|
||||||
|
><b>[entries.rev]</b></a>
|
||||||
|
[else]
|
||||||
|
<a href="[entries.view_href]"><b>[entries.rev]</b></a>
|
||||||
|
/
|
||||||
|
(<a href="[entries.href]" target="cvs_checkout"
|
||||||
|
onClick="window.open('about:blank', 'cvs_checkout',
|
||||||
|
'resizeable=1,scrollbars=1[is mime_type "text/html"],status,toolbar[end]')"
|
||||||
|
><b>download</b></a>)
|
||||||
|
[end]
|
||||||
|
[is mime_type "text/plain"]
|
||||||
|
[else]
|
||||||
|
/
|
||||||
|
(<a href="[entries.text_href]" target="cvs_checkout"
|
||||||
|
onClick="window.open('about:blank', 'cvs_checkout',
|
||||||
|
'resizeable=1,scrollbars=1')"><b>as text</b></a>)
|
||||||
|
[end]
|
||||||
|
[if-any viewable]
|
||||||
|
/
|
||||||
|
<a href="[entries.view_href]"><b>(view)</b></a>
|
||||||
|
[end]
|
||||||
|
|
||||||
|
[# if you don't want to allow annotation, then remove this line]
|
||||||
|
- <a href="[href]?annotate=[entries.rev][query]">annotate</a>
|
||||||
|
|
||||||
|
[# if you don't want to allow select for diffs then remove this section]
|
||||||
|
[is entries.rev rev_selected]
|
||||||
|
- <b>[[]selected]</b>
|
||||||
|
[else]
|
||||||
|
- <a href="[href]?r1=[entries.rev][query]">[[]select for diffs]</a>
|
||||||
|
[end]
|
||||||
|
[end]
|
||||||
|
|
||||||
|
[if-any entries.vendor_branch]
|
||||||
|
<i>(vendor branch)</i>
|
||||||
|
[end]
|
||||||
|
|
||||||
|
, <i>[entries.utc_date] UTC</i> ([entries.ago] ago) by <i>[entries.author]</i>
|
||||||
|
|
||||||
|
[if-any entries.branches]
|
||||||
|
<br>Branch:
|
||||||
|
[for entries.branches]
|
||||||
|
<a href="[entries.branches.href]"><b>[entries.branches.name]</b></a>[if-index entries.branches last][else],[end]
|
||||||
|
[end]
|
||||||
|
[end]
|
||||||
|
|
||||||
|
[if-any entries.tags]
|
||||||
|
<br>CVS Tags:
|
||||||
|
[for entries.tags]
|
||||||
|
<a href="[entries.tags.href]"><b>[entries.tags.name]</b></a>[if-index entries.tags last][else],[end]
|
||||||
|
[end]
|
||||||
|
[end]
|
||||||
|
|
||||||
|
[if-any entries.branch_points]
|
||||||
|
<br>Branch point for:
|
||||||
|
[for entries.branch_points]
|
||||||
|
<a href="[entries.branch_points.href]"><b>[entries.branch_points.name]</b></a>[if-index entries.branch_points last][else],[end]
|
||||||
|
[end]
|
||||||
|
[end]
|
||||||
|
|
||||||
|
[if-any entries.prev]
|
||||||
|
[if-any entries.changed]
|
||||||
|
<br>Changes since <b>[entries.prev]: [entries.changed] lines</b>
|
||||||
|
[end]
|
||||||
|
[end]
|
||||||
|
|
||||||
|
[is entries.state "dead"]
|
||||||
|
<br><b><i>FILE REMOVED</i></b>
|
||||||
|
[else]
|
||||||
|
[if-any entries.prev]
|
||||||
|
<br>Diff to <a href="[href].diff?r1=[entries.prev]&r2=[entries.rev][query]">previous [entries.prev]</a>
|
||||||
|
[if-any human_readable]
|
||||||
|
[else]
|
||||||
|
(<a href="[href].diff?r1=[entries.prev]&r2=[entries.rev]&diff_format=h[query]">colored</a>)
|
||||||
|
[end]
|
||||||
|
[end]
|
||||||
|
|
||||||
|
[if-any entries.branch_point]
|
||||||
|
to <a href="[href].diff?r1=[entries.branch_point]&r2=[entries.rev][query]">branch point [entries.branch_point]</a>
|
||||||
|
[if-any human_readable]
|
||||||
|
[else]
|
||||||
|
(<a href="[href].diff?r1=[entries.branch_point]&r2=[entries.rev]&diff_format=h[query]">colored</a>)
|
||||||
|
[end]
|
||||||
|
[end]
|
||||||
|
|
||||||
|
[if-any entries.next_main]
|
||||||
|
to <a href="[href].diff?r1=[entries.next_main]&r2=[entries.rev][query]">next main [entries.next_main]</a>
|
||||||
|
[if-any human_readable]
|
||||||
|
[else]
|
||||||
|
(<a href="[href].diff?r1=[entries.next_main]&r2=[entries.rev]&diff_format=h[query]">colored</a>)
|
||||||
|
[end]
|
||||||
|
[end]
|
||||||
|
|
||||||
|
[if-any entries.to_selected]
|
||||||
|
[if-any entries.prev][else]<br>Diff[end]
|
||||||
|
to <a href="[href].diff?r1=[rev_selected]&r2=[entries.rev][query]">selected [rev_selected]</a>
|
||||||
|
[if-any human_readable]
|
||||||
|
[else]
|
||||||
|
(<a href="[href].diff?r1=[rev_selected]&r2=[entries.rev]&diff_format=h[query]">colored</a>)
|
||||||
|
[end]
|
||||||
|
[end]
|
||||||
|
[end]
|
||||||
|
|
||||||
|
<pre>[entries.html_log]</pre>
|
||||||
|
[end]
|
||||||
|
|
||||||
|
<a name=diff></a>
|
||||||
|
<hr noshade>
|
||||||
|
This form allows you to request diffs between any two revisions of
|
||||||
|
a file. You may select a symbolic revision name using the selection
|
||||||
|
box or you may type in a numeric name using the type-in text box.
|
||||||
|
<p>
|
||||||
|
<form method="GET" action="[href].diff[qquery]" name="diff_select">
|
||||||
|
|
||||||
|
Diffs between
|
||||||
|
<select name="r1">
|
||||||
|
<option value="text" selected>Use Text Field</option>
|
||||||
|
[for tags]
|
||||||
|
<option value="[tags.rev]:[tags.name]">[tags.name]</option>
|
||||||
|
[end]
|
||||||
|
</select>
|
||||||
|
<input type="TEXT" size="12" name="tr1" value="[tr1]"
|
||||||
|
onChange="document.diff_select.r1.selectedIndex=0">
|
||||||
|
|
||||||
|
and
|
||||||
|
<select name="r2">
|
||||||
|
<option value="text" selected>Use Text Field</option>
|
||||||
|
[for tags]
|
||||||
|
<option value="[tags.rev]:[tags.name]">[tags.name]</option>
|
||||||
|
[end]
|
||||||
|
</select>
|
||||||
|
<input type="TEXT" size="12" name="tr2" value="[tr2]"
|
||||||
|
onChange="document.diff_select.r1.selectedIndex=0">
|
||||||
|
|
||||||
|
<br>Type of Diff should be a
|
||||||
|
<select name="diff_format" onchange="submit()">
|
||||||
|
<option value="h" [is diff_format "h"]selected[end]>Colored Diff</option>
|
||||||
|
<option value="l" [is diff_format "l"]selected[end]>Long Colored Diff</option>
|
||||||
|
<option value="u" [is diff_format "u"]selected[end]>Unidiff</option>
|
||||||
|
<option value="c" [is diff_format "c"]selected[end]>Context Diff</option>
|
||||||
|
<option value="s" [is diff_format "c"]selected[end]>Side by Side</option>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<input type=submit value=" Get Diffs "></form>
|
||||||
|
<hr noshade>
|
||||||
|
|
||||||
|
[if-any branch_names]
|
||||||
|
<a name=branch></a>
|
||||||
|
<form method="GET" action="[href]">
|
||||||
|
[hidden_values]
|
||||||
|
View only Branch:
|
||||||
|
<select name="only_with_tag" onchange="submit()">
|
||||||
|
<option value="" [is view_tag ""]selected[end]>Show all branches</option>
|
||||||
|
[for branch_names]
|
||||||
|
<option value="[branch_names]" [is branch_names view_tag]selected[end]>[branch_names]</option>
|
||||||
|
[end]
|
||||||
|
</select>
|
||||||
|
<input type=submit value=" View Branch ">
|
||||||
|
</form>
|
||||||
|
[end]
|
||||||
|
|
||||||
|
<a name=logsort></a>
|
||||||
|
<form method="GET" action="[href]">
|
||||||
|
[hidden_values]
|
||||||
|
Sort log by:
|
||||||
|
<select name="logsort" onchange="submit()">
|
||||||
|
<option value="cvs" [is logsort "cvs"]selected[end]>Not sorted</option>
|
||||||
|
<option value="date" [is logsort "date"]selected[end]>Commit date</option>
|
||||||
|
<option value="rev" [is logsort "rev"]selected[end]>Revision</option>
|
||||||
|
</select>
|
||||||
|
<input type=submit value=" Sort ">
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<hr noshade>
|
||||||
|
<table width="100%" border=0 cellpadding=0 cellspacing=0><tr>
|
||||||
|
<td align=left><address>[address]</address></td>
|
||||||
|
<td align=right>
|
||||||
|
Powered by<br><a href="http://viewcvs.sourceforge.net/">ViewCVS [vsn]</a>
|
||||||
|
</td></tr></table>
|
||||||
|
</body></html>
|
253
templates/log_table.ezt
Normal file
253
templates/log_table.ezt
Normal file
@@ -0,0 +1,253 @@
|
|||||||
|
<!doctype html public "-//W3C//DTD HTML 4.0 Transitional//EN"
|
||||||
|
"http://www.w3.org/TR/REC-html40/loose.dtd">
|
||||||
|
<html><head>
|
||||||
|
<!-- ViewCVS -- http://viewcvs.sourceforge.net/
|
||||||
|
by Greg Stein -- mailto:gstein@lyra.org
|
||||||
|
-->
|
||||||
|
<title>CVS log for [where]</title>
|
||||||
|
</head>
|
||||||
|
<body text="#000000" bgcolor="#ffffff">
|
||||||
|
<table width="100%" border=0 cellspacing=0 cellpadding=0>
|
||||||
|
<tr>
|
||||||
|
<td><h1>CVS log for [where]</h1></td>
|
||||||
|
<td align=right><img src="/icons/apache_pb.gif" alt="(logo)" border=0
|
||||||
|
width=259 height=32></td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<a href="[back_url]"><img src="/icons/small/back.gif" alt="(back)" border=0
|
||||||
|
width=16 height=16></a>
|
||||||
|
<b>Up to [nav_path]</b><p>
|
||||||
|
<a href="#diff">Request diff between arbitrary revisions</a>
|
||||||
|
[if-any graph_href]
|
||||||
|
/ <a href="[graph_href]">Display revisions graphically</a>
|
||||||
|
[end]
|
||||||
|
<hr noshade>
|
||||||
|
|
||||||
|
[if-any branch]
|
||||||
|
Default branch: [branch]
|
||||||
|
<br>
|
||||||
|
Bookmark a link to:
|
||||||
|
[if-any viewable]
|
||||||
|
<a href="[head_href]"><b>HEAD</b></a>
|
||||||
|
/
|
||||||
|
(<a href="[head_abs_href]" target="cvs_checkout"
|
||||||
|
onClick="window.open('about:blank', 'cvs_checkout',
|
||||||
|
'resizeable=1,scrollbars=1[is mime_type "text/html"],status,toolbar[end]')"
|
||||||
|
><b>download</b></a>)
|
||||||
|
[else]
|
||||||
|
<a href="[head_href]" target="cvs_checkout"
|
||||||
|
onClick="window.open('about:blank', 'cvs_checkout',
|
||||||
|
'resizeable=1,scrollbars=1[is mime_type "text/html"],status,toolbar[end]')"
|
||||||
|
><b>HEAD</b></a>
|
||||||
|
[end]
|
||||||
|
[else]
|
||||||
|
No default branch
|
||||||
|
[end]
|
||||||
|
<br>
|
||||||
|
|
||||||
|
[if-any view_tag]
|
||||||
|
Current tag: [view_tag] <br>
|
||||||
|
[end]
|
||||||
|
<hr noshade>
|
||||||
|
<table width="100%" border=0 cellspacing=1 cellpadding=2>
|
||||||
|
<th align=left bgcolor="#88ff88">Revision</th>
|
||||||
|
<th align=left bgcolor="#88ff88">Tasks</th>
|
||||||
|
<th align=left bgcolor="#88ff88">Diffs</th>
|
||||||
|
<th align=left bgcolor="#88ff88">Branches and Tags</th>
|
||||||
|
<th align=left bgcolor="#88ff88">Age</th>
|
||||||
|
<th align=left bgcolor="#88ff88">Author</th>
|
||||||
|
[for entries]
|
||||||
|
<tr valign="top" bgcolor="[if-index entries even]#ffffff[else]#ccccee[end]">
|
||||||
|
|
||||||
|
[# Revision column]
|
||||||
|
<td rowspan=2>
|
||||||
|
<b>[entries.rev]</b>
|
||||||
|
<a name="rev[entries.rev]"></a>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<td>
|
||||||
|
[# Tasks column]
|
||||||
|
<a href="[entries.href]" target="cvs_checkout"
|
||||||
|
onClick="window.open('about:blank', 'cvs_checkout',
|
||||||
|
'resizeable=1,menubar=1,scrollbars=1[is mime_type "text/html"],status,
|
||||||
|
toolbar[end]')"><b>Download</b></a><br>
|
||||||
|
[is mime_type "text/plain"]
|
||||||
|
[else]
|
||||||
|
<a href="[entries.text_href]" target="cvs_checkout"
|
||||||
|
onClick="window.open('about:blank', 'cvs_checkout',
|
||||||
|
'resizeable=1,scrollbars=1')"><b>View as text</b></a><br>
|
||||||
|
[end]
|
||||||
|
[if-any viewable]
|
||||||
|
<a href="[entries.view_href]"><b>View [entries.rev]</b></a><br>
|
||||||
|
[end]
|
||||||
|
|
||||||
|
[# if you don't want to allow annotation, then remove this line]
|
||||||
|
<a href="[href]?annotate=[entries.rev][query]"><b>View annotated</b></a><br>
|
||||||
|
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
[# if you don't want to allow select for diffs then remove this section]
|
||||||
|
[is entries.rev rev_selected]
|
||||||
|
<b>[[]selected]</b><br>
|
||||||
|
[else]
|
||||||
|
<a href="[href]?r1=[entries.rev][query]"><b>[[]select for diffs]</b></a><br>
|
||||||
|
[end]
|
||||||
|
|
||||||
|
[is entries.state "dead"]
|
||||||
|
<b><i>FILE REMOVED</i></b>
|
||||||
|
[else]
|
||||||
|
[if-any entries.prev]
|
||||||
|
<a href="[href].diff?r1=[entries.prev]&r2=[entries.rev][query]"><b>Diff to previous [entries.prev]</b></a><br>
|
||||||
|
[if-any human_readable]
|
||||||
|
[else]
|
||||||
|
<a href="[href].diff?r1=[entries.prev]&r2=[entries.rev]&diff_format=h[query]"><b>colored</b></a><br>
|
||||||
|
[end]
|
||||||
|
[end]
|
||||||
|
[if-any entries.to_selected]
|
||||||
|
<a href="[href].diff?r1=[rev_selected]&r2=[entries.rev][query]"><b>Diff to selected [rev_selected]</b></a><br>
|
||||||
|
[if-any human_readable]
|
||||||
|
[else]
|
||||||
|
<a href="[href].diff?r1=[rev_selected]&r2=[entries.rev]&diff_format=h[query]"><b>colored</b></a><br>
|
||||||
|
[end]
|
||||||
|
[end]
|
||||||
|
[end]
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<td>
|
||||||
|
[# Branches column]
|
||||||
|
[if-any entries.vendor_branch]
|
||||||
|
<i>vendor branch</i><br>
|
||||||
|
[end]
|
||||||
|
[if-any entries.branches]
|
||||||
|
[for entries.branches]
|
||||||
|
<a href="[entries.branches.href]"><b>[entries.branches.name]</b></a><br>
|
||||||
|
[end]
|
||||||
|
[end]
|
||||||
|
[if-any entries.branch_points]
|
||||||
|
Branch point for:
|
||||||
|
[for entries.branch_points]
|
||||||
|
<a href="[entries.branch_points.href]"><b>[entries.branch_points.name]</b></a><br>
|
||||||
|
[end]
|
||||||
|
[end]
|
||||||
|
[if-any entries.next_main]
|
||||||
|
<a href="[href].diff?r1=[entries.next_main]&r2=[entries.rev][query]"><b>Diff to next MAIN [entries.next_main]</b></a><br>
|
||||||
|
[if-any human_readable]
|
||||||
|
[else]
|
||||||
|
<a href="[href].diff?r1=[entries.next_main]&r2=[entries.rev]&diff_format=h[query]"><b>colored</b></a><br>
|
||||||
|
[end]
|
||||||
|
[end]
|
||||||
|
[if-any entries.branch_point]
|
||||||
|
<a href="[href].diff?r1=[entries.branch_point]&r2=[entries.rev][query]"><b>Diff to branch point [entries.branch_point]</b></a><br>
|
||||||
|
[if-any human_readable]
|
||||||
|
[else]
|
||||||
|
<a href="[href].diff?r1=[entries.branches.name]&r2=[entries.rev]&diff_format=h[query]"><b>colored</b></a><br>
|
||||||
|
[end]
|
||||||
|
[end]
|
||||||
|
|
||||||
|
[# Tags ]
|
||||||
|
[if-any entries.tags]
|
||||||
|
<form action="[href]" >
|
||||||
|
<select name="only_with_tag" onChange="submit()">
|
||||||
|
<option value="" [is view_tag ""]selected[end]>Show all tags</option>
|
||||||
|
[for entries.tags]
|
||||||
|
<option [is view_tag entries.tags.name]selected[end]>[entries.tags.name]</option>
|
||||||
|
[end]
|
||||||
|
</select>
|
||||||
|
</form>
|
||||||
|
[else]
|
||||||
|
[end]
|
||||||
|
</td>
|
||||||
|
|
||||||
|
[# Time column]
|
||||||
|
<td>
|
||||||
|
[entries.ago] ago<br><i>[entries.utc_date]</i>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
[# Author column]
|
||||||
|
<td>
|
||||||
|
[entries.author]
|
||||||
|
</td>
|
||||||
|
|
||||||
|
</tr>
|
||||||
|
<tr bgcolor="[if-index entries even]#ffffff[else]#ccccee[end]">
|
||||||
|
<td colspan=5><b>Log: </b><i><pre>[entries.html_log]</pre></i></td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
[end]
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<a name=diff></a>
|
||||||
|
<hr noshade>
|
||||||
|
This form allows you to request diffs between any two revisions of
|
||||||
|
a file. You may select a symbolic revision name using the selection
|
||||||
|
box or you may type in a numeric name using the type-in text box.
|
||||||
|
<p>
|
||||||
|
<form method="GET" action="[href].diff[qquery]" name="diff_select">
|
||||||
|
|
||||||
|
Diffs between
|
||||||
|
<select name="r1">
|
||||||
|
<option value="text" selected>Use Text Field</option>
|
||||||
|
[for tags]
|
||||||
|
<option value="[tags.rev]:[tags.name]">[tags.name]</option>
|
||||||
|
[end]
|
||||||
|
</select>
|
||||||
|
<input type="TEXT" size="12" name="tr1" value="[tr1]"
|
||||||
|
onChange="document.diff_select.r1.selectedIndex=0">
|
||||||
|
|
||||||
|
and
|
||||||
|
<select name="r2">
|
||||||
|
<option value="text" selected>Use Text Field</option>
|
||||||
|
[for tags]
|
||||||
|
<option value="[tags.rev]:[tags.name]">[tags.name]</option>
|
||||||
|
[end]
|
||||||
|
</select>
|
||||||
|
<input type="TEXT" size="12" name="tr2" value="[tr2]"
|
||||||
|
onChange="document.diff_select.r1.selectedIndex=0">
|
||||||
|
|
||||||
|
<br>Type of Diff should be a
|
||||||
|
<select name="diff_format" onchange="submit()">
|
||||||
|
<option value="h" [is diff_format "h"]selected[end]>Colored Diff</option>
|
||||||
|
<option value="l" [is diff_format "l"]selected[end]>Long Colored Diff</option>
|
||||||
|
<option value="u" [is diff_format "u"]selected[end]>Unidiff</option>
|
||||||
|
<option value="c" [is diff_format "c"]selected[end]>Context Diff</option>
|
||||||
|
<option value="s" [is diff_format "c"]selected[end]>Side by Side</option>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<input type=submit value=" Get Diffs "></form>
|
||||||
|
<hr noshade>
|
||||||
|
|
||||||
|
[if-any branch_names]
|
||||||
|
<a name=branch></a>
|
||||||
|
<form method="GET" action="[href]">
|
||||||
|
[hidden_values]
|
||||||
|
View only Branch:
|
||||||
|
<select name="only_with_tag" onchange="submit()">
|
||||||
|
<option value="" [is view_tag ""]selected[end]>Show all branches</option>
|
||||||
|
[for branch_names]
|
||||||
|
<option value="[branch_names]" [is branch_names view_tag]selected[end]>[branch_names]</option>
|
||||||
|
[end]
|
||||||
|
</select>
|
||||||
|
<input type=submit value=" View Branch ">
|
||||||
|
</form>
|
||||||
|
[end]
|
||||||
|
|
||||||
|
<a name=logsort></a>
|
||||||
|
<form method="GET" action="[href]">
|
||||||
|
[hidden_values]
|
||||||
|
Sort log by:
|
||||||
|
<select name="logsort" onchange="submit()">
|
||||||
|
<option value="cvs" [is logsort "cvs"]selected[end]>Not sorted</option>
|
||||||
|
<option value="date" [is logsort "date"]selected[end]>Commit date</option>
|
||||||
|
<option value="rev" [is logsort "rev"]selected[end]>Revision</option>
|
||||||
|
</select>
|
||||||
|
<input type=submit value=" Sort ">
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<hr noshade>
|
||||||
|
<table width="100%" border=0 cellpadding=0 cellspacing=0><tr>
|
||||||
|
<td align=left><address>[address]</address></td>
|
||||||
|
<td align=right>
|
||||||
|
Powered by<br><a href="http://viewcvs.sourceforge.net/">ViewCVS [vsn]</a>
|
||||||
|
</td></tr></table>
|
||||||
|
</body></html>
|
234
templates/query.ezt
Normal file
234
templates/query.ezt
Normal file
@@ -0,0 +1,234 @@
|
|||||||
|
<!doctype html public "-//W3C//DTD HTML 4.0 Transitional//EN"
|
||||||
|
"http://www.w3.org/TR/REC-html40/loose.dtd">
|
||||||
|
<html><head>
|
||||||
|
<!-- ViewCVS -- http://viewcvs.sourceforge.net/
|
||||||
|
by Greg Stein -- mailto:gstein@lyra.org
|
||||||
|
-->
|
||||||
|
<title>[cfg.general.main_title]</title>
|
||||||
|
</head>
|
||||||
|
<body text="#000000" bgcolor="#ffffff">
|
||||||
|
<table width="100%" border=0 cellspacing=0 cellpadding=0>
|
||||||
|
<tr>
|
||||||
|
<td><h1>[cfg.general.main_title]</h1></td>
|
||||||
|
<td align=right><img src="/icons/apache_pb.gif" alt="(logo)" border=0
|
||||||
|
width=259 height=32></td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Select your parameters for querying the CVS commit database. You
|
||||||
|
can search for multiple matches by typing a comma-seperated list
|
||||||
|
into the text fields. Regular expressions, and wildcards are also
|
||||||
|
supported. Blank text input fields are treated as wildcards.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Any of the text entry fields can take a comma-seperated list of
|
||||||
|
search arguments. For example, to search for all commits from
|
||||||
|
authors <i>jpaint</i> and <i>gstein</i>, just type: <b>jpaint,
|
||||||
|
gstein</b> in the <i>Author</i> input box. If you are searching
|
||||||
|
for items containing spaces or quotes, you will need to quote your
|
||||||
|
request. For example, the same search above with quotes is:
|
||||||
|
<b>"jpaint", "gstein"</b>.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
|
||||||
|
Wildcard and regular expression searches are entered in a similar
|
||||||
|
way to the quoted requests. You must quote any wildcard or
|
||||||
|
regular expression request, and a command charactor preceeds the
|
||||||
|
first quote. The command charactor <b>l</b> is for wildcard
|
||||||
|
searches, and the wildcard charactor is a percent (<b>%</b>). The
|
||||||
|
command charactor for regular expressions is <b>r</b>, and is
|
||||||
|
passed directly to MySQL, so you'll need to refer to the MySQL
|
||||||
|
manual for the exact regex syntax. It is very similar to Perl. A
|
||||||
|
wildard search for all files with a <i>.py</i> extention is:
|
||||||
|
<b>l"%.py"</b> in the <i>File</i> input box. The same search done
|
||||||
|
with a regular expression is: <b>r".*\.py"</b>.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
All search types can be mixed, as long as they are seperated by
|
||||||
|
commas.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<form method=get action="query.cgi">
|
||||||
|
|
||||||
|
<table border=0 cellspacing=0 cellpadding=2 width=100% bgcolor=e6e6e6>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<td valign=top>
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<td align=right>CVS Repository:</td>
|
||||||
|
<td>
|
||||||
|
<input type=text name=repository size=40 value="[repository]">
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td align=right>CVS Branch:</td>
|
||||||
|
<td>
|
||||||
|
<input type=text name=branch size=40 value="[branch]">
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td align=right>Directory:</td>
|
||||||
|
<td>
|
||||||
|
<input type=text name=directory size=40 value="[directory]">
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td align=right>File:</td>
|
||||||
|
<td>
|
||||||
|
<input type=text name=file size=40 value="[file]">
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td align=right>Author:</td>
|
||||||
|
<td>
|
||||||
|
<input type=text name=who size=40 value="[who]">
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
</td>
|
||||||
|
<td valign=top>
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<td align=left>Sort By:</td>
|
||||||
|
<td>
|
||||||
|
<select name=sortby>
|
||||||
|
<option value=date [is sortby "date"]selected[end]>Date</option>
|
||||||
|
<option value=author [is sortby "author"]selected[end]>Author</option>
|
||||||
|
<option value=file [is sortby "file"]selected[end]>File</option>
|
||||||
|
</select>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td colspan=2>
|
||||||
|
<table border=0 cellspacing=0 cellpadding=0>
|
||||||
|
<tr>
|
||||||
|
<td>Date:</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><input type=radio name=date value=hours
|
||||||
|
[is date "hours"]checked[end]></td>
|
||||||
|
<td>In the last
|
||||||
|
<input type=text name=hours value=[hours] size=4>hours
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><input type=radio name=date value=day
|
||||||
|
[is date "day"]checked[end]></td>
|
||||||
|
<td>In the last day</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><input type=radio name=date value=week
|
||||||
|
[is date "week"]checked[end]></td>
|
||||||
|
<td>In the last week</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><input type=radio name=date value=month
|
||||||
|
[is date "month"]checked[end]></td>
|
||||||
|
<td>In the last month</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><input type=radio name=date value=all
|
||||||
|
[is date "all"]checked[end]></td>
|
||||||
|
<td>Since the beginning of time</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<input type=submit value="Search">
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
</form>
|
||||||
|
|
||||||
|
[is query "skipped"]
|
||||||
|
[else]
|
||||||
|
<p><b>[num_commits]</b> matches found.</p>
|
||||||
|
|
||||||
|
[if-any commits]
|
||||||
|
<table width="100%" border=0 cellspacing=0 cellpadding=2>
|
||||||
|
<tr bgcolor="#88ff88">
|
||||||
|
<th align=left valign=top>Revision</th>
|
||||||
|
<th align=left valign=top>File</th>
|
||||||
|
<th align=left valign=top>Branch</th>
|
||||||
|
<th align=left valign=top>+/-</th>
|
||||||
|
<th align=left valign=top>Date</th>
|
||||||
|
<th align=left valign=top>Author</th>
|
||||||
|
[# uncommment, if you want a separate Description column: (also see below)
|
||||||
|
<th align=left valign=top>Description</th>
|
||||||
|
]
|
||||||
|
</tr>
|
||||||
|
[for commits]
|
||||||
|
[for commits.files]
|
||||||
|
<tr bgcolor="[if-index commits even]#ffffff[else]#ccccee[end]">
|
||||||
|
<td align=left valign=top>
|
||||||
|
[if-any commits.files.rev][commits.files.rev][else] [end]
|
||||||
|
</td>
|
||||||
|
<td align=left valign=top>[commits.files.link]</td>
|
||||||
|
<td align=left valign=top>
|
||||||
|
[if-any commits.files.branch][commits.files.branch][else] [end]
|
||||||
|
</td>
|
||||||
|
<td align=left valign=top>[commits.files.plus]/[commits.files.minus]</td>
|
||||||
|
<td align=left valign=top>
|
||||||
|
[if-any commits.files.date][commits.files.date][else] [end]
|
||||||
|
</td>
|
||||||
|
<td align=left valign=top>
|
||||||
|
[if-any commits.files.author][commits.files.author][else] [end]
|
||||||
|
</td>
|
||||||
|
|
||||||
|
[# uncommment, if you want a separate Description column:
|
||||||
|
{if-index commits.files first{
|
||||||
|
<td align=left valign=top rowspan={commits.num_files}>
|
||||||
|
{commits.desc}
|
||||||
|
</td>
|
||||||
|
{end}
|
||||||
|
|
||||||
|
(substitute brackets for the braces)
|
||||||
|
]
|
||||||
|
</tr>
|
||||||
|
[# and also take the following out in the "Description column"-case:]
|
||||||
|
[if-index commits.files last]
|
||||||
|
<tr bgcolor="[if-index commits even]#ffffff[else]#ccccee[end]">
|
||||||
|
<td> </td>
|
||||||
|
<td colspan=5><b>Log:</b><br>
|
||||||
|
<pre>[commits.desc]</pre></td>
|
||||||
|
</tr>
|
||||||
|
[end]
|
||||||
|
[# ---]
|
||||||
|
[end]
|
||||||
|
[end]
|
||||||
|
|
||||||
|
<tr bgcolor="#88ff88">
|
||||||
|
<th align=left valign=top> </th>
|
||||||
|
<th align=left valign=top> </th>
|
||||||
|
<th align=left valign=top> </th>
|
||||||
|
<th align=left valign=top> </th>
|
||||||
|
<th align=left valign=top> </th>
|
||||||
|
<th align=left valign=top> </th>
|
||||||
|
<th align=left valign=top> </th>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
[end]
|
||||||
|
[end]
|
||||||
|
|
||||||
|
<hr noshade>
|
||||||
|
<table width="100%" border=0 cellpadding=0 cellspacing=0><tr>
|
||||||
|
<td align=left><address>[address]</address></td>
|
||||||
|
<td align=right>
|
||||||
|
Powered by<br><a href="http://viewcvs.sourceforge.net/">ViewCVS [vsn]</a>
|
||||||
|
</td></tr></table>
|
||||||
|
</body></html>
|
139
tests/timelog.py
Normal file
139
tests/timelog.py
Normal file
@@ -0,0 +1,139 @@
|
|||||||
|
|
||||||
|
import time
|
||||||
|
import string
|
||||||
|
import profile
|
||||||
|
|
||||||
|
import rcsparse
|
||||||
|
import viewcvs
|
||||||
|
|
||||||
|
def lines_changed(delta):
|
||||||
|
idx = 0
|
||||||
|
added = deleted = 0
|
||||||
|
while idx < len(delta):
|
||||||
|
op = delta[idx]
|
||||||
|
i = string.find(delta, ' ', idx + 1)
|
||||||
|
j = string.find(delta, '\n', i + 1)
|
||||||
|
line = int(delta[idx+1:i])
|
||||||
|
count = int(delta[i+1:j])
|
||||||
|
idx = j + 1
|
||||||
|
if op == 'd':
|
||||||
|
deleted = deleted + count
|
||||||
|
else: # 'a' for adding text
|
||||||
|
added = added + count
|
||||||
|
# skip new text
|
||||||
|
while count > 0:
|
||||||
|
nl = string.find(delta, '\n', idx)
|
||||||
|
assert nl > 0, 'missing a newline in the delta in the RCS file'
|
||||||
|
idx = nl + 1
|
||||||
|
count = count - 1
|
||||||
|
return added, deleted
|
||||||
|
|
||||||
|
class FetchSink(rcsparse.Sink):
|
||||||
|
def __init__(self, which_rev=None):
|
||||||
|
self.head = self.branch = ''
|
||||||
|
self.tags = { }
|
||||||
|
self.meta = { }
|
||||||
|
self.revs = [ ]
|
||||||
|
self.base = { }
|
||||||
|
self.entries = { }
|
||||||
|
self.which = which_rev
|
||||||
|
|
||||||
|
def set_head_revision(self, revision):
|
||||||
|
self.head = revision
|
||||||
|
|
||||||
|
def set_principal_branch(self, branch_name):
|
||||||
|
self.branch = branch_name
|
||||||
|
|
||||||
|
def define_tag(self, name, revision):
|
||||||
|
self.tags[name] = revision
|
||||||
|
|
||||||
|
def define_revision(self, revision, timestamp, author, state,
|
||||||
|
branches, next):
|
||||||
|
self.meta[revision] = (timestamp, author, state)
|
||||||
|
self.base[next] = revision
|
||||||
|
for b in branches:
|
||||||
|
self.base[b] = revision
|
||||||
|
|
||||||
|
def set_revision_info(self, revision, log, text):
|
||||||
|
timestamp, author, state = self.meta[revision]
|
||||||
|
entry = viewcvs.LogEntry(revision, int(timestamp) - time.timezone, author,
|
||||||
|
state, None, log)
|
||||||
|
|
||||||
|
# .revs is "order seen" and .entries is for random access
|
||||||
|
self.revs.append(entry)
|
||||||
|
self.entries[revision] = entry
|
||||||
|
|
||||||
|
if revision != self.head:
|
||||||
|
added, deleted = lines_changed(text)
|
||||||
|
if string.count(revision, '.') == 1:
|
||||||
|
# on the trunk. reverse delta.
|
||||||
|
changed = '+%d -%d' % (deleted, added)
|
||||||
|
self.entries[self.base[revision]].changed = changed
|
||||||
|
else:
|
||||||
|
# on a branch. forward delta.
|
||||||
|
changed = '+%d -%d' % (added, deleted)
|
||||||
|
self.entries[revision].changed = changed
|
||||||
|
|
||||||
|
def parse_completed(self):
|
||||||
|
if self.which:
|
||||||
|
self.revs = [ self.entries[self.which] ]
|
||||||
|
|
||||||
|
def fetch_log2(full_name, which_rev=None):
|
||||||
|
sink = FetchSink(which_rev)
|
||||||
|
rcsparse.Parser().parse(open(full_name), sink)
|
||||||
|
return sink.head, sink.branch, sink.tags, sink.revs
|
||||||
|
|
||||||
|
def compare_fetch(full_name, which_rev=None):
|
||||||
|
d1 = viewcvs.fetch_log(full_name, which_rev)
|
||||||
|
d2 = fetch_log2(full_name, which_rev)
|
||||||
|
if d1[:3] != d2[:3]:
|
||||||
|
print 'd1:', d1[:3]
|
||||||
|
print 'd2:', d2[:3]
|
||||||
|
return
|
||||||
|
if len(d1[3]) != len(d2[3]):
|
||||||
|
print 'len(d1[3])=%d len(d2[3])=%d' % (len(d1[3]), len(d2[3]))
|
||||||
|
return
|
||||||
|
def sort_func(e, f):
|
||||||
|
return cmp(e.rev, f.rev)
|
||||||
|
d1[3].sort(sort_func)
|
||||||
|
d2[3].sort(sort_func)
|
||||||
|
import pprint
|
||||||
|
for i in range(len(d1[3])):
|
||||||
|
if vars(d1[3][i]) != vars(d2[3][i]):
|
||||||
|
pprint.pprint((i, vars(d1[3][i]), vars(d2[3][i])))
|
||||||
|
|
||||||
|
def time_fetch(full_name, which_rev=None):
|
||||||
|
t = time.time()
|
||||||
|
viewcvs.fetch_log(full_name, which_rev)
|
||||||
|
t1 = time.time() - t
|
||||||
|
t = time.time()
|
||||||
|
fetch_log2(full_name, which_rev)
|
||||||
|
t2 = time.time() - t
|
||||||
|
print t1, t2
|
||||||
|
|
||||||
|
def profile_fetch(full_name, which_rev=None):
|
||||||
|
p = profile.Profile()
|
||||||
|
def many_calls(*args):
|
||||||
|
for i in xrange(10):
|
||||||
|
apply(fetch_log2, args)
|
||||||
|
p.runcall(many_calls, full_name, which_rev)
|
||||||
|
p.print_stats()
|
||||||
|
|
||||||
|
def varysize(full_name, which_rev=None):
|
||||||
|
def one_run(n, *args):
|
||||||
|
rcsparse._TokenStream.CHUNK_SIZE = n
|
||||||
|
t = time.time()
|
||||||
|
for i in xrange(5):
|
||||||
|
apply(fetch_log2, args)
|
||||||
|
print n, time.time() - t
|
||||||
|
|
||||||
|
#one_run(2020, full_name, which_rev)
|
||||||
|
#one_run(4070, full_name, which_rev)
|
||||||
|
#one_run(8170, full_name, which_rev)
|
||||||
|
#one_run(8192, full_name, which_rev)
|
||||||
|
#one_run(16384, full_name, which_rev)
|
||||||
|
one_run(32740, full_name, which_rev)
|
||||||
|
one_run(65500, full_name, which_rev)
|
||||||
|
one_run(100000, full_name, which_rev)
|
||||||
|
one_run(200000, full_name, which_rev)
|
||||||
|
one_run(500000, full_name, which_rev)
|
116
tools/bin2inline_py.py
Executable file
116
tools/bin2inline_py.py
Executable file
@@ -0,0 +1,116 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
## vim:ts=4:et:nowrap
|
||||||
|
# [Emacs: -*- python -*-]
|
||||||
|
"""bin2inline_py.py -- creates Python source from directories containing images.
|
||||||
|
|
||||||
|
This is a very simple tool to pack a lot of small icons into a single file.
|
||||||
|
|
||||||
|
This version is a quick rape-and-past for the ViewCVS project.
|
||||||
|
Run this script from within the tools subdirectory to recreate the source file
|
||||||
|
'../lib/apache_icons.py'.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sys, os, string, fnmatch
|
||||||
|
|
||||||
|
|
||||||
|
PREAMBLE="""#! /usr/bin/env python
|
||||||
|
# This file was automatically generated! DO NOT EDIT!
|
||||||
|
# Howto regenerate: see ../tools/bin2inline.py
|
||||||
|
# $Id$
|
||||||
|
# You have been warned. But if you want to edit, go ahead using your
|
||||||
|
# favorite editor :-)
|
||||||
|
## vim:ts=4:et:nowrap
|
||||||
|
# [Emacs: -*- python -*-]
|
||||||
|
|
||||||
|
_ap_icons = """
|
||||||
|
|
||||||
|
def encodefile(filename):
|
||||||
|
"""returns the binary content of 'filename' as string"""
|
||||||
|
s = open(filename, 'rb').read()
|
||||||
|
result = [ ]
|
||||||
|
while s:
|
||||||
|
result.append(repr(s[:16]))
|
||||||
|
s = s[16:]
|
||||||
|
return string.join(result, '\n ')
|
||||||
|
|
||||||
|
class Encode:
|
||||||
|
"""Starting at a given directory find all files matching a certain
|
||||||
|
filename pattern in this subtree, encode them as base64 strings and
|
||||||
|
return a Python language dictionary with the filenames as keys and
|
||||||
|
the files contents as values.
|
||||||
|
"""
|
||||||
|
def __init__(self, startdir=os.curdir, fn_pattern="*.gif"):
|
||||||
|
self.startdir = startdir
|
||||||
|
self.fn_pattern = fn_pattern
|
||||||
|
self.walk()
|
||||||
|
|
||||||
|
def walk(self):
|
||||||
|
"""walk through the subtree starting at self.startdir"""
|
||||||
|
self.result = ['{\n']
|
||||||
|
os.path.walk(self.startdir, self.visitor, None)
|
||||||
|
self.result.append('}\n')
|
||||||
|
|
||||||
|
def visitor(self, dummy, dirname, filenames):
|
||||||
|
"""A visitor compatible with os.path.walk()."""
|
||||||
|
for candidate in filenames:
|
||||||
|
pathname = os.path.join(dirname, candidate)
|
||||||
|
if not os.path.isfile(pathname):
|
||||||
|
continue
|
||||||
|
if self.match(pathname):
|
||||||
|
self.put_item(pathname)
|
||||||
|
|
||||||
|
def match(self, candidate):
|
||||||
|
"""should return false, if pathname 'candidate' should be skipped.
|
||||||
|
"""
|
||||||
|
return fnmatch.fnmatch(candidate, self.fn_pattern)
|
||||||
|
|
||||||
|
def put_item(self, pathname):
|
||||||
|
self.result.append(' "%s" :\n %s,\n\n'
|
||||||
|
% (self.compute_key(pathname),
|
||||||
|
encodefile(pathname)))
|
||||||
|
|
||||||
|
def compute_key(self, pathname):
|
||||||
|
"""computes the dictionary key. Tkinter compatible"""
|
||||||
|
return os.path.splitext(pathname)[0]
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return string.join(self.result, '')
|
||||||
|
|
||||||
|
#
|
||||||
|
# The remainder of this module is best described as hack, run and throw away
|
||||||
|
# software. You may want to edit it, if you want to other icons.
|
||||||
|
#
|
||||||
|
class WebserverIconEncode(Encode):
|
||||||
|
minimal_list = [ # List of icons actually used by ViewCVS as of 2001-11-17
|
||||||
|
"/icons/apache_pb.gif",
|
||||||
|
"/icons/small/back.gif",
|
||||||
|
"/icons/small/dir.gif",
|
||||||
|
"/icons/small/text.gif",
|
||||||
|
]
|
||||||
|
def match(self, candidate):
|
||||||
|
return self.compute_key(candidate) in self.minimal_list
|
||||||
|
|
||||||
|
def compute_key(self, pathname):
|
||||||
|
l = len(self.startdir)
|
||||||
|
if pathname[:l] == self.startdir:
|
||||||
|
return pathname[l:]
|
||||||
|
return pathname
|
||||||
|
|
||||||
|
POSTAMBLE="""
|
||||||
|
|
||||||
|
def serve_icon(pathname, fp):
|
||||||
|
if _ap_icons.has_key(pathname):
|
||||||
|
fp.write(_ap_icons[pathname])
|
||||||
|
else:
|
||||||
|
raise OSError # icon not found
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
import sys
|
||||||
|
|
||||||
|
f = open("../lib/apache_icons.py", "wt")
|
||||||
|
f.write(PREAMBLE)
|
||||||
|
f.write(str(WebserverIconEncode(startdir="/usr/local/httpd")))
|
||||||
|
f.write(POSTAMBLE)
|
@@ -40,13 +40,15 @@ else:
|
|||||||
|
|
||||||
#########################################################################
|
#########################################################################
|
||||||
|
|
||||||
import os, string, cvsdbapi
|
import os
|
||||||
|
import string
|
||||||
|
import cvsdb
|
||||||
|
|
||||||
|
|
||||||
def UpdateFile(db, repository, path):
|
def UpdateFile(db, repository, path):
|
||||||
try:
|
try:
|
||||||
commit_list = cvsdbapi.GetUnrecordedCommitList(repository, path)
|
commit_list = cvsdb.GetUnrecordedCommitList(repository, path)
|
||||||
except cvsdbapi.error, e:
|
except cvsdb.error, e:
|
||||||
print '[ERROR] %s' % (e)
|
print '[ERROR] %s' % (e)
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -78,15 +80,15 @@ def RecurseUpdate(db, repository, directory):
|
|||||||
|
|
||||||
def CommandUpdate():
|
def CommandUpdate():
|
||||||
## connect to the database we are updating
|
## connect to the database we are updating
|
||||||
db = cvsdbapi.ConnectDatabase()
|
db = cvsdb.ConnectDatabase()
|
||||||
repository = sys.argv[2]
|
repository = sys.argv[2]
|
||||||
RecurseUpdate(db, repository, repository)
|
RecurseUpdate(db, repository, repository)
|
||||||
|
|
||||||
|
|
||||||
def RebuildFile(db, repository, path):
|
def RebuildFile(db, repository, path):
|
||||||
try:
|
try:
|
||||||
commit_list = cvsdbapi.GetCommitListFromRCSFile(repository, path)
|
commit_list = cvsdb.GetCommitListFromRCSFile(repository, path)
|
||||||
except cvsdbapi.error, e:
|
except cvsdb.error, e:
|
||||||
print '[ERROR] %s' % (e)
|
print '[ERROR] %s' % (e)
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -118,7 +120,7 @@ def RecurseRebuild(db, repository, directory):
|
|||||||
|
|
||||||
def CommandRebuild():
|
def CommandRebuild():
|
||||||
## connect to the database we are updating
|
## connect to the database we are updating
|
||||||
db = cvsdbapi.ConnectDatabase()
|
db = cvsdb.ConnectDatabase()
|
||||||
repository = sys.argv[2]
|
repository = sys.argv[2]
|
||||||
RecurseRebuild(db, repository, repository)
|
RecurseRebuild(db, repository, repository)
|
||||||
|
|
||||||
|
@@ -39,14 +39,20 @@ else:
|
|||||||
|
|
||||||
#########################################################################
|
#########################################################################
|
||||||
|
|
||||||
import os, string, getopt, re, cvsdbapi
|
import os
|
||||||
|
import string
|
||||||
|
import getopt
|
||||||
|
import re
|
||||||
|
import cvsdb
|
||||||
|
import rlog
|
||||||
|
import config
|
||||||
|
|
||||||
|
|
||||||
DEBUG_FLAG = 0
|
DEBUG_FLAG = 0
|
||||||
|
|
||||||
## pre-compiled regular expressions
|
## pre-compiled regular expressions
|
||||||
_re_fileversion = re.compile("([^,]+)\,([^,]+)\,([^,]+)")
|
_re_fileversion = re.compile("([^,]+)\,([^,]+)\,([^,]+)")
|
||||||
|
|
||||||
|
|
||||||
## output functions
|
## output functions
|
||||||
def debug(text):
|
def debug(text):
|
||||||
if DEBUG_FLAG:
|
if DEBUG_FLAG:
|
||||||
@@ -59,13 +65,7 @@ def error(text):
|
|||||||
print 'ERROR(loginfo): %s' % (text)
|
print 'ERROR(loginfo): %s' % (text)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
class FileData:
|
class FileData:
|
||||||
|
|
||||||
CHANGED = 1
|
|
||||||
ADDED = 2
|
|
||||||
REMOVED = 3
|
|
||||||
|
|
||||||
def __init__(self, file, directory, old_version, new_version):
|
def __init__(self, file, directory, old_version, new_version):
|
||||||
self.file = file
|
self.file = file
|
||||||
self.directory = directory
|
self.directory = directory
|
||||||
@@ -75,40 +75,43 @@ class FileData:
|
|||||||
## set the state of this file from the
|
## set the state of this file from the
|
||||||
## old_version and new_version information
|
## old_version and new_version information
|
||||||
if self.old_version == 'NONE':
|
if self.old_version == 'NONE':
|
||||||
self.type = self.ADDED
|
self.ctype = "added"
|
||||||
elif self.new_version == 'NONE':
|
elif self.new_version == 'NONE':
|
||||||
self.type = self.REMOVED
|
self.ctype = "removed"
|
||||||
else:
|
else:
|
||||||
self.type = self.CHANGED
|
self.ctype = "changed"
|
||||||
|
|
||||||
|
def CommitFromFileData(cfg, repository, file_data):
|
||||||
def CommitFromFileData(repository, file_data):
|
## construct the full path for the RCS file
|
||||||
## consturct the full path for the RCS file
|
|
||||||
filename = os.path.join(repository, file_data.directory, file_data.file)
|
filename = os.path.join(repository, file_data.directory, file_data.file)
|
||||||
|
|
||||||
## get the 'rlog' output for just this revision, and then convert
|
## get the 'rlog' output for just this revision, and then convert
|
||||||
## to a commit object
|
## to a commit object
|
||||||
rlog_data = cvsdbapi.GetRLogData(filename, file_data.new_version)
|
rlog_data = rlog.GetRLogData(cfg, filename, file_data.new_version)
|
||||||
commit_list = cvsdbapi.RLogDataToCommitList(repository, rlog_data)
|
commit_list = cvsdb.RLogDataToCommitList(repository, rlog_data)
|
||||||
commit = commit_list[0]
|
commit = commit_list[0]
|
||||||
|
|
||||||
## set the type of commit from the file_data setting
|
## set the type of commit from the file_data setting
|
||||||
if file_data.type == file_data.CHANGED:
|
if file_data.ctype == "changed":
|
||||||
commit.SetTypeChange()
|
commit.SetTypeChange()
|
||||||
elif file_data.type == file_data.ADDED:
|
elif file_data.ctype == "added":
|
||||||
commit.SetTypeAdd()
|
commit.SetTypeAdd()
|
||||||
elif file_data.type == file_data.REMOVED:
|
elif file_data.ctype == "removed":
|
||||||
commit.SetTypeRemove()
|
commit.SetTypeRemove()
|
||||||
|
|
||||||
return commit
|
return commit
|
||||||
|
|
||||||
|
|
||||||
def GetUnrecordedCommitList(repository, file_data):
|
def GetUnrecordedCommitList(repository, file_data):
|
||||||
filename = os.path.join(repository, file_data.directory, file_data.file)
|
filename = os.path.join(repository, file_data.directory, file_data.file)
|
||||||
return cvsdbapi.GetUnrecordedCommitList(repository, filename)
|
return cvsdb.GetUnrecordedCommitList(repository, filename)
|
||||||
|
|
||||||
|
|
||||||
def ProcessLoginfo(repository, stdin_list):
|
def ProcessLoginfo(repository, stdin_list):
|
||||||
|
## XXX This is a somewhat dirty hack:
|
||||||
|
## cvsdb already loads the configuration file and provides a cfg
|
||||||
|
## instance. Pick it from there in order to be able to pass it down
|
||||||
|
## to rlog (see below).
|
||||||
|
cfg = cvsdb.cfg
|
||||||
|
|
||||||
## the first line in stdin is a space-separated list; the first
|
## the first line in stdin is a space-separated list; the first
|
||||||
## item in the list is the directory path being updated this run;
|
## item in the list is the directory path being updated this run;
|
||||||
## the rest of the items are the files being updated
|
## the rest of the items are the files being updated
|
||||||
@@ -155,14 +158,14 @@ def ProcessLoginfo(repository, stdin_list):
|
|||||||
## given enough information to find it in the rlog output!
|
## given enough information to find it in the rlog output!
|
||||||
## So instead, we rlog everything in the removed file, and
|
## So instead, we rlog everything in the removed file, and
|
||||||
## add any commits not already in the database
|
## add any commits not already in the database
|
||||||
if file_data.type == file_data.REMOVED:
|
if file_data.ctype == "removed":
|
||||||
temp = GetUnrecordedCommitList(repository, file_data)
|
temp = GetUnrecordedCommitList(repository, file_data)
|
||||||
commit_list = commit_list + temp
|
commit_list = commit_list + temp
|
||||||
else:
|
else:
|
||||||
commit_list.append(CommitFromFileData(repository, file_data))
|
commit_list.append(CommitFromFileData(cfg, repository, file_data))
|
||||||
|
|
||||||
## add to the database
|
## add to the database
|
||||||
db = cvsdbapi.ConnectDatabase()
|
db = cvsdb.ConnectDatabase()
|
||||||
db.AddCommitList(commit_list)
|
db.AddCommitList(commit_list)
|
||||||
|
|
||||||
|
|
||||||
|
@@ -1,15 +1,15 @@
|
|||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
# -*- Mode: python -*-
|
# -*- Mode: python -*-
|
||||||
#
|
#
|
||||||
# Copyright (C) 2000 The ViewCVS Group. All Rights Reserved.
|
# Copyright (C) 2000-2001 The ViewCVS Group. All Rights Reserved.
|
||||||
#
|
#
|
||||||
# By using this file, you agree to the terms and conditions set forth in
|
# By using this file, you agree to the terms and conditions set forth in
|
||||||
# the LICENSE.html file which can be found at the top level of the ViewCVS
|
# the LICENSE.html file which can be found at the top level of the ViewCVS
|
||||||
# distribution or at http://www.lyra.org/viewcvs/license-1.html.
|
# distribution or at http://viewcvs.sourceforge.net/license-1.html.
|
||||||
#
|
#
|
||||||
# Contact information:
|
# Contact information:
|
||||||
# Greg Stein, PO Box 760, Palo Alto, CA, 94302
|
# Greg Stein, PO Box 760, Palo Alto, CA, 94302
|
||||||
# gstein@lyra.org, http://www.lyra.org/viewcvs/
|
# gstein@lyra.org, http://viewcvs.sourceforge.net/
|
||||||
#
|
#
|
||||||
# -----------------------------------------------------------------------
|
# -----------------------------------------------------------------------
|
||||||
#
|
#
|
||||||
@@ -18,7 +18,9 @@
|
|||||||
#
|
#
|
||||||
# -----------------------------------------------------------------------
|
# -----------------------------------------------------------------------
|
||||||
#
|
#
|
||||||
|
|
||||||
import os, sys, string
|
import os, sys, string
|
||||||
|
import popen2
|
||||||
|
|
||||||
INTRO_TEXT = """\
|
INTRO_TEXT = """\
|
||||||
This script creates the database and tables in MySQL used by the ViewCVS
|
This script creates the database and tables in MySQL used by the ViewCVS
|
||||||
@@ -133,9 +135,11 @@ if __name__ == "__main__":
|
|||||||
cmd = "{ mysql --user=%s --password=%s ; } 2>&1" % (user, passwd)
|
cmd = "{ mysql --user=%s --password=%s ; } 2>&1" % (user, passwd)
|
||||||
dscript = string.replace(DATABASE_SCRIPT, "<dbname>", dbase)
|
dscript = string.replace(DATABASE_SCRIPT, "<dbname>", dbase)
|
||||||
|
|
||||||
mysql = os.popen(cmd, "w")
|
pipes = popen2.Popen3(cmd)
|
||||||
mysql.write(dscript)
|
pipes.tochild.write(dscript)
|
||||||
status = mysql.close()
|
pipes.tochild.close()
|
||||||
|
print pipes.fromchild.read()
|
||||||
|
status = pipes.wait()
|
||||||
|
|
||||||
if status:
|
if status:
|
||||||
print "[ERROR] the database did not create sucessfully."
|
print "[ERROR] the database did not create sucessfully."
|
||||||
|
@@ -1,14 +1,14 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
#
|
#
|
||||||
# Copyright (C) 2000 The ViewCVS Group. All Rights Reserved.
|
# Copyright (C) 2000-2001 The ViewCVS Group. All Rights Reserved.
|
||||||
#
|
#
|
||||||
# By using this file, you agree to the terms and conditions set forth in
|
# By using this file, you agree to the terms and conditions set forth in
|
||||||
# the LICENSE.html file which can be found at the top level of the ViewCVS
|
# the LICENSE.html file which can be found at the top level of the ViewCVS
|
||||||
# distribution or at http://www.lyra.org/viewcvs/license-1.html.
|
# distribution or at http://viewcvs.sourceforge.net/license-1.html.
|
||||||
#
|
#
|
||||||
# Contact information:
|
# Contact information:
|
||||||
# Greg Stein, PO Box 760, Palo Alto, CA, 94302
|
# Greg Stein, PO Box 760, Palo Alto, CA, 94302
|
||||||
# gstein@lyra.org, http://www.lyra.org/viewcvs/
|
# gstein@lyra.org, http://viewcvs.sourceforge.net/
|
||||||
#
|
#
|
||||||
# -----------------------------------------------------------------------
|
# -----------------------------------------------------------------------
|
||||||
#
|
#
|
||||||
@@ -17,23 +17,37 @@
|
|||||||
# -----------------------------------------------------------------------
|
# -----------------------------------------------------------------------
|
||||||
#
|
#
|
||||||
|
|
||||||
if [ "x$1" = "x" ]; then
|
if test $# != 2; then
|
||||||
echo "USAGE: $0 target-directory"
|
echo "USAGE: $0 tagname target-directory"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# grab a copy of the CVS repository (export requires a tag, so just use 'co')
|
if test -e $2; then
|
||||||
rm -r $1
|
echo "ERROR: must remove $2 first."
|
||||||
echo 'Checking out into:' $1
|
exit 1
|
||||||
cvs -d /home/cvsroot co -d $1 -kv -P -R viewcvs
|
fi
|
||||||
|
|
||||||
echo 'Cleaning up...'
|
# grab a copy of the CVS repository
|
||||||
# clean out the CVS crap
|
echo 'Checking out into:' $2
|
||||||
find $1 -name CVS | xargs rm -r
|
cvs -d :pserver:anonymous@cvs.viewcvs.sourceforge.net:/cvsroot/viewcvs export -r $1 -d $2 viewcvs
|
||||||
|
|
||||||
# various shifting, cleanup
|
# various shifting, cleanup.
|
||||||
mv $1/website/license-1.html $1/LICENSE.html
|
# documentation is now also distributed together with the release,
|
||||||
rm -r $1/website
|
# but we still copy the license file to its traditional place (it is small
|
||||||
rm $1/tools/make-release
|
# and many files still contain comments refering to this location):
|
||||||
|
cp $2/website/license-1.html $2/LICENSE.html
|
||||||
|
# rm -r $2/website
|
||||||
|
# remove some tools only useful for ViewCVS developers:
|
||||||
|
rm $2/tools/make-release
|
||||||
|
rm -f $2/tools/bin2inline_py.py
|
||||||
|
|
||||||
|
# Make sure, permissions are reasonable:
|
||||||
|
find $2 -print | xargs chmod uoa+r
|
||||||
|
find $2 -type d -print | xargs chmod uoa+x
|
||||||
|
|
||||||
|
# cut the tarball:
|
||||||
|
tar cfz - $2 | gzip -9 > $2.tar.gz
|
||||||
|
# create also a ZIP file for those poor souls :-) still using Windows:
|
||||||
|
zip -qor9 $2.zip $2
|
||||||
|
|
||||||
echo 'Done.'
|
echo 'Done.'
|
||||||
|
@@ -1,15 +1,15 @@
|
|||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
# -*- Mode: python -*-
|
# -*- Mode: python -*-
|
||||||
#
|
#
|
||||||
# Copyright (C) 2000 The ViewCVS Group. All Rights Reserved.
|
# Copyright (C) 2000-2001 The ViewCVS Group. All Rights Reserved.
|
||||||
#
|
#
|
||||||
# By using this file, you agree to the terms and conditions set forth in
|
# By using this file, you agree to the terms and conditions set forth in
|
||||||
# the LICENSE.html file which can be found at the top level of the ViewCVS
|
# the LICENSE.html file which can be found at the top level of the ViewCVS
|
||||||
# distribution or at http://www.lyra.org/viewcvs/license-1.html.
|
# distribution or at http://viewcvs.sourceforge.net/license-1.html.
|
||||||
#
|
#
|
||||||
# Contact information:
|
# Contact information:
|
||||||
# Greg Stein, PO Box 760, Palo Alto, CA, 94302
|
# Greg Stein, PO Box 760, Palo Alto, CA, 94302
|
||||||
# gstein@lyra.org, http://www.lyra.org/viewcvs/
|
# gstein@lyra.org, http://viewcvs.sourceforge.net/
|
||||||
#
|
#
|
||||||
# -----------------------------------------------------------------------
|
# -----------------------------------------------------------------------
|
||||||
#
|
#
|
||||||
@@ -32,52 +32,75 @@ import py_compile
|
|||||||
sys.path.insert(0, os.path.join(os.path.dirname(sys.argv[0]), 'lib'))
|
sys.path.insert(0, os.path.join(os.path.dirname(sys.argv[0]), 'lib'))
|
||||||
|
|
||||||
import compat
|
import compat
|
||||||
|
import viewcvs
|
||||||
|
version = viewcvs.__version__
|
||||||
|
|
||||||
|
|
||||||
## installer text
|
## installer text
|
||||||
INFO_TEXT = """\
|
INFO_TEXT = """\
|
||||||
|
|
||||||
This is the ViewCVS installer. It will allow you to choose the install
|
This is the ViewCVS %s installer.
|
||||||
path for ViewCVS. You will now be asked some installation questions.
|
|
||||||
|
It will allow you to choose the install path for ViewCVS. You will
|
||||||
|
now be asked some installation questions.
|
||||||
|
|
||||||
Defaults are given in square brackets. Just hit [Enter] if a default
|
Defaults are given in square brackets. Just hit [Enter] if a default
|
||||||
is okay.
|
is okay.
|
||||||
"""
|
""" % version
|
||||||
|
|
||||||
## installer defaults
|
## installer defaults
|
||||||
ROOT_DIR = "/usr/local/viewcvs"
|
ROOT_DIR = "/usr/local/viewcvs-" + version
|
||||||
|
|
||||||
|
|
||||||
## list of files for installation
|
## list of files for installation
|
||||||
## tuple (source path, destination path, install mode, true/false flag for
|
## tuple (source path, destination path, install mode, true/false flag for
|
||||||
## search-and-replace, flag for prompt before replace, compile_it)
|
## search-and-replace, flag or text for prompt before replace,
|
||||||
|
## compile_it)
|
||||||
##
|
##
|
||||||
|
|
||||||
FILE_INFO_LIST = [
|
FILE_INFO_LIST = [
|
||||||
("cgi/viewcvs.cgi", "cgi/viewcvs.cgi", 0755, 1, 0, 0),
|
("cgi/viewcvs.cgi", "cgi/viewcvs.cgi", 0755, 1, 0, 0),
|
||||||
("cgi/queryform.cgi", "cgi/queryform.cgi", 0755, 1, 0, 0),
|
|
||||||
("cgi/query.cgi", "cgi/query.cgi", 0755, 1, 0, 0),
|
("cgi/query.cgi", "cgi/query.cgi", 0755, 1, 0, 0),
|
||||||
|
("standalone.py", "standalone.py", 0755, 1, 0, 0),
|
||||||
|
|
||||||
("cgi/viewcvs.conf.dist", "viewcvs.conf", 0644, 0, 1, 0),
|
("cgi/viewcvs.conf.dist", "viewcvs.conf", 0644, 1,
|
||||||
|
"""Note: If you are upgrading from viewcvs-0.7 or earlier:
|
||||||
|
The section [text] has been removed from viewcvs.conf. The functionality
|
||||||
|
went into the new files in subdirectory templates.""", 0),
|
||||||
|
("cgi/cvsgraph.conf.dist", "cvsgraph.conf", 0644, 0, 1, 0),
|
||||||
|
|
||||||
("lib/PyFontify.py", "lib/PyFontify.py", 0644, 0, 0, 1),
|
("lib/PyFontify.py", "lib/PyFontify.py", 0644, 0, 0, 1),
|
||||||
("lib/blame.py", "lib/blame.py", 0644, 0, 0, 1),
|
("lib/blame.py", "lib/blame.py", 0644, 0, 0, 1),
|
||||||
("lib/commit.py", "lib/commit.py", 0644, 0, 0, 1),
|
|
||||||
("lib/compat.py", "lib/compat.py", 0644, 0, 0, 1),
|
("lib/compat.py", "lib/compat.py", 0644, 0, 0, 1),
|
||||||
("lib/config.py", "lib/config.py", 0644, 0, 0, 1),
|
("lib/config.py", "lib/config.py", 0644, 0, 0, 1),
|
||||||
("lib/cvsdbapi.py", "lib/cvsdbapi.py", 0644, 1, 0, 1),
|
("lib/cvsdb.py", "lib/cvsdb.py", 0644, 1, 0, 1),
|
||||||
("lib/database.py", "lib/database.py", 0644, 0, 0, 1),
|
("lib/dbi.py", "lib/dbi.py", 0644, 0, 0, 1),
|
||||||
|
("lib/debug.py", "lib/debug.py", 0644, 0, 0, 1),
|
||||||
("lib/popen.py", "lib/popen.py", 0644, 0, 0, 1),
|
("lib/popen.py", "lib/popen.py", 0644, 0, 0, 1),
|
||||||
("lib/py2html.py", "lib/py2html.py", 0644, 0, 0, 1),
|
("lib/py2html.py", "lib/py2html.py", 0644, 0, 0, 1),
|
||||||
("lib/rlog.py", "lib/rlog.py", 0644, 1, 0, 1),
|
("lib/query.py", "lib/query.py", 0644, 1, 0, 1),
|
||||||
|
("lib/rcsparse.py", "lib/rcsparse.py", 0644, 0, 0, 1),
|
||||||
|
("lib/rlog.py", "lib/rlog.py", 0644, 0, 0, 1),
|
||||||
|
("lib/viewcvs.py", "lib/viewcvs.py", 0644, 1, 0, 1),
|
||||||
|
("lib/ezt.py", "lib/ezt.py", 0644, 0, 0, 1),
|
||||||
|
("lib/apache_icons.py", "lib/apache_icons.py", 0644, 0, 0, 1),
|
||||||
|
|
||||||
|
("templates/directory.ezt", "templates/directory.ezt", 0644, 0, 1, 0),
|
||||||
|
("templates/log.ezt", "templates/log.ezt", 0644, 0, 1, 0),
|
||||||
|
("templates/log_table.ezt", "templates/log_table.ezt", 0644, 0, 1, 0),
|
||||||
|
("templates/query.ezt", "templates/query.ezt", 0644, 0, 1, 0),
|
||||||
|
|
||||||
("tools/loginfo-handler", "loginfo-handler", 0755, 1, 0, 0),
|
("tools/loginfo-handler", "loginfo-handler", 0755, 1, 0, 0),
|
||||||
("tools/cvsdbadmin", "cvsdbadmin", 0755, 1, 0, 0),
|
("tools/cvsdbadmin", "cvsdbadmin", 0755, 1, 0, 0),
|
||||||
("tools/make-database", "make-database", 0755, 1, 0, 0),
|
("tools/make-database", "make-database", 0755, 1, 0, 0),
|
||||||
|
|
||||||
("html-templates/queryformtemplate.html",
|
("website/help_rootview.html", "doc/help_rootview.html", 0644, 0, 0, 0),
|
||||||
"html-templates/queryformtemplate.html", 0644, 0, 1, 0),
|
("website/help_dirview.html", "doc/help_dirview.html", 0644, 0, 0, 0),
|
||||||
("html-templates/querytemplate.html",
|
|
||||||
"html-templates/querytemplate.html", 0644, 0, 1, 0),
|
("website/images/logo.png", "doc/images/logo.png", 0644, 0, 0, 0),
|
||||||
|
("website/images/chalk.jpg", "doc/images/chalk.jpg", 0644, 0, 0, 0),
|
||||||
|
("website/images/cvsgraph_16x16.png", "doc/images/cvsgraph_16x16.png", 0644, 0, 0, 0),
|
||||||
|
("website/images/cvsgraph_32x32.png", "doc/images/cvsgraph_32x32.png", 0644, 0, 0, 0),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
@@ -93,11 +116,11 @@ def Error(text, etype=None, evalue=None):
|
|||||||
def MkDir(path):
|
def MkDir(path):
|
||||||
try:
|
try:
|
||||||
compat.makedirs(path)
|
compat.makedirs(path)
|
||||||
except OSError, e:
|
except os.error, e:
|
||||||
if e.errno == 17:
|
if e[0] == 17:
|
||||||
# EEXIST: file exists
|
# EEXIST: file exists
|
||||||
return
|
return
|
||||||
if e.errno == 13:
|
if e[0] == 13:
|
||||||
# EACCES: permission denied
|
# EACCES: permission denied
|
||||||
Error("You do not have permission to create directory %s" % path)
|
Error("You do not have permission to create directory %s" % path)
|
||||||
Error("Unknown error creating directory %s" % path, OSError, e)
|
Error("Unknown error creating directory %s" % path, OSError, e)
|
||||||
@@ -113,9 +136,9 @@ def SetPythonPaths(contents):
|
|||||||
if contents[:2] == '#!':
|
if contents[:2] == '#!':
|
||||||
shbang = '#!' + sys.executable
|
shbang = '#!' + sys.executable
|
||||||
contents = re.sub('^#![^\n]*', shbang, contents)
|
contents = re.sub('^#![^\n]*', shbang, contents)
|
||||||
|
contents = re.sub("<VIEWCVS_INSTALL_DIRECTORY>", ROOT_DIR, contents)
|
||||||
contents = SetOnePath(contents, 'LIBRARY_DIR', 'lib')
|
contents = SetOnePath(contents, 'LIBRARY_DIR', 'lib')
|
||||||
contents = SetOnePath(contents, 'CONF_PATHNAME', 'viewcvs.conf')
|
contents = SetOnePath(contents, 'CONF_PATHNAME', 'viewcvs.conf')
|
||||||
contents = SetOnePath(contents, 'HTML_TEMPLATE_DIR', 'html-templates')
|
|
||||||
return contents
|
return contents
|
||||||
|
|
||||||
|
|
||||||
@@ -124,7 +147,9 @@ def InstallFile(src_path, dest_path, mode, set_python_paths, prompt_replace,
|
|||||||
dest_path = os.path.join(ROOT_DIR, dest_path)
|
dest_path = os.path.join(ROOT_DIR, dest_path)
|
||||||
|
|
||||||
if prompt_replace and os.path.exists(dest_path):
|
if prompt_replace and os.path.exists(dest_path):
|
||||||
temp = raw_input("File %s exists, overwright? [y/N]: " % (dest_path))
|
if type(prompt_replace) == type(""):
|
||||||
|
print prompt_replace
|
||||||
|
temp = raw_input("File %s exists, overwrite? [y/N]: " % (dest_path))
|
||||||
if not temp or string.lower(temp[0]) != "y":
|
if not temp or string.lower(temp[0]) != "y":
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -143,7 +168,7 @@ def InstallFile(src_path, dest_path, mode, set_python_paths, prompt_replace,
|
|||||||
try:
|
try:
|
||||||
open(dest_path, "w").write(contents)
|
open(dest_path, "w").write(contents)
|
||||||
except IOError, e:
|
except IOError, e:
|
||||||
if e.errno == 13:
|
if e[0] == 13:
|
||||||
# EACCES: permission denied
|
# EACCES: permission denied
|
||||||
Error("You do not have permission to write file %s" % dest_path)
|
Error("You do not have permission to write file %s" % dest_path)
|
||||||
Error("Unknown error writing file %s" % dest_path, IOError, e)
|
Error("Unknown error writing file %s" % dest_path, IOError, e)
|
||||||
|
173
website/contributing.html
Normal file
173
website/contributing.html
Normal file
@@ -0,0 +1,173 @@
|
|||||||
|
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Contributing to ViewCVS Development</title>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body background="images/chalk.jpg">
|
||||||
|
|
||||||
|
<table width="100%" cellspacing=5>
|
||||||
|
<tr>
|
||||||
|
<td width="1%"><a href="index.html"><img border=0
|
||||||
|
src="images/logo.png"></a>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<h1>Contributing to ViewCVS Development</h1>
|
||||||
|
</td>
|
||||||
|
<td width="1%"><a href="http://sourceforge.net/"><img border=0
|
||||||
|
src="http://sourceforge.net/sflogo.php?group_id=18760&type=1"></a><br><a href="http://sourceforge.net/projects/viewcvs/">ViewCVS project page</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr><td width="1%" valign=top>
|
||||||
|
<a href="index.html">Home</a><br>
|
||||||
|
<a href="upgrading.html">Upgrading</a><br>
|
||||||
|
Contributing<br>
|
||||||
|
<a href="license-1.html">License</a><br>
|
||||||
|
<a href="who.html">Who</a><br>
|
||||||
|
</td><td colspan=2>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Contributions to ViewCVS are very welcome.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2>Getting Started</h2>
|
||||||
|
<p>
|
||||||
|
Some basic knowledge about <a href="http://www.python.org"><i>Python</i></a> and
|
||||||
|
development Tools like <code>diff</code> is required. Best is you start
|
||||||
|
with a fresh snapshot, which you may obtain from the
|
||||||
|
<a href="http://sourceforge.net/cvs/?group_id=18760">CVS-Repository</a>
|
||||||
|
at SourceForge.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2>Testing and reporting</h2>
|
||||||
|
<p>
|
||||||
|
Testing usability and the installation process on different platforms is
|
||||||
|
also a valuable contribution. Please report your results back to
|
||||||
|
us developers. But always tell us, which version of ViewCVS
|
||||||
|
you used on which platform.
|
||||||
|
</p><p>
|
||||||
|
If you are using the latest current development version please note
|
||||||
|
the date of your latest download or cvs update when reporting.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2>Does a Certain Coding Style Guide apply?</h2>
|
||||||
|
<p>
|
||||||
|
Maintain whatever style is present in the code being modified. New
|
||||||
|
code can use anything sane (which generally means
|
||||||
|
<a href="http://python.sourceforge.net/peps/pep-0008.html"><i>PEP 8</i></a>).
|
||||||
|
Greg's only real peeve is if someone writes a function call as:
|
||||||
|
<code>some_func (args)</code> -- that space is Huge Badness.
|
||||||
|
Otherwise... <i>shrug</i>.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2>Submitting patches</h2>
|
||||||
|
<p>
|
||||||
|
Please use the
|
||||||
|
<a href="http://sourceforge.net/tracker/?func=add&group_id=18760&atid=318760">SourceForge project patch manager</a>
|
||||||
|
to submit your patches. Unified context diffs relative to the latest
|
||||||
|
development version are preferred.
|
||||||
|
</p><p>
|
||||||
|
If you have commit access, then you should know what
|
||||||
|
you're doing. Just make changes directly. Subscribing to
|
||||||
|
the <a href="http://mailman.lyra.org/mailman/listinfo/viewcvs-dev"><i>developer mailing list</i></a>
|
||||||
|
is recommended in any case.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2>Preserving security</h2>
|
||||||
|
<p>
|
||||||
|
Since ViewCVS is used in the internet, security is a major issue.
|
||||||
|
If you need to pass data from the request into an external program,
|
||||||
|
please don't use <code>os.system()</code> or <code>os.popen()</code>.
|
||||||
|
Please use the module <code>lib/popen</code> that comes with ViewCVS
|
||||||
|
instead.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2>Adding new features</h2>
|
||||||
|
<p>
|
||||||
|
If a new file or module is added, a new line in the installer program
|
||||||
|
<code>viewcvs-install</code> is required.
|
||||||
|
</p><p>
|
||||||
|
The library subdirectory contains a module <code>debug</code>, which may
|
||||||
|
useful to make performance tests.
|
||||||
|
</p><p>
|
||||||
|
If you need a new configuration option think carefully, into
|
||||||
|
which section it belongs. Try to keep the content of
|
||||||
|
<code>cgi/viewcvs.conf.dist</code> file and the library module
|
||||||
|
<code>lib/config.py</code> in sync.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2>Hacking on templates</h2>
|
||||||
|
<p>
|
||||||
|
The library module <code>ezt</code> contains a module doc string which
|
||||||
|
describes the directives used in the HTML templates, which can be found
|
||||||
|
in the <code>templates</code> sub directory.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2>Cutting a release</h2>
|
||||||
|
<p>
|
||||||
|
Also there actually is a script <code>tools/make-release</code>,
|
||||||
|
which creates a release directory, all other steps required to get
|
||||||
|
a ViewCVS release out of the door will be currently executed manually
|
||||||
|
by Greg Stein.
|
||||||
|
</p><p>
|
||||||
|
Nevertheless in case he ever wants to retire from this job, it is
|
||||||
|
probably a good idea to write the procedure down here:
|
||||||
|
<ol>
|
||||||
|
<li>Add a new subsection to the file
|
||||||
|
<code>website/upgrading.html</code> describing all user visible
|
||||||
|
changes for users of previous releases of ViewCVS.
|
||||||
|
<li>Test, Test, Test! At the time of this writing (0.8-dev) there
|
||||||
|
is no automatic testsuite available. So just run with permuting
|
||||||
|
different <code>viewcvs.conf</code> settings and ... pray.
|
||||||
|
<li>Review any <a
|
||||||
|
href="http://sourceforge.net/tracker/?atid=118760&group_id=18760&func=browse">
|
||||||
|
bug reports, that are still marked open.</a>
|
||||||
|
<li>Edit the file <code>lib/viewcvs.py</code> and remove the
|
||||||
|
<tt>"-dev"</tt> suffix from <code>__version__</code>. The remainder
|
||||||
|
should be of the form X.Y, where X is a positive number and
|
||||||
|
Y is a single digit.
|
||||||
|
<li>commit this changed version of <code>lib/viewcvs.py</code>
|
||||||
|
and than <code>cvs tag V</code><i>X</i><code>_</code><i>Y</i>, where
|
||||||
|
<i>X</i> and <i>Y</i> should be replaced by the release number
|
||||||
|
from above. If a developer is willing to volunteer as a
|
||||||
|
bug fix patch release manager, it is now possible to start here
|
||||||
|
at this point with a feature freezed branch using the command
|
||||||
|
<blockquote>
|
||||||
|
<code>cvs tag -b V</code><i>X</i><code>_</code><i>Y</i><code>_maint</code>
|
||||||
|
</blockquote>
|
||||||
|
<li>go into an empty directory and run the command:
|
||||||
|
<blockquote>
|
||||||
|
<code>tools/make-release V</code><i>X_Y</i> <code>viewcvs-</code><i>X.Y</i>
|
||||||
|
</blockquote>
|
||||||
|
This step requires anonymous CVS access to repository at SourceForge.
|
||||||
|
<li>pack the content of this <code>viewcvs-</code><i>X.Y</i> directory
|
||||||
|
into a tarball.
|
||||||
|
<li>Upload the created tarball into the download files section of the
|
||||||
|
ViewCVS project at SourceForge.
|
||||||
|
<br><b>Greg:</b><i>Could you please elaborate this step?</i>
|
||||||
|
<li>Edit the file <code>lib/viewcvs.py</code> again and this time
|
||||||
|
increment the <code>__version__</code> for the next release cycle,
|
||||||
|
again append the <code>"-dev"</code> to the version and again
|
||||||
|
<blockquote>
|
||||||
|
<code>cvs commit -m "new release cycle begins" lib/viewcvs.py</code>.
|
||||||
|
</blockquote>
|
||||||
|
<li>Write an announcement explaining all the cool new features
|
||||||
|
and put it out to:
|
||||||
|
<ul>
|
||||||
|
<li><a href="http://www.freshmeat.net"><i>www.freshmeat.net</i></a>
|
||||||
|
<li><a href="http://www.vex.net/parnassus/apyllo.py?i=91022454"><i>Vaults of Parnassus</i></a>
|
||||||
|
<li><a href="news:comp.lang.python">comp.lang.python</a>
|
||||||
|
<li><i>Where else? I dunno. Suggestions welcome.</i>
|
||||||
|
</ul>
|
||||||
|
</ol>
|
||||||
|
</td></tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
<address><a href="mailto:viewcvs-dev@lyra.org">ViewCVS Group</a></address>
|
||||||
|
<!-- Created: Thu Oct 18 09:05:40 CEST 2001 -->
|
||||||
|
<!-- hhmts start -->
|
||||||
|
Last modified: Mon Nov 19 20:25:38 CEST 2001
|
||||||
|
<!-- hhmts end -->
|
||||||
|
</body>
|
||||||
|
</html>
|
49
website/help_dirview.html
Normal file
49
website/help_dirview.html
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>ViewCVS Help: Directory View</title>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body background="images/chalk.jpg">
|
||||||
|
<table width="100%" cellspacing=5>
|
||||||
|
<tr>
|
||||||
|
<td width="1%"><a href="http://viewcvs.sf.net/index.html"><img border=0
|
||||||
|
src="images/logo.png"></a>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<h1>ViewCVS Help: Directory View</h1>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr><td width="1%" valign=top bgcolor="#ffffff">
|
||||||
|
<h3>Other Help</h3>
|
||||||
|
<a href="help_rootview.html">General</a><br>
|
||||||
|
<a href="help_query.html">Query Database</a><br>
|
||||||
|
<h3>Internet</h3>
|
||||||
|
<a href="http://viewcvs.sf.net/index.html">Home</a><br>
|
||||||
|
<a href="http://viewcvs.sf.net/upgrading.html">Upgrading</a><br>
|
||||||
|
<a href="http://viewcvs.sf.net/contributing.html">Contributing</a><br>
|
||||||
|
<a href="http://viewcvs.sf.net/license-1.html">License</a><br>
|
||||||
|
</td><td colspan=2>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Click on a directory to enter that directory. Click in the leftmost
|
||||||
|
column of a row to display the revision history of a file and to get
|
||||||
|
a chance to display diffs between revisions. Click on second column
|
||||||
|
to view the content of the latest revision of that file.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Directories are always displayed first in alphabetically order.
|
||||||
|
Ordinary files follow and are sorted according to the selected
|
||||||
|
sort criteria. You may click on a particular column header to
|
||||||
|
select this column as sort criteria. It is than displayed in
|
||||||
|
another color (e.g. light green, if using the default configuration).
|
||||||
|
</p>
|
||||||
|
</td></tr></table>
|
||||||
|
<hr>
|
||||||
|
<address><a href="mailto:viewcvs-dev@lyra.org">ViewCVS Group</a></address>
|
||||||
|
<!-- Created: Thu Oct 25 22:08:29 CEST 2001 -->
|
||||||
|
<!-- hhmts start -->
|
||||||
|
Last modified: Tue Nov 13 20:40:02 CEST 2001
|
||||||
|
<!-- hhmts end -->
|
||||||
|
</body>
|
||||||
|
</html>
|
60
website/help_query.html
Normal file
60
website/help_query.html
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"
|
||||||
|
"http://www.w3.org/TR/REC-html40/loose.dtd">
|
||||||
|
<html><head>
|
||||||
|
<title>ViewCVS Help: Query The Commit Database</title>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body background="images/chalk.jpg">
|
||||||
|
<table width="100%" border=0 cellspacing=5 cellpadding=0>
|
||||||
|
<tr>
|
||||||
|
<td width="1%"><a href=".."><img border=0
|
||||||
|
src="images/logo.png"></a>
|
||||||
|
</td>
|
||||||
|
<td><h1>ViewCVS Help: Query The Commit Database</h1></td>
|
||||||
|
</tr>
|
||||||
|
<tr><td width="1%" valign="top" bgcolor="#ffffff">
|
||||||
|
<h3>Other Help:</h3>
|
||||||
|
<a href="help_rootview.html">General</a><br>
|
||||||
|
<a href="help_dirview.html">Directory View</a><br>
|
||||||
|
<h3>Internet</h3>
|
||||||
|
<a href="http://viewcvs.sf.net/index.html">Home</a><br>
|
||||||
|
<a href="http://viewcvs.sf.net/upgrading.html">Upgrading</a><br>
|
||||||
|
<a href="http://viewcvs.sf.net/contributing.html">Contributing</a><br>
|
||||||
|
<a href="http://viewcvs.sf.net/license-1.html">License</a><br>
|
||||||
|
</td><td colspan=2>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Select your parameters for querying the CVS commit database in the
|
||||||
|
form at the top of the page. You
|
||||||
|
can search for multiple matches by typing a comma-seperated list
|
||||||
|
into the text fields. Regular expressions, and wildcards are also
|
||||||
|
supported. Blank text input fields are treated as wildcards.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Any of the text entry fields can take a comma-seperated list of
|
||||||
|
search arguments. For example, to search for all commits from
|
||||||
|
authors <i>jpaint</i> and <i>gstein</i>, just type: <code>jpaint,
|
||||||
|
gstein</code> in the <i>Author</i> input box. If you are searching
|
||||||
|
for items containing spaces or quotes, you will need to quote your
|
||||||
|
request. For example, the same search above with quotes is:
|
||||||
|
<code>"jpaint", "gstein"</code>.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Wildcard and regular expression searches are entered in a similar
|
||||||
|
way to the quoted requests. You must quote any wildcard or
|
||||||
|
regular expression request, and a command character preceeds the
|
||||||
|
first quote. The command character <code>l</code>(lowercase L) is for wildcard
|
||||||
|
searches, and the wildcard character is a percent (<code>%</code>). The
|
||||||
|
command character for regular expressions is <code>r</code>, and is
|
||||||
|
passed directly to MySQL, so you'll need to refer to the MySQL
|
||||||
|
manual for the exact regex syntax. It is very similar to Perl. A
|
||||||
|
wildard search for all files with a <i>.py</i> extention is:
|
||||||
|
<code>l"%.py"</code> in the <i>File</i> input box. The same search done
|
||||||
|
with a regular expression is: <code>r".*\.py"</code>.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
All search types can be mixed, as long as they are seperated by
|
||||||
|
commas.
|
||||||
|
</p>
|
||||||
|
</td></tr></table>
|
||||||
|
</body></html>
|
79
website/help_rootview.html
Normal file
79
website/help_rootview.html
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>ViewCVS Help: Toplevel Directory View</title>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body background="images/chalk.jpg">
|
||||||
|
<table width="100%" cellspacing=5>
|
||||||
|
<tr>
|
||||||
|
<td width="1%"><a href=".."><img border=0
|
||||||
|
src="images/logo.png"></a>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<h1>ViewCVS Help: Toplevel Directory View</h1>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr><td width="1%" valign=top bgcolor="#ffffff">
|
||||||
|
<h3>Other Help</h3>
|
||||||
|
<a href="help_dirview.html">Directory View</a><br>
|
||||||
|
<a href="help_query.html">Query Database</a><br>
|
||||||
|
<h3>Internet</h3>
|
||||||
|
<a href="http://viewcvs.sf.net/index.html">Home</a><br>
|
||||||
|
<a href="http://viewcvs.sf.net/upgrading.html">Upgrading</a><br>
|
||||||
|
<a href="http://viewcvs.sf.net/contributing.html">Contributing</a><br>
|
||||||
|
<a href="http://viewcvs.sf.net/license-1.html">License</a><br>
|
||||||
|
</td><td colspan=2>
|
||||||
|
|
||||||
|
<p><b>ViewCVS</b> is a WWW interface for CVS Repositories.</b>
|
||||||
|
You can browse the
|
||||||
|
file hierarchy by picking directories (which have slashes after
|
||||||
|
them, <i>e.g.</i>, <b>src/</b>). If you pick a file, you will see
|
||||||
|
the revision history for that file. Selecting a revision number
|
||||||
|
will display that revision of the file. There is a link at each
|
||||||
|
revision to display diffs between that revision and the previous
|
||||||
|
one, and a form at the bottom of the page that allows you to
|
||||||
|
display diffs between arbitrary revisions.
|
||||||
|
</p>
|
||||||
|
<h3>ViewCVS History and Credits</h3>
|
||||||
|
<p>
|
||||||
|
This program
|
||||||
|
(<a href="http://viewcvs.sourceforge.net/">ViewCVS</a>)
|
||||||
|
has been written by Greg Stein
|
||||||
|
<<a href="mailto:gstein@lyra.org">gstein@lyra.org</a>>
|
||||||
|
based on the
|
||||||
|
<a href="http://linux.fh-heilbronn.de/~zeller/cgi/cvsweb.cgi">cvsweb</a>
|
||||||
|
script by Henner Zeller
|
||||||
|
<<a href="mailto:zeller@think.de">zeller@think.de</a>>;
|
||||||
|
it is covered by the
|
||||||
|
<a href="http://www.opensource.org/licenses/bsd-license.html">BSD-License</a>.
|
||||||
|
If you would like to use this CGI script on your own web server and
|
||||||
|
CVS tree, see Greg's
|
||||||
|
<a href="http://viewcvs.sourceforge.net/">ViewCVS distribution site</a>.
|
||||||
|
Please send any suggestions, comments, etc. to the
|
||||||
|
<a href="mailto:viewcvs-dev@lyra.org">ViewCVS Developers Mailinglist</a>.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h3>Documentation about CVS</h3>
|
||||||
|
<blockquote>
|
||||||
|
<p>
|
||||||
|
<a href="http://cvsbook.red-bean.com/">Karl Fogel's CVS book</a><br>
|
||||||
|
<a href="http://www.loria.fr/~molli/cvs/doc/cvs_toc.html">CVS
|
||||||
|
User's Guide</a><br>
|
||||||
|
<a href="http://cellworks.washington.edu/pub/docs/cvs/tutorial/cvs_tutorial_1.html">Another CVS tutorial</a><br>
|
||||||
|
<a href="http://www.csc.calpoly.edu/~dbutler/tutorials/winter96/cvs/">Yet another CVS tutorial (a little old, but nice)</a><br>
|
||||||
|
<a href="http://www.cs.utah.edu/dept/old/texinfo/cvs/FAQ.txt">An old but very useful FAQ about CVS</a>
|
||||||
|
</p>
|
||||||
|
</blockquote>
|
||||||
|
|
||||||
|
<!-- insert repository access instructions here -->
|
||||||
|
|
||||||
|
</td></tr></table>
|
||||||
|
<hr>
|
||||||
|
<address><a href="mailto:viewcvs-dev@lyra.org">ViewCVS Group</a></address>
|
||||||
|
<!-- Created: Thu Oct 25 22:16:29 CEST 2001 -->
|
||||||
|
<!-- hhmts start -->
|
||||||
|
Last modified: Tue Nov 13 20:41:02 CEST 2001
|
||||||
|
<!-- hhmts end -->
|
||||||
|
</body>
|
||||||
|
</html>
|
BIN
website/images/chalk.jpg
Normal file
BIN
website/images/chalk.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 755 B |
BIN
website/images/cvsgraph_16x16.png
Normal file
BIN
website/images/cvsgraph_16x16.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 185 B |
BIN
website/images/cvsgraph_32x32.png
Normal file
BIN
website/images/cvsgraph_32x32.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 275 B |
BIN
website/images/logo.png
Normal file
BIN
website/images/logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 7.1 KiB |
@@ -3,77 +3,153 @@
|
|||||||
<head>
|
<head>
|
||||||
<title>ViewCVS: Viewing CVS Repositories</title>
|
<title>ViewCVS: Viewing CVS Repositories</title>
|
||||||
</head>
|
</head>
|
||||||
|
<!-- Editors: Please keep all links to external sites in italics. -->
|
||||||
<body background="/images/chalk.jpg">
|
<body background="images/chalk.jpg">
|
||||||
|
<table width="100%" cellspacing=5>
|
||||||
|
<tr>
|
||||||
|
<td width="1%"><img border=0 src="images/logo.png"></td>
|
||||||
|
<td>
|
||||||
<h1>ViewCVS: Viewing CVS Repositories</h1>
|
<h1>ViewCVS: Viewing CVS Repositories</h1>
|
||||||
|
</td>
|
||||||
|
<td align=center valign=top bgcolor="white" width="1%">
|
||||||
|
<b>Quickstart:</b>
|
||||||
|
<a href="viewcvs-0.8.tar.gz">download</a>
|
||||||
|
</td>
|
||||||
|
<td width="1%"><a href="http://sourceforge.net/"><img border=0
|
||||||
|
src="http://sourceforge.net/sflogo.php?group_id=18760&type=1"></a><br><a href="http://sourceforge.net/projects/viewcvs/">ViewCVS project page</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr><td width="1%" valign=top>
|
||||||
|
<h3>Links:</h3>
|
||||||
|
<a href="http://viewcvs.sourceforge.net">Home</a><br>
|
||||||
|
<a href="upgrading.html">Upgrading</a><br>
|
||||||
|
<a href="contributing.html">Contributing</a><br>
|
||||||
|
<a href="license-1.html">License</a><br>
|
||||||
|
<a href="who.html">Who</a><br>
|
||||||
|
<h3>Sections:</h3>
|
||||||
|
<a href="#Features">Features</a><br>
|
||||||
|
<a href="#History">History</a><br>
|
||||||
|
<a href="#Mail">Mailing Lists</a><br>
|
||||||
|
<a href="#Cvsweb">vs. cvsweb</a><br>
|
||||||
|
<a href="#Download">Download</a><br>
|
||||||
|
<a href="#Future">Future directions</a><br>
|
||||||
|
<a href="#Colorize">Colorization</a><br>
|
||||||
|
</td>
|
||||||
|
<td colspan=3>
|
||||||
|
|
||||||
<p>
|
<hr width="75%">
|
||||||
The ViewCVS software was inspired by
|
<h2><a name="Features">Features</a></h2>
|
||||||
<a href="http://linux.fh-heilbronn.de/~zeller/cgi/cvsweb.cgi"><i>cvsweb</i></a>
|
|
||||||
(by <a href="mailto:zeller@think.de">Henner Zeller</a>).
|
|
||||||
I wanted to make some changes and updates, but cvsweb was
|
|
||||||
implemented in Perl (and rather poorly, IMO). So I undertook the
|
|
||||||
task to convert the software to
|
|
||||||
<a href="http://www.python.org/"><i>Python</i></a>.
|
|
||||||
</p>
|
|
||||||
<p>
|
<p>
|
||||||
ViewCVS can browse directories, change logs, and specific
|
ViewCVS can browse directories, change logs, and specific
|
||||||
revisions of files. It can display diffs between versions and
|
revisions of files. It can display diffs between versions and
|
||||||
show selections of files based on tags or branches. In addition,
|
show selections of files based on tags or branches. In addition,
|
||||||
ViewCVS has "annotation" or "blame" support, and the beginnings
|
ViewCVS has "annotation" or "blame" support, Bonsai-like query
|
||||||
of Bonsai-like query facilities.
|
facilities, template-based page generation, and support for
|
||||||
</p>
|
individually configuring virtual hosts.
|
||||||
<p>
|
|
||||||
ViewCVS is currently at version 0.5. It was a port of the cvsweb
|
|
||||||
script, but has had numerous cleanups and other modifications,
|
|
||||||
based on some of Python's strengths. There is still some minor
|
|
||||||
"badness" in there, but I've been working on flushing that out,
|
|
||||||
while adding new features. Currently, the functionality of ViewCVS
|
|
||||||
surpasses that of cvsweb.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
The software is available for download:
|
|
||||||
</p>
|
|
||||||
<blockquote>
|
|
||||||
<a href="viewcvs-0.5.tar.gz">Version 0.5 of ViewCVS</a>
|
|
||||||
</blockquote>
|
|
||||||
<p>
|
|
||||||
Of course, it is also available through ViewCVS itself:
|
|
||||||
</p>
|
|
||||||
<blockquote>
|
|
||||||
<a href="/cgi-bin/viewcvs.cgi/viewcvs/">http://www.lyra.org/cgi-bin/viewcvs.cgi/viewcvs/</a>
|
|
||||||
</blockquote>
|
|
||||||
|
|
||||||
<p>
|
It also includes support for
|
||||||
ViewCVS requires <strong>Python 1.5</strong> (which has been out
|
<a href="http://www.akhphd.au.dk/~bertho/cvsgraph/"><i>CvsGraph</i></a>
|
||||||
for a couple years and is readily available for your favorite
|
-- a program to display the tree of revisions and branches
|
||||||
operating system).
|
graphically.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
[ <a href="/greg/">Greg's page</a> ]
|
Currently, the functionality of ViewCVS surpasses that of cvsweb.
|
||||||
[ <a href="/greg/python/">other Python software</a> ]
|
See <a href="#Cvsweb">below</a> for a list of additional features.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<hr width="75%">
|
<hr width="75%">
|
||||||
<h2>Mailing List</h2>
|
<h2><a name="History">History</a></h2>
|
||||||
|
<p>
|
||||||
|
The ViewCVS software was inspired by
|
||||||
|
<a href="http://stud.fh-heilbronn.de/~zeller/cgi/cvsweb.cgi/"><i>cvsweb</i></a>
|
||||||
|
(originally written by Bill Fenner and then further developed by
|
||||||
|
<a href="mailto:zeller@think.de">Henner Zeller</a>).
|
||||||
|
Greg Stein wanted to make some changes and updates, but cvsweb was
|
||||||
|
implemented in Perl. He wrote:
|
||||||
|
<blockquote><i>While I can manage some Perl, cvsweb was
|
||||||
|
rather unmaintainable for me. So I undertook the
|
||||||
|
task to convert the software to
|
||||||
|
<a href="http://www.python.org/">Python</a>. As a result,
|
||||||
|
I've actually been able to go <em>way</em> beyond the simple
|
||||||
|
changes that I had envisioned.
|
||||||
|
</i></blockquote>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
ViewCVS started as a port of the cvsweb script, but has had
|
||||||
|
numerous cleanups and other modifications, based on some of
|
||||||
|
Python's strengths. There is still some minor "badness"
|
||||||
|
remaining from the Perl code, but Greg has been working on
|
||||||
|
flushing that out, while adding new features.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
ViewCVS has been developed by the <a href="who.html">ViewCVS
|
||||||
|
Group</a> and is made available under a
|
||||||
|
<a href="license-1.html">BSD-type license</a>.
|
||||||
|
</p>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
If you have any comments, questions, suggestions, or patches,
|
ViewCVS requires <strong>Python 1.5</strong> or later (Python
|
||||||
|
1.5 has been out for a couple years and is readily available for
|
||||||
|
your favorite operating system). If you choose to use the SQL Checkin
|
||||||
|
Database feature, then you must use <strong>Python 1.5.2</strong>
|
||||||
|
or later and have the
|
||||||
|
<a href="http://sourceforge.net/projects/mysql-python"><i>MySQLdb
|
||||||
|
module</i></a> installed which itself requires
|
||||||
|
Marc-Andre Lemburgs
|
||||||
|
<a href="http://www.lemburg.com/files/python/eGenix-mx-Extensions.html#Download-mxBASE"><i>mxDateTime extension</i></a>.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<hr width="75%">
|
||||||
|
<h2><a name="Mail">Mailing Lists</a></h2>
|
||||||
|
<p>
|
||||||
|
If you have any comments, questions, or suggestions,
|
||||||
then please send them to the
|
then please send them to the
|
||||||
<a href="http://mailman.lyra.org/mailman/listinfo/viewcvs">ViewCVS
|
<a href="http://mailman.lyra.org/mailman/listinfo/viewcvs"><i>ViewCVS
|
||||||
mailing list</a>.
|
mailing list</i></a>, which is also
|
||||||
|
<a href="http://mailman.lyra.org/pipermail/viewcvs/"><i>archived</i></a>.
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
A <a href="http://mailman.lyra.org/mailman/listinfo/viewcvs-dev">mailing
|
A <a href="http://mailman.lyra.org/mailman/listinfo/viewcvs-dev"><i>mailing
|
||||||
list for ViewCVS developers</a> is also available.
|
list for ViewCVS developers</i></a> is also available
|
||||||
|
(<a href="http://mailman.lyra.org/pipermail/viewcvs-dev/"><i>Archive</i></a>).
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
ViewCVS is an <a href="http://www.opensource.org/"><i>Open
|
||||||
|
Source</i></a> project, and all
|
||||||
|
<a href="contributing.html">contributions</a> are welcome.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<hr width="75%">
|
<hr width="75%">
|
||||||
<h2>Additional features over cvsweb</h2>
|
<h2><a name="Cvsweb">Additional features over cvsweb</a></h2>
|
||||||
|
|
||||||
<ul>
|
<ul>
|
||||||
<li>Colorization for many file types via <code>enscript</code>.</li>
|
<li>
|
||||||
|
Template support: you can now customize the look and feel of
|
||||||
|
ViewCVS by editing the provided EZT templates, which are used
|
||||||
|
to generate the pages.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Colorization for many file types via <code>enscript</code>.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Bonsai-like query features. (Requires MySQL and some other
|
||||||
|
prerequisites)
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Annotation/blame viewing support against a <strong>read-only</strong>
|
||||||
|
repository.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Configuration on a per-virtual-host basis. This allows you
|
||||||
|
to share the configuration file and ViewCVS installation
|
||||||
|
across virtual hosts, yet still be able to fine-tune the
|
||||||
|
options when necessary.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Runs either as CGI script, called from an installed web server
|
||||||
|
(such as <a href="http://httpd.apache.org/"><i>Apache</i></a>),
|
||||||
|
or as a standalone server.
|
||||||
|
</li>
|
||||||
<li>Better reporting for unreadable files.</li>
|
<li>Better reporting for unreadable files.</li>
|
||||||
<li>
|
<li>
|
||||||
More robust when given varying <code>rcsdiff</code> or
|
More robust when given varying <code>rcsdiff</code> or
|
||||||
@@ -89,59 +165,108 @@
|
|||||||
<li>
|
<li>
|
||||||
Directories with a large number of files can be viewed.
|
Directories with a large number of files can be viewed.
|
||||||
</li>
|
</li>
|
||||||
<li>Bonsai-like query features.</li>
|
|
||||||
<li>
|
<li>
|
||||||
Annotation/blame support against a <strong>read-only</strong>
|
<strong>Security</strong>: ViewCVS only requires read access
|
||||||
repository.
|
to the CVS repository (not read/write). With the correct
|
||||||
|
security partitioning, this means that even if ViewCVS were to
|
||||||
|
be subverted, your source code is safe. Further, ViewCVS does
|
||||||
|
not use any <code>system()</code> or <code>popen()</code>
|
||||||
|
calls, which are very susceptible to abuse.
|
||||||
|
<br>
|
||||||
|
<small>(cvsweb had a hole due to a popen() call)</small>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
Configuration on a per-virtual-host basis. This allows you
|
Last but not least: it doesn't suffer from the "unmaintainable
|
||||||
to share the configuration file and ViewCVS installation
|
code effect" that hits most Perl projects sooner or later:
|
||||||
across virtual hosts, yet still be able to fine-tune the
|
<blockquote><i>[Perl] combines all the worst aspects of C and Lisp:
|
||||||
options when necessary.
|
a billion different sublanguages in one monolithic executable.
|
||||||
|
It combines the power of C with the readability of PostScript.</i>
|
||||||
|
-- Jamie Zawinski
|
||||||
|
</blockquote>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
Future features, coming soon:
|
The changes present in each release are available in
|
||||||
|
<a href="http://cvs.sourceforge.net/cgi-bin/viewcvs.cgi/viewcvs/viewcvs/CHANGES?rev=HEAD">ViewCVS's
|
||||||
|
CHANGES file</a>.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<hr width="75%">
|
||||||
|
<h2><a name="Download">Download</a></h2>
|
||||||
|
<p>
|
||||||
|
The software is available for download:
|
||||||
|
</p>
|
||||||
|
<blockquote>
|
||||||
|
<a href="viewcvs-0.8.tar.gz">Version 0.8 of ViewCVS as a gzipped
|
||||||
|
tar</a>
|
||||||
|
<br>
|
||||||
|
<a href="viewcvs-0.8.zip">Version 0.8 of ViewCVS as a ZIP
|
||||||
|
file</a>
|
||||||
|
</blockquote>
|
||||||
|
<p>
|
||||||
|
Of course the current development version is also available
|
||||||
|
through ViewCVS itself:
|
||||||
|
</p>
|
||||||
|
<blockquote>
|
||||||
|
<a href="http://cvs.sourceforge.net/cgi-bin/viewcvs.cgi/viewcvs/">http://cvs.sourceforge.net/cgi-bin/viewcvs.cgi/viewcvs/</a>
|
||||||
|
</blockquote>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
You can also
|
||||||
|
<a href="http://cvs.sourceforge.net/cgi-bin/viewcvs.cgi/viewcvs/viewcvs/CHANGES?rev=HEAD">see
|
||||||
|
the changes</a> for this release.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<hr width="75%">
|
||||||
|
<h2><a name="Future">Future features, directions</a></h2>
|
||||||
|
<p>
|
||||||
|
ViewCVS is a Open Source project. So any future development depends
|
||||||
|
on the contributions that will be made by its user community.
|
||||||
|
Certainly working patches have a greater chance to become realized
|
||||||
|
quickly than feature requests. <i>But don't hesitate to submit your
|
||||||
|
suggestions! Send mail to the
|
||||||
|
<a href="mailto:viewcvs@lyra.org">viewcvs@lyra.org</a>
|
||||||
|
mailing list or even better use the
|
||||||
|
<a href="http://sourceforge.net/tracker/?func=add&group_id=18760&atid=368760">SF tracker</a>.
|
||||||
|
</i>
|
||||||
</p>
|
</p>
|
||||||
<ul>
|
<ul>
|
||||||
|
<li>See the feature requests already submitted through
|
||||||
|
the <a href="http://sourceforge.net/tracker/?atid=368760&group_id=18760&func=browse">SF feature request tracker</a>
|
||||||
|
</li>
|
||||||
<li>UI streamlining/simplification</li>
|
<li>UI streamlining/simplification</li>
|
||||||
<li>Integration with CVS checkin auto-mail scripts</li>
|
<li>Integration with CVS checkin auto-mail scripts</li>
|
||||||
<li>Tighter integration the query features</li>
|
<li>Tighter integration with the query features</li>
|
||||||
<li>
|
<p>
|
||||||
<i>Suggestions? Send mail to the
|
|
||||||
<a href="mailto:viewcvs@lyra.org">viewcvs@lyra.org</a>
|
|
||||||
mailing list.
|
|
||||||
</i>
|
|
||||||
</li>
|
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
Longer term:
|
And another longer term pet of Greg Stein:
|
||||||
</p>
|
</p>
|
||||||
<ul>
|
<ul>
|
||||||
<li>Integration with an indexer such as LXR</li>
|
<li>Integration with an indexer such as LXR</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
</p>
|
||||||
|
|
||||||
<hr width="75%">
|
<hr width="75%">
|
||||||
<h2>Colorization of files</h2>
|
<h2><a name="Colorize">Colorization of files</a></h2>
|
||||||
<p>
|
<p>
|
||||||
ViewCVS can make use of the <code>enscript</code> program to
|
ViewCVS can make use of the <code>enscript</code> program to
|
||||||
colorize files in the CVS repository. If <code>enscript</code>
|
colorize files in the CVS repository. If <code>enscript</code>
|
||||||
is present on your system, then set the
|
is present on your system, then set the
|
||||||
<code>use_enscript</code> option in the
|
<code>use_enscript</code> option in the
|
||||||
<code>viewcvs.conf</code> configuration file. If necessary,
|
<code>viewcvs.conf</code> configuration file to <code>1</code>.
|
||||||
|
If necessary,
|
||||||
update the <code>enscript_path</code> option to point to your
|
update the <code>enscript_path</code> option to point to your
|
||||||
installation directory. ... That's it! Now, as you view files
|
installation directory. ... That's it! Now, as you view files
|
||||||
through ViewCVS, they will be colored.
|
through ViewCVS, they will be colored.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<h3>Colorization of Python files</h3>
|
<h3>Colorization of Python files</h3>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
ViewCVS currently comes with a builtin colorizer for Python
|
ViewCVS currently also comes with a builtin colorizer for Python
|
||||||
source files. This may go away, given the new
|
source files. This may go away in a future version, given the new
|
||||||
<code>enscript</code> support...
|
<code>enscript</code> support...
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
@@ -157,11 +282,12 @@
|
|||||||
you want to use this feature.
|
you want to use this feature.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
</td></tr></table>
|
||||||
<hr>
|
<hr>
|
||||||
<address><a href="mailto:gstein@lyra.org">Greg Stein</a></address>
|
<address><a href="mailto:viewcvs@lyra.org">ViewCVS Users Group</a></address>
|
||||||
<!-- Created: Fri Dec 3 02:51:37 PST 1999 -->
|
<!-- Created: Fri Dec 3 02:51:37 PST 1999 -->
|
||||||
<!-- hhmts start -->
|
<!-- hhmts start -->
|
||||||
Last modified: Fri May 12 03:53:51 PDT 2000
|
Last modified: Mon Dec 10 05:38:45 PST 2001
|
||||||
<!-- hhmts end -->
|
<!-- hhmts end -->
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
@@ -4,8 +4,26 @@
|
|||||||
<title>ViewCVS License Agreement (v1)</title>
|
<title>ViewCVS License Agreement (v1)</title>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body background="images/chalk.jpg">
|
||||||
|
<table width="100%" cellspacing=5>
|
||||||
|
<tr>
|
||||||
|
<td width="1%"><a href="index.html"><img border=0
|
||||||
|
src="images/logo.png"></a>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
<h1>ViewCVS License Agreement (v1)</h1>
|
<h1>ViewCVS License Agreement (v1)</h1>
|
||||||
|
</td>
|
||||||
|
<td width="1%"><a href="http://sourceforge.net/"><img border=0
|
||||||
|
src="http://sourceforge.net/sflogo.php?group_id=18760&type=1"></a><br><a href="http://sourceforge.net/projects/viewcvs/">ViewCVS project page</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr><td width="1%" valign=top>
|
||||||
|
<a href="index.html">Overview</a><br>
|
||||||
|
<a href="upgrading.html">Upgrading</a><br>
|
||||||
|
<a href="contributing.html">Contributing</a><br>
|
||||||
|
License<br>
|
||||||
|
<a href="who.html">Who</a><br>
|
||||||
|
</td><td colspan=2>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
The following text constitutes the license agreement for the
|
The following text constitutes the license agreement for the
|
||||||
@@ -14,12 +32,20 @@
|
|||||||
<a href="who.html">The ViewCVS
|
<a href="who.html">The ViewCVS
|
||||||
Group</a> and the users of ViewCVS.
|
Group</a> and the users of ViewCVS.
|
||||||
</p>
|
</p>
|
||||||
|
<p>
|
||||||
|
<small>
|
||||||
|
<em>
|
||||||
|
Note: the copyright years were updated on May 12, 2001. No
|
||||||
|
other changes were made to the license.
|
||||||
|
</em>
|
||||||
|
</small>
|
||||||
|
</p>
|
||||||
|
|
||||||
<hr>
|
<hr>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
<b>
|
<b>
|
||||||
Copyright © 1999-2000 The ViewCVS Group. All rights reserved.
|
Copyright © 1999-2001 The ViewCVS Group. All rights reserved.
|
||||||
</b>
|
</b>
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
@@ -65,11 +91,12 @@
|
|||||||
SUCH DAMAGE.
|
SUCH DAMAGE.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
</td></tr></table>
|
||||||
<hr>
|
<hr>
|
||||||
<address><a href="mailto:gstein@lyra.org">Greg Stein</a></address>
|
<address><a href="mailto:viewcvs@lyra.org">ViewCVS Users Group</a></address>
|
||||||
<!-- Created: Mon May 8 19:01:27 PDT 2000 -->
|
<!-- Created: Mon May 8 19:01:27 PDT 2000 -->
|
||||||
<!-- hhmts start -->
|
<!-- hhmts start -->
|
||||||
Last modified: Mon May 8 19:28:37 PDT 2000
|
Last modified: Sat Oct 20 16:09:37 PDT 2001
|
||||||
<!-- hhmts end -->
|
<!-- hhmts end -->
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
178
website/upgrading.html
Normal file
178
website/upgrading.html
Normal file
@@ -0,0 +1,178 @@
|
|||||||
|
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Upgrading a ViewCVS Installation</title>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body background="images/chalk.jpg">
|
||||||
|
<table width="100%" cellspacing=5>
|
||||||
|
<tr>
|
||||||
|
<td width="1%"><a href="index.html"><img border=0
|
||||||
|
src="images/logo.png"></a>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<h1>Upgrading a ViewCVS Installation</h1>
|
||||||
|
</td>
|
||||||
|
<td width="1%"><a href="http://sourceforge.net/"><img border=0
|
||||||
|
src="http://sourceforge.net/sflogo.php?group_id=18760&type=1"></a><br><a href="http://sourceforge.net/projects/viewcvs/">ViewCVS project page</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr><td width="1%" valign=top>
|
||||||
|
<a href="index.html">Overview</a><br>
|
||||||
|
Upgrading<br>
|
||||||
|
<a href="contributing.html">Contributing</a><br>
|
||||||
|
<a href="license-1.html">License</a><br>
|
||||||
|
<a href="who.html">Who</a><br>
|
||||||
|
</td><td colspan=2>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
This document describes some of the things that you will need to
|
||||||
|
consider, change, or handle when upgrading an existing ViewCVS
|
||||||
|
installation to a newer version.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
It is always recommended to install the new version in a fresh directory
|
||||||
|
and to carefully compare the configuration files. A possible approach
|
||||||
|
is to name the directories <code>/usr/local/viewcvs-0.6</code>,
|
||||||
|
<code>/usr/local/viewcvs-0.7</code> and so on and than create a
|
||||||
|
symbolic link <code>viewcvs</code> pointing to the production
|
||||||
|
version. This way you can easily test several versions and switch
|
||||||
|
back, if your users start to complain.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li><a href="#from7">Upgrading from ViewCVS 0.7 or earlier</a></li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
<h2><a name="from7">Upgrading from ViewCVS 0.7 or earlier</a></h2>
|
||||||
|
<p>
|
||||||
|
This section discusses how to upgrade ViewCVS 0.7 or earlier to
|
||||||
|
0.8 or a later version of the software.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h3>Templates</h3>
|
||||||
|
<p>
|
||||||
|
The largest change from 0.7 to 0.8, that you will need to deal
|
||||||
|
with, is the introduction of templates. This shifted many
|
||||||
|
configuration file options into the templates, for more direct
|
||||||
|
editing of the output style, colors, and layout. Below is a list
|
||||||
|
of options that no longer exist, and where you can find their
|
||||||
|
counterpart in the current version of ViewCVS.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
The following options have all been removed in ViewCVS 0.8. If
|
||||||
|
you made local changes to your ViewCVS configuration, then you
|
||||||
|
will need to edit templates in the <code>templates/</code>
|
||||||
|
subdirectory.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<blockquote>
|
||||||
|
<dl>
|
||||||
|
<dt>
|
||||||
|
The [text] section:
|
||||||
|
<strong>short_intro</strong>,
|
||||||
|
<strong>long_intro</strong>,
|
||||||
|
and <strong>doc_info</strong>
|
||||||
|
</dt>
|
||||||
|
<dd>
|
||||||
|
These options have been incorporated into the
|
||||||
|
<code>doc/help_rootview.html</code> page and the
|
||||||
|
<code>doc/help_dirview.html</code> page.
|
||||||
|
|
||||||
|
<p></p>
|
||||||
|
</dd>
|
||||||
|
|
||||||
|
<dt><strong>repository_info</strong></dt>
|
||||||
|
<dd>
|
||||||
|
This option is now incorporated into the
|
||||||
|
<code>directory.ezt</code> template.
|
||||||
|
|
||||||
|
<p></p>
|
||||||
|
</dd>
|
||||||
|
|
||||||
|
<dt><strong>table_padding</strong></dt>
|
||||||
|
<dd>
|
||||||
|
The table padding values can be changed in the
|
||||||
|
<code>directory.ezt</code> template.
|
||||||
|
|
||||||
|
<p></p>
|
||||||
|
</dd>
|
||||||
|
|
||||||
|
<dt><strong>table_border</strong></dt>
|
||||||
|
<dd>
|
||||||
|
Edit <code>directory.ezt</code> to add a border around the
|
||||||
|
directory table.
|
||||||
|
|
||||||
|
<p></p>
|
||||||
|
</dd>
|
||||||
|
|
||||||
|
<dt>
|
||||||
|
<strong>column_header_normal</strong> and
|
||||||
|
<strong>column_header_sorted</strong>
|
||||||
|
</dt>
|
||||||
|
<dd>
|
||||||
|
Edit <code>directory.ezt</code> to modify the colors of the
|
||||||
|
column headers.
|
||||||
|
|
||||||
|
<p></p>
|
||||||
|
</dd>
|
||||||
|
|
||||||
|
<dt>
|
||||||
|
<strong>extern_window_width</strong> and
|
||||||
|
<strong>extern_window_height</strong>
|
||||||
|
</dt>
|
||||||
|
<dd>
|
||||||
|
These options were never used and have been removed.
|
||||||
|
<p></p>
|
||||||
|
</dd>
|
||||||
|
|
||||||
|
<dt><strong>logo</strong></dt>
|
||||||
|
<dd>
|
||||||
|
Edit the templates directly (<code>directory.ezt</code>,
|
||||||
|
<code>log.ezt</code> or <code>log_table.ezt</code> and if
|
||||||
|
needed <code>query.ezt</code>) to alter the URL and size of
|
||||||
|
your logo.
|
||||||
|
|
||||||
|
<p></p>
|
||||||
|
</dd>
|
||||||
|
|
||||||
|
<dt><strong>allow_version_select</strong></dt>
|
||||||
|
<dd>
|
||||||
|
Edit the <code>log.ezt</code> template if you want to remove
|
||||||
|
the link which allows the user to select a revision for a
|
||||||
|
diff.
|
||||||
|
|
||||||
|
<p></p>
|
||||||
|
</dd>
|
||||||
|
|
||||||
|
<dt><strong>input_text_size</strong></dt>
|
||||||
|
<dd>
|
||||||
|
Edit the <code>log.ezt</code> template if you want to change
|
||||||
|
the size of the entry box for revisions for performing
|
||||||
|
diffs.
|
||||||
|
|
||||||
|
<p></p>
|
||||||
|
</dd>
|
||||||
|
|
||||||
|
<dt><strong>even_odd</strong></dt>
|
||||||
|
<dd>
|
||||||
|
Edit the <code>directory.ezt</code> and
|
||||||
|
<code>query.ezt</code> templates if you want to change the
|
||||||
|
colors of the rows in the directory and query result tables.
|
||||||
|
|
||||||
|
<p></p>
|
||||||
|
</dd>
|
||||||
|
</dl>
|
||||||
|
</blockquote>
|
||||||
|
|
||||||
|
</td></tr></table>
|
||||||
|
<hr>
|
||||||
|
<address><a href="mailto:viewcvs@lyra.org">ViewCVS Users Group</a></address>
|
||||||
|
<!-- Created: Mon Sep 24 04:23:53 PDT 2001 -->
|
||||||
|
<!-- hhmts start -->
|
||||||
|
Last modified: Mon Dec 10 02:06:31 PST 2001
|
||||||
|
<!-- hhmts end -->
|
||||||
|
</body>
|
||||||
|
</html>
|
@@ -4,29 +4,56 @@
|
|||||||
<title>The ViewCVS Group</title>
|
<title>The ViewCVS Group</title>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body background="/images/chalk.jpg">
|
<body background="images/chalk.jpg">
|
||||||
|
<table width="100%" cellspacing=5>
|
||||||
|
<tr>
|
||||||
|
<td width="1%"><a href="index.html"><img border=0
|
||||||
|
src="images/logo.png"></a>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
<h1>The ViewCVS Group</h1>
|
<h1>The ViewCVS Group</h1>
|
||||||
|
</td>
|
||||||
|
<td width="1%"><a href="http://sourceforge.net/"><img border=0
|
||||||
|
src="http://sourceforge.net/sflogo.php?group_id=18760&type=1"></a><br><a href="http://sourceforge.net/projects/viewcvs/">ViewCVS project page</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr><td width="1%" valign=top>
|
||||||
|
<a href="index.html">Overview</a><br>
|
||||||
|
<a href="upgrading.html">Upgrading</a><br>
|
||||||
|
<a href="contributing.html">Contributing</a><br>
|
||||||
|
<a href="license-1.html">License</a><br>
|
||||||
|
Who<br>
|
||||||
|
</td><td colspan=2>
|
||||||
|
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
The ViewCVS Group is an informal group of people working on and
|
The ViewCVS Group is an informal group of people working on and
|
||||||
developing the ViewCVS package. The current set of members are:
|
developing the ViewCVS package. The current set of members are:
|
||||||
</p>
|
</p>
|
||||||
<ul>
|
<ul>
|
||||||
<li><a href="/greg/"><b>Greg Stein</b></a></li>
|
<li><a href="http://www.lyra.org/greg/"><b>Greg Stein</b></a></li>
|
||||||
|
<li>Tanaka Akira</li>
|
||||||
|
<li>Tim Cera</li>
|
||||||
|
<li>Peter Funk</li>
|
||||||
<li>Jay Painter</li>
|
<li>Jay Painter</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
<p>
|
||||||
|
In 2001 the project has been moved to SourceForge and some
|
||||||
|
<a href="http://sourceforge.net/project/memberlist.php?group_id=18760">more
|
||||||
|
developers</a> were given commit access.
|
||||||
|
</p>
|
||||||
<p>
|
<p>
|
||||||
Please note that the <a href="./">ViewCVS</a> package is offered
|
Please note that the <a href="./">ViewCVS</a> package is offered
|
||||||
under a BSD-type license, which is detailed on the
|
under a BSD-type license, which is detailed on the
|
||||||
<a href="license-1.html">ViewCVS License</a> page.
|
<a href="license-1.html">ViewCVS License</a> page.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
</td></tr></table>
|
||||||
<hr>
|
<hr>
|
||||||
<address><a href="mailto:gstein@lyra.org">Greg Stein</a></address>
|
<address><a href="mailto:viewcvs-dev@lyra.org">ViewCVS Group</a></address>
|
||||||
<!-- Created: Mon May 8 19:08:58 PDT 2000 -->
|
<!-- Created: Mon May 8 19:08:58 PDT 2000 -->
|
||||||
<!-- hhmts start -->
|
<!-- hhmts start -->
|
||||||
Last modified: Mon May 8 19:14:45 PDT 2000
|
Last modified: Thu Oct 25 01:47:02 PDT 2001
|
||||||
<!-- hhmts end -->
|
<!-- hhmts end -->
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
Reference in New Issue
Block a user