Changesets can be listed by changeset number.
The Git repository is here.
- Revision:
- 105
- Log:
Updated configuration and CGI-based settings to operate within
ROOL prototype site.
- Author:
- adh
- Date:
- Mon Oct 23 18:03:55 +0100 2006
- Size:
- 29091 Bytes
- Properties:
- Property svn:executable is set
1 | #!/bin/env python |
2 | |
3 | # CVSHistory -- A cvsweb/viewcvs-integrating CVS history |
4 | # browsing script/web frontend/thingie. |
5 | |
6 | # Jamie Turner <jamwt@jamwt.com> |
7 | |
8 | ##### USER EDITABLE SECTION ##### |
9 | CONFIGFILE = "/home/adh/python/cvshistory/cvshistory.conf" |
10 | # END OF USER EDITABLE SECTION! |
11 | |
12 | |
13 | ##### LICENSE ##### |
14 | |
15 | # Modified BSD |
16 | # ------------ |
17 | # |
18 | # All of the documentation and software included in this software |
19 | # is copyrighted by Jamie Turner <jamwt@jamwt.com> |
20 | # |
21 | # Copyright 2004 Jamie Turner. All rights reserved. |
22 | # |
23 | # Redistribution and use in source and binary forms, with or without |
24 | # modification, are permitted provided that the following conditions |
25 | # are met: |
26 | # 1. Redistributions of source code must retain the above copyright |
27 | # notice, this list of conditions and the following disclaimer. |
28 | # 2. Redistributions in binary form must reproduce the above copyright |
29 | # notice, this list of conditions and the following disclaimer in the |
30 | # documentation and/or other materials provided with the distribution. |
31 | # 3. The name of the author may not be used to endorse or promote products |
32 | # derived from this software without specific prior written permission. |
33 | # |
34 | # THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR |
35 | # IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES |
36 | # OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. |
37 | # IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
38 | # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED |
39 | # TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
40 | # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF |
41 | # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING |
42 | # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, |
43 | # EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
44 | |
45 | # ViewCVS Look & feel modeled after the ViewCVS project |
46 | # viewcvs.sourceforge.net |
47 | |
48 | # CVSweb Look & feel modeled after the FreeBSD CVSweb project |
49 | # www.freebsd.org/projects/cvsweb.html |
50 | |
51 | ##### CODE ##### |
52 | |
53 | # Integration mode constants. |
54 | INT_NONE = 0 # don't edit |
55 | INT_VIEWCVS = 1 # don't edit |
56 | INT_CVSWEB = 2 # don't edit |
57 | |
58 | # Predefined time formats. |
59 | USTIME = "%m-%d-%Y %H:%M" |
60 | WORLDTIME = "%d-%m-%Y %H:%M" |
61 | ISOTIME = "%Y-%m-%d %H:%M" |
62 | |
63 | # Performance mode constants. |
64 | MODE_FAST = 1 |
65 | MODE_SLOW = 2 |
66 | |
67 | # Load configuration. |
68 | |
69 | execfile(CONFIGFILE) |
70 | |
71 | # let's handle the options above |
72 | # first, formatting |
73 | |
74 | # we default to ViewCVS layout, with no integration. it's prettier! |
75 | if INTEGRATION == INT_NONE or INTEGRATION == INT_VIEWCVS: |
76 | _SORTEDCOL = "88ff88" |
77 | _REGCOL = "cccccc" |
78 | _ROWS = ["ffffff","ccccee"] |
79 | _TABLETOP = '<table width="100%%" border=0 cellspacing=1 cellpadding=2>' |
80 | |
81 | else: |
82 | _SORTEDCOL = "ffcc66" |
83 | _REGCOL = "ffffcc" |
84 | _ROWS = ["ffffff","ffffff"] |
85 | _TABLETOP = '<table style="border-width: 0; background-color: #cccccc" width="100%" cellspacing="1" cellpadding="2">' |
86 | |
87 | _SCRIPT = SCRIPTPATH |
88 | |
89 | _LOGO = " " |
90 | _ICON = '<img src="/perl/cvsweb/icons/icon_folder.gif" alt="[DIR]" border="0" width="15" height="13">' |
91 | |
92 | if INTEGRATION == INT_VIEWCVS: |
93 | |
94 | _SCRIPT = SCRIPTPATH |
95 | |
96 | _LOGO = '<img src="/icons/apache_pb.gif" alt="(logo)" border=0 width=259 height=32>' |
97 | _ICON = '<img src="/icons/small/dir.gif" alt="(dir)" border=0 width=16 height=16>' |
98 | |
99 | elif INTEGRATION == INT_NONE: |
100 | _LOGO = '' |
101 | _ICON = '' |
102 | |
103 | |
104 | |
105 | |
106 | |
107 | # dependencies |
108 | import time |
109 | import os |
110 | import urllib |
111 | import urlparse |
112 | import cgi |
113 | import re |
114 | import xml.sax.saxutils |
115 | |
116 | |
117 | _VERSION = "2.2" |
118 | # basic fields: |
119 | # Operation Type(Operation Date)|username|<ignore>|directory|revision|fn |
120 | |
121 | _OPTYPES = { |
122 | "O" : "Checkout" , |
123 | "F" : "Release" , |
124 | "T" : "RTag", |
125 | "W" : "Delete on update", |
126 | "U" : "Update", |
127 | "P" : "Update by patch", |
128 | "G" : "Merge on update", |
129 | "C" : "Conflict on update", |
130 | "M" : "Commit", |
131 | "A" : "Addition", |
132 | "R" : "Removal", |
133 | "E" : "Export", |
134 | } |
135 | |
136 | def op2text(op): |
137 | return _OPTYPES.get(op, "Unknown (%s)" % op) |
138 | |
139 | _SELF_URL = os.environ['SCRIPT_NAME'] |
140 | |
141 | class ReverseReadline: |
142 | def __init__(self,fd,BUFSIZ=262144): |
143 | self.fd = fd |
144 | self.ind = 0 |
145 | self.BUFSIZ = BUFSIZ |
146 | self.fd.seek(0,2) |
147 | self.siz = self.fd.tell() |
148 | self.buff = "" |
149 | self.ind = 0 |
150 | self.scount = -1 |
151 | self.eof = 0 |
152 | self.first = 1 |
153 | |
154 | def readline(self): |
155 | # find the start index |
156 | nind = self.buff.rfind("\n",0,self.ind-1) |
157 | if nind != -1 and self.ind != 0: |
158 | ret = self.buff[nind+1:self.ind+1] |
159 | self.ind = nind |
160 | return ret |
161 | # oops.. outta newlines |
162 | |
163 | # keep the existing data |
164 | old = self.buff[:self.ind+1] |
165 | |
166 | # can't seek backward anymore? |
167 | if self.eof: |
168 | self.buff = "" |
169 | return old |
170 | |
171 | # seek to the right location |
172 | sksiz = self.BUFSIZ * self.scount |
173 | if sksiz * -1 > self.siz: |
174 | sksiz = self.siz * -1 |
175 | self.eof = 1 |
176 | rd = self.siz - ( ( (self.scount * -1) - 1) * self.BUFSIZ) |
177 | else: |
178 | rd = self.BUFSIZ |
179 | |
180 | self.fd.seek(sksiz,2) |
181 | |
182 | self.buff = self.fd.read(rd) |
183 | |
184 | if self.first: |
185 | self.ind = rd - 1 |
186 | self.first = 0 |
187 | else: |
188 | self.ind = rd |
189 | |
190 | self.scount -= 1 |
191 | |
192 | rl = self.readline() |
193 | if rl[-1] == "\n": |
194 | return old + rl |
195 | else: |
196 | return rl + old |
197 | |
198 | # fast mode, for big servers. No sorting! |
199 | def get_history_fast(conds,opts): |
200 | reader = ReverseReadline(open(HISTORY[opts["cvsroot"]])) |
201 | |
202 | # datelimit |
203 | lasttime = time.time() |
204 | if LIMITDAYS: |
205 | ltm = lasttime |
206 | ltm -= (86400 * LIMITDAYS) |
207 | else: |
208 | ltm = 0 |
209 | |
210 | offset = 0 |
211 | if opts.has_key("offset"): |
212 | offset = int(opts["offset"]) |
213 | |
214 | data = [] |
215 | skip = 0 |
216 | |
217 | line = reader.readline() |
218 | |
219 | while len(data) < PERPAGE and line and lasttime > ltm: |
220 | cur = line.strip().split("|",5) # maxsplit @ 5 |
221 | |
222 | # we play a little game here. we don't care about the data |
223 | # at index two, so we'll put the time there |
224 | |
225 | lasttime = get_time(cur[0][1:]) |
226 | cur[2] = lasttime |
227 | cur[0] = cur[0][0] # the op code |
228 | |
229 | failed = 0 |
230 | for cond in conds: |
231 | if not cond.test(cur): |
232 | failed = 1 |
233 | break |
234 | |
235 | if not failed and skip < offset: |
236 | skip += 1 |
237 | elif not failed and lasttime > ltm: |
238 | data.append(cur) |
239 | |
240 | line = reader.readline() |
241 | |
242 | return data |
243 | |
244 | |
245 | # this is a bit uneasy for especially large logs, but.. |
246 | # we'll worry about that later. |
247 | def get_history(opts): |
248 | |
249 | # datelimit |
250 | lasttime = time.time() |
251 | if LIMITDAYS: |
252 | ltm = lasttime |
253 | ltm -= (86400 * LIMITDAYS) |
254 | else: |
255 | ltm = 0 |
256 | |
257 | fd = open(HISTORY[opts["cvsroot"]],"r") |
258 | |
259 | data = [] |
260 | |
261 | line = fd.readline() |
262 | while line: |
263 | cur = line.strip().split("|",5) |
264 | |
265 | # we play a little game here. we don't care about the data |
266 | # at index two, so we'll put the time there |
267 | |
268 | lasttime = get_time(cur[0][1:]) |
269 | cur[2] = lasttime |
270 | cur[0] = cur[0][0] # the op code |
271 | |
272 | if lasttime > ltm: |
273 | data.append(cur) |
274 | |
275 | line = fd.readline() |
276 | |
277 | fd.close() |
278 | |
279 | # data is: |
280 | # <string>op:<string>user:<int>time:dir:revision:file/module |
281 | # * number of entries |
282 | data.reverse() |
283 | |
284 | return data |
285 | |
286 | _OTYPE_CHECKBOX = 1 |
287 | _OTYPE_TEXT = 2 |
288 | _OTYPE_SELECT = 3 |
289 | |
290 | def opt_pre(opts,type,opt,opt2=None): |
291 | if type == _OTYPE_CHECKBOX: |
292 | if opts.has_key(opt) and opts[opt] == "on": |
293 | return "CHECKED" |
294 | elif type == _OTYPE_TEXT: |
295 | if opts.has_key(opt) and opts[opt].strip() != "": |
296 | return "value=\"%s\"" % opts[opt] |
297 | elif type == _OTYPE_SELECT: |
298 | if opts.has_key(opt) and opts[opt] == opt2: |
299 | return "SELECTED" |
300 | return "" |
301 | |
302 | def get_time(hextime): |
303 | tm = 0 |
304 | ht_len = len(hextime) |
305 | |
306 | for x in xrange(0,ht_len): |
307 | d = ord(hextime[x]) - 48 |
308 | if d > 9: |
309 | d -= 39 |
310 | tm += d * (16 ** (ht_len - x - 1 ) ) |
311 | |
312 | return tm |
313 | |
314 | _COLUMNS = ["Date","User","Operation","Directory","File","Revision"] |
315 | |
316 | |
317 | def get_script_absolute_url (): |
318 | |
319 | port = "" |
320 | if os.environ.get('HTTPS') or os.environ['SERVER_PROTOCOL'][:5] == 'HTTPS': |
321 | url = 'https://' |
322 | if os.environ['SERVER_PORT'] != '443': |
323 | port = os.environ['SERVER_PORT'] |
324 | else: |
325 | url = 'http://' |
326 | if os.environ['SERVER_PORT'] != '8080': |
327 | port = os.environ['SERVER_PORT'] |
328 | |
329 | url += os.environ['SERVER_NAME'] |
330 | |
331 | if port: url += ':' + port |
332 | |
333 | url += _SELF_URL |
334 | |
335 | return url |
336 | |
337 | def pretty_print_rss(data,options): |
338 | |
339 | cvsHistoryURL = get_script_absolute_url () |
340 | |
341 | print 'Content-Type: text/xml; charset=iso-8859-1' |
342 | |
343 | print \ |
344 | '''<?xml version="1.0" encoding="ISO-8859-1"?> |
345 | <rss version="2.0"> |
346 | <channel> |
347 | <title>CVSHistory</title> |
348 | <link>%s</link> |
349 | <description>CVS Changelog History</description> |
350 | <docs>http://blogs.law.harvard.edu/tech/rss</docs> |
351 | <generator>CVSHistory</generator> |
352 | <webMaster>%s</webMaster> |
353 | <language>en</language> |
354 | ''' % (cvsHistoryURL, SITE_ADMIN) |
355 | |
356 | for row in data: |
357 | fileName = row[5] |
358 | dirName = row[3] |
359 | author = row[1] |
360 | revision = row[4] |
361 | op = op2text(row[0]) |
362 | |
363 | if fileName == dirName: |
364 | link = '%s/%s/' % (_SCRIPT, dirName) |
365 | else: |
366 | link = '%s/%s/%s' % (_SCRIPT, dirName, fileName) |
367 | |
368 | link = urlparse.urljoin (cvsHistoryURL, link) |
369 | |
370 | title = fileName |
371 | |
372 | description = '''%s: %s %s (%s)''' % (author, op, revision, dirName) |
373 | |
374 | milliseconds = row[2] |
375 | |
376 | date = time.strftime ('%a, %d %b %Y %H:%M:%S %Z', time.localtime (milliseconds)) |
377 | |
378 | print \ |
379 | ''' <item> |
380 | <title>%s</title> |
381 | <description>%s</description> |
382 | <link>%s</link> |
383 | <category>%s</category> |
384 | <pubDate>%s</pubDate> |
385 | <guid>%s</guid>''' % ( |
386 | xml.sax.saxutils.escape (title), |
387 | xml.sax.saxutils.escape (description), |
388 | xml.sax.saxutils.escape (link), |
389 | op, |
390 | date, |
391 | author + '/' + op + '/' + fileName + '/' + str (milliseconds) |
392 | ) |
393 | if AUTHOR_EMAIL_DOMAIN: |
394 | print " <author>%s@%s (%s)</author>" % (author, AUTHOR_EMAIL_DOMAIN, author) |
395 | print ' </item>' |
396 | |
397 | print \ |
398 | ''' </channel> |
399 | </rss> |
400 | ''' |
401 | |
402 | def pretty_print(data,options): |
403 | if options.has_key("rss"): |
404 | pretty_print_rss(data,options) |
405 | return |
406 | |
407 | # set up paging |
408 | offset = 0 |
409 | if options.has_key("offset"): |
410 | try: |
411 | offset = int(options["offset"]) |
412 | except: |
413 | offset = 0 |
414 | |
415 | pstr = "" |
416 | nstr = "" |
417 | |
418 | if PERFORMANCE == MODE_SLOW: |
419 | sortby = "Date" |
420 | if options.has_key("sortby"): |
421 | sortby = options["sortby"] |
422 | |
423 | |
424 | |
425 | nend = PERPAGE |
426 | |
427 | if len(data) - offset - PERPAGE < PERPAGE: |
428 | nend = len(data) - offset - PERPAGE |
429 | |
430 | if offset - PERPAGE >= 0: |
431 | pstr = '<td><font size="2"><a href="%s?%s">( Previous %s )</a></font></td>' % ( |
432 | _SELF_URL,getstring(options,["offset","%d" % (offset - PERPAGE)]),PERPAGE) |
433 | if offset + PERPAGE < len(data): |
434 | nstr = '<td><font size="2"><a href="%s?%s">( Next %s )</a></font></td>' % ( |
435 | _SELF_URL,getstring(options,["offset","%d" % (offset + PERPAGE)]),nend) |
436 | |
437 | cend = offset + PERPAGE |
438 | if cend > len(data): |
439 | cend = len(data) |
440 | ofstr = " of <b>%s</b>" % len(data) |
441 | srange = offset |
442 | erange = cend |
443 | elif PERFORMANCE == MODE_FAST: |
444 | if offset >= PERPAGE: |
445 | pstr = '<td><font size="2"><a href="%s?%s">( Newer Entries )</a></font></td>' % ( |
446 | _SELF_URL,getstring(options,["offset","%d" % (offset - PERPAGE)])) |
447 | |
448 | nstr = '<td><font size="2"><a href="%s?%s">( Older Entries )</a></font></td>' % ( |
449 | _SELF_URL,getstring(options,["offset","%d" % (offset + PERPAGE)])) |
450 | cend = offset + PERPAGE |
451 | if cend - offset > len(data): |
452 | cend = len(data) + offset |
453 | |
454 | ofstr = "" |
455 | srange = 0 |
456 | erange = len(data) |
457 | |
458 | rssURL = get_script_absolute_url () |
459 | |
460 | rssURL += '?' |
461 | |
462 | form = cgi.FieldStorage() |
463 | for paramName in form.keys(): |
464 | paramValue = form[paramName].value |
465 | if type(paramValue) == type([]): |
466 | paramValue = paramValue[0] |
467 | rssURL += paramName + '=' + urllib.quote (paramValue) + '&' |
468 | |
469 | rssURL += 'rss=1' |
470 | |
471 | print \ |
472 | '''Content-Type: text/html\r |
473 | \r |
474 | <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> |
475 | <html> |
476 | <head><title>CVSHistory</title> |
477 | <!-- CVSHistory: Jamie Turner, jamwt@jamwt.com --> |
478 | <link rel="alternate" type="application/rss+xml" title="RSS" href="%s"> |
479 | </head> |
480 | <body bgcolor="#ffffff" text="#000000"> |
481 | <table width="100%%" border=0 cellspacing=0 cellpadding=0> |
482 | <tr> |
483 | <td rowspan=2><h1>CVSHistory</h1></td> |
484 | <td align=right>%s</td> |
485 | </tr> |
486 | |
487 | </table> |
488 | |
489 | <hr noshade size="1"> |
490 | <table> |
491 | <tr><td> |
492 | <form action="%s" method="get"> |
493 | <table> |
494 | <tr> |
495 | <td valign="top" width="100">User:</td> |
496 | <td valign="top"><input type="text" name="usearch" %s><br> |
497 | <font size="2">Regular expression? <input type="checkbox" name="usearchre" %s></font></td> |
498 | <td width="50"> </td> |
499 | <td valign="top">Revision: |
500 | <br><select name="revsel1"> |
501 | <option value="na" %s>(No Restriction)</option> |
502 | <option value="eq" %s>Equal to</option> |
503 | <option value="gt" %s>Greater than</option> |
504 | <option value="lt" %s>Less than</option> |
505 | </select> |
506 | <input type="text" name="revval1" size="6" %s> |
507 | <select name="revsel2"> |
508 | <option value="na" %s>(No Restriction)</option> |
509 | <option value="eq" %s>Equal to</option> |
510 | <option value="gt" %s>Greater than</option> |
511 | <option value="lt" %s>Less than</option> |
512 | </select> |
513 | <input type="text" name="revval2" size="6" %s> |
514 | </td> |
515 | |
516 | </tr> |
517 | <tr> |
518 | <td valign="top" width="100">File:</td> |
519 | <td><input type="text" name="fsearch" %s><br> |
520 | <font size="2">Regular expression? <input type="checkbox" name="fsearchre" %s></font></td> |
521 | <td width="50"> </td> |
522 | <td valign="top">Date: |
523 | |
524 | <font size="2"><i>format: %s or %s</i></font> |
525 | <br><select name="datesel1"> |
526 | <option value="na" %s>(No Restriction)</option> |
527 | <option value="eq" %s>On/At</option> |
528 | <option value="gt" %s>On/After</option> |
529 | <option value="lt" %s>Before</option> |
530 | </select> |
531 | <input type="text" name="dateval1" size="30" %s> |
532 | </td> |
533 | |
534 | </tr> |
535 | <tr> |
536 | <td valign="top" width="100">Directory:</td> |
537 | <td><input type="text" name="dsearch" %s><br> |
538 | <font size="2">Regular expression? <input type="checkbox" name="dsearchre" %s></font><br> |
539 | <font size="2">Include subdirectories? <input type="checkbox" name="dsearchsub" %s></font></td> |
540 | <td width="50"> </td> |
541 | <td valign="top"> |
542 | <select name="datesel2"> |
543 | <option value="na" %s>(No Restriction)</option> |
544 | <option value="eq" %s>On/At</option> |
545 | <option value="gt" %s>On/After</option> |
546 | <option value="lt" %s>Before</option> |
547 | </select> |
548 | <input type="text" name="dateval2" size="30" %s> |
549 | </td> |
550 | </tr> |
551 | <tr> |
552 | <td valign="top">Operations:</td> |
553 | <td colspan="3"> |
554 | <select name="selop"> |
555 | <option value="na" %s>(No Restriction)</option> |
556 | <option value="in" %s>Include</option> |
557 | <option value="out" %s>Exclude</option> |
558 | </select> |
559 | Addition <input type="checkbox" name="opA" %s> |
560 | Checkout <input type="checkbox" name="opO" %s> |
561 | Commit <input type="checkbox" name="opM" %s> |
562 | Conflict on update <input type="checkbox" name="opC" %s> |
563 | <br> |
564 | Delete on update <input type="checkbox" name="opW" %s> |
565 | Merge on update <input type="checkbox" name="opG" %s> |
566 | Release <input type="checkbox" name="opF" %s> |
567 | Removal <input type="checkbox" name="opR" %s> |
568 | Rtag <input type="checkbox" name="opT" %s> |
569 | Update <input type="checkbox" name="opU" %s> |
570 | Update by patch <input type="checkbox" name="opP" %s> |
571 | </td> |
572 | <tr><td colspan="4" align="center"><input type="submit" value="Limit History"> |
573 | <a href="%s">(Reset/View All)</a></td></tr> |
574 | </table> |
575 | <input type="hidden" name="limit" value="1"> |
576 | %s |
577 | </form> |
578 | |
579 | </td></tr> |
580 | </table> |
581 | |
582 | <hr noshade> |
583 | <table align="center" cellpadding="10"><tr><td>Showing records <b>%s-%s</b>%s</td> |
584 | %s%s</tr></table> |
585 | |
586 | %s |
587 | <tr>''' % (cgi.escape(rssURL), _LOGO, _SELF_URL, |
588 | opt_pre(options,_OTYPE_TEXT,"usearch"), |
589 | opt_pre(options,_OTYPE_CHECKBOX,"usearchre"), |
590 | opt_pre(options,_OTYPE_SELECT,"revsel1","na"), |
591 | opt_pre(options,_OTYPE_SELECT,"revsel1","eq"), |
592 | opt_pre(options,_OTYPE_SELECT,"revsel1","gt"), |
593 | opt_pre(options,_OTYPE_SELECT,"revsel1","lt"), |
594 | opt_pre(options,_OTYPE_TEXT,"revval1"), |
595 | opt_pre(options,_OTYPE_SELECT,"revsel2","na"), |
596 | opt_pre(options,_OTYPE_SELECT,"revsel2","eq"), |
597 | opt_pre(options,_OTYPE_SELECT,"revsel2","gt"), |
598 | opt_pre(options,_OTYPE_SELECT,"revsel2","lt"), |
599 | opt_pre(options,_OTYPE_TEXT,"revval2"), |
600 | opt_pre(options,_OTYPE_TEXT,"fsearch"), |
601 | opt_pre(options,_OTYPE_CHECKBOX,"fsearchre"), |
602 | |
603 | #date break... |
604 | time.strftime(TIMEFORMAT,time.localtime(time.time())), |
605 | time.strftime(TIMEFORMAT[:-6],time.localtime(time.time())) , |
606 | |
607 | opt_pre(options,_OTYPE_SELECT,"datesel1","na"), |
608 | opt_pre(options,_OTYPE_SELECT,"datesel1","eq"), |
609 | opt_pre(options,_OTYPE_SELECT,"datesel1","gt"), |
610 | opt_pre(options,_OTYPE_SELECT,"datesel1","lt"), |
611 | opt_pre(options,_OTYPE_TEXT,"dateval1"), |
612 | opt_pre(options,_OTYPE_TEXT,"dsearch"), |
613 | opt_pre(options,_OTYPE_CHECKBOX,"dsearchre"), |
614 | opt_pre(options,_OTYPE_CHECKBOX,"dsearchsub"), |
615 | opt_pre(options,_OTYPE_SELECT,"datesel2","na"), |
616 | opt_pre(options,_OTYPE_SELECT,"datesel2","eq"), |
617 | opt_pre(options,_OTYPE_SELECT,"datesel2","gt"), |
618 | opt_pre(options,_OTYPE_SELECT,"datesel2","lt"), |
619 | opt_pre(options,_OTYPE_TEXT,"dateval2"), |
620 | opt_pre(options,_OTYPE_SELECT,"selop","na"), |
621 | opt_pre(options,_OTYPE_SELECT,"selop","in"), |
622 | opt_pre(options,_OTYPE_SELECT,"selop","out"), |
623 | opt_pre(options,_OTYPE_CHECKBOX,"opA"), |
624 | opt_pre(options,_OTYPE_CHECKBOX,"opO"), |
625 | opt_pre(options,_OTYPE_CHECKBOX,"opM"), |
626 | opt_pre(options,_OTYPE_CHECKBOX,"opC"), |
627 | opt_pre(options,_OTYPE_CHECKBOX,"opW"), |
628 | opt_pre(options,_OTYPE_CHECKBOX,"opG"), |
629 | opt_pre(options,_OTYPE_CHECKBOX,"opF"), |
630 | opt_pre(options,_OTYPE_CHECKBOX,"opR"), |
631 | opt_pre(options,_OTYPE_CHECKBOX,"opT"), |
632 | opt_pre(options,_OTYPE_CHECKBOX,"opU"), |
633 | opt_pre(options,_OTYPE_CHECKBOX,"opP"), |
634 | |
635 | # so we submit to the same place! |
636 | not options["cvsroot"] and _SELF_URL or |
637 | (_SELF_URL + ("?cvsroot=%s" % options["cvsroot"])), |
638 | |
639 | # so we submit to the same place! |
640 | not options["cvsroot"] and "" or ( |
641 | '<input type="hidden" name="cvsroot" value="%s" />' % options["cvsroot"]), |
642 | |
643 | # paging |
644 | offset + 1, cend, ofstr, pstr,nstr, |
645 | _TABLETOP, |
646 | ) |
647 | options["offset"] = "%d" % offset |
648 | |
649 | for x in xrange(0,len(_COLUMNS)): |
650 | if ( (PERFORMANCE == MODE_SLOW and _COLUMNS[x] == sortby) or |
651 | (PERFORMANCE == MODE_FAST and _COLUMNS[x] == "Date") ): |
652 | print '''<th align=left bgcolor="#%s" |
653 | >%s</th> ''' % (_SORTEDCOL, _COLUMNS[x]) |
654 | elif PERFORMANCE == MODE_FAST: |
655 | print '''<th align=left bgcolor="#%s" |
656 | >%s</th> ''' % (_REGCOL, _COLUMNS[x]) |
657 | else: |
658 | print '''<th align=left bgcolor="#%s" |
659 | ><a href="%s?%s">%s</a></th> ''' % (_REGCOL, _SELF_URL, |
660 | getstring(options,["sortby",_COLUMNS[x]]),_COLUMNS[x]) |
661 | |
662 | |
663 | print "</tr>\n" |
664 | |
665 | |
666 | for x in xrange(srange,erange): |
667 | fn = data[x][5] |
668 | dn = data[x][3] |
669 | if fn == dn: |
670 | fn = "<i>N/A</I>" |
671 | elif INTEGRATION: |
672 | fn = '<a href="%s/%s/%s">%s</a>' % ( |
673 | _SCRIPT, dn ,fn,fn ) |
674 | if INTEGRATION: |
675 | dn = '<a href="%s/%s/">%s%s</a>' % ( |
676 | _SCRIPT,dn,_ICON,dn ) |
677 | |
678 | |
679 | |
680 | rev = data[x][4] |
681 | if rev == '': |
682 | rev = "<i>N/A</I>" |
683 | |
684 | print \ |
685 | '''<tr bgcolor="#%s"> |
686 | |
687 | <td>%s</td> |
688 | <td>%s</td> |
689 | <td>%s</td> |
690 | <td>%s</td> |
691 | <td>%s</td> |
692 | <td>%s</td> |
693 | |
694 | </tr> |
695 | ''' % (_ROWS[x % 2], # background color |
696 | time.strftime(TIMEFORMAT,time.localtime(data[x][2])), #Date |
697 | data[x][1], # user |
698 | op2text(data[x][0]), # operation |
699 | dn, # directory |
700 | fn, # file/module |
701 | rev, # revision |
702 | ) |
703 | |
704 | print \ |
705 | '''</table><hr noshade> |
706 | <table width="100%%" border=0 cellpadding=0 cellspacing=0><tr> |
707 | <td align=left valign="top"> |
708 | <a href="%s">RSS</a>''' % cgi.escape(rssURL) |
709 | |
710 | if PUBLIC_SERVER: |
711 | print ' <a href="http://purl.org/net/syndication/subscribe/?rss=%s">Subscribe</a>' % urllib.quote (rssURL) |
712 | |
713 | print \ |
714 | '''</td> |
715 | <td align=right> |
716 | Powered by<br><a href="http://www.jamwt.com/CVSHistory/">CVSHistory %s</a> |
717 | </td></tr></table> |
718 | </body></html>''' % _VERSION |
719 | |
720 | |
721 | |
722 | def getstring(options,add=None,ignore=[]): |
723 | |
724 | if add: |
725 | options[add[0]] = add[1] |
726 | |
727 | gs = [] |
728 | for k in options.keys(): |
729 | if not k in ignore: |
730 | gs.append("%s=%s" % (k,urllib.quote_plus(options[k]))) |
731 | |
732 | |
733 | return cgi.escape("&".join(gs), 1) |
734 | |
735 | |
736 | def get_options(): |
737 | form = cgi.FieldStorage() |
738 | opts = {} |
739 | |
740 | for o in form.keys(): |
741 | d = form[o].value |
742 | if type(d) == type([]): |
743 | d = d[0] |
744 | opts[o] = d |
745 | if not opts.has_key("cvsroot") or not HISTORY.has_key(opts["cvsroot"]): |
746 | opts["cvsroot"] = "" |
747 | |
748 | return opts |
749 | |
750 | def sm_user(a,b): |
751 | if a[1].upper() < b[1].upper(): |
752 | return -1 |
753 | if a[1].upper() == b[1].upper(): |
754 | return 0 |
755 | return 1 |
756 | |
757 | def sm_op(a,b): |
758 | oa = op2text(a[0]) |
759 | ob = op2text(b[0]) |
760 | if oa < ob: |
761 | return -1 |
762 | if oa == ob: |
763 | return 0 |
764 | return 1 |
765 | |
766 | def sm_dir(a,b): |
767 | if a[3].upper() < b[3].upper(): |
768 | return -1 |
769 | if a[3].upper() == b[3].upper(): |
770 | return 0 |
771 | return 1 |
772 | |
773 | def sm_fn(a,b): |
774 | if a[3] == a[5]: |
775 | return 1 |
776 | if a[5].upper() < b[5].upper(): |
777 | return -1 |
778 | if a[5].upper() == b[5].upper(): |
779 | return 0 |
780 | return 1 |
781 | |
782 | def str_to_float(a): |
783 | if a == '': |
784 | return 0.0 |
785 | return float(a) |
786 | |
787 | def cmp_rev(a,b): |
788 | al = map(str_to_float, a.split('.')) |
789 | bl = map(str_to_float, b.split('.')) |
790 | if al > bl: |
791 | return 1 |
792 | if al < bl: |
793 | return -1 |
794 | return 0 |
795 | |
796 | def sm_rev(a,b): |
797 | return cmp_rev(a[4],b[4]) |
798 | |
799 | _SORTMETHODS = { |
800 | "User" : sm_user, |
801 | "Operation" : sm_op, |
802 | "Directory" : sm_dir, |
803 | "File" : sm_fn, |
804 | "Revision" : sm_rev, |
805 | } |
806 | |
807 | #conds = [ |
808 | |
809 | _CTYPE_MATCH = 1 |
810 | _CTYPE_REMATCH = 2 |
811 | _CTYPE_DATE = 3 |
812 | _CTYPE_REV = 4 |
813 | _CTYPE_OPS = 5 |
814 | |
815 | _ARG2_OUT = 0 |
816 | _ARG2_IN = 1 |
817 | |
818 | _ARG2_EQ = 2 |
819 | _ARG2_GT = 3 |
820 | _ARG2_LT = 4 |
821 | |
822 | _ARG2_SEQ = 5 |
823 | _ARG2_NUM_GT = 6 |
824 | _ARG2_NUM_LT = 7 |
825 | |
826 | class Condition: |
827 | def __init__(self,type,dfield,arg,arg2=None): |
828 | self.dfield = dfield |
829 | self.error = None |
830 | |
831 | if type == _CTYPE_MATCH: |
832 | self.arg = arg.strip() |
833 | self.test = self.MATCH_test |
834 | elif type == _CTYPE_REMATCH: |
835 | try: |
836 | self.arg = re.compile(arg) |
837 | self.test = self.REMATCH_test |
838 | except: |
839 | self.error = "invalid regular expression" |
840 | |
841 | elif type == _CTYPE_DATE: |
842 | try: |
843 | arg = arg.strip() |
844 | arg = re.sub(" +"," ",arg) |
845 | if arg.count(" "): |
846 | self.arg = time.mktime(time.strptime(arg,TIMEFORMAT)) |
847 | longdate = 1 |
848 | else: |
849 | self.arg = time.mktime(time.strptime(arg,TIMEFORMAT[:-6])) |
850 | longdate = 0 |
851 | self.test = self.COMP_test |
852 | except: |
853 | self.error = "invalid date" |
854 | if arg2 == "eq": |
855 | if longdate: |
856 | self.arg2 = _ARG2_EQ |
857 | else: |
858 | self.arg2 = _ARG2_SEQ |
859 | elif arg2 == "gt": |
860 | self.arg2 = _ARG2_GT |
861 | elif arg2 == "lt": |
862 | self.arg2 = _ARG2_LT |
863 | else: |
864 | self.error = "unexpected comparison specification for date" |
865 | |
866 | elif type == _CTYPE_REV: |
867 | self.arg = arg |
868 | self.test = self.COMP_test |
869 | if arg2 == "eq": |
870 | self.arg2 = _ARG2_EQ |
871 | elif arg2 == "gt": |
872 | self.arg2 = _ARG2_NUM_GT |
873 | elif arg2 == "lt": |
874 | self.arg2 = _ARG2_NUM_LT |
875 | else: |
876 | self.error = "unexpected comparison specification for revisions" |
877 | elif type == _CTYPE_OPS: |
878 | self.arg = arg |
879 | self.test = self.OPS_test |
880 | if arg2 == "in": |
881 | self.arg2 = _ARG2_IN |
882 | elif arg2 == "out": |
883 | self.arg2 = _ARG2_OUT |
884 | else: |
885 | self.error = "unexpected argument for operation exclusion/inclusion" |
886 | |
887 | |
888 | |
889 | |
890 | def MATCH_test(self,dataitem): |
891 | if dataitem[self.dfield] == self.arg: |
892 | return 1 |
893 | return 0 |
894 | |
895 | def REMATCH_test(self,dataitem): |
896 | if self.arg.search(dataitem[self.dfield]): |
897 | return 1 |
898 | return 0 |
899 | |
900 | def COMP_test(self,dataitem): |
901 | if self.arg2 == _ARG2_SEQ: |
902 | # we have a date... |
903 | dt = time.localtime(dataitem[self.dfield]) |
904 | mt = dt[:3] + (0,0,0) + dt[6:] |
905 | if time.mktime(mt) == self.arg: |
906 | return 1 |
907 | return 0 |
908 | elif self.arg2 == _ARG2_EQ: |
909 | if dataitem[self.dfield] == self.arg: |
910 | return 1 |
911 | return 0 |
912 | elif self.arg2 == _ARG2_GT: |
913 | if dataitem[self.dfield] > self.arg: |
914 | return 1 |
915 | return 0 |
916 | elif self.arg2 == _ARG2_LT: |
917 | if dataitem[self.dfield] < self.arg: |
918 | return 1 |
919 | return 0 |
920 | elif self.arg2 == _ARG2_NUM_GT: |
921 | if cmp_rev(dataitem[self.dfield], self.arg) == 1: |
922 | return 1 |
923 | return 0 |
924 | elif self.arg2 == _ARG2_NUM_LT: |
925 | if cmp_rev(dataitem[self.dfield], self.arg) == -1: |
926 | return 1 |
927 | return 0 |
928 | return 0 |
929 | |
930 | |
931 | def OPS_test(self,dataitem): |
932 | if dataitem[self.dfield] in self.arg: |
933 | return 1 * self.arg2 # will 0 out if OUT |
934 | return 1 - self.arg2 # inverse |
935 | |
936 | |
937 | |
938 | |
939 | |
940 | def get_conds(opts): |
941 | conds = [] |
942 | if not HISTORY.get(opts["cvsroot"]): |
943 | return "Error: unknown cvsroot: '%s'" % opts["cvsroot"] |
944 | for opt in opts.keys(): |
945 | # username |
946 | madecond = 0 |
947 | if opt == "usearch" and opts[opt].strip() != "": |
948 | madecond = 1 |
949 | if opts.has_key("usearchre") and opts["usearchre"] == "on": |
950 | conds.append(Condition(_CTYPE_REMATCH,1,opts["usearch"]) ) |
951 | else: |
952 | conds.append(Condition(_CTYPE_MATCH,1,opts["usearch"]) ) |
953 | if opt == "fsearch" and opts[opt].strip() != "": |
954 | madecond = 1 |
955 | if opts.has_key("fsearchre") and opts["fsearchre"] == "on": |
956 | conds.append(Condition(_CTYPE_REMATCH,5,opts["fsearch"]) ) |
957 | else: |
958 | conds.append(Condition(_CTYPE_MATCH,5,opts["fsearch"]) ) |
959 | elif opt == "dsearch" and opts[opt].strip() != "": |
960 | madecond = 1 |
961 | if opts.has_key("dsearchre") and opts["dsearchre"] == "on": |
962 | conds.append(Condition(_CTYPE_REMATCH,3,opts["dsearch"]) ) |
963 | else: |
964 | conds.append(Condition(_CTYPE_MATCH,3,opts["dsearch"]) ) |
965 | elif opt == "dsearch" and opts[opt].strip() != "": |
966 | madecond = 1 |
967 | if opts.has_key("dsearchre") and opts["dsearchre"] == "on": |
968 | conds.append(Condition(_CTYPE_REMATCH,3,opts["dsearch"]) ) |
969 | else: |
970 | conds.append(Condition(_CTYPE_MATCH,3,opts["dsearch"]) ) |
971 | elif opt == "revsel1" and opts[opt].strip() != "na": |
972 | madecond = 1 |
973 | if not opts.has_key("revval1") or opts["revval1"].strip() == "": |
974 | return "Error processing revision: please include revision value" |
975 | conds.append(Condition(_CTYPE_REV,4,opts["revval1"],opts["revsel1"]) ) |
976 | elif opt == "revsel2" and opts[opt].strip() != "na": |
977 | madecond = 1 |
978 | if not opts.has_key("revval2") or opts["revval2"].strip() == "": |
979 | return "Error processing revision: please include revision value" |
980 | conds.append(Condition(_CTYPE_REV,4,opts["revval2"],opts["revsel2"]) ) |
981 | elif opt == "datesel1" and opts[opt].strip() != "na": |
982 | madecond = 1 |
983 | if not opts.has_key("dateval1") or opts["dateval1"].strip() == "": |
984 | return "Error processing date: please include date value" |
985 | conds.append(Condition(_CTYPE_DATE,2,opts["dateval1"],opts["datesel1"]) ) |
986 | elif opt == "datesel2" and opts[opt].strip() != "na": |
987 | madecond = 1 |
988 | if not opts.has_key("dateval2") or opts["dateval2"].strip() == "": |
989 | return "Error processing date: please include date value" |
990 | conds.append(Condition(_CTYPE_DATE,2,opts["dateval2"],opts["datesel2"]) ) |
991 | |
992 | elif opt == "selop" and opts["selop"] != "na": |
993 | madecond = 1 |
994 | ops_arg = [] |
995 | for inkey in opts.keys(): |
996 | if inkey[:2] == "op" and opts[inkey] == "on": |
997 | ops_arg.append(inkey[2]) |
998 | conds.append(Condition(_CTYPE_OPS,0,"".join(ops_arg),opts["selop"]) ) |
999 | |
1000 | |
1001 | if madecond and conds[-1].error: |
1002 | return "Error processing form input: " + conds[-1].error |
1003 | |
1004 | return conds |
1005 | |
1006 | def limit(data,conds): |
1007 | x = 0 |
1008 | while x < len(data): |
1009 | for c in conds: |
1010 | if not c.test(data[x]): |
1011 | del data[x] |
1012 | x -= 1 |
1013 | break |
1014 | x += 1 |
1015 | |
1016 | return data |
1017 | |
1018 | def correct_opts(opts): |
1019 | # revision |
1020 | if opts.has_key("revsel1") and opts["revsel1"] == "na": |
1021 | opts["revval1"] = "" |
1022 | if opts.has_key("revsel2") and opts["revsel2"] == "na": |
1023 | opts["revval2"] = "" |
1024 | if opts.has_key("datesel1") and opts["datesel1"] == "na": |
1025 | opts["dateval1"] = "" |
1026 | if opts.has_key("datesel2") and opts["datesel2"] == "na": |
1027 | opts["dateval2"] = "" |
1028 | if opts.has_key("selop") and opts["selop"] == "na": |
1029 | for ok in opts.keys(): |
1030 | if ok[:2] == "op": |
1031 | opts[ok] = "off" |
1032 | |
1033 | return opts |
1034 | |
1035 | def error(mess): |
1036 | print \ |
1037 | '''Content-Type: text/html\r |
1038 | \r |
1039 | <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> |
1040 | <html><head><title>CVSHistory -- Error</title> |
1041 | </head><body bgcolor="#ffffff"> |
1042 | <h2>Error</h2> |
1043 | CVSHistory encountered an error on form input:<br> |
1044 | <i>%s</i></body></html>''' % cgi.escape(mess) |
1045 | |
1046 | |
1047 | def go_slow(): |
1048 | opts = get_options() |
1049 | data = get_history(opts) |
1050 | |
1051 | # fix directory recursion |
1052 | if opts.has_key("dsearchsub") and opts["dsearchsub"] == "on": |
1053 | if not opts.has_key("dsearchre") or opts["dsearchre"] != "on": |
1054 | opts["dsearch"] = re.escape(opts["dsearch"]) |
1055 | opts["dsearchre"] = "on" |
1056 | opts["dsearch"] = "^" + opts["dsearch"] + ".*" |
1057 | opts["dsearchsub"] = "off" |
1058 | |
1059 | printed = 0 |
1060 | |
1061 | conds = None |
1062 | if opts.has_key("limit") and opts["limit"] == "1": |
1063 | conds = get_conds(opts) |
1064 | if type(conds) == type(""): |
1065 | error(conds) |
1066 | printed = 1 |
1067 | else: |
1068 | opts = correct_opts(opts) |
1069 | data = limit(data,conds) |
1070 | |
1071 | # sorting |
1072 | if not printed: |
1073 | if opts.has_key("sortby") and opts["sortby"] != "Date": |
1074 | data.sort(_SORTMETHODS[opts["sortby"]]) |
1075 | |
1076 | pretty_print(data,opts) |
1077 | |
1078 | #fast mode |
1079 | def go_fast(): |
1080 | |
1081 | opts = get_options() |
1082 | |
1083 | # fix directory recursion |
1084 | if opts.has_key("dsearchsub") and opts["dsearchsub"] == "on": |
1085 | if not opts.has_key("dsearchre") or opts["dsearchre"] != "on": |
1086 | opts["dsearch"] = re.escape(opts["dsearch"]) |
1087 | opts["dsearchre"] = "on" |
1088 | opts["dsearch"] = "^" + opts["dsearch"] + ".*" |
1089 | opts["dsearchsub"] = "off" |
1090 | printed = 0 |
1091 | |
1092 | conds = [] |
1093 | if opts.has_key("limit") and opts["limit"] == "1": |
1094 | conds = get_conds(opts) |
1095 | if type(conds) == type(""): |
1096 | error(conds) |
1097 | printed = 1 |
1098 | else: |
1099 | opts = correct_opts(opts) |
1100 | |
1101 | if not printed: |
1102 | data = get_history_fast(conds,opts) |
1103 | pretty_print(data,opts) |
1104 | |
1105 | if __name__ == "__main__": |
1106 | if PERFORMANCE == MODE_FAST: |
1107 | go_fast() |
1108 | if PERFORMANCE == MODE_SLOW: |
1109 | go_slow() |