1
0
mirror of https://github.com/vitalif/viewvc-4intranet synced 2019-04-16 04:14:59 +03:00

Compare commits

...

76 Commits
0.4.0 ... 0.5.0

Author SHA1 Message Date
cmpilato
138cd0cf83 Tag rename: V0_5 -> 0.5.0
git-svn-id: http://viewvc.tigris.org/svn/viewvc/tags/0.5.0@1317 8cb11bc2-c004-0410-86c3-e597b4017df7
2006-04-06 19:20:32 +00:00
(no author)
99216ae1da This commit was manufactured by cvs2svn to create tag 'V0_5'.
git-svn-id: http://viewvc.tigris.org/svn/viewvc/tags/V0_5@140 8cb11bc2-c004-0410-86c3-e597b4017df7
2000-07-02 00:44:05 +00:00
gstein
b21597d96d bunch of stuff about 0.5
additional updates, discussion, etc


git-svn-id: http://viewvc.tigris.org/svn/viewvc/trunk@122 8cb11bc2-c004-0410-86c3-e597b4017df7
2000-05-12 11:07:37 +00:00
gstein
c553401843 roll 0.5
git-svn-id: http://viewvc.tigris.org/svn/viewvc/trunk@121 8cb11bc2-c004-0410-86c3-e597b4017df7
2000-05-12 10:34:01 +00:00
gstein
ee8a705131 add compilation of lib/ files (running as the web server probably will not
allow a write to lib/, so we do it ahead of time)


git-svn-id: http://viewvc.tigris.org/svn/viewvc/trunk@120 8cb11bc2-c004-0410-86c3-e597b4017df7
2000-05-12 10:26:11 +00:00
gstein
665d46a59d minor tweaks. additional info/notes/etc
git-svn-id: http://viewvc.tigris.org/svn/viewvc/trunk@119 8cb11bc2-c004-0410-86c3-e597b4017df7
2000-05-12 10:20:56 +00:00
gstein
15d7883bb0 add 'use_enscript' option
change the default allow_annotate to true.


git-svn-id: http://viewvc.tigris.org/svn/viewvc/trunk@118 8cb11bc2-c004-0410-86c3-e597b4017df7
2000-05-12 10:18:17 +00:00
gstein
6ed78e918e fix the links that are generated
the "primordial" revision no longer links to the directory. the user can do
    that from the navbar at the top of the page (a bit more intuitive).


git-svn-id: http://viewvc.tigris.org/svn/viewvc/trunk@117 8cb11bc2-c004-0410-86c3-e597b4017df7
2000-05-12 07:24:13 +00:00
jpaint
5463d8ff1b Updated the docs for the modified installation paths (ie, cgi/ directory
went away).  Maybe we should provide a sample .htaccess file which
instructs the webserver to refuse access to the non-cgi scripts.

Keep in mind I'm a Roxen fan, the the .htaccess files don't have all the
features they have in Apache ;)


git-svn-id: http://viewvc.tigris.org/svn/viewvc/trunk@116 8cb11bc2-c004-0410-86c3-e597b4017df7
2000-05-12 03:23:33 +00:00
jpaint
934209abb9 have rlog.py use the rcs path set in the config file
git-svn-id: http://viewvc.tigris.org/svn/viewvc/trunk@115 8cb11bc2-c004-0410-86c3-e597b4017df7
2000-05-12 03:19:09 +00:00
jpaint
a19338dd0a first draft of updated INSTALL instructions -- comments/spelling fixed
probably needed


git-svn-id: http://viewvc.tigris.org/svn/viewvc/trunk@114 8cb11bc2-c004-0410-86c3-e597b4017df7
2000-05-12 02:57:11 +00:00
gstein
ee6c5f5da3 don't create a cgi/ directory.
update the shell-bang line at the top of the executable files with the
    correct python executable path.


git-svn-id: http://viewvc.tigris.org/svn/viewvc/trunk@113 8cb11bc2-c004-0410-86c3-e597b4017df7
2000-05-12 01:56:50 +00:00
jpaint
43d6d82b34 added popen.py to the installer
git-svn-id: http://viewvc.tigris.org/svn/viewvc/trunk@112 8cb11bc2-c004-0410-86c3-e597b4017df7
2000-05-12 00:25:00 +00:00
jpaint
7ad684b001 let's not broadcast my passwords to the world...
git-svn-id: http://viewvc.tigris.org/svn/viewvc/trunk@111 8cb11bc2-c004-0410-86c3-e597b4017df7
2000-05-12 00:24:10 +00:00
jpaint
25e4e5d18a Fixed up viewcvs-install script. It includes a few
files, excludes a few we don't use, and added a new field to the install
file list which will prompt before overwriting a file (viewcvs.conf, for
example).


git-svn-id: http://viewvc.tigris.org/svn/viewvc/trunk@110 8cb11bc2-c004-0410-86c3-e597b4017df7
2000-05-11 23:55:07 +00:00
gstein
2719168ddc be more specific about what kind of markers we look for in a log output.
git-svn-id: http://viewvc.tigris.org/svn/viewvc/trunk@109 8cb11bc2-c004-0410-86c3-e597b4017df7
2000-05-11 22:34:46 +00:00
gstein
31272b7cea use dict.get() to simplify/speed some code.
git-svn-id: http://viewvc.tigris.org/svn/viewvc/trunk@108 8cb11bc2-c004-0410-86c3-e597b4017df7
2000-05-11 22:34:14 +00:00
jpaint
eb940183e3 script to create the MySQL database for the CVSdb part of ViewCVS
git-svn-id: http://viewvc.tigris.org/svn/viewvc/trunk@107 8cb11bc2-c004-0410-86c3-e597b4017df7
2000-05-11 20:03:45 +00:00
gstein
33356d5a30 fix some previous-revision handling (generally failed on files with just a
single revision in them)
strip out the header/footer stuff from make_html()


git-svn-id: http://viewvc.tigris.org/svn/viewvc/trunk@106 8cb11bc2-c004-0410-86c3-e597b4017df7
2000-05-11 11:15:54 +00:00
gstein
7ba05d4b43 revise some of the MIME type handling.
improve the header/footer for the annotated files


git-svn-id: http://viewvc.tigris.org/svn/viewvc/trunk@105 8cb11bc2-c004-0410-86c3-e597b4017df7
2000-05-11 11:09:41 +00:00
gstein
083de4d0c7 initial testing/support for annotation using Curt's new module
git-svn-id: http://viewvc.tigris.org/svn/viewvc/trunk@104 8cb11bc2-c004-0410-86c3-e597b4017df7
2000-05-11 01:13:23 +00:00
gstein
cbe1f36d8a remove unused code.
slight change to use math.log10()
fix problem with finding the end of a @-delimited token. It didn't work
    when the terminating @ was at the end of a line.
remove the CGI script stuff, but leave a direct-invocation for testing


git-svn-id: http://viewvc.tigris.org/svn/viewvc/trunk@103 8cb11bc2-c004-0410-86c3-e597b4017df7
2000-05-11 01:12:33 +00:00
gstein
efa434d7fa use new popen.popen() for more security and to deal with filenames that
have quotes in them.
improve the check for "unified" format in diff output
detect and properly handle "\ No newline at end of file" during diff output


git-svn-id: http://viewvc.tigris.org/svn/viewvc/trunk@102 8cb11bc2-c004-0410-86c3-e597b4017df7
2000-05-10 01:09:16 +00:00
gstein
7fc9018074 allow errors to be captured or tossed.
fix small bug with mode=='w' (use correct file for stdout/stderr)


git-svn-id: http://viewvc.tigris.org/svn/viewvc/trunk@101 8cb11bc2-c004-0410-86c3-e597b4017df7
2000-05-10 00:58:44 +00:00
gstein
a7fe838521 better version of popen(), which allows us to not worry about quoting the
arguments to the target program.


git-svn-id: http://viewvc.tigris.org/svn/viewvc/trunk@100 8cb11bc2-c004-0410-86c3-e597b4017df7
2000-05-09 22:38:21 +00:00
gstein
a1e7108d76 small tweak to get this working right. 'token' had an empty string, so the
reference to token[-1] raised an error.


git-svn-id: http://viewvc.tigris.org/svn/viewvc/trunk@99 8cb11bc2-c004-0410-86c3-e597b4017df7
2000-05-09 11:48:49 +00:00
gstein
867883e525 untabify. indent changed, too.
despite the size of this diff, there were NO code changes. "cvs diff -b"
    to see that the change was whitespace only.


git-svn-id: http://viewvc.tigris.org/svn/viewvc/trunk@98 8cb11bc2-c004-0410-86c3-e597b4017df7
2000-05-09 11:21:30 +00:00
gstein
2f376b11e3 quicky script to build releases
git-svn-id: http://viewvc.tigris.org/svn/viewvc/trunk@97 8cb11bc2-c004-0410-86c3-e597b4017df7
2000-05-09 11:00:50 +00:00
gstein
07763938a3 fix typo
git-svn-id: http://viewvc.tigris.org/svn/viewvc/trunk@96 8cb11bc2-c004-0410-86c3-e597b4017df7
2000-05-09 10:30:29 +00:00
gstein
182aeaa7fa various simplification of the code. use re.sub() rather than line-by-line
replacements for the config variable stuff.
output a bit more information when unknown errors occur.


git-svn-id: http://viewvc.tigris.org/svn/viewvc/trunk@95 8cb11bc2-c004-0410-86c3-e597b4017df7
2000-05-09 10:29:13 +00:00
gstein
75d59eb292 add os.makedirs() compatibility
git-svn-id: http://viewvc.tigris.org/svn/viewvc/trunk@94 8cb11bc2-c004-0410-86c3-e597b4017df7
2000-05-09 10:28:07 +00:00
gstein
18048c0a80 update the copyright and license information.
git-svn-id: http://viewvc.tigris.org/svn/viewvc/trunk@93 8cb11bc2-c004-0410-86c3-e597b4017df7
2000-05-09 08:36:29 +00:00
gstein
fb262791a1 first draft of the license and the "who" pages.
git-svn-id: http://viewvc.tigris.org/svn/viewvc/trunk@92 8cb11bc2-c004-0410-86c3-e597b4017df7
2000-05-09 02:27:16 +00:00
gstein
b2d9bf88a1 add virtual host capability: each set of vhosts can override the common
settings.


git-svn-id: http://viewvc.tigris.org/svn/viewvc/trunk@91 8cb11bc2-c004-0410-86c3-e597b4017df7
2000-05-09 02:00:56 +00:00
gstein
8e94cc5775 add more complex "forbidden" patterns (requested by Jon Stevens @ ASF)
git-svn-id: http://viewvc.tigris.org/svn/viewvc/trunk@90 8cb11bc2-c004-0410-86c3-e597b4017df7
2000-05-08 23:54:35 +00:00
gstein
e8c829df09 expand the use of enscript. provide for disabling specific languages in case
the local installation of enscript does not support them.
add a footer to the bottom of all "marked up" displays
pass the "guessed" MIME type to markup_stream() so that we can properly
    detect images and display them properly.


git-svn-id: http://viewvc.tigris.org/svn/viewvc/trunk@89 8cb11bc2-c004-0410-86c3-e597b4017df7
2000-05-08 04:57:31 +00:00
gstein
b892974ffe deal with a diff that reports no change
git-svn-id: http://viewvc.tigris.org/svn/viewvc/trunk@88 8cb11bc2-c004-0410-86c3-e597b4017df7
2000-05-07 13:16:34 +00:00
gstein
78022b0e38 from Tanaka Akira <akr@m17n.org>: add elisp colorization via the "enscript"
program.
  Greg mods: configured with allow_elisp_coloring and enscript_path. read
  and write in chunks rather than slurping in the entire source file.


git-svn-id: http://viewvc.tigris.org/svn/viewvc/trunk@87 8cb11bc2-c004-0410-86c3-e597b4017df7
2000-05-07 12:54:02 +00:00
gstein
17c4db41dc initial checkin from Curt.
this currently operates as a CGI, but we will be integrating it as a part
    of viewcvs.cgi; therefore, it is going into lib/ to become a module.
this file is "pure" Curt except for the license in the header (changed per
    Curt's email to the viewcvs-dev list), and some comments about it
    being maintained as part of the ViewCVS project.
    [ change will progress from this snapshot ]


git-svn-id: http://viewvc.tigris.org/svn/viewvc/trunk@86 8cb11bc2-c004-0410-86c3-e597b4017df7
2000-04-28 14:01:48 +00:00
jpaint
8070b6bba1 import string -- fix bug
git-svn-id: http://viewvc.tigris.org/svn/viewvc/trunk@85 8cb11bc2-c004-0410-86c3-e597b4017df7
2000-04-27 17:48:05 +00:00
jpaint
c8ebba802e * CVSdb now uses viewcvs.conf
* added licence text for lib/compat.py lib/config.py
* viewcvs-install now installs + sets all the paths and modes for
	installed files


git-svn-id: http://viewvc.tigris.org/svn/viewvc/trunk@84 8cb11bc2-c004-0410-86c3-e597b4017df7
2000-04-26 22:46:47 +00:00
jpaint
5b24650755 just a little bit more work
git-svn-id: http://viewvc.tigris.org/svn/viewvc/trunk@83 8cb11bc2-c004-0410-86c3-e597b4017df7
2000-04-25 22:26:59 +00:00
jpaint
8b9a143376 fixed bug where we called the old exit function and it's no longer
there!


git-svn-id: http://viewvc.tigris.org/svn/viewvc/trunk@82 8cb11bc2-c004-0410-86c3-e597b4017df7
2000-04-25 20:35:13 +00:00
jpaint
741a3daefe working on quick-n-dirty installer in Python which will allow us to get
a release out before too long; I need to make a list of to-do items
so this is easy to get working...


git-svn-id: http://viewvc.tigris.org/svn/viewvc/trunk@81 8cb11bc2-c004-0410-86c3-e597b4017df7
2000-04-25 18:44:33 +00:00
jpaint
1cf42799f7 fix bug in rlog parser when it unexpectedly comes to the end of the
pipe


git-svn-id: http://viewvc.tigris.org/svn/viewvc/trunk@79 8cb11bc2-c004-0410-86c3-e597b4017df7
2000-04-24 22:13:41 +00:00
jpaint
fb144dfa9d okay, found the date problem: glibc2.0.7's mktime has problems, so I
re-wrote the time calculation code to side-step those bugs


git-svn-id: http://viewvc.tigris.org/svn/viewvc/trunk@78 8cb11bc2-c004-0410-86c3-e597b4017df7
2000-04-24 19:53:12 +00:00
jpaint
7963916b11 syntax error...
git-svn-id: http://viewvc.tigris.org/svn/viewvc/trunk@77 8cb11bc2-c004-0410-86c3-e597b4017df7
2000-04-23 22:54:45 +00:00
jpaint
f79830925a work around more time bugs in MySQLdb
git-svn-id: http://viewvc.tigris.org/svn/viewvc/trunk@76 8cb11bc2-c004-0410-86c3-e597b4017df7
2000-04-23 22:50:51 +00:00
jpaint
2217f50d1c oh, please let me get this one right
git-svn-id: http://viewvc.tigris.org/svn/viewvc/trunk@75 8cb11bc2-c004-0410-86c3-e597b4017df7
2000-04-23 22:24:38 +00:00
jpaint
7cd158651d okay, all the MySQLdb dataobject tick-based time handling methods are
broken in various ways -- reconstruct GMT ticks from time module's
mktime...


git-svn-id: http://viewvc.tigris.org/svn/viewvc/trunk@74 8cb11bc2-c004-0410-86c3-e597b4017df7
2000-04-23 22:09:43 +00:00
jpaint
b35c4f4e9f okay, now time handling is REALLY fixed!
git-svn-id: http://viewvc.tigris.org/svn/viewvc/trunk@73 8cb11bc2-c004-0410-86c3-e597b4017df7
2000-04-23 20:30:49 +00:00
jpaint
1f5eb1b433 finally fixed up my time handling; now all classes store time in
seconds-from-epoc, GMT


git-svn-id: http://viewvc.tigris.org/svn/viewvc/trunk@72 8cb11bc2-c004-0410-86c3-e597b4017df7
2000-04-23 18:29:10 +00:00
gstein
0eee32c64c initial checkin. yanked from viewcvs.cgi
git-svn-id: http://viewvc.tigris.org/svn/viewvc/trunk@71 8cb11bc2-c004-0410-86c3-e597b4017df7
2000-04-23 09:51:45 +00:00
jpaint
61d80c751e *doh* let's not send my passwords out as the default; added the
"cvsdb.enabled" attribute to enable/disable cvsdb support in
viewcvs


git-svn-id: http://viewvc.tigris.org/svn/viewvc/trunk@70 8cb11bc2-c004-0410-86c3-e597b4017df7
2000-04-23 01:33:14 +00:00
jpaint
db44b541e0 shaved off another 0.5 second on my test RCS file, which has 4800 commits
and now parses in about 7.5 seconds on my K7-500; it was parsing at about
12.0 seconds before profiling


git-svn-id: http://viewvc.tigris.org/svn/viewvc/trunk@69 8cb11bc2-c004-0410-86c3-e597b4017df7
2000-04-22 19:59:36 +00:00
jpaint
3d2559276c speed improvments to rlog.py by using match.groups() instead of match.group
git-svn-id: http://viewvc.tigris.org/svn/viewvc/trunk@68 8cb11bc2-c004-0410-86c3-e597b4017df7
2000-04-22 19:05:05 +00:00
gstein
0c963cf529 add CVSdb config entries (these went into config.py, too)
update the URLs that lead to the ViewCVS web site


git-svn-id: http://viewvc.tigris.org/svn/viewvc/trunk@67 8cb11bc2-c004-0410-86c3-e597b4017df7
2000-04-22 09:27:00 +00:00
gstein
50b3d40958 shift the config code to ../lib/config.py
shift the 1.5.x compatibility code to ../lib/compat.py
update the URL pointing to the ViewCVS web site
revise handle_config() for new config mgmt.
add LIBRARY_DIR and CONF_PATHNAME with notes about install-time behavior.
use the above two values to find our library and config file.


git-svn-id: http://viewvc.tigris.org/svn/viewvc/trunk@66 8cb11bc2-c004-0410-86c3-e597b4017df7
2000-04-22 09:24:53 +00:00
gstein
7bb0ae1b29 initial checkin. yanked out of viewcvs.cgi.
moved defaults into a set_defaults() method.
update the URLs that point to the ViewCVS web site.


git-svn-id: http://viewvc.tigris.org/svn/viewvc/trunk@65 8cb11bc2-c004-0410-86c3-e597b4017df7
2000-04-22 09:22:35 +00:00
gstein
01b472e9fd add these modules -- we probably want to redistribute them.
git-svn-id: http://viewvc.tigris.org/svn/viewvc/trunk@64 8cb11bc2-c004-0410-86c3-e597b4017df7
2000-04-22 08:56:46 +00:00
jpaint
1f7ea22511 okay, I've fixed the file/attic/",v" problem; now RLogData's filename is
always set with the idealized path name for the RCS file: this means no
",v" and no "Attic" directory.  There needs to be a flag set in RLogData
to show it is an Attic file, but it's not there yet


git-svn-id: http://viewvc.tigris.org/svn/viewvc/trunk@63 8cb11bc2-c004-0410-86c3-e597b4017df7
2000-04-21 22:20:26 +00:00
jpaint
3044ae7fba test for the ",v" before stripping until I get the rlog
logic figured out


git-svn-id: http://viewvc.tigris.org/svn/viewvc/trunk@62 8cb11bc2-c004-0410-86c3-e597b4017df7
2000-04-21 16:06:49 +00:00
jpaint
fafa768bea don't have RLogData remove the ",v" from the end of the file, in fact, we
may want to manually add it so we have an exact file match


git-svn-id: http://viewvc.tigris.org/svn/viewvc/trunk@61 8cb11bc2-c004-0410-86c3-e597b4017df7
2000-04-21 16:01:38 +00:00
jpaint
9bc63db2b4 really, really lame queryform.cgi script just to make things work for now
git-svn-id: http://viewvc.tigris.org/svn/viewvc/trunk@60 8cb11bc2-c004-0410-86c3-e597b4017df7
2000-04-21 15:37:42 +00:00
jpaint
d87876bb54 bug fixes to make logentries for removed files work
git-svn-id: http://viewvc.tigris.org/svn/viewvc/trunk@59 8cb11bc2-c004-0410-86c3-e597b4017df7
2000-04-21 15:33:22 +00:00
jpaint
cd7aa6b5da moved the RLogData->CommitList converter to cvsdbapi
git-svn-id: http://viewvc.tigris.org/svn/viewvc/trunk@58 8cb11bc2-c004-0410-86c3-e597b4017df7
2000-04-21 06:32:20 +00:00
jpaint
024ccc6da3 cgi/granny.cgi: CVS annotate a file, and color code it to show how old lines are;
red is newer, blue is older; contributed by Brian Lenahan (brianl@real.com)

various updates to bootstrap the sys.path madness by keeping the root path in the
file /etc/viewcvs/root; this is open to debate, but works for now

html-templates/querytemplate.html: HTML template used by query.cgi, this will change and/or
	be removed in the future as we figure out a sane/cool way to generate the HTML


git-svn-id: http://viewvc.tigris.org/svn/viewvc/trunk@57 8cb11bc2-c004-0410-86c3-e597b4017df7
2000-04-21 06:23:37 +00:00
jpaint
36684bde60 administration program for CVSdb
git-svn-id: http://viewvc.tigris.org/svn/viewvc/trunk@56 8cb11bc2-c004-0410-86c3-e597b4017df7
2000-04-21 00:43:46 +00:00
jpaint
d08fb441d7 added loginfo handler for CVSdb updates
git-svn-id: http://viewvc.tigris.org/svn/viewvc/trunk@55 8cb11bc2-c004-0410-86c3-e597b4017df7
2000-04-21 00:43:04 +00:00
jpaint
ebe4f1cbeb adding CVSdb cgi
git-svn-id: http://viewvc.tigris.org/svn/viewvc/trunk@54 8cb11bc2-c004-0410-86c3-e597b4017df7
2000-04-21 00:40:54 +00:00
jpaint
0f8b06c9e0 CVSdb library files
git-svn-id: http://viewvc.tigris.org/svn/viewvc/trunk@53 8cb11bc2-c004-0410-86c3-e597b4017df7
2000-04-21 00:37:20 +00:00
gstein
f707ad1d3c more items.
git-svn-id: http://viewvc.tigris.org/svn/viewvc/trunk@52 8cb11bc2-c004-0410-86c3-e597b4017df7
2000-04-19 22:29:33 +00:00
gstein
186b003a76 small patch to fix the popup windows
git-svn-id: http://viewvc.tigris.org/svn/viewvc/trunk@51 8cb11bc2-c004-0410-86c3-e597b4017df7
2000-04-19 07:31:29 +00:00
gstein
621d08cb9f Zachary Bean <xach@mint.net> reported this.
git-svn-id: http://viewvc.tigris.org/svn/viewvc/trunk@50 8cb11bc2-c004-0410-86c3-e597b4017df7
2000-03-25 02:24:24 +00:00
gstein
8288be7f6f one more feature
git-svn-id: http://viewvc.tigris.org/svn/viewvc/trunk@49 8cb11bc2-c004-0410-86c3-e597b4017df7
2000-03-24 12:12:23 +00:00
gstein
95e1ab3303 updates for the 0.4 release
git-svn-id: http://viewvc.tigris.org/svn/viewvc/trunk@48 8cb11bc2-c004-0410-86c3-e597b4017df7
2000-03-24 11:16:41 +00:00
29 changed files with 5319 additions and 389 deletions

123
INSTALL
View File

@@ -9,20 +9,32 @@ INSTALLING VIEWCVS
You need to have RCS installed. Specifically, "rlog", "rcsdiff",
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
access to the repository (or a copy of it). Therefore, rsh/ssh or
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
pserver access doesn't work yet.
2) Copy viewcvs.cgi to the cgi-script location of your web server.
For the checkin database to work, you will need MySQL >= 3.22,
and the Python DBAPI 2.0 module, MySQLdb. This was tested with
MySQLdb 1.12.
If Python is not located in /usr/local/bin, then you'll need to
edit the first line of viewcvs.cgi.
2) Installation is handled by the ./viewcvs-install script. Run this
script and you will be prompted for a installation root path.
The default is /usr/local/viewcvs. The installer sets the install
path in some of the files, and ViewCVS cannot be moved to a
different path after the install.
3) Copy viewcvs.conf.dist to the same directory and RENAME it to
viewcvs.conf
Note: while 'root' is usually required to create /usr/local/viewcvs,
ViewCVS does not have to be installed as root, nor does it run as root.
It is just as valid to place ViewCVS in a home directory, too.
4) Edit viewcvs.conf for your specific configuration. In particular,
examine the following configuration options:
Note: viewcvs-install will create directories if needed. It will
prompt before overwriting files that may have been modified (such
as viewcvs.conf), thus making it safe to install over the top of
a previous installation. It will always overwrite program files,
however.
3) Edit <install-root>viewcvs.conf for your specific configuration.
In particular, examine the following configuration options:
cvs_roots
default_root
@@ -32,11 +44,96 @@ INSTALLING VIEWCVS
There are some other options that are usually nice to change. See
viewcvs.conf for more information.
5) That's it. Try it out.
4) The CGI programs are in <install-root>/cgi/. You can symlink to this
directory from somewhere in your published HTTP server path if your
webserver is configured to follow symbolic links. You can also copy
the installed <install-root>/cgi/*.cgi scripts after the install
(unlike the other files in ViewCVS, the CGI scripts can be moved).
Warning: ViewCVS has not been tested on web servers operating on the
Win32 platform.
NOTE: for security reasons, it is not advisable to install ViewCVS
directly into your published HTTP directory tree (due to the MySQL
passwords in viewcvs.conf).
5) That's it for repository browsing. Instructions for getting the
SQL checkin database working are below.
WARNING: ViewCVS has not been tested on web servers operating on the
Win32 platform.
SQL Checkin Database
--------------------
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,
the databases are 100% compatible.
Various queries can be preformed on the database. After installing ViewCVS,
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.
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
database.
3) Run the <install-root>/make-database script. It will prompt you for
your MySQL user, password, and the name of database you want to
create. The database name defaults to "ViewCVS". This script creates
the database and sets up the empty tables. If you run this on a
existing ViewCVS database, you will loose all your data!
4) Edit your <install-root>/viewcvs.conf file. There is a [cvsdb]
section. You will need to set:
host = # MySQL database server host
database_name = # the name of the database you created with
# make-database
user = # the read/write database user
passwd = # password for read/write database user
readonly_user = # the readonly database user -- it's pretty
# safe to use the read/write user here
readonly_passwd = # password for the readonly user
5) Two programs are provided for updating the checkin database,
cvsdbadmin and loginfo-handler. They serve two different purposes.
The cvsdbadmin program walks through your CVS repository and adds
every commit in every file. This is commonly used for initalizing
the database from a repository which has been in use. The
loginfo-handler script is executed by the CVS server's CVSROOT/loginfo
system upon each commit. It makes real-time updates to the checkin
database as commits are made to the repository.
To build a database of all the commits in the CVS repository /home/cvs,
invoke: "./cvsdbadmin rebuild /home/cvs". If you want to update
the checkin database, invoke: "./cvsdbadmin update /home/cvs". The
update mode checks to see if a commit is already in the database,
and only adds it if it is abscent.
To get real-time updates, you'll want to checkout the CVSROOT module
from your CVS repository and edit CVSROOT/loginfo. Add the line:
ALL (echo %{sVv}; cat) | <install-root>/loginfo-handler
If you have other scripts invoked by CVSROOT/loginfo, you will want
to make sure to change any running under the "DEFAULT" keyword to
"ALL" like the loginfo handler, and probably carefully read the
execution rules for CVSROOT/loginfo from the CVS manual.
6) You may want to modify the HTML template files:
<install-root>/html-templates/queryformtemplate.html
<install-root>/html-templates/querytemplate.html
They're used by the queryform.cgi and query.cgi scripts generate
HTML output. At some point, viewcvs.cgi, query.cgi, and queryform.cgi
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
it a try.
IF YOU HAVE PROBLEMS ...
@@ -75,7 +172,7 @@ If you've trouble to make viewcvs.cgi work:
o check the ViewCVS home page:
http://www.lyra.org/greg/python/viewcvs/
http://www.lyra.org/viewcvs/
o review the ViewCVS mailing list archive to see if somebody else had
the same problem, and it was solved:

15
TODO
View File

@@ -14,6 +14,19 @@ TODO ITEMS
*) committing with a specific revision number:
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:
http://mailman.lyra.org/pipermail/viewcvs/2000q2/000050.html
suggestion from Chris Meyer <cmeyer@gatan.com>.
KNOWN BUGS
----------
@@ -25,3 +38,5 @@ KNOWN BUGS
*) no scroll bar in Netscape browser when you click "as text" on an
HTML document.
*) time.timezone seems to not be available on some 1.5.2 installs

529
cgi/granny.cgi Executable file
View File

@@ -0,0 +1,529 @@
#!/usr/bin/python
# -*- Mode: python -*-
"""
Granny.py - display CVS annotations in HTML
with lines colored by code age in days.
Original Perl version by J. Gabriel Foster (gabe@sgrail.com)
Posted to info-cvs on 02/27/1997
You can still get the original shell archive here:
http://www.geocrawler.com/archives/3/382/1997/2/0/2103824/
Perl modifications for NT by Marc Paquette (marcpa@cam.org)
Python port and CGI modifications by Brian Lenihan (brianl@real.com)
From the original granny.pl README:
--
What is Granny? Why do I care?
Granny is a tool for viewing cvs annotation information graphically. Using
Netscape, granny indicates the age of lines by color. Red lines are new,
blue lines are old. This information can be very useful in determining
what lines are 'hot'. New lines are more likely to contain bugs, and this
is an easy way to visualize that information.
Requirements:
Netscape (version 2.0 or better)
Perl5
CVS 1.9 (1.8 should work, but I have not tried it.)
Installation:
Put granny somewhere in your path. You may need to edit the
first line to point to your perl5 binary and libraries.
What to do:
Run granny just like you would 'cvs annotate' for a single file.
granny thefile.C
To find out who is to blame for that new 'feature'.
granny -U thefile.C
For all your options:
granny -h
Questions, Comments, Assertions?
send e-mail to the author: Gabe Foster (gabe@sgrail.com)
Notes:
I'm not the first person to try this sort of display, I just read about it
in a magazine somewhere and decided that cvs had all the information
I needed. To whomever first had this idea, it's a great one.
Granny is free, please use it as you see fit. I give no warranties.
As a courtesy, I ask that you tell me about any modifications you have made,
and also ask that you acknowledge my work if you use Granny in your own
software.
--
Granny.py:
granny.py [-h][-d days][-i input][-o output][-DUV] file
-h: Get this help display.
-i: Specify the input file. (Use - for stdin.)
-o: Specify the output file. (Use - for stdout.)
-d: Specify the day range for the coloring.\n
-r: Specify the cvs revision of the file.
-D: Display the date the line was last edited.
-U Display the user that last edited the line.
-V: Display the version the line was last edited.
By default, granny.py executes cvs annotate on a FILE and
runs netscape to display the graphic.
It is assumed that cvs and Netscape (for command line version) are
in your path.
If granny.py is placed in the cgi-bin directory of your Web
server, it will act as a CGI script. The working directory
defaults to /usr/tmp, but it can be overridden in the class
constructor:
A = CGIAnnotate(tempdir='/tmp')
Required fields:
root The full path to the cvs root directory.
Name The module/filename of the annotated file.
Optional fields:
rev The cvs revision number to use. (default HEAD).
Set the following fields to display extra info:
showUser Display the user that last edited the line.
showVersion Display version that the line was last edited in.
showDate Display the date the line was last edited.
http://yourserver.yourdomain.com/cgi-bin/granny.py?root=/cvsroot&Name=module/file
TODO:
Add support for determining the MIME type of files and/or a binary check.
- easily done by parsing Apache (mime.conf) or Roxen (extensions) MIME
files.
Consider adding buttons to HTML for optional display fields.
Add support for launching other browsers.
"""
import os
import sys
import string
import re
import time
import getopt
import cStringIO
import tempfile
import traceback
month_num = {
'Jan' : 1, 'Feb' : 2, 'Mar' : 3, 'Apr' : 4, 'May' : 5, 'Jun' : 6,
'Jul' : 7, 'Aug' : 8, 'Sep' : 9, 'Oct' : 10, 'Nov' : 11, 'Dec' : 12
}
class Annotate:
def __init__(self):
self.day_range = 365
self.counter = 0
self.color_table = {}
self.user = {}
self.version = {}
self.rtime = {}
self.source = {}
self.tmp = None
self.tmpfile = None
self.revision = ''
self.showUser = 0
self.showDate = 0
self.showVersion = 0
self.set_today()
def run(self):
try:
self.process_args()
self.parse_raw_annotated_file()
self.write_annotated_html_file()
if self.tmp:
self.display_annotated_html_file()
finally:
if sys.exc_info()[0] is not None:
traceback.print_exc()
self.cleanup()
def cleanup(self):
if self.tmp:
self.tmp.close()
os.unlink(self.tmpfile)
sys.exit(0)
def getoutput(self, cmd):
"""
Get stdin and stderr from cmd and
return exit status and captured output
"""
if os.name == 'nt':
# os.popen is broken on win32, but seems to work so far...
pipe = os.popen('%s 2>&1' % cmd, 'r')
else:
pipe = os.popen('{ %s ; } 2>&1' % cmd, 'r')
text = pipe.read()
sts = pipe.close()
if sts == None:
sts = 0
if text[:-1] == '\n':
text = text[-1:]
return sts, text
def set_today(self):
"""
compute the start of this day
"""
(year,mon,day,hour,min,sec,dow,doy,dst) = time.gmtime(time.time())
self.today = time.mktime((year,mon,day,0,0,0,0,0,0))
def get_today(self):
return self.today
# entify stuff which breaks HTML display
# this was lifted from some Zope Code in
# StructuredText.py
#
# XXX try it with string.replace and run it in the profiler
def html_quote(self,v,
character_entities=((re.compile('&'), '&amp;'),
(re.compile("<"), '&lt;' ),
(re.compile(">"), '&gt;' ),
(re.compile('"'), '&quot;'))):
gsub = re.sub
text=str(v)
for regexp,name in character_entities:
text=gsub(regexp,name,text)
return text
def display_annotated_html_file(self):
if os.name == 'nt':
path = '"C:\\Program Files\\Netscape\\Communicator'\
'\\Program\\Netscape"'
if os.system('%s %s' % (path, self.tmpfile)) != 0:
sys.stderr.write('%s: Unable to start Netscape' % sys.argv[0])
sys.exit(1)
else:
if os.system('netscape -remote openFile\(%s\)' %
self.tmpfile) != 0:
sys.stderr.write('%s: Trying to run netscape, please wait\n' %
sys.argv[0])
if os.system('netscape &') == 0:
for i in range(10):
time.sleep(1)
if os.system('netscape -remote openFile\(%s\)' %
self.tmpfile) == 0:
break
if i == 10:
sys.stderr.write('%s:Unable to start netscape\n' %
sys.argv[0])
else:
sys.stderr.write('%s:Unable to start netscape\n' %
sys.argv[0])
# give Netscape time to read the file
# XXX big files may raise an OSError exception on NT
# if the sleep is too short.
time.sleep(5)
def get_opts(self):
opt_dict = {}
if not len(sys.argv[1:]) > 0:
self.usage()
opts, args = getopt.getopt(sys.argv[1:], 'DUVhi:o:d:r:')
for k,v in opts:
opt_dict[k] = v
return opt_dict
def process_args(self):
opts = self.get_opts()
if opts.has_key('-r'):
self.revision = '-r%s' % opts['-r']
if opts.has_key('-h'):
self.usage(help=1)
if opts.has_key('-i'):
if opts['-i'] != '-':
self.filename = v
infile = open(filename, 'r')
sys.stdin = infile
else:
self.file = sys.stdin
else:
self.filename = sys.argv[len(sys.argv) - 1]
cmd = 'cvs annotate %s %s' % (self.revision, self.filename)
status, text = self.getoutput(cmd)
if status != 0 or text == '':
sys.stderr.write("Can't run cvs annotate on %s\n" %
self.filename)
sys.stderr.write('%s\n' % text)
sys.exit(1)
self.file = cStringIO.StringIO(text)
if opts.has_key('-o'):
if opts['-o'] != '-':
outfile = open(v, 'w')
sys.stdout = outfile
else:
# this could be done without a temp file
target = sys.argv[len(sys.argv) -1]
self.tmpfile = tempfile.mktemp()
self.tmp = open(self.tmpfile, 'w')
sys.stdout = self.tmp
if opts.has_key('-d'):
if opts['-d'] > 0:
self.day_range = opts['-d']
if opts.has_key('-D'):
self.showDate = 1
if opts.has_key('-U'):
self.showUser = 1
if opts.has_key('-V'):
self.showVersion = 1
def parse_raw_annotated_file(self):
ann = re.compile('((\d+\.?)+)\s+\((\w+)\s+(\d\d)-'\
'(\w{3})-(\d\d)\): (.*)')
text = self.file.read()
lines = string.split(text, '\n')
for line in lines:
# Parse an annotate string
m = ann.search(line)
if m:
self.version[self.counter] = m.group(1)
self.user[self.counter] = m.group(3)
oldtime = self.today - time.mktime((
int(m.group(6)),
int(month_num[m.group(5)]),
int(m.group(4)),0,0,0,0,0,0))
self.rtime[self.counter] = oldtime / 86400
self.source[self.counter] = self.html_quote(m.group(7))
else:
self.source[self.counter] = self.html_quote(line)
pass
self.counter = self.counter + 1
def write_annotated_html_file(self):
if os.environ.has_key('SCRIPT_NAME'):
print 'Status: 200 OK\r\n'
print 'Content-type: text/html\r\n\r\n'
print ('<html><head><title>%s</title></head>\n' \
'<body bgcolor="#000000">\n' \
'<font color="#FFFFFF"><H1>File %s</H1>\n' \
'<H3>Code age in days</H2>' % (self.filename, self.filename))
for i in range(self.day_range + 1):
self.color_table[i] = \
self.hsvtorgb(240.0 * i / self.day_range, 1.0, 1.0)
step = self.day_range / 40
if step < 5:
step = 1
while self.day_range/step > 40:
step = step + 1
if step >= 5:
if step != 5:
step = step + 5 - (step % 5)
while self.day_range / step > 20:
step = step + 5
for i in range(self.day_range + 1, step):
print '<font color=%s>%s ' % (self.color_table[i], i),
print '<pre><code>'
for i in range(self.counter):
if self.showUser and self.user.has_key(i):
print '%s%s ' % ('<font color=#FFFFFF>',
string.ljust(self.user[i],10)),
if self.showVersion and self.version.has_key(i):
print '%s%s ' % ('<font color=#FFFFFF>',
string.ljust(self.version[i],6)),
if self.showDate and self.rtime.has_key(i):
(year,mon,day,hour,min,sec,dow,doy,dst) = time.gmtime(
self.today - self.rtime[i] * 86400)
print '<font color=#FFFFFF>%02d/%02d/%4d ' % (mon, day, year),
if self.rtime.get(i, self.day_range) < self.day_range:
fcolor = self.color_table.get(
self.rtime[i],
self.color_table[self.day_range])
else:
fcolor = self.color_table[self.day_range]
print '<font color=%s> %s' % (fcolor, self.source[i])
print ('</code></pre>\n' \
'<font color=#FFFFFF>\n' \
'<H5>Granny original Perl version by' \
'<I>J. Gabriel Foster</I>\n' \
'<ADDRESS><A HREF=\"mailto:gabe@sgrail.com\">'\
'gabe@sgrail.com</A></ADDRESS>\n' \
'Python version by <I>Brian Lenihan</I>\n' \
'<ADDRESS><A HREF=\"mailto:brianl@real.com\">' \
'brianl@real.com</A></ADDRESS>\n' \
'</body></html>')
sys.stdout.flush()
def hsvtorgb(self,h,s,v):
"""
a veritable technicolor spew
"""
if s == 0.0:
r = v; g = v; b = v
else:
if h < 0:
h = h + 360.0
elif h >= 360.0:
h = h - 360.0
h = h / 60.0
i = int(h)
f = h - i
if s > 1.0:
s = 1.0
p = v * (1.0 - s)
q = v * (1.0 - (s * f))
t = v * (1.0 - (s * (1.0 - f)))
if i == 0: r = v; g = t; b = p
elif i == 1: r = q; g = v; b = p
elif i == 2: r = p; g = v; b = t
elif i == 3: r = p; g = q; b = v
elif i == 4: r = t; g = p; b = v
elif i == 5: r = v; g = p; b = q
return '#%02X%02X%02X' % (r * 255 + 0.5, g * 255 + 0.5, b * 255 + 0.5)
def usage(self, help=None):
sys.stderr.write('\nusage: %s %s\n\n' % (
sys.argv[0],
'[-hDUV][-d days][-i input][-o output][-r rev] FILE')
)
if help is not None:
sys.stderr.write(
'-h: Get this help display.\n' \
'-i: Specify the input file. (Use - for stdin.)\n' \
'-o: Specify the output file. (Use - for stdout.)\n' \
'-d: Specify the day range for the coloring.\n' \
'-r: Specify the cvs revision of the file.\n' \
'-D: Display the date the line was last edited.\n' \
'-U Display the user that last edited the line.\n' \
'-V: Display the version the line was last edited.\n\n' \
'By default, %s executes a cvs annotate on a FILE and\n' \
'runs netscape to display the graphical ' \
'annotation\n' % sys.argv[0]
)
sys.exit(0)
class CGIAnnotate(Annotate):
def __init__(self,tempdir='/usr/tmp'):
Annotate.__init__(self)
if os.name == 'nt':
self.tempdir = os.environ.get('TEMP') or os.environ.get('TMP')
else:
# XXX need a sanity check here
self.tempdir = tempdir
os.chdir(self.tempdir)
def process_args(self):
f = cgi.FieldStorage()
cvsroot = f['root'].value
if f.has_key('showUser'):
self.showUser = 1
if f.has_key('showDate'):
self.showDate = 1
if f.has_key('showVersion'):
self.showVersion = 1
if f.has_key('rev'):
self.revision = '-r%s' % f['rev'].value
path = f['Name'].value
module = os.path.dirname(path)
self.workingdir = 'ann.%s' % os.getpid()
self.filename = os.path.basename(path)
os.mkdir(self.workingdir)
os.chdir(os.path.join(self.tempdir, self.workingdir))
os.system('cvs -d %s co %s' % (cvsroot, path))
os.chdir(module)
cmd = 'cvs annotate %s %s' % (self.revision, self.filename)
status, text = self.getoutput(cmd)
if status != 0 or text == '':
text = "Can't run cvs annotate on %s\n" % path
self.file = cStringIO.StringIO(text)
def cleanup(self):
os.chdir(self.tempdir)
os.system('rm -rf %s' % self.workingdir)
def display_annotated_html_file(self):
pass
def usage(self):
pass
if __name__ == '__main__':
if os.environ.has_key('SCRIPT_NAME'):
import cgi
A = CGIAnnotate()
else:
A = Annotate()
A.run()

199
cgi/query.cgi Executable file
View File

@@ -0,0 +1,199 @@
#!/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/
#
# -----------------------------------------------------------------------
#
# 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.
#
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, cgi, time, cvsdbapi
## 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 = "&nbsp";
else:
cTime = time.strftime("%y/%m/%d %H:%M", time.localtime(cTime))
cAuthor = commit.GetAuthor()
if not cAuthor:
cAuthor = "&nbsp";
cFile = os.path.join(commit.GetDirectory(), commit.GetFile())
if not cFile:
cFile = "&nbsp";
cRevision = commit.GetRevision()
if not cRevision:
cRevision = "&nbsp";
cBranch = commit.GetBranch()
if not cBranch:
cBranch = "&nbsp";
cPlusMinus = '%d/%d' % (commit.GetPlusCount(), commit.GetMinusCount())
cDescription = commit.GetDescription()
if not cDescription:
cDescription = "&nbsp";
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()

54
cgi/queryform.cgi Executable file
View File

@@ -0,0 +1,54 @@
#!/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()

View File

@@ -1,40 +1,19 @@
#!/usr/local/bin/python
# -*-python-*-
#
# viewcvs: View CVS repositories via a web browser
# Copyright (C) 1999-2000 The ViewCVS Group. All Rights Reserved.
#
# Copyright (C) 1999-2000 Greg Stein. 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.
#
# By using this file, you agree to the terms and conditions set forth below:
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# 2. 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 AUTHOR 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 AUTHOR 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.
# Contact information:
# Greg Stein, PO Box 760, Palo Alto, CA, 94302
# gstein@lyra.org, http://www.lyra.org/viewcvs/
#
# -----------------------------------------------------------------------
#
# This module is maintained by Greg and is available at:
# http://www.lyra.org/greg/python/viewcvs/
#
# For tracking purposes, this software is identified by:
# $Id$
# viewcvs: View CVS repositories via a web browser
#
# -----------------------------------------------------------------------
#
@@ -47,205 +26,19 @@
# -----------------------------------------------------------------------
#
__version__ = '0.4'
__version__ = '0.5'
#########################################################################
#
# CONFIGURATION
# INSTALL-TIME CONFIGURATION
#
# There are three forms of configuration:
#
# 1) copy viewcvs.conf.dist to viewcvs.conf and edit
# 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
#
# Most users will want to use (1), but there are slight speed advantages
# to the other two options. Note that viewcvs.conf values are a bit easier
# to work with since it is raw text, rather than python literal values.
# These values will be set during the installation process. During
# development, they will remain None.
#
class Config:
_sections = ('general', 'images', 'options', 'colors', 'text')
_force_multi_value = ('cvs_roots', 'forbidden')
LIBRARY_DIR = None
CONF_PATHNAME = None
def __init__(self):
for section in self._sections:
setattr(self, section, _sub_config())
def load_config(self, fname):
this_dir = os.path.dirname(sys.argv[0])
pathname = os.path.join(this_dir, fname)
parser = ConfigParser.ConfigParser()
parser.read(pathname)
for section in self._sections:
if not parser.has_section(section):
continue
sc = getattr(self, section)
for opt in parser.options(section):
value = parser.get(section, opt)
if (section != 'text' and ',' in value) or \
opt in self._force_multi_value:
value = map(string.strip, string.split(value, ','))
else:
try:
value = int(value)
except ValueError:
pass
if opt == 'cvs_roots':
roots = { }
for root in value:
name, path = map(string.strip, string.split(root, ':'))
roots[name] = path
value = roots
setattr(sc, opt, value)
class _sub_config:
def get_image(self, which):
text = '[%s]' % string.upper(which)
path, width, height = getattr(self, which)
if path:
return '<img src="%s" alt="%s" border=0 width=%s height=%s>' % \
(path, text, width, height)
return text
cfg = Config()
cfg.general.cvs_roots = {
# user-visible-name : path
"Development" : "/home/cvsroot",
}
cfg.general.default_root = "Development"
cfg.general.rcs_path = ''
cfg.general.mime_types_file = ''
cfg.general.address = '<a href="mailto:gstein@lyra.org">gstein@lyra.org</a>'
cfg.general.main_title = 'CVS Repository'
cfg.general.forbidden = ()
cfg.images.logo = "/icons/apache_pb.gif", 259, 32
cfg.images.back_icon = "/icons/small/back.gif", 16, 16
cfg.images.dir_icon = "/icons/small/dir.gif", 16, 16
cfg.images.file_icon = "/icons/small/text.gif", 16, 16
cfg.colors.markup_log = "#ffffff"
cfg.colors.diff_heading = "#99cccc"
cfg.colors.diff_empty = "#cccccc"
cfg.colors.diff_remove = "#ff9999"
cfg.colors.diff_change = "#99ff99"
cfg.colors.diff_add = "#ccccff"
cfg.colors.diff_dark_change = "#99cc99"
cfg.colors.even_odd = ("#ccccee", "#ffffff")
cfg.colors.nav_header = "#9999ee"
cfg.colors.text = "#000000"
cfg.colors.background = "#ffffff"
cfg.colors.alt_background = "#eeeeee"
cfg.colors.column_header_normal = "#cccccc"
cfg.colors.column_header_sorted = "#88ff88"
cfg.colors.table_border = None # no border
cfg.options.sort_by = 'file'
cfg.options.hide_attic = 1
cfg.options.log_sort = 'date'
cfg.options.diff_format = 'h'
cfg.options.hide_cvsroot = 1
cfg.options.hide_non_readable = 1
cfg.options.show_author = 1
cfg.options.hr_breakable = 1
cfg.options.hr_funout = 1
cfg.options.hr_ignore_white = 1
cfg.options.hr_ignore_keyword_subst = 1
cfg.options.allow_annotate = 0 ### doesn't work yet!
cfg.options.allow_markup = 1
cfg.options.allow_compress = 1
cfg.options.use_java_script = 1
cfg.options.open_extern_window = 1
cfg.options.extern_window_width = 600
cfg.options.extern_window_height = 440
cfg.options.checkout_magic = 1
cfg.options.show_subdir_lastmod = 0
cfg.options.show_logs = 1
cfg.options.show_log_in_markup = 1
cfg.options.allow_version_select = 1
cfg.options.py2html_path = '.'
cfg.options.short_log_len = 80
cfg.options.table_padding = 2
cfg.options.diff_font_face = 'Helvetica,Arial'
cfg.options.diff_font_size = -1
cfg.options.input_text_size = 12
cfg.text.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/greg/python/viewcvs/">ViewCVS</a>)
has been written by Greg Stein
&lt;<a href="mailto:gstein@lyra.org">gstein@lyra.org</a>&gt;
based on the
<a href="http://linux.fh-heilbronn.de/~zeller/cgi/cvsweb.cgi">cvsweb</a>
script by Henner Zeller
&lt;<a href="mailto:zeller@think.de">zeller@think.de</a>&gt;;
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/greg/python/viewcvs/">ViewCVS distribution
site</a>.
Please send any suggestions, comments, etc. to
<a href="mailto:gstein@lyra.org">Greg Stein</a>.
</p>
"""
# ' stupid emacs...
cfg.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.
cfg.text.repository_info = """
<!-- insert repository access instructions here -->
"""
cfg.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>
"""
#
# CONFIGURATION END
#
#########################################################################
import sys
@@ -257,8 +50,24 @@ import mimetypes
import time
import re
import stat
import ConfigParser
#########################################################################
#
# Adjust sys.path to include our library directory
#
if LIBRARY_DIR:
sys.path.insert(0, LIBRARY_DIR)
else:
sys.path[:0] = ['../lib'] # any other places to look?
# time for our imports now
import compat
import config
import popen
#########################################################################
checkout_magic_path = '~checkout~/'
viewcvs_mime_type = 'text/vnd.viewcvs-markup'
@@ -284,11 +93,18 @@ _EOF_ERROR = 'error message found' # rlog issued an error
_FILE_HAD_ERROR = 'could not read file'
header_comment = '''\
<!-- ViewCVS -- http://www.lyra.org/greg/python/viewcvs/
<!-- ViewCVS -- http://www.lyra.org/viewcvs/
by Greg Stein -- mailto:gstein@lyra.org
-->
'''
# for reading/writing between a couple descriptors
CHUNK_SIZE = 8192
# if your rlog doesn't use 77 '=' characters, then this must change
LOG_END_MARKER = '=' * 77 + '\n'
ENTRY_END_MARKER = '-' * 28 + '\n'
class Request:
def __init__(self):
@@ -393,34 +209,6 @@ class LogEntry:
self.log = log
#
# Compatibility stuff for pre-1.5.2 versions of Python
#
# Two items: urllib.urlencode and time.strptime
#
try:
my_urlencode = urllib.urlencode
except AttributeError:
def my_urlencode(dict):
if not dict:
return ''
quote = urllib.quote_plus
keyvalue = [ ]
for key, value in dict.items():
keyvalue.append(quote(key) + '=' + quote(str(value)))
return '?' + string.join(keyvalue, '&')
if hasattr(time, 'strptime'):
def my_strptime(timestr):
return time.strptime(timestr, '%Y/%m/%d %H:%M:%S')
else:
_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])')
def my_strptime(timestr):
matches = _re_rev_date.match(timestr).groups()
return tuple(map(int, matches)) + (0, 1, -1)
def redirect(location):
print 'Status: 301 Moved'
print 'Location:', location
@@ -462,7 +250,7 @@ def html_header(title):
def html_footer():
print '<hr noshade><table width="100&#37;" border=0 cellpadding=0 cellspacing=0><tr>'
print '<td align=left><address>%s</address></td>' % cfg.general.address
print '<td align=right><a href="http://www.lyra.org/greg/python/viewcvs/">ViewCVS %s</a><br>' % __version__
print '<td align=right><a href="http://www.lyra.org/viewcvs/">ViewCVS %s</a><br>' % __version__
print 'by <a href="mailto:gstein@lyra.org">Greg Stein</a>'
print '</td></tr></table>'
print '</body></html>'
@@ -473,7 +261,7 @@ def sticky_query(dict):
value = dict.get(varname)
if value is not None and value != default_settings.get(varname, ''):
sticky_dict[varname] = value
return my_urlencode(sticky_dict)
return compat.urlencode(sticky_dict)
def toggle_query(query_dict, which, value=None):
dict = query_dict.copy()
@@ -571,7 +359,7 @@ def download_link(request, url, revision, text, mime_type=None):
print ' target="cvs_checkout"'
if cfg.options.use_java_script:
print " onClick=\"window.open('%s','cvs_checkout'," \
"'resizeable,scrollbars" % full_url,
"'resizeable=1,scrollbars=1" % full_url,
if mime_type == 'text/html':
print ',status,toolbar',
print "');\""
@@ -667,7 +455,7 @@ def markup_stream_default(fp):
while 1:
### technically, the htmlify() could fail if something falls across
### the chunk boundary. TFB.
chunk = fp.read(8192)
chunk = fp.read(CHUNK_SIZE)
if not chunk:
break
sys.stdout.write(htmlify(chunk))
@@ -694,8 +482,83 @@ def markup_stream_python(fp):
html = re.sub(_re_rewrite_email, r'<a href="mailto:\1">\1</a>', html)
sys.stdout.write(html)
def markup_stream_enscript(lang, fp):
sys.stdout.flush()
cmd = "%senscript --color -W html -E%s -o - - 2> /dev/null " \
"| sed -n '/^<PRE>$/,/<\\/PRE>$/p'" % \
(cfg.options.enscript_path, lang,)
enscript = os.popen(cmd, "w")
while 1:
chunk = fp.read(CHUNK_SIZE)
if not chunk:
break
enscript.write(chunk)
enscript.close()
markup_streamers = {
'.py' : markup_stream_python,
# '.py' : markup_stream_python,
}
### this sucks... we have to duplicate the extensions defined by enscript
enscript_extensions = {
'.c' : 'c',
'.h' : 'c',
'.c++' : 'cpp',
'.C' : 'cpp',
'.H' : 'cpp',
'.cpp' : 'cpp',
'.cc' : 'cpp',
'.cxx' : 'cpp',
'.m' : 'objc',
'.scm' : 'scheme',
'.scheme' : 'scheme',
'.el' : 'elisp',
'.ada' : 'ada',
'.adb' : 'ada',
'.ads' : 'ada',
'.s' : 'asm',
'.S' : 'asm',
'.st' : 'states',
'.tcl' : 'tcl',
'.v' : 'verilog',
'.vh' : 'verilog',
'.htm' : 'html',
'.html' : 'html',
'.shtml' : 'html',
'.vhd' : 'vhdl',
'.vhdl' : 'vhdl',
'.scr' : 'synopsys',
'.syn' : 'synopsys',
'.synth' : 'synopsys',
'.idl' : 'idl',
'.hs' : 'haskell',
'.lhs' : 'haskell',
'.gs' : 'haskell',
'.lgs' : 'haskell',
'.pm' : 'perl',
'.pl' : 'perl',
'.eps' : 'postscript',
'.EPS' : 'postscript',
'.ps' : 'postscript',
'.PS' : 'postscript',
'.js' : 'javascript',
'.java' : 'java',
'.pas' : 'pascal',
'.pp' : 'pascal',
'.p' : 'pascal',
'.f' : 'fortran',
'.F' : 'fortran',
'.awk' : 'awk',
'.sh' : 'sh',
'.vba' : 'vba',
### use enscript or py2html?
'.py' : 'python',
}
enscript_filenames = {
'.emacs' : 'elisp',
'Makefile' : 'makefile',
'makefile' : 'makefile',
}
def markup_stream(request, fp, revision, mime_type):
@@ -730,14 +593,26 @@ def markup_stream(request, fp, revision, mime_type):
print 'Tag: <b>%s</b><br>' % tag
print '</td></tr></table>'
url = download_url(request, file_url, revision, mime_type)
print '<hr noshade>'
if mime_type[:6] == 'image/':
url = download_url(request, file_url, revision, mime_type)
print '<img src="%s%s"><br>' % (url, request.amp_query)
else:
basename, ext = os.path.splitext(filename)
streamer = markup_streamers.get(ext, markup_stream_default)
streamer(fp)
streamer = markup_streamers.get(ext)
if streamer:
streamer(fp)
elif not cfg.options.use_enscript:
markup_stream_default(fp)
else:
lang = enscript_extensions.get(ext)
if not lang:
lang = enscript_filenames.get(basename)
if lang and lang not in cfg.options.disable_enscript_lang:
markup_stream_enscript(lang, fp)
else:
markup_stream_default(fp)
html_footer()
def get_file_data(full_name):
"""Return a sequence of tuples containing various data about the files.
@@ -836,10 +711,10 @@ def parse_log_header(fp):
elif line[:14] == 'symbolic names':
# start parsing the tag information
parsing_tags = 1
elif line == '----------------------------\n':
elif line == ENTRY_END_MARKER:
# end of the headers
break
elif line[:10] == '==========':
elif line == LOG_END_MARKER:
# end of this file's log information
eof = _EOF_FILE
break
@@ -899,9 +774,9 @@ def parse_log_entry(fp):
break
if line[:9] == 'branches:':
continue
if line == '----------------------------\n':
if line == ENTRY_END_MARKER:
break
if line[:10] == '==========':
if line == LOG_END_MARKER:
# end of this file's log information
eof = _EOF_FILE
break
@@ -912,7 +787,7 @@ def parse_log_entry(fp):
# there was a parsing error
return None, eof
date = int(time.mktime(my_strptime(match.group(1)))) - time.timezone
date = int(time.mktime(compat.cvs_strptime(match.group(1)))) - time.timezone
return LogEntry(rev, date,
# author, state, lines changed
@@ -925,7 +800,7 @@ def skip_file(fp):
line = fp.readline()
if not line:
break
if line[:10] == '==========':
if line == LOG_END_MARKER:
break
def process_rlog_output(rlog, full_name, view_tag, fileinfo, alltags):
@@ -1062,18 +937,18 @@ def get_logs(full_name, files, view_tag):
chunk = files[:chunk_size]
del files[:chunk_size]
arglist = string.join(chunk, "' '" + full_name + '/')
# prepend the full pathname for each file
for i in range(len(chunk)):
chunk[i] = full_name + '/' + chunk[i]
if view_tag:
# NOTE: can't pass tag on command line since a tag may contain "-"
# we'll search the output for the appropriate revision
rlog = os.popen("%srlog '%s/%s' 2>&1" %
(cfg.general.rcs_path, full_name, arglist),
"r")
rlog = popen.popen(cfg.general.rcs_path + 'rlog', chunk, 'r')
else:
# fetch the latest revision on the default branch
rlog = os.popen("%srlog -r '%s/%s' 2>&1" %
(cfg.general.rcs_path, full_name, arglist),
"r")
chunk = ('-r',) + tuple(chunk)
rlog = popen.popen(cfg.general.rcs_path + 'rlog', chunk, 'r')
process_rlog_output(rlog, full_name, view_tag, fileinfo, alltags)
@@ -1086,6 +961,10 @@ def get_logs(full_name, files, view_tag):
###
### more work for later...
if rlog.close():
### what to do?
pass
return fileinfo, alltags.keys()
def revcmp(rev1, rev2):
@@ -1277,7 +1156,7 @@ def view_directory(request):
if isdir:
if not hideattic and file == 'Attic':
continue
if where == '' and (file == 'CVSROOT' or file in cfg.general.forbidden):
if where == '' and (file == 'CVSROOT' or cfg.is_forbidden(file)):
continue
print '<tr bgcolor="%s"><td>' % cfg.colors.even_odd[cur_row % 2]
@@ -1412,12 +1291,10 @@ def view_directory(request):
def fetch_log(full_name, which_rev=None):
if which_rev:
rev_flag = '-r' + which_rev
args = ('-r' + which_rev, full_name)
else:
rev_flag = ''
rlog = os.popen("%srlog %s '%s' 2>&1" %
(cfg.general.rcs_path, rev_flag, full_name),
"r")
args = (full_name,)
rlog = popen.popen(cfg.general.rcs_path + 'rlog', args, 'r')
header, eof = parse_log_header(rlog)
filename = header.filename
@@ -1905,18 +1782,14 @@ def view_checkout(request):
### validate it?
pass
else:
mime_type, encoding = mimetypes.guess_type(where)
if not mime_type:
mime_type = 'text/plain'
mime_type = request.mime_type
if rev:
rev_flag = '-p' + rev
else:
rev_flag = '-p'
fp = os.popen("%sco '%s' '%s' 2>&1" %
(cfg.general.rcs_path, rev_flag, full_name),
'r')
fp = popen.popen(cfg.general.rcs_path + 'co', (rev_flag, full_name), 'r')
# header from co:
@@ -1945,7 +1818,8 @@ def view_checkout(request):
(header, filename, where))
if mime_type == viewcvs_mime_type:
markup_stream(request, fp, revision, mime_type)
# use the "real" MIME type
markup_stream(request, fp, revision, request.mime_type)
else:
http_header(mime_type)
while 1:
@@ -1955,14 +1829,22 @@ def view_checkout(request):
sys.stdout.write(chunk)
def view_annotate(request):
### dunno what this is for... check against cvsweb
some_value = request.query_dict['annotate']
rev = request.query_dict['annotate']
pathname, filename = os.path.split(request.where)
if pathname[-6:] == '/Attic':
pathname = pathname[:-6]
http_header()
navigate_header(request, request.url, pathname, filename, rev, 'view')
print '<hr noshade>'
import blame
blame.make_html(request.cvsroot, request.where + ',v', rev)
### testing
html_header('annotate')
print "annotate"
html_footer()
_re_extract_rev = re.compile(r'^[-+]+ [^\t]+\t([^\t]+)\t((\d+\.)+\d+)$')
_re_extract_info = re.compile(r'@@ \-([0-9]+).*\+([0-9]+).*@@(.*)')
_re_extract_diff = re.compile(r'^([-+ ])(.*)')
@@ -2026,11 +1908,17 @@ def human_readable_diff(request, fp, rev1, rev2, sym1, sym2):
(cfg.options.diff_font_face, cfg.options.diff_font_size)
left_row = right_row = 0
# this will be set to true if any changes are found
changes_seen = 0
while 1:
line = fp.readline()
if not line:
break
# we've seen some kind of change
changes_seen = 1
if line[:2] == '@@':
match = _re_extract_info.match(line)
print '<tr bgcolor="%s"><td width="50&#37;">' % cfg.colors.diff_heading
@@ -2046,6 +1934,11 @@ def human_readable_diff(request, fp, rev1, rev2, sym1, sym2):
state = 'dump'
left_col = [ ]
right_col = [ ]
elif line[0] == '\\':
# \ No newline at end of file
flush_diff_rows(state, left_col, right_col)
left_col = [ ]
right_col = [ ]
else:
match = _re_extract_diff.match(line)
line = spaced_html_text(match.group(2))
@@ -2072,10 +1965,11 @@ def human_readable_diff(request, fp, rev1, rev2, sym1, sym2):
left_col = [ ]
right_col = [ ]
flush_diff_rows(state, left_col, right_col)
if not state:
if changes_seen:
flush_diff_rows(state, left_col, right_col)
else:
print '<tr><td colspan=2>&nbsp;</td></tr>'
print '<tr bgcolor="%s"><td colspan=2 align=center><b>- No viewable change -</b></td></tr>' % (cfg.colors.diff_empty)
print '<tr bgcolor="%s"><td colspan=2 align=center><br><b>- No changes -</b><br>&nbsp;</td></tr>' % (cfg.colors.diff_empty)
print '</table><br><hr noshade width="100&#37;">'
print '<table border=0 cellpadding=10><tr><td>'
@@ -2168,45 +2062,51 @@ def view_diff(request, cvs_filename):
rev2 = r2[:idx]
sym2 = r2[idx+1:]
### check rev1, rev2 for well-formed-ness (security reasons)
if revcmp(rev1, rev2) > 0:
rev1, rev2 = rev2, rev1
sym1, sym2 = sym2, sym1
human_readable = 0
unified = 0
args = [ ]
format = query_dict['diff_format']
if format == 'c':
diff_type = '-c'
args.append('-c')
diff_name = 'Context diff'
elif format == 's':
diff_type = '--side-by-side --width=164'
args.append('--side-by-side')
args.append('--width=164')
diff_name = 'Side by Side'
elif format == 'H':
diff_type = '--unified=15'
args.append('--unified=15')
diff_name = 'Long Human readable'
human_readable = 1
unified = 1
elif format == 'h':
diff_type = '-u'
args.append('-u')
diff_name = 'Human readable'
human_readable = 1
unified = 1
elif format == 'u':
diff_type = '-u'
args.append('-u')
diff_name = 'Unidiff'
unified = 1
else:
error('Diff format %s not understood' % format, '400 Bad arguments')
if human_readable:
if cfg.options.hr_funout:
diff_type = diff_type + ' -p'
args.append('-p')
if cfg.options.hr_ignore_white:
diff_type = diff_type + ' -w'
args.append('-w')
if cfg.options.hr_ignore_keyword_subst:
diff_type = diff_type + ' -kk'
args.append('-kk')
args[len(args):] = ['-r' + rev1, '-r' + rev2, cvs_filename]
fp = popen.popen(cfg.general.rcs_path + 'rcsdiff', args, 'r')
fp = os.popen("%srcsdiff %s '-r%s' '-r%s' '%s' 2>&1" %
(cfg.general.rcs_path, diff_type, rev1, rev2, cvs_filename),
'r')
if human_readable:
http_header()
human_readable_diff(request, fp, rev1, rev2, sym1, sym2)
@@ -2214,7 +2114,7 @@ def view_diff(request, cvs_filename):
http_header('text/plain')
if diff_type == '-u':
if unified:
f1 = '--- ' + cvsroot
f2 = '+++ ' + cvsroot
else:
@@ -2238,9 +2138,13 @@ def view_diff(request, cvs_filename):
print line[:-1]
def handle_config():
global cfg
cfg = config.Config()
cfg.set_defaults()
# load in configuration information from the config file
### allow changes and paths here...??
cfg.load_config('viewcvs.conf')
pathname = CONF_PATHNAME or 'viewcvs.conf'
cfg.load_config(pathname, os.environ.get('HTTP_HOST'))
global default_settings
default_settings = {
@@ -2280,7 +2184,7 @@ def main():
redirect(url + '/' + request.qmark_query)
# check the forbidden list
if request.module in cfg.general.forbidden:
if cfg.is_forbidden(request.module):
error('Access to "%s" is forbidden.' % request.module, '403 Forbidden')
if isdir:

View File

@@ -3,7 +3,7 @@
# Configuration file for ViewCVS
#
# Information on ViewCVS is located at the following web site:
# http://www.lyra.org/greg/python/viewcvs/
# http://www.lyra.org/viewcvs/
#
#---------------------------------------------------------------------------
@@ -28,6 +28,8 @@
# long_intro
# repository_info
#
# use_enscript
#
# For Python source colorization:
#
# py2html_path
@@ -94,9 +96,55 @@ main_title = CVS Repository
# This should contain a list of modules in the repository that should not be
# displayed (by default or by explicit path specification).
#
# This configuration can be a simple list of modules, or it can get quite
# complex:
#
# *) The "!" can be used before a module to explicitly state that it
# is NOT forbidden. Whenever this form is seen, then all modules will
# be forbidden unless one of the "!" modules match.
#
# *) Shell-style "glob" expressions may be used. "*" will match any
# sequence of zero or more characters, "?" will match any single
# character, "[seq]" will match any character in seq, and "[!seq]"
# will match any character not in seq.
#
# *) Tests are performed in sequence. The first match will terminate the
# testing. This allows for more complex allow/deny patterns.
#
# Tests are case-sensitive.
#
forbidden =
# forbidden = example
# forbidden = example1, example2
# Some examples:
#
# Disallow "example" but allow all others:
# forbidden = example
#
# Disallow "example1" and "example2" but allow all others:
# forbidden = example1, example2
#
# Allow *only* "example1" and "example2":
# forbidden = !example1, !example2
#
# Forbid modules starting with "x":
# forbidden = x*
#
# Allow modules starting with "x" but no others:
# forbidden = !x*
#
# Allow "xml", forbid other modules starting with "x", and allow the rest:
# forbidden = !xml, x*, !*
#
#---------------------------------------------------------------------------
[cvsdb]
#host = localhost
#database_name = ViewCVS
#user =
#passwd =
#readonly_user =
#readonly_passwd =
#---------------------------------------------------------------------------
[images]
@@ -192,7 +240,7 @@ long_intro =
</p>
<p>
This script
(<a href="http://www.lyra.org/greg/python/viewcvs/">ViewCVS</a>)
(<a href="http://www.lyra.org/viewcvs/">ViewCVS</a>)
has been written by Greg Stein
&lt;<a href="mailto:gstein@lyra.org">gstein@lyra.org</a>&gt;
based on the
@@ -204,7 +252,7 @@ long_intro =
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/greg/python/viewcvs/">ViewCVS distribution
<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>.
@@ -297,7 +345,7 @@ hr_ignore_keyword_subst = 1
# 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 = 0
allow_annotate = 1
# allow pretty-printed version of files
allow_markup = 1
@@ -368,4 +416,53 @@ 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?
use_enscript = 0
#
# if the enscript program is not on the path, set this value
# Note: there should be a trailing slash
#
enscript_path =
# enscript_path = /usr/bin/
#
# ViewCVS has its own set of mappings from filename extensions and filenames
# to languages. If the language is not supported by enscript, then it can
# be listed here to disable the use of enscript.
#
disable_enscript_lang =
# disable_enscript_lang = perl, cpp
#---------------------------------------------------------------------------
[vhosts]
### DOC
# vhost1 = glob1, glob2
# vhost2 = glob3, glob4
# [vhost1-section]
# option = value
# [vhost1-othersection]
# option = value
# [vhost2-section]
# option = value
#
# Here is an example:
#
# [vhosts]
# lyra = *lyra.org
#
# [lyra-general]
# forbidden = hideme
#
# [lyra-options]
# show_logs = 0
#
# Note that "lyra" is the "canonical" name for all hosts in the lyra.org
# domain. This canonical name is then used within the additional, vhost-
# specific sections to override specific values in the common sections.
#
#---------------------------------------------------------------------------

View File

@@ -0,0 +1,81 @@
<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>

View File

@@ -0,0 +1,30 @@
<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>&nbsp</th>
<th align=left valign=top>&nbsp</th>
<th align=left valign=top>&nbsp</th>
<th align=left valign=top>&nbsp</th>
<th align=left valign=top>&nbsp</th>
<th align=left valign=top>&nbsp</th>
<th align=left valign=top>&nbsp</th>
</tr>
</table>
</body>
</html>

153
lib/PyFontify.py Normal file
View File

@@ -0,0 +1,153 @@
"""Module to analyze Python source code; for syntax coloring tools.
Interface:
tags = fontify(pytext, searchfrom, searchto)
The 'pytext' argument is a string containing Python source code.
The (optional) arguments 'searchfrom' and 'searchto' may contain a slice in pytext.
The returned value is a list of tuples, formatted like this:
[('keyword', 0, 6, None), ('keyword', 11, 17, None), ('comment', 23, 53, None), etc. ]
The tuple contents are always like this:
(tag, startindex, endindex, sublist)
tag is one of 'keyword', 'string', 'comment' or 'identifier'
sublist is not used, hence always None.
"""
# Based on FontText.py by Mitchell S. Chapman,
# which was modified by Zachary Roadhouse,
# then un-Tk'd by Just van Rossum.
# Many thanks for regular expression debugging & authoring are due to:
# Tim (the-incredib-ly y'rs) Peters and Cristian Tismer
# So, who owns the copyright? ;-) How about this:
# Copyright 1996-1997:
# Mitchell S. Chapman,
# Zachary Roadhouse,
# Tim Peters,
# Just van Rossum
__version__ = "0.3.1"
import string, regex
# First a little helper, since I don't like to repeat things. (Tismer speaking)
import string
def replace(where, what, with):
return string.join(string.split(where, what), with)
# This list of keywords is taken from ref/node13.html of the
# Python 1.3 HTML documentation. ("access" is intentionally omitted.)
keywordsList = [
"del", "from", "lambda", "return",
"and", "elif", "global", "not", "try",
"break", "else", "if", "or", "while",
"class", "except", "import", "pass",
"continue", "finally", "in", "print",
"def", "for", "is", "raise"]
# Build up a regular expression which will match anything
# interesting, including multi-line triple-quoted strings.
commentPat = "#.*"
pat = "q[^\q\n]*\(\\\\[\000-\377][^\q\n]*\)*q"
quotePat = replace(pat, "q", "'") + "\|" + replace(pat, 'q', '"')
# Way to go, Tim!
pat = """
qqq
[^\\q]*
\(
\( \\\\[\000-\377]
\| q
\( \\\\[\000-\377]
\| [^\\q]
\| q
\( \\\\[\000-\377]
\| [^\\q]
\)
\)
\)
[^\\q]*
\)*
qqq
"""
pat = string.join(string.split(pat), '') # get rid of whitespace
tripleQuotePat = replace(pat, "q", "'") + "\|" + replace(pat, 'q', '"')
# Build up a regular expression which matches all and only
# Python keywords. This will let us skip the uninteresting
# identifier references.
# nonKeyPat identifies characters which may legally precede
# a keyword pattern.
nonKeyPat = "\(^\|[^a-zA-Z0-9_.\"']\)"
keyPat = nonKeyPat + "\("
for keyword in keywordsList:
keyPat = keyPat + keyword + "\|"
keyPat = keyPat[:-2] + "\)" + nonKeyPat
matchPat = keyPat + "\|" + commentPat + "\|" + tripleQuotePat + "\|" + quotePat
matchRE = regex.compile(matchPat)
idKeyPat = "[ \t]*[A-Za-z_][A-Za-z_0-9.]*" # Ident w. leading whitespace.
idRE = regex.compile(idKeyPat)
def fontify(pytext, searchfrom = 0, searchto = None):
if searchto is None:
searchto = len(pytext)
# Cache a few attributes for quicker reference.
search = matchRE.search
group = matchRE.group
idSearch = idRE.search
idGroup = idRE.group
tags = []
tags_append = tags.append
commentTag = 'comment'
stringTag = 'string'
keywordTag = 'keyword'
identifierTag = 'identifier'
start = 0
end = searchfrom
while 1:
start = search(pytext, end)
if start < 0 or start >= searchto:
break # EXIT LOOP
match = group(0)
end = start + len(match)
c = match[0]
if c not in "#'\"":
# Must have matched a keyword.
if start <> searchfrom:
# there's still a redundant char before and after it, strip!
match = match[1:-1]
start = start + 1
else:
# this is the first keyword in the text.
# Only a space at the end.
match = match[:-1]
end = end - 1
tags_append((keywordTag, start, end, None))
# If this was a defining keyword, look ahead to the
# following identifier.
if match in ["def", "class"]:
start = idSearch(pytext, end)
if start == end:
match = idGroup(0)
end = start + len(match)
tags_append((identifierTag, start, end, None))
elif c == "#":
tags_append((commentTag, start, end, None))
else:
tags_append((stringTag, start, end, None))
return tags
def test(path):
f = open(path)
text = f.read()
f.close()
tags = fontify(text)
for tag, start, end, sublist in tags:
print tag, `text[start:end]`

733
lib/blame.py Normal file
View File

@@ -0,0 +1,733 @@
#!/usr/local/bin/python
# -*-python-*-
#
# Copyright (C) 2000 The ViewCVS Group. All Rights Reserved.
# Copyright (C) 2000 Curt Hagenlocher <curt@hagenlocher.org>
#
# 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/
#
# -----------------------------------------------------------------------
#
# blame.py: Annotate each line of a CVS file with its author,
# revision #, date, etc.
#
# -----------------------------------------------------------------------
#
# This software is being maintained as part of the ViewCVS project.
# Information is available at:
# http://www.lyra.org/viewcvs/
#
# This file is based on the cvsblame.pl portion of the Bonsai CVS tool,
# developed by Steve Lamm for Netscape Communications Corporation. More
# information about Bonsai can be found at
# http://www.mozilla.org/bonsai.html
#
# cvsblame.pl, in turn, was based on Scott Furman's cvsblame script
#
# -----------------------------------------------------------------------
#
import string
import sys
import os
import re
import time
import math
import cgi
path_sep = os.path.normpath('/')[-1]
class CVSParser:
# 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]+$')
last_branch = re.compile('(.*)\\.[0-9]+')
is_branch = re.compile('(.*)\\.0\\.([0-9]+)')
d_command = re.compile('^d(\d+)\\s(\\d+)')
a_command = re.compile('^a(\d+)\\s(\\d+)')
SECONDS_PER_DAY = 86400
def __init__(self):
self.Reset()
def Reset(self):
self.line_buffer = ''
self.rcsfile = None
self.debug = 0
self.last_revision = {}
self.prev_revision = {}
self.revision_date = {}
self.revision_author = {}
self.revision_branches = {}
self.next_delta = {}
self.prev_delta = {}
self.feof = 0
self.tag_revision = {}
self.revision_symbolic_name = {}
self.timestamp = {}
self.revision_ctime = {}
self.revision_age = {}
self.revision_log = {}
self.revision_deltatext = {}
self.revision_map = []
self.lines_added = {}
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
# branch tag, a symbolic revision tag, or an ordinary numerical
# revision number.
def map_tag_to_revision(self, tag_or_revision):
try:
revision = self.tag_revision[tag_or_revision]
match = self.is_branch.match(revision)
if match:
branch = match.group(1) + '.' + match.group(2)
if self.last_revision.get(branch):
return self.last_revision[branch]
else:
return match.group(1)
else:
return revision
except:
return ''
# Construct an ordered list of ancestor revisions to the given
# revision, starting with the immediate ancestor and going back
# to the primordial revision (1.1).
#
# Note: The generated path does not traverse the tree the same way
# that the individual revision deltas do. In particular,
# the path traverses the tree "backwards" on branches.
def ancestor_revisions(self, revision):
ancestors = []
revision = self.prev_revision.get(revision)
while revision:
ancestors.append(revision)
revision = self.prev_revision.get(revision)
return ancestors
# Extract the given revision from the digested RCS file.
# (Essentially the equivalent of cvs up -rXXX)
def extract_revision(self, revision):
path = []
add_lines_remaining = 0
start_line = 0
count = 0
while revision:
path.append(revision)
revision = self.prev_delta.get(revision)
path.reverse()
path = path[1:] # Get rid of head revision
text = string.split(self.revision_deltatext[self.head_revision], '\n')
# Iterate, applying deltas to previous revision
for revision in path:
adjust = 0
diffs = string.split(self.revision_deltatext[revision], '\n')
self.lines_added[revision] = 0
self.lines_removed[revision] = 0
lines_added_now = 0
lines_removed_now = 0
for command in diffs:
dmatch = self.d_command.match(command)
amatch = self.a_command.match(command)
if add_lines_remaining > 0:
# Insertion lines from a prior "a" command
text.insert(start_line + adjust, command)
add_lines_remaining = add_lines_remaining - 1
adjust = adjust + 1
elif dmatch:
# "d" - Delete command
start_line = string.atoi(dmatch.group(1))
count = string.atoi(dmatch.group(2))
begin = start_line + adjust - 1
del text[begin:begin + count]
adjust = adjust - count
lines_removed_now = lines_removed_now + count
elif amatch:
# "a" - Add command
start_line = string.atoi(amatch.group(1))
count = string.atoi(amatch.group(2))
add_lines_remaining = count
lines_added_now = lines_added_now + count
else:
raise RuntimeError, 'Error parsing diff commands'
self.lines_added[revision] = self.lines_added[revision] + lines_added_now
self.lines_removed[revision] = self.lines_removed[revision] + lines_removed_now
return text
def parse_rcs_admin(self):
while 1:
# Read initial token at beginning of line
token = self.get_token()
# We're done once we reach the description of the RCS tree
if self.rcs_tree.match(token):
self.unget_token(token)
return
# print "token:", token
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
# revision number and vice-versa.
while 1:
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.
elif token in ("locks", "strict", "expand", "access"):
while 1:
tag = self.get_token()
if tag == ';':
break
else:
pass
# warn("Unexpected RCS token: $token\n")
raise RuntimeError, "Unexpected EOF";
# Construct dicts that represent the topology of the RCS tree
# and other arrays that contain info about individual revisions.
#
# The following dicts are created, keyed by revision number:
# self.revision_date -- e.g. "96.02.23.00.21.52"
# self.timestamp -- seconds since 12:00 AM, Jan 1, 1970 GMT
# self.revision_author -- e.g. "tom"
# self.revision_branches -- descendant branch revisions, separated by spaces,
# e.g. "1.21.4.1 1.21.2.6.1"
# self.prev_revision -- revision number of previous *ancestor* in RCS tree.
# Traversal of this array occurs in the direction
# of the primordial (1.1) revision.
# self.prev_delta -- revision number of previous revision which forms
# the basis for the edit commands in this revision.
# This causes the tree to be traversed towards the
# trunk when on a branch, and towards the latest trunk
# revision when on the trunk.
# self.next_delta -- revision number of next "delta". Inverts prev_delta.
#
# Also creates self.last_revision, keyed by a branch revision number, which
# indicates the latest revision on a given branch,
# e.g. self.last_revision{"1.2.8"} == 1.2.8.5
def parse_rcs_tree(self):
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
branch = self.last_branch.match(revision).group(1)
self.last_revision[branch] = revision
# Parse date
self.match_token('date')
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
ltime = time.localtime(self.timestamp[revision])
formatted_date = time.strftime("%d %b %Y %H:%M", ltime)
self.revision_ctime[revision] = formatted_date
# Save age
self.revision_age[revision] = (
(time.time() - self.timestamp[revision]) / self.SECONDS_PER_DAY)
# Parse author
self.match_token('author')
author = self.get_token()
self.revision_author[revision] = author
self.match_token(';')
# Parse state
self.match_token('state')
while self.get_token() != ';':
pass
# Parse branches
self.match_token('branches')
branches = ''
while 1:
token = self.get_token()
if token == ';':
break
self.prev_revision[token] = revision
self.prev_delta[token] = revision
branches = branches + token + ' '
self.revision_branches[revision] = branches
# Parse revision of next delta in chain
self.match_token('next')
next = ''
token = self.get_token()
if token != ';':
next = token
self.get_token() # Eat semicolon
self.next_delta[revision] = next
self.prev_delta[next] = revision
if is_trunk_revision:
self.prev_revision[revision] = next
else:
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.
#
# The following associative arrays are created, keyed by revision number:
# revision_log -- log message
# revision_deltatext -- Either the complete text of the revision,
# in the case of the head revision, or the
# encoded delta between this revision and another.
# The delta is either with respect to the successor
# revision if this revision is on the trunk or
# relative to its immediate predecessor if this
# revision is on a branch.
def parse_rcs_deltatext(self):
while not self.feof:
revision = self.get_token()
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):
# Args in: opt_rev - requested revision
# opt_m - time since modified
# Args out: revision_map
# timestamp
# revision_deltatext
# CheckHidden(rcs_pathname);
try:
self.rcsfile = open(rcs_pathname, 'r')
except:
raise RuntimeError, ('error: %s appeared to be under CVS control, ' +
'but the RCS file is inaccessible.') % rcs_pathname
self.parse_rcs_file()
self.rcsfile.close()
if opt_rev in [None, '', 'HEAD']:
# Explicitly specified topmost revision in tree
revision = self.head_revision
else:
# Symbolic tag or specific revision number specified.
revision = self.map_tag_to_revision(opt_rev)
if revision == '':
raise RuntimeError, 'error: -r: No such revision: ' + opt_rev
# The primordial revision is not always 1.1! Go find it.
primordial = revision
while self.prev_revision.get(primordial):
primordial = self.prev_revision[primordial]
# Don't display file at all, if -m option is specified and no
# changes have been made in the specified file.
if opt_m_timestamp and self.timestamp[revision] < opt_m_timestamp:
return ''
# Figure out how many lines were in the primordial, i.e. version 1.1,
# check-in by moving backward in time from the head revision to the
# first revision.
line_count = 0
if self.revision_deltatext.get(self.head_revision):
tmp_array = string.split(self.revision_deltatext[self.head_revision], '\n')
line_count = len(tmp_array)
skip = 0
rev = self.prev_revision.get(self.head_revision)
while rev:
diffs = string.split(self.revision_deltatext[rev], '\n')
for command in diffs:
dmatch = self.d_command.match(command)
amatch = self.a_command.match(command)
if skip > 0:
# Skip insertion lines from a prior "a" command
skip = skip - 1
elif dmatch:
# "d" - Delete command
start_line = string.atoi(dmatch.group(1))
count = string.atoi(dmatch.group(2))
line_count = line_count - count
elif amatch:
# "a" - Add command
start_line = string.atoi(amatch.group(1))
count = string.atoi(amatch.group(2))
skip = count;
line_count = line_count + count
else:
raise RuntimeError, 'error: illegal RCS file'
rev = self.prev_revision.get(rev)
# Now, play the delta edit commands *backwards* from the primordial
# revision forward, but rather than applying the deltas to the text of
# each revision, apply the changes to an array of revision numbers.
# This creates a "revision map" -- an array where each element
# represents a line of text in the given revision but contains only
# the revision number in which the line was introduced rather than
# the line text itself.
#
# Note: These are backward deltas for revisions on the trunk and
# forward deltas for branch revisions.
# Create initial revision map for primordial version.
self.revision_map = [primordial] * line_count
ancestors = [revision, ] + self.ancestor_revisions(revision)
ancestors = ancestors[:-1] # Remove "1.1"
last_revision = primordial
ancestors.reverse()
for revision in ancestors:
is_trunk_revision = self.trunk_rev.match(revision) is not None
if is_trunk_revision:
diffs = string.split(self.revision_deltatext[last_revision], '\n')
# Revisions on the trunk specify deltas that transform a
# revision into an earlier revision, so invert the translation
# of the 'diff' commands.
for command in diffs:
if skip > 0:
skip = skip - 1
else:
dmatch = self.d_command.match(command)
amatch = self.a_command.match(command)
if dmatch:
start_line = string.atoi(dmatch.group(1))
count = string.atoi(dmatch.group(2))
temp = []
while count > 0:
temp.append(revision)
count = count - 1
self.revision_map = (self.revision_map[:start_line - 1] +
temp + self.revision_map[start_line - 1:])
elif amatch:
start_line = string.atoi(amatch.group(1))
count = string.atoi(amatch.group(2))
del self.revision_map[start_line:start_line + count]
skip = count
else:
raise RuntimeError, 'Error parsing diff commands'
else:
# Revisions on a branch are arranged backwards from those on
# the trunk. They specify deltas that transform a revision
# into a later revision.
adjust = 0
diffs = string.split(self.revision_deltatext[revision], '\n')
for command in diffs:
if skip > 0:
skip = skip - 1
else:
dmatch = self.d_command.match(command)
amatch = self.a_command.match(command)
if dmatch:
start_line = string.atoi(dmatch.group(1))
count = string.atoi(dmatch.group(2))
del self.revision_map[start_line + adjust - 1:start_line + adjust - 1 + count]
adjust = adjust - count
elif amatch:
start_line = string.atoi(amatch.group(1))
count = string.atoi(amatch.group(2))
skip = count
temp = []
while count > 0:
temp.append(revision)
count = count - 1
self.revision_map = (self.revision_map[:start_line + adjust] +
temp + self.revision_map[start_line + adjust:])
adjust = adjust + skip
else:
raise RuntimeError, 'Error parsing diff commands'
last_revision = revision
return revision
re_includes = re.compile('\\#(\\s*)include(\\s*)"(.*?)"')
re_filename = re.compile('(.*[\\\\/])?(.+)')
def link_includes(text, root, rcs_path):
match = re_includes.match(text)
if match:
incfile = match.group(3)
for rel_path in ('', 'Attic', '..'):
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))
if os.access(file, os.F_OK):
return '#%sinclude%s"<a href="%s">%s</a>"' % \
(match.group(1), match.group(2),
os.path.join(rel_path, incfile), incfile)
return text
def make_html(root, rcs_path, opt_rev = None):
filename = root + path_sep + rcs_path
parser = CVSParser()
revision = parser.parse_cvs_file(filename, opt_rev)
count = len(parser.revision_map)
text = parser.extract_revision(revision)
if len(text) != count:
raise RuntimeError, 'Internal consistency error'
match = re_filename.match(rcs_path)
if not match:
raise RuntimeError, 'Unable to parse filename'
file_head = match.group(1)
file_tail = match.group(2)
open_table_tag = '<table border=0 cellpadding=0 cellspacing=0 width="100%">'
startOfRow = '<tr><td colspan=3%s><pre>'
endOfRow = '</td></tr>'
print open_table_tag + (startOfRow % '')
if count == 0:
count = 1
line_num_width = int(math.log10(count)) + 1
revision_width = 3
author_width = 5
line = 0
usedlog = {}
usedlog[revision] = 1
old_revision = 0
row_color = ''
lines_in_table = 0
inMark = 0
rev_count = 0
for revision in parser.revision_map:
thisline = text[line]
line = line + 1
usedlog[revision] = 1
line_in_table = lines_in_table + 1
# Escape HTML meta-characters
thisline = cgi.escape(thisline)
# Add a link to traverse to included files
if 1: # opt_includes
thisline = link_includes(thisline, root, file_head)
output = ''
# Highlight lines
#mark_cmd;
#if (defined($mark_cmd = $mark_line{$line}) and mark_cmd != 'end':
# output = output + endOfRow + '<tr><td bgcolor=LIGHTGREEN width="100%"><pre>'
# inMark = 1
if old_revision != revision and line != 1:
if row_color == '':
row_color = ' bgcolor="#e7e7e7"'
else:
row_color = ''
if not inMark:
if lines_in_table > 100:
output = output + endOfRow + '</table>' + open_table_tag + (startOfRow % row_color)
lines_in_table = 0
else:
output = output + endOfRow + (startOfRow % row_color)
elif lines_in_table > 200 and not inMark:
output = output + endOfRow + '</table>' + open_table_tag + (startOfRow % row_color)
lines_in_table = 0
output = output + "<a name=%d></a>" % (line, )
if 1: # opt_line_nums
output = output + ('%%%dd' % (line_num_width, )) % (line, )
if old_revision != revision or rev_count > 20:
revision_width = max(revision_width, len(revision))
if parser.prev_revision.get(revision):
fname = file_tail[:-2] # strip the ",v"
### need the sticky options! need cvsroot if not-default
output = output + ' <a href="%s.diff?r1=%s&amp;r2=%s"' % \
(fname, parser.prev_revision[revision], revision)
if 0: # use_layers
output = output + " onmouseover='return log(event,\"%s\",\"%s\");'" % (
parser.prev_revision[revision], revision)
output = output + ">"
else:
output = output + " "
parser.prev_revision[revision] = ''
author = parser.revision_author[revision]
# $author =~ s/%.*$//;
author_width = max(author_width, len(author))
output = output + ('%%-%ds ' % (author_width, )) % (author, )
output = output + revision
if parser.prev_revision.get(revision):
output = output + '</a>'
output = output + (' ' * (revision_width - len(revision) + 1))
old_revision = revision
rev_count = 0
else:
output = output + ' ' + (' ' * (author_width + revision_width))
rev_count = rev_count + 1
output = output + thisline
# Close the highlighted section
#if (defined $mark_cmd and mark_cmd != 'begin'):
# chop($output)
# output = output + endOfRow + (startOfRow % row_color)
# inMark = 0
print output
print endOfRow + '</table>'
def main():
if len(sys.argv) != 3:
print 'USAGE: %s cvsroot rcs-file' % sys.argv[0]
sys.exit(1)
make_html(sys.argv[1], sys.argv[2])
if __name__ == '__main__':
main()

160
lib/commit.py Normal file
View File

@@ -0,0 +1,160 @@
# -*- 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

67
lib/compat.py Normal file
View File

@@ -0,0 +1,67 @@
# -*- 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/
#
# -----------------------------------------------------------------------
#
# compat.py: compatibility functions for operation across Python 1.5.x
#
# -----------------------------------------------------------------------
#
import urllib
import string
import time
import re
import os
#
# urllib.urlencode() is new to Python 1.5.2
#
try:
urlencode = urllib.urlencode
except AttributeError:
def urlencode(dict):
"Encode a dictionary as application/x-url-form-encoded."
if not dict:
return ''
quote = urllib.quote_plus
keyvalue = [ ]
for key, value in dict.items():
keyvalue.append(quote(key) + '=' + quote(str(value)))
return '?' + string.join(keyvalue, '&')
#
# time.strptime() is new to Python 1.5.2
#
if hasattr(time, 'strptime'):
def cvs_strptime(timestr):
return time.strptime(timestr, '%Y/%m/%d %H:%M:%S')
else:
_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])')
def cvs_strptime(timestr):
matches = _re_rev_date.match(timestr).groups()
return tuple(map(int, matches)) + (0, 1, -1)
cvs_strptime.__doc__ = 'Parse a CVS-style date/time value.'
#
# os.makedirs() is new to Python 1.5.2
#
try:
makedirs = os.makedirs
except AttributeError:
def makedirs(path, mode=0777):
head, tail = os.path.split(path)
if head and tail and not os.path.exists(head):
makedirs(head, mode)
os.mkdir(path, mode)

274
lib/config.py Normal file
View File

@@ -0,0 +1,274 @@
# -*- 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/
#
# -----------------------------------------------------------------------
#
# config.py: configuration utilities
#
# -----------------------------------------------------------------------
#
import sys
import os
import string
import ConfigParser
import fnmatch
#########################################################################
#
# CONFIGURATION
#
# There are three forms of configuration:
#
# 1) copy viewcvs.conf.dist to viewcvs.conf and edit
# 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
#
# Most users will want to use (1), but there are slight speed advantages
# to the other two options. Note that viewcvs.conf values are a bit easier
# to work with since it is raw text, rather than python literal values.
#
#########################################################################
class Config:
_sections = ('general', 'images', 'options', 'colors', 'text', 'cvsdb')
_force_multi_value = ('cvs_roots', 'forbidden', 'even_odd',
'disable_enscript_lang')
def __init__(self):
for section in self._sections:
setattr(self, section, _sub_config())
def load_config(self, fname, vhost=None):
this_dir = os.path.dirname(sys.argv[0])
pathname = os.path.join(this_dir, fname)
parser = ConfigParser.ConfigParser()
parser.read(pathname)
for section in self._sections:
if parser.has_section(section):
self._process_section(parser, section, section)
if vhost:
self._process_vhost(parser, vhost)
def _process_section(self, parser, section, subcfg_name):
sc = getattr(self, subcfg_name)
for opt in parser.options(section):
value = parser.get(section, opt)
if opt in self._force_multi_value or subcfg_name == 'images':
value = map(string.strip, filter(None, string.split(value, ',')))
else:
try:
value = int(value)
except ValueError:
pass
if opt == 'cvs_roots':
roots = { }
for root in value:
name, path = map(string.strip, string.split(root, ':'))
roots[name] = path
value = roots
setattr(sc, opt, value)
def _process_vhost(self, parser, vhost):
canon_vhost = self._find_canon_vhost(parser, vhost)
if not canon_vhost:
# none of the vhost sections matched
return
cv = canon_vhost + '-'
lcv = len(cv)
for section in parser.sections():
if section[:lcv] == cv:
self._process_section(parser, section, section[lcv:])
def _find_canon_vhost(self, parser, vhost):
vhost = string.lower(vhost)
for canon_vhost in parser.options('vhosts'):
value = parser.get('vhosts', canon_vhost)
patterns = map(string.lower, map(string.strip,
filter(None, string.split(value, ','))))
for pat in patterns:
if fnmatch.fnmatchcase(vhost, pat):
return canon_vhost
return None
def set_defaults(self):
"Set some default values in the configuration."
self.general.cvs_roots = {
# user-visible-name : path
"Development" : "/home/cvsroot",
}
self.general.default_root = "Development"
self.general.rcs_path = ''
self.general.mime_types_file = ''
self.general.address = '<a href="mailto:gstein@lyra.org">gstein@lyra.org</a>'
self.general.main_title = 'CVS Repository'
self.general.forbidden = ()
self.cvsdb.enabled = 0
self.cvsdb.host = ''
self.cvsdb.database_name = ''
self.cvsdb.user = ''
self.cvsdb.passwd = ''
self.cvsdb.readonly_user = ''
self.cvsdb.readonly_passwd = ''
self.images.logo = "/icons/apache_pb.gif", 259, 32
self.images.back_icon = "/icons/small/back.gif", 16, 16
self.images.dir_icon = "/icons/small/dir.gif", 16, 16
self.images.file_icon = "/icons/small/text.gif", 16, 16
self.colors.markup_log = "#ffffff"
self.colors.diff_heading = "#99cccc"
self.colors.diff_empty = "#cccccc"
self.colors.diff_remove = "#ff9999"
self.colors.diff_change = "#99ff99"
self.colors.diff_add = "#ccccff"
self.colors.diff_dark_change = "#99cc99"
self.colors.even_odd = ("#ccccee", "#ffffff")
self.colors.nav_header = "#9999ee"
self.colors.text = "#000000"
self.colors.background = "#ffffff"
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.hide_attic = 1
self.options.log_sort = 'date'
self.options.diff_format = 'h'
self.options.hide_cvsroot = 1
self.options.hide_non_readable = 1
self.options.show_author = 1
self.options.hr_breakable = 1
self.options.hr_funout = 1
self.options.hr_ignore_white = 1
self.options.hr_ignore_keyword_subst = 1
self.options.allow_annotate = 0 ### doesn't work yet!
self.options.allow_markup = 1
self.options.allow_compress = 1
self.options.use_java_script = 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.show_subdir_lastmod = 0
self.options.show_logs = 1
self.options.show_log_in_markup = 1
self.options.allow_version_select = 1
self.options.py2html_path = '.'
self.options.short_log_len = 80
self.options.table_padding = 2
self.options.diff_font_face = 'Helvetica,Arial'
self.options.diff_font_size = -1
self.options.input_text_size = 12
self.options.use_enscript = 0
self.options.enscript_path = ''
self.options.disable_enscript_lang = ()
self.text.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
&lt;<a href="mailto:gstein@lyra.org">gstein@lyra.org</a>&gt;
based on the
<a href="http://linux.fh-heilbronn.de/~zeller/cgi/cvsweb.cgi">cvsweb</a>
script by Henner Zeller
&lt;<a href="mailto:zeller@think.de">zeller@think.de</a>&gt;;
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):
if not module:
return 0
default = 0
for pat in self.general.forbidden:
if pat[0] == '!':
default = 1
if fnmatch.fnmatchcase(module, pat[1:]):
return 0
elif fnmatch.fnmatchcase(module, pat):
return 1
return default
class _sub_config:
def get_image(self, which):
text = '[%s]' % string.upper(which)
path, width, height = getattr(self, which)
if path:
return '<img src="%s" alt="%s" border=0 width=%s height=%s>' % \
(path, text, width, height)
return text

136
lib/cvsdbapi.py Normal file
View File

@@ -0,0 +1,136 @@
# -*- 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 Normal file
View File

@@ -0,0 +1,476 @@
# -*- 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()

71
lib/dbi.py Normal file
View File

@@ -0,0 +1,71 @@
# -*- 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 sys
import MySQLdb
dbi_error = "dbi error"
## make some checks in MySQLdb
_no_datetime = """\
ERROR: Your version of MySQLdb requires the mxDateTime module
for the Timestamp() and TimestampFromTicks() methods.
You will need to install mxDateTime to use the ViewCVS
database.
"""
if not hasattr(MySQLdb, "Timestamp") or \
not hasattr(MySQLdb, "TimestampFromTicks"):
sys.stderr.write(_no_datetime)
sys.exit(1)
class Cursor:
def __init__(self, mysql_cursor):
self.__cursor = mysql_cursor
def execute(self, *args):
apply(self.__cursor.execute, args)
def fetchone(self):
try:
row = self.__cursor.fetchone()
except IndexError:
row = None
return row
class Connection:
def __init__(self, host, user, passwd, db):
self.__mysql = MySQLdb.connect(
host=host, user=user, passwd=passwd, db=db)
def cursor(self):
return Cursor(self.__mysql.cursor())
def Timestamp(year, month, date, hour, minute, second):
return MySQLdb.Timestamp(year, month, date, hour, minute, second)
def TimestampFromTicks(ticks):
return MySQLdb.TimestampFromTicks(ticks)
def connect(host, user, passwd, db):
return Connection(host, user, passwd, db)

90
lib/popen.py Normal file
View File

@@ -0,0 +1,90 @@
#
# 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/
#
# -----------------------------------------------------------------------
#
# popen.py: a replacement for os.popen()
#
# This implementation of popen() provides a cmd + args calling sequence,
# rather than a system() type of convention. The shell facilities are not
# available, but that implies we can avoid worrying about shell hacks in
# the arguments.
#
# -----------------------------------------------------------------------
#
import os
def popen(cmd, args, mode, capture_err=1):
r, w = os.pipe()
pid = os.fork()
if pid:
# in the parent
# close the descriptor that we don't need and return the other one.
if mode == 'r':
os.close(w)
return _pipe(os.fdopen(r, 'r'), pid)
os.close(r)
return _pipe(os.fdopen(w, 'w'), pid)
# in the child
# we'll need /dev/null for the discarded I/O
null = os.open('/dev/null', os.O_RDWR)
if mode == 'r':
# hook stdout/stderr to the "write" channel
os.dup2(w, 1)
# "close" stdin; the child shouldn't use it
os.dup2(null, 0)
# what to do with errors?
if capture_err:
os.dup2(w, 2)
else:
os.dup2(null, 2)
else:
# hook stdin to the "read" channel
os.dup2(r, 0)
# "close" stdout/stderr; the child shouldn't use them
os.dup2(null, 1)
os.dup2(null, 2)
# don't need these FDs any more
os.close(null)
os.close(r)
os.close(w)
# the stdin/stdout/stderr are all set up. exec the target
os.execvp(cmd, (cmd,) + tuple(args))
# crap. shouldn't be here.
sys.exit(127)
class _pipe:
"Wrapper for a file which can wait() on a child process at close time."
def __init__(self, file, child_pid):
self.file = file
self.child_pid = child_pid
def close(self):
self.file.close()
self.file = None
return os.waitpid(self.child_pid, 0)[1] or None
def __getattr__(self, name):
return getattr(self.file, name)
def __del__(self):
if self.file:
self.close()

489
lib/py2html.py Normal file
View File

@@ -0,0 +1,489 @@
#!/usr/local/bin/python -u
""" Python Highlighter for HTML Version: 0.5
py2html.py [options] files...
options:
-h print help
- read from stdin, write to stdout
-stdout read from files, write to stdout
-files read from files, write to filename+'.html' (default)
-format:
html output HTML page (default)
rawhtml output pure HTML (without headers, titles, etc.)
-mode:
color output in color (default)
mono output b/w (for printing)
-title:Title use 'Title' as title of the generated page
-bgcolor:color use color as background-color for page
-header:file use contents of file as header
-footer:file use contents of file as footer
-URL replace all occurances of 'URL: link' with
'<A HREF="link">link</A>'; this is always enabled
in CGI mode
-v verbose
Takes the input, assuming it is Python code and formats it into
colored HTML. When called without parameters the script tries to
work in CGI mode. It looks for a field 'script=URL' and tries to
use that URL as input file. If it can't find this field, the path
info (the part of the URL following the CGI script name) is
tried. In case no host is given, the host where the CGI script
lives and HTTP are used.
* Uses Just van Rossum's PyFontify version 0.3 to tag Python scripts.
You can get it via his homepage on starship:
URL: http://starship.skyport.net/crew/just
"""
__comments__ = """
The following snippet is a small shell script I use for viewing
Python scripts per less on Unix:
#!/bin/sh
# Browse pretty printed Python code using ANSI codes for highlighting
py2html -stdout -format:ansi -mode:mono $* | less -r
History:
0.5: Added a few suggestions by Kevin Ng to make the CGI version
a little more robust.
"""
__copyright__ = """
-----------------------------------------------------------------------------
(c) Copyright by Marc-Andre Lemburg, 1998 (mailto:mal@lemburg.com)
Permission to use, copy, modify, and distribute this software and its
documentation for any purpose and without fee or royalty is hereby granted,
provided that the above copyright notice appear in all copies and that
both that copyright notice and this permission notice appear in
supporting documentation or portions thereof, including modifications,
that you make.
THE AUTHOR MARC-ANDRE LEMBURG DISCLAIMS ALL WARRANTIES WITH REGARD TO
THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS, IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL,
INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
WITH THE USE OR PERFORMANCE OF THIS SOFTWARE !
"""
__version__ = '0.5'
__cgifooter__ = ('\n<PRE># code highlighted using <A HREF='
'"http://starship.skyport.net/~lemburg/">py2html.py</A> '
'version %s</PRE>\n' % __version__)
import sys,string,re
# Adjust path so that PyFontify is found...
sys.path.append('.')
### Constants
# URL of the input form the user is redirected to in case no script=xxx
# form field is given. The URL *must* be absolute. Leave blank to
# have the script issue an error instead.
INPUT_FORM = 'http://starship.skyport.net/~lemburg/SoftwareDescriptions.html#py2html.py'
### Helpers
def fileio(file, mode='rb', data=None, close=0):
if type(file) == type(''):
f = open(file,mode)
close = 1
else:
f = file
if data:
f.write(data)
else:
data = f.read()
if close: f.close()
return data
### Converter class
class PrettyPrint:
""" generic Pretty Printer class
* supports tagging Python scripts in the following ways:
# format/mode | color mono
# --------------------------
# rawhtml | x x (HTML without headers, etc.)
# html | x x (a HTML page with HEAD&BODY:)
# ansi | x (with Ansi-escape sequences)
* interfaces:
file_filter -- takes two files: input & output (may be stdin/stdout)
filter -- takes a string and returns the highlighted version
* to create an instance use:
c = PrettyPrint(tagfct,format,mode)
where format and mode must be strings according to the
above table if you plan to use PyFontify.fontify as
tagfct
* the tagfct has to take one argument, text, and return a taglist
(format: [(id,left,right,sublist),...], where id is the
"name" given to the slice left:right in text and sublist is a
taglist for tags inside the slice or None)
"""
# misc settings
title = ''
bgcolor = '#FFFFFF'
header = ''
footer = ''
replace_URLs = 0
# formats to be used
formats = {}
def __init__(self,tagfct=None,format='html',mode='color'):
self.tag = tagfct
self.set_mode = getattr(self,'set_mode_'+format+'_'+mode)
self.filter = getattr(self,'filter_'+format)
self.set_mode()
def file_filter(self,infile,outfile):
text = fileio(infile,'r')
if type(infile) == type('') and self.title == '':
self.title = infile
fileio(outfile,'w',self.filter(text))
### set pre- and postfixes for formats & modes
#
# These methods must set self.formats to a dictionary having
# an entry for every tag returned by the tagging function.
#
# The format used is simple:
# tag:(prefix,postfix)
# where prefix and postfix are either strings or callable objects,
# that return a string (they are called with the matching tag text
# as only parameter). prefix is inserted in front of the tag, postfix
# is inserted right after the tag.
def set_mode_html_color(self):
self.formats = {
'all':('<PRE>','</PRE>'),
'comment':('<FONT COLOR=#1111CC>','</FONT>'),
'keyword':('<FONT COLOR=#3333CC><B>','</B></FONT>'),
'parameter':('<FONT COLOR=#000066>','</FONT>'),
'identifier':( lambda x,strip=string.strip:
'<A NAME="%s"><FONT COLOR=#CC0000><B>' % (strip(x)),
'</B></FONT></A>'),
'string':('<FONT COLOR=#115511>','</FONT>')
}
set_mode_rawhtml_color = set_mode_html_color
def set_mode_html_mono(self):
self.formats = {
'all':('<PRE>','</PRE>'),
'comment':('',''),
'keyword':( '<U>','</U>'),
'parameter':('',''),
'identifier':( lambda x,strip=string.strip:
'<A NAME="%s"><B>' % (strip(x)),
'</B>'),
'string':('','')
}
set_mode_rawhtml_mono = set_mode_html_mono
def set_mode_ansi_mono(self):
self.formats = {
'all':('',''),
'comment':('\033[2m','\033[m'),
'keyword':('\033[4m','\033[m'),
'parameter':('',''),
'identifier':('\033[1m','\033[m'),
'string':('','')
}
### filter for Python scripts given as string
def escape_html(self,text):
t = (('<','&lt;'),('>','&gt;'))
for x,y in t:
text = string.join(string.split(text,x),y)
return text
def filter_html(self,text):
output = self.fontify(self.escape_html(text))
if self.replace_URLs:
output = re.sub('URL:([ \t]+)([^ \n\r<]+)',
'URL:\\1<A HREF="\\2">\\2</A>',output)
html = """<HTML><HEAD><TITLE>%s</TITLE></HEAD>
<BODY BGCOLOR=%s>
<!--header-->%s
<!--script-->%s
<!--footer-->%s
</BODY>\n"""%(self.title,self.bgcolor,self.header,output,self.footer)
return html
def filter_rawhtml(self,text):
output = self.fontify(self.escape_html(text))
if self.replace_URLs:
output = re.sub('URL:([ \t]+)([^ \n\r<]+)',
'URL:\\1<A HREF="\\2">\\2</A>',output)
return self.header+output+self.footer
def filter_ansi(self,text):
output = self.fontify(text)
return self.header+output+self.footer
### fontify engine
def fontify(self,pytext):
# parse
taglist = self.tag(pytext)
# prepend special 'all' tag:
taglist[:0] = [('all',0,len(pytext),None)]
# prepare splitting
splits = []
addsplits(splits,pytext,self.formats,taglist)
# do splitting & inserting
splits.sort()
l = []
li = 0
for ri,dummy,insert in splits:
if ri > li: l.append(pytext[li:ri])
l.append(insert)
li = ri
if li < len(pytext): l.append(pytext[li:])
return string.join(l,'')
def addsplits(splits,text,formats,taglist):
# helper for fontify()
for id,left,right,sublist in taglist:
try:
pre,post = formats[id]
except KeyError:
# sys.stderr.write('Warning: no format for %s specified\n'%repr(id))
pre,post = '',''
if type(pre) != type(''):
pre = pre(text[left:right])
if type(post) != type(''):
post = post(text[left:right])
# len(splits) is a dummy used to make sorting stable
splits.append((left,len(splits),pre))
if sublist:
addsplits(splits,text,formats,sublist)
splits.append((right,len(splits),post))
def write_html_error(titel,text):
print """\
<HTML><HEADER><TITLE>%s</TITLE></HEADER>
<BODY>
<H2>%s</H2>
%s
</BODY></HTML>
""" % (titel,titel,text)
def redirect_to(url):
sys.stdout.write('Content-Type: text/html\r\n')
sys.stdout.write('Status: 302\r\n')
sys.stdout.write('Location: %s\r\n\r\n' % url)
print """
<HTML><HEAD>
<TITLE>302 Moved Temporarily</TITLE>
</HEAD><BODY>
<H1>302 Moved Temporarily</H1>
The document has moved to <A HREF="%s">%s</A>.<P>
</BODY></HTML>
""" % (url,url)
def main(cmdline):
""" main(cmdline) -- process cmdline as if it were sys.argv
"""
# parse options/files
options = []
optvalues = {}
for o in cmdline[1:]:
if o[0] == '-':
if ':' in o:
k,v = tuple(string.split(o,':'))
optvalues[k] = v
options.append(k)
else:
options.append(o)
else:
break
files = cmdline[len(options)+1:]
# create converting object
# load fontifier
if '-marcs' in options:
# use mxTextTool's tagging engine
from mxTextTools import tag
from mxTextTools.Examples.Python import python_script
tagfct = lambda text,tag=tag,pytable=python_script: \
tag(text,pytable)[1]
print "Py2HTML: using Marc's tagging engine"
else:
# load Just's
try:
import PyFontify
if PyFontify.__version__ < '0.3': raise ValueError
tagfct = PyFontify.fontify
except:
print """
Sorry, but this script needs the PyFontify.py module version 0.3;
You can download it from Just's homepage at
URL: http://starship.skyport.net/crew/just
"""
sys.exit()
if '-format' in options:
format = optvalues['-format']
else:
# use default
format = 'html'
if '-mode' in options:
mode = optvalues['-mode']
else:
# use default
mode = 'color'
c = PrettyPrint(tagfct,format,mode)
convert = c.file_filter
# start working
if '-title' in options:
c.title = optvalues['-title']
if '-bgcolor' in options:
c.bgcolor = optvalues['-bgcolor']
if '-header' in options:
try:
f = open(optvalues['-header'])
c.header = f.read()
f.close()
except IOError:
if verbose: print 'IOError: header file not found'
if '-footer' in options:
try:
f = open(optvalues['-footer'])
c.footer = f.read()
f.close()
except IOError:
if verbose: print 'IOError: footer file not found'
if '-URL' in options:
c.replace_URLs = 1
if '-' in options:
convert(sys.stdin,sys.stdout)
sys.exit()
if '-h' in options:
print __doc__
sys.exit()
if len(files) == 0:
# Turn URL processing on
c.replace_URLs = 1
# Try CGI processing...
import cgi,urllib,urlparse,os
form = cgi.FieldStorage()
if not form.has_key('script'):
# Ok, then try pathinfo
if not os.environ.has_key('PATH_INFO'):
if INPUT_FORM:
redirect_to(INPUT_FORM)
else:
sys.stdout.write('Content-Type: text/html\r\n\r\n')
write_html_error('Missing Parameter',
'Missing script=URL field in request')
sys.exit(1)
url = os.environ['PATH_INFO'][1:] # skip the leading slash
else:
url = form['script'].value
sys.stdout.write('Content-Type: text/html\r\n\r\n')
scheme, host, path, params, query, frag = urlparse.urlparse(url)
if not host:
scheme = 'http'
if os.environ.has_key('HTTP_HOST'):
host = os.environ['HTTP_HOST']
else:
host = 'localhost'
url = urlparse.urlunparse((scheme, host, path, params, query, frag))
#print url; sys.exit()
network = urllib.URLopener()
try:
tempfile,headers = network.retrieve(url)
except IOError,reason:
write_html_error('Error opening "%s"' % url,
'The given URL could not be opened. Reason: %s' %\
str(reason))
sys.exit(1)
f = open(tempfile,'rb')
c.title = url
c.footer = __cgifooter__
convert(f,sys.stdout)
f.close()
network.close()
sys.exit()
if '-stdout' in options:
filebreak = '-'*72
for f in files:
try:
if len(files) > 1:
print filebreak
print 'File:',f
print filebreak
convert(f,sys.stdout)
except IOError:
pass
else:
verbose = ('-v' in options)
if verbose:
print 'Py2HTML: working on',
for f in files:
try:
if verbose: print f,
convert(f,f+'.html')
except IOError:
if verbose: print '(IOError!)',
if verbose:
print
print 'Done.'
if __name__=='__main__':
main(sys.argv)

102
lib/query.py Normal file
View File

@@ -0,0 +1,102 @@
# -*- 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 time
import dbi
## 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 CreateCheckinQuery():
return CheckinDatabaseQuery()

348
lib/rlog.py Normal file
View File

@@ -0,0 +1,348 @@
# -*- 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, 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
## objects describing all the checkins from a given RCS file; this
## parser is fairly optimized, and therefore can be delicate if the
## rlog output varies between versions of rlog; I don't know if it does;
## to make really fast, I should wrap the C rcslib
## there's definately not much error checking here; I'll assume things
## will go smoothly, and trap errors via exception handlers above this
## function
## exception for this class
error = 'rlog error'
class RLogData:
"Container object for all data parsed from a 'rlog' output."
def __init__(self, filename):
self.filename = filename
self.symbolic_name_hash = {}
self.rlog_entry_list = []
def LookupBranch(self, rlog_entry):
index = string.rfind(rlog_entry.revision, '.')
branch_revision = rlog_entry.revision[:index]
try:
branch = self.symbolic_name_hash[branch_revision]
except KeyError:
branch = ''
return branch
class RLogEntry:
## static constants for type of log entry; this will be changed
## to strings I guess -JMP
CHANGE = 0
ADD = 1
REMOVE = 2
## Here's the init function, which isn't needed since this class
## is fully initalized by RLogParser when creating a new log entry.
## Let's keep this initializer as a description of what is held in
## the class, but keep it commented out since it only makes things
## slow.
##
## def __init__(self):
## self.revision = ''
## self.author = ''
## self.branch = ''
## self.pluscount = ''
## self.minuscount = ''
## self.description = ''
## self.time = None
## self.type = RLogEntry.CHANGE
class RLog:
"Provides a alternative file-like interface for running 'rlog'."
def __init__(self, filename, revision, date):
self.filename = self.fix_filename(filename)
self.checkout_filename = self.create_checkout_filename(self.filename)
self.revision = revision
self.date = date
arg_list = []
if self.revision:
arg_list.append('-r%s' % (self.revision))
if self.date:
arg_list.append('-d%s' % (self.date))
temp = os.path.join(cfg.general.rcs_path, "rlog")
self.cmd = '%s %s "%s"' % (temp, string.join(arg_list), self.filename)
self.rlog = os.popen(self.cmd, 'r')
def fix_filename(self, filename):
## all RCS files have the ",v" ending
if filename[-2:] != ",v":
filename = "%s,v" % (filename)
if os.path.isfile(filename):
return filename
## check the Attic for the RCS file
path, basename = os.path.split(filename)
filename = os.path.join(path, "Attic", basename)
if os.path.isfile(filename):
return filename
raise error, "rlog file not found: %s" % (filename)
def create_checkout_filename(self, filename):
## cut off the ",v"
checkout_filename = filename[:-2]
## check if the file is in the Attic
path, basename = os.path.split(checkout_filename)
if path[-6:] != '/Attic':
return checkout_filename
## remove the "Attic" part of the path
checkout_filename = os.path.join(path[:-6], basename)
return checkout_filename
def readline(self):
try:
line = self.rlog.readline()
except AttributeError:
self.error()
if line:
return line
status = self.close()
if status:
self.error()
return None
def close(self):
status = self.rlog.close()
self.rlog = None
return status
def error(self):
raise error, "unexpected rlog exit: %s" % (self.cmd)
## constants used in the output parser
_rlog_commit_sep = '----------------------------\n'
_rlog_end = '=============================================================================\n'
## regular expression used in the output parser
_re_symbolic_name = re.compile("\s+([^:]+):\s+(.+)$")
_re_revision = re.compile("^revision\s+([0-9.]+).*")
_re_data_line = re.compile(
"^date:\s+(\d+)/(\d+)/(\d+)\s+(\d+):(\d+):(\d+);\s+"\
"author:\s+([^;]+);\s+"\
"state:\s+([^;]+);\s+"\
"lines:\s+\+(\d+)\s+\-(\d+)$")
_re_data_line_add = re.compile(
"^date:\s+(\d+)/(\d+)/(\d+)\s+(\d+):(\d+):(\d+);\s+"\
"author:\s+([^;]+);\s+"\
"state:\s+([^;]+);$")
class RLogOutputParser:
def __init__(self, rlog):
self.rlog = rlog
self.rlog_data = RLogData(rlog.checkout_filename)
## run the parser
self.parse_to_symbolic_names()
self.parse_symbolic_names()
self.parse_to_description()
self.parse_rlog_entries()
def parse_to_symbolic_names(self):
while 1:
line = self.rlog.readline()
if line[:15] == 'symbolic names:':
break
def parse_symbolic_names(self):
## parse all the tags int the branch_hash, it's used later to get
## the text names of non-head branches
while 1:
line = self.rlog.readline()
match = _re_symbolic_name.match(line)
if not match:
break
(tag, revision) = match.groups()
## check if the tag represents a branch, in RCS this means
## the second-to-last number is a zero
index = string.rfind(revision, '.')
if revision[index-2:index] == '.0':
revision = revision[:index-2] + revision[index:]
self.rlog_data.symbolic_name_hash[revision] = tag
def parse_to_description(self):
while 1:
line = self.rlog.readline()
if line[:12] == 'description:':
break
## eat all lines until we reach '-----' seperator
while 1:
line = self.rlog.readline()
if line == _rlog_commit_sep:
break
def parse_rlog_entries(self):
while 1:
rlog_entry = self.parse_one_rlog_entry()
if not rlog_entry:
break
self.rlog_data.rlog_entry_list.append(rlog_entry)
def parse_one_rlog_entry(self):
## revision line/first line
line = self.rlog.readline()
if not line:
return None
## revision
match = _re_revision.match(line)
(revision,) = match.groups()
## data line
line = self.rlog.readline()
match = _re_data_line.match(line)
if not match:
match = _re_data_line_add.match(line)
if not match:
raise error, "bad rlog parser, no cookie!"
## retrieve the matched grops as a tuple in hopes
## this will be faster (ala profiler)
groups = match.groups()
year = string.atoi(groups[0])
month = string.atoi(groups[1])
day = string.atoi(groups[2])
hour = string.atoi(groups[3])
minute = string.atoi(groups[4])
second = string.atoi(groups[5])
author = groups[6]
state = groups[7]
## very strange; here's the deal: if this is a newly added file,
## then there is no plus/minus count count of lines; if there
## is, then this could be a "CHANGE" or "REMOVE", you can tell
## if the file has been removed by looking if state == 'dead'
try:
pluscount = groups[8]
minuscount = groups[9]
except IndexError:
pluscount = ''
minuscount = ''
cmit_type = RLogEntry.ADD
else:
if state == 'dead':
cmit_type = RLogEntry.REMOVE
else:
cmit_type = RLogEntry.CHANGE
## branch line: pretty much ignored if it's there
desc_line_list = []
line = self.rlog.readline()
if not line[:10] == 'branches: ':
desc_line_list.append(string.rstrip(line))
## suck up description
while 1:
line = self.rlog.readline()
## the last line printed out by rlog is '===='...
## or '------'... between entries
if line == _rlog_commit_sep or line == _rlog_end:
break
## append line to the descripton list
desc_line_list.append(string.rstrip(line))
## compute time using time routines in seconds from epoc GMT
## NOTE: mktime's arguments are in local time, and we have
## them in GMT from RCS; therefore, we have to manually
## subtract out the timezone correction
##
## XXX: Linux glib2.0.7 bug: it looks like mktime doesn't honor
## the '0' flag to force no timezone correction, so we look
## at the correction ourself and do the right thing after
## mktime mangles the date
gmt_time = \
time.mktime((year, month, day, hour, minute, second, 0, 0, -1))
if time.daylight:
gmt_time = gmt_time - time.altzone
else:
gmt_time = gmt_time - time.timezone
## now create and return the RLogEntry
rlog_entry = RLogEntry()
rlog_entry.type = cmit_type
rlog_entry.revision = revision
rlog_entry.author = author
rlog_entry.description = string.join(desc_line_list, '\n')
rlog_entry.time = gmt_time
rlog_entry.pluscount = pluscount
rlog_entry.minuscount = minuscount
return rlog_entry
## entrypoints
def GetRLogData(path, revision = '', date = ''):
rlog = RLog(path, revision, date)
rlog_parser = RLogOutputParser(rlog)
return rlog_parser.rlog_data

161
tools/cvsdbadmin Executable file
View File

@@ -0,0 +1,161 @@
#!/usr/bin/env 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/
#
# -----------------------------------------------------------------------
#
# administrative program for CVSdb; this is primarily
# used to add/rebuild CVS repositories to the database
#
# -----------------------------------------------------------------------
#
#########################################################################
#
# INSTALL-TIME CONFIGURATION
#
# These values will be set during the installation process. During
# development, they will remain None.
#
LIBRARY_DIR = None
CONF_PATHNAME = 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, cvsdbapi
def UpdateFile(db, repository, path):
try:
commit_list = cvsdbapi.GetUnrecordedCommitList(repository, path)
except cvsdbapi.error, e:
print '[ERROR] %s' % (e)
return
print '[%s[%d new commits]]' % (path, len(commit_list)),
## add the commits into the database
for commit in commit_list:
db.AddCommit(commit)
sys.stdout.write('.')
sys.stdout.flush()
print
def RecurseUpdate(db, repository, directory):
for path in os.listdir(directory):
path = os.path.join(directory, path)
if os.path.islink(path):
continue
if os.path.isdir(path):
RecurseUpdate(db, repository, path)
continue
if os.path.isfile(path):
if path[-2:] == ',v':
UpdateFile(db, repository, path)
def CommandUpdate():
## connect to the database we are updating
db = cvsdbapi.ConnectDatabase()
repository = sys.argv[2]
RecurseUpdate(db, repository, repository)
def RebuildFile(db, repository, path):
try:
commit_list = cvsdbapi.GetCommitListFromRCSFile(repository, path)
except cvsdbapi.error, e:
print '[ERROR] %s' % (e)
return
print '[%s[%d commits]]' % (path, len(commit_list)),
## add the commits into the database
for commit in commit_list:
db.AddCommit(commit)
sys.stdout.write('.')
sys.stdout.flush()
print
def RecurseRebuild(db, repository, directory):
for path in os.listdir(directory):
path = os.path.join(directory, path)
if os.path.islink(path):
continue
if os.path.isdir(path):
RecurseRebuild(db, repository, path)
continue
if os.path.isfile(path):
if path[-2:] == ',v':
RebuildFile(db, repository, path)
def CommandRebuild():
## connect to the database we are updating
db = cvsdbapi.ConnectDatabase()
repository = sys.argv[2]
RecurseRebuild(db, repository, repository)
def usage():
print 'Usage: %s <command> [arguments]' % (sys.argv[0])
print 'Preforms administrative functions for the CVSdb database'
print 'Commands:'
print ' rebuild <repository> rebuilds the CVSdb database'
print ' for all files in the repository'
print ' update <repository> updates the CVSdb database for'
print ' all unrecorded commits'
print
sys.exit(1)
## main
if __name__ == '__main__':
## check that a command was given
if len(sys.argv) < 2:
usage()
## set the handler function for the command
command = sys.argv[1]
if string.lower(command) == 'rebuild':
commandFunction = CommandRebuild
elif string.lower(command) == 'update':
commandFunction = CommandUpdate
else:
print 'ERROR: unknown command %s' % (command)
usage()
## run command
try:
commandFunction()
except KeyboardInterrupt:
print
print '** break **'
sys.exit(0)

187
tools/loginfo-handler Executable file
View File

@@ -0,0 +1,187 @@
#!/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/
#
# -----------------------------------------------------------------------
#
# updates SQL database with new commit records
#
# -----------------------------------------------------------------------
#
#########################################################################
#
# INSTALL-TIME CONFIGURATION
#
# These values will be set during the installation process. During
# development, they will remain None.
#
LIBRARY_DIR = None
CONF_PATHNAME = 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, getopt, re, cvsdbapi
DEBUG_FLAG = 0
## pre-compiled regular expressions
_re_fileversion = re.compile("([^,]+)\,([^,]+)\,([^,]+)")
## output functions
def debug(text):
if DEBUG_FLAG:
print 'DEBUG(loginfo): %s' % (text)
def warning(text):
print 'WARNING(loginfo): %s' % (text)
def error(text):
print 'ERROR(loginfo): %s' % (text)
sys.exit(1)
class FileData:
CHANGED = 1
ADDED = 2
REMOVED = 3
def __init__(self, file, directory, old_version, new_version):
self.file = file
self.directory = directory
self.old_version = old_version
self.new_version = new_version
## set the state of this file from the
## old_version and new_version information
if self.old_version == 'NONE':
self.type = self.ADDED
elif self.new_version == 'NONE':
self.type = self.REMOVED
else:
self.type = self.CHANGED
def CommitFromFileData(repository, file_data):
## consturct the full path for the RCS file
filename = os.path.join(repository, file_data.directory, file_data.file)
## get the 'rlog' output for just this revision, and then convert
## to a commit object
rlog_data = cvsdbapi.GetRLogData(filename, file_data.new_version)
commit_list = cvsdbapi.RLogDataToCommitList(repository, rlog_data)
commit = commit_list[0]
## set the type of commit from the file_data setting
if file_data.type == file_data.CHANGED:
commit.SetTypeChange()
elif file_data.type == file_data.ADDED:
commit.SetTypeAdd()
elif file_data.type == file_data.REMOVED:
commit.SetTypeRemove()
return commit
def GetUnrecordedCommitList(repository, file_data):
filename = os.path.join(repository, file_data.directory, file_data.file)
return cvsdbapi.GetUnrecordedCommitList(repository, filename)
def ProcessLoginfo(repository, stdin_list):
## the first line in stdin is a space-separated list; the first
## item in the list is the directory path being updated this run;
## the rest of the items are the files being updated
list = string.split(stdin_list[0])
## clean up the directory the following way: we don't want it
## to begin with a path seperator, and we don't want it to end
## with a path seperator
directory = list[0]
while directory[0] == os.sep:
directory = directory[1:]
while directory[-1] == os.sep:
directory = directory[:-1]
## NOTE: SPECIAL HANDLING FOR NEW DIRECTORIES
## new directories have the first line form
## path/of/dir - New directory
if len(list) == 4:
if list[1] == '-' and list[2] == 'New' and list[3] == 'directory':
debug('new directory')
return
## each file in the file list _should_ be of the form:
## file-name,<old-ver>,<new-ver>
## a new file has the keyword 'NONE' for old-ver
file_data_list = []
for item in list[1:]:
temp = _re_fileversion.match(item)
if not temp:
debug('failed match %s' % (item))
continue
filename = temp.group(1)
old_version = temp.group(2)
new_version = temp.group(3)
file_data = FileData(filename, directory, old_version, new_version)
file_data_list.append(file_data)
## convert FileData objects into Commit objects so we can insert them
## into the database
commit_list = []
for file_data in file_data_list:
## XXX: this is nasty: in the case of a removed file, we are not
## given enough information to find it in the rlog output!
## So instead, we rlog everything in the removed file, and
## add any commits not already in the database
if file_data.type == file_data.REMOVED:
temp = GetUnrecordedCommitList(repository, file_data)
commit_list = commit_list + temp
else:
commit_list.append(CommitFromFileData(repository, file_data))
## add to the database
db = cvsdbapi.ConnectDatabase()
db.AddCommitList(commit_list)
## MAIN
if __name__ == '__main__':
## get the repository from the environment
try:
repository = os.environ['CVSROOT']
except KeyError:
error('CVSROOT not in environment')
## clean up the repository string: remove any trailing path seperater
while repository[-1] == os.sep:
repository = repository[:-1]
## read all the lines from stdin
stdin_list = []
for line in sys.stdin.readlines():
stdin_list.append(string.rstrip(line))
ProcessLoginfo(repository, stdin_list)
sys.exit(0)

146
tools/make-database Executable file
View File

@@ -0,0 +1,146 @@
#!/usr/bin/env 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/
#
# -----------------------------------------------------------------------
#
# administrative program for CVSdb; creates a clean database in
# MySQL 3.22 or later
#
# -----------------------------------------------------------------------
#
import os, sys, string
INTRO_TEXT = """\
This script creates the database and tables in MySQL used by the ViewCVS
checkin database. You will be prompted for: database user, database user
password, and database name. This script will use mysql to create the
database for you. You will then need to set the appropriate parameters
in your viewcvs.conf file under the [cvsdb] section.
"""
DATABASE_SCRIPT="""\
DROP DATABASE IF EXISTS <dbname>;
CREATE DATABASE <dbname>;
USE <dbname>;
DROP TABLE IF EXISTS branches;
CREATE TABLE branches (
id mediumint(9) DEFAULT '0' NOT NULL auto_increment,
branch varchar(64) binary DEFAULT '' NOT NULL,
PRIMARY KEY (id),
UNIQUE branch (branch)
);
DROP TABLE IF EXISTS checkins;
CREATE TABLE checkins (
type enum('Change','Add','Remove'),
ci_when datetime DEFAULT '0000-00-00 00:00:00' NOT NULL,
whoid mediumint(9) DEFAULT '0' NOT NULL,
repositoryid mediumint(9) DEFAULT '0' NOT NULL,
dirid mediumint(9) DEFAULT '0' NOT NULL,
fileid mediumint(9) DEFAULT '0' NOT NULL,
revision varchar(32) binary DEFAULT '' NOT NULL,
stickytag varchar(255) binary DEFAULT '' NOT NULL,
branchid mediumint(9) DEFAULT '0' NOT NULL,
addedlines int(11) DEFAULT '0' NOT NULL,
removedlines int(11) DEFAULT '0' NOT NULL,
descid mediumint(9),
UNIQUE repositoryid (repositoryid,dirid,fileid,revision),
KEY ci_when (ci_when),
KEY whoid (whoid),
KEY repositoryid_2 (repositoryid),
KEY dirid (dirid),
KEY fileid (fileid),
KEY branchid (branchid)
);
DROP TABLE IF EXISTS descs;
CREATE TABLE descs (
id mediumint(9) DEFAULT '0' NOT NULL auto_increment,
description text,
hash bigint(20) DEFAULT '0' NOT NULL,
PRIMARY KEY (id),
KEY hash (hash)
);
DROP TABLE IF EXISTS dirs;
CREATE TABLE dirs (
id mediumint(9) DEFAULT '0' NOT NULL auto_increment,
dir varchar(128) binary DEFAULT '' NOT NULL,
PRIMARY KEY (id),
UNIQUE dir (dir)
);
DROP TABLE IF EXISTS files;
CREATE TABLE files (
id mediumint(9) DEFAULT '0' NOT NULL auto_increment,
file varchar(128) binary DEFAULT '' NOT NULL,
PRIMARY KEY (id),
UNIQUE file (file)
);
DROP TABLE IF EXISTS people;
CREATE TABLE people (
id mediumint(9) DEFAULT '0' NOT NULL auto_increment,
who varchar(32) binary DEFAULT '' NOT NULL,
PRIMARY KEY (id),
UNIQUE who (who)
);
DROP TABLE IF EXISTS repositories;
CREATE TABLE repositories (
id mediumint(9) DEFAULT '0' NOT NULL auto_increment,
repository varchar(64) binary DEFAULT '' NOT NULL,
PRIMARY KEY (id),
UNIQUE repository (repository)
);
DROP TABLE IF EXISTS tags;
CREATE TABLE tags (
repositoryid mediumint(9) DEFAULT '0' NOT NULL,
branchid mediumint(9) DEFAULT '0' NOT NULL,
dirid mediumint(9) DEFAULT '0' NOT NULL,
fileid mediumint(9) DEFAULT '0' NOT NULL,
revision varchar(32) binary DEFAULT '' NOT NULL,
UNIQUE repositoryid (repositoryid,dirid,fileid,branchid,revision),
KEY repositoryid_2 (repositoryid),
KEY dirid (dirid),
KEY fileid (fileid),
KEY branchid (branchid)
);
"""
if __name__ == "__main__":
print INTRO_TEXT
user = raw_input("MySQL User: ")
passwd = raw_input("MySQL Password: ")
dbase = raw_input("ViewCVS Database Name [default: ViewCVS]: ")
if not dbase:
dbase = "ViewCVS"
cmd = "{ mysql --user=%s --password=%s ; } 2>&1" % (user, passwd)
dscript = string.replace(DATABASE_SCRIPT, "<dbname>", dbase)
mysql = os.popen(cmd, "w")
mysql.write(dscript)
status = mysql.close()
if status:
print "[ERROR] the database did not create sucessfully."
sys.exit(1)
print "Database created successfully."
sys.exit(0)

39
tools/make-release Executable file
View File

@@ -0,0 +1,39 @@
#!/bin/sh
#
# 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/
#
# -----------------------------------------------------------------------
#
# make-release: internal tool for creating ViewCVS releases
#
# -----------------------------------------------------------------------
#
if [ "x$1" = "x" ]; then
echo "USAGE: $0 target-directory"
exit 1
fi
# grab a copy of the CVS repository (export requires a tag, so just use 'co')
rm -r $1
echo 'Checking out into:' $1
cvs -d /home/cvsroot co -d $1 -kv -P -R viewcvs
echo 'Cleaning up...'
# clean out the CVS crap
find $1 -name CVS | xargs rm -r
# various shifting, cleanup
mv $1/website/license-1.html $1/LICENSE.html
rm -r $1/website
rm $1/tools/make-release
echo 'Done.'

176
viewcvs-install Executable file
View File

@@ -0,0 +1,176 @@
#!/usr/bin/env 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 script for viewcvs -- temporary?
#
# ### this will eventually be replaced by autoconf plus tools. an
# ### interactive front-end to ./configure may be provided.
#
# -----------------------------------------------------------------------
#
import os
import sys
import string
import re
import traceback
import py_compile
# get access to our library modules
sys.path.insert(0, os.path.join(os.path.dirname(sys.argv[0]), 'lib'))
import compat
## installer text
INFO_TEXT = """\
This is the ViewCVS installer. 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
is okay.
"""
## installer defaults
ROOT_DIR = "/usr/local/viewcvs"
## list of files for installation
## tuple (source path, destination path, install mode, true/false flag for
## search-and-replace, flag for prompt before replace, compile_it)
##
FILE_INFO_LIST = [
("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/viewcvs.conf.dist", "viewcvs.conf", 0644, 0, 1, 0),
("lib/PyFontify.py", "lib/PyFontify.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/config.py", "lib/config.py", 0644, 0, 0, 1),
("lib/cvsdbapi.py", "lib/cvsdbapi.py", 0644, 1, 0, 1),
("lib/database.py", "lib/database.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/rlog.py", "lib/rlog.py", 0644, 1, 0, 1),
("tools/loginfo-handler", "loginfo-handler", 0755, 1, 0, 0),
("tools/cvsdbadmin", "cvsdbadmin", 0755, 1, 0, 0),
("tools/make-database", "make-database", 0755, 1, 0, 0),
("html-templates/queryformtemplate.html",
"html-templates/queryformtemplate.html", 0644, 0, 1, 0),
("html-templates/querytemplate.html",
"html-templates/querytemplate.html", 0644, 0, 1, 0),
]
def Error(text, etype=None, evalue=None):
print
print "[ERROR] %s" % text
if etype:
print '[ERROR] ',
traceback.print_exception(etype, evalue, None, file=sys.stdout)
sys.exit(1)
def MkDir(path):
try:
compat.makedirs(path)
except OSError, e:
if e.errno == 17:
# EEXIST: file exists
return
if e.errno == 13:
# EACCES: permission denied
Error("You do not have permission to create directory %s" % path)
Error("Unknown error creating directory %s" % path, OSError, e)
def SetOnePath(contents, var, value):
pattern = re.compile('^' + var + r'\s*=\s*.*$', re.MULTILINE)
repl = '%s = "%s"' % (var, os.path.join(ROOT_DIR, value))
return re.sub(pattern, repl, contents)
def SetPythonPaths(contents):
if contents[:2] == '#!':
shbang = '#!' + sys.executable
contents = re.sub('^#![^\n]*', shbang, contents)
contents = SetOnePath(contents, 'LIBRARY_DIR', 'lib')
contents = SetOnePath(contents, 'CONF_PATHNAME', 'viewcvs.conf')
contents = SetOnePath(contents, 'HTML_TEMPLATE_DIR', 'html-templates')
return contents
def InstallFile(src_path, dest_path, mode, set_python_paths, prompt_replace,
compile_it):
dest_path = os.path.join(ROOT_DIR, dest_path)
if prompt_replace and os.path.exists(dest_path):
temp = raw_input("File %s exists, overwright? [y/N]: " % (dest_path))
if not temp or string.lower(temp[0]) != "y":
return
try:
contents = open(src_path, "r").read()
except IOError, e:
Error(str(e))
if set_python_paths:
contents = SetPythonPaths(contents)
## write the file to the destination location
path, basename = os.path.split(dest_path)
MkDir(path)
try:
open(dest_path, "w").write(contents)
except IOError, e:
if e.errno == 13:
# EACCES: permission denied
Error("You do not have permission to write file %s" % dest_path)
Error("Unknown error writing file %s" % dest_path, IOError, e)
os.chmod(dest_path, mode)
if compile_it:
py_compile.compile(dest_path)
## MAIN
if __name__ == "__main__":
print INFO_TEXT
## get the install path
temp = raw_input("Installation Path [%s]: " % ROOT_DIR)
temp = string.strip(temp)
if len(temp):
ROOT_DIR = temp
## install the files
print
print "Installing ViewCVS to:", ROOT_DIR
for args in FILE_INFO_LIST:
print " ", args[0]
apply(InstallFile, args)
print
print "Installation Complete"

View File

@@ -19,41 +19,40 @@
<p>
ViewCVS can browse directories, change logs, and specific
revisions of files. It can display diffs between versions and
show selections of files based on tags or branches.
show selections of files based on tags or branches. In addition,
ViewCVS has "annotation" or "blame" support, and the beginnings
of Bonsai-like query facilities.
</p>
<p>
ViewCVS is currently at version 0.3. It is almost a straight
port of the cvsweb script, but has had numerous cleanups and other
modifications, based on some of Python's strengths. There is
still some "badness" in there, but I've been working on flushing
that out, along with a few new features. The
functionality of ViewCVS is equal to that of cvsweb, minus the
"annotation" support. Annotation requires read/write access to
the CVS repository (at this time), and I believe that is a
Bad Thing to do. One of my tasks will be eliminating the
read/write requirement.
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.3.tar.gz">Version 0.3 of ViewCVS</a>
<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/gjspy/viewcvs/">http://www.lyra.org/cgi-bin/viewcvs.cgi/gjspy/viewcvs/</a>
<a href="/cgi-bin/viewcvs.cgi/viewcvs/">http://www.lyra.org/cgi-bin/viewcvs.cgi/viewcvs/</a>
</blockquote>
<p>
ViewCVS <font color=red>requires <strong>Python
1.5.2</strong></font>.
ViewCVS requires <strong>Python 1.5</strong> (which has been out
for a couple years and is readily available for your favorite
operating system).
</p>
<p>
[ <a href="../">up to Python software</a> ]
[ <a href="../../">up to Greg's page</a> ]
[ <a href="/greg/">Greg's page</a> ]
[ <a href="/greg/python/">other Python software</a> ]
</p>
<hr width="75&#37;">
@@ -65,56 +64,51 @@
<a href="http://mailman.lyra.org/mailman/listinfo/viewcvs">ViewCVS
mailing list</a>.
</p>
<hr width="75&#37;">
<h2>FAQ</h2>
<p>Kinda short :-)</p>
<dl>
<!-- one of (dd dt) -->
<dt><strong>I got an <code>AttributeError: urlencode</code></strong></dt>
<dd>
If you see the following traceback in your browser:
<blockquote>
<pre>
Traceback (innermost last):
File "/home/httpd/cgi-bin/viewcvs.cgi", line 2205, in ?
main()
File "/home/httpd/cgi-bin/viewcvs.cgi", line 2123, in main
query_string = sticky_query(query_dict)
File "/home/httpd/cgi-bin/viewcvs.cgi", line 444, in sticky_query
bare_query = urllib.urlencode(sticky_dict)
AttributeError: urlencode
</pre>
</blockquote>
This is indicative of using an earlier version of
Python. <code>urllib.urlencode()</code> was introduced in
Python 1.5.2.
</dd>
</dl>
<p>
A <a href="http://mailman.lyra.org/mailman/listinfo/viewcvs-dev">mailing
list for ViewCVS developers</a> is also available.
</p>
<hr width="75&#37;">
<h2>Additional features over cvsweb</h2>
<ul>
<li>Colorization for Python files</li>
<li>Better reporting for unreadable files</li>
<li>More robust when given varying <code>rcsdiff</code> or
<code>rlog</code> outputs</li>
<li>Hard breaks in human-readable diffs</li>
<li>Colorization for many file types via <code>enscript</code>.</li>
<li>Better reporting for unreadable files.</li>
<li>
More robust when given varying <code>rcsdiff</code> or
<code>rlog</code> outputs.
</li>
<li>Hard breaks in human-readable diffs.</li>
<li>
The configuration file is optional (you can change the values
right in the CGI script and avoid the config file, if you so
choose). The config file syntax is also cleaner, since it is
human-manageable rather than source code.
</li>
<li>
Directories with a large number of files can be viewed.
</li>
<li>Bonsai-like query features.</li>
<li>
Annotation/blame 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>
</ul>
<p>
Future features, coming soon:
</p>
<ul>
<li>
Optional, separate configuration file (similar to
cvsweb). This will make upgrading easier.
</li>
<li>UI streamlining/simplification</li>
<li>Integration with CVS checkin auto-mail scripts</li>
<li>Tighter integration the query features</li>
<li>
<i>Suggestions? Send mail to the
<a href="mailto:viewcvs@lyra.org">viewcvs@lyra.org</a>
@@ -127,20 +121,35 @@ AttributeError: urlencode
Longer term:
</p>
<ul>
<li>Annotation ("blame") support (similar to cvsweb)</li>
<li>Integration with an indexer such as LXR</li>
<li>Additional colorizers</li>
</ul>
<hr width="75&#37;">
<h2>Colorization of Python files</h2>
<h2>Colorization of files</h2>
<p>
ViewCVS can make use of the <code>enscript</code> program to
colorize files in the CVS repository. If <code>enscript</code>
is present on your system, then set the
<code>use_enscript</code> option in the
<code>viewcvs.conf</code> configuration file. If necessary,
update the <code>enscript_path</code> option to point to your
installation directory. ... That's it! Now, as you view files
through ViewCVS, they will be colored.
</p>
<h3>Colorization of Python files</h3>
<p>
ViewCVS currently comes with a builtin colorizer for Python
source files. This may go away, given the new
<code>enscript</code> support...
</p>
<p>
Christophe Pelte suggested this feature: colorize Python source
files using
<a href="http://starship.python.net/crew/lemburg/SoftwareDescriptions.html#py2html.py">py2html</a>
<a href="http://starship.python.net/crew/lemburg/SoftwareDescriptions.html#py2html.py"><i>py2html</i></a>
(by Marc-Andrew Lemburg, based on
<a href="http://starship.python.net/crew/just/code/PyFontify.py">PyFontify</a>
<a href="http://starship.python.net/crew/just/code/PyFontify.py"><i>PyFontify</i></a>
by Just van Rossum). I've added this feature to ViewCVS 0.3,
along with a generalized plugin mechanism for custom coloring other
types of files. See the instructions within the viewcvs.cgi for
@@ -152,7 +161,7 @@ AttributeError: urlencode
<address><a href="mailto:gstein@lyra.org">Greg Stein</a></address>
<!-- Created: Fri Dec 3 02:51:37 PST 1999 -->
<!-- hhmts start -->
Last modified: Fri Feb 11 07:51:53 PST 2000
Last modified: Fri May 12 03:53:51 PDT 2000
<!-- hhmts end -->
</body>
</html>

75
website/license-1.html Normal file
View File

@@ -0,0 +1,75 @@
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
<html>
<head>
<title>ViewCVS License Agreement (v1)</title>
</head>
<body>
<h1>ViewCVS License Agreement (v1)</h1>
<p>
The following text constitutes the license agreement for the
<a href="./">ViewCVS</a> software. It
is an agreement between
<a href="who.html">The ViewCVS
Group</a> and the users of ViewCVS.
</p>
<hr>
<p>
<b>
Copyright &copy; 1999-2000 The ViewCVS Group. All rights reserved.
</b>
</p>
<p>
By using ViewCVS, you agree to the terms and conditions set
forth below:
</p>
<p>
Redistribution and use in source and binary forms, with or
without modification, are permitted provided that the following
conditions are met:
</p>
<ol>
<li>
<p>
Redistributions of source code must retain the above
copyright notice, this list of conditions and the following
disclaimer.
</p>
</li>
<li>
<p>
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.
</p>
</li>
</ol>
<p>
THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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.
</p>
<hr>
<address><a href="mailto:gstein@lyra.org">Greg Stein</a></address>
<!-- Created: Mon May 8 19:01:27 PDT 2000 -->
<!-- hhmts start -->
Last modified: Mon May 8 19:28:37 PDT 2000
<!-- hhmts end -->
</body>
</html>

32
website/who.html Normal file
View File

@@ -0,0 +1,32 @@
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
<html>
<head>
<title>The ViewCVS Group</title>
</head>
<body background="/images/chalk.jpg">
<h1>The ViewCVS Group</h1>
<p>
The ViewCVS Group is an informal group of people working on and
developing the ViewCVS package. The current set of members are:
</p>
<ul>
<li><a href="/greg/"><b>Greg Stein</b></a></li>
<li>Jay Painter</li>
</ul>
<p>
Please note that the <a href="./">ViewCVS</a> package is offered
under a BSD-type license, which is detailed on the
<a href="license-1.html">ViewCVS License</a> page.
</p>
<hr>
<address><a href="mailto:gstein@lyra.org">Greg Stein</a></address>
<!-- Created: Mon May 8 19:08:58 PDT 2000 -->
<!-- hhmts start -->
Last modified: Mon May 8 19:14:45 PDT 2000
<!-- hhmts end -->
</body>
</html>