Changesets can be listed by changeset number.
The Git repository is here.
- Revision:
- 158
- Log:
Reconfigured all applications for use in the ROOL account with its
new layout. All files are set up for the development branch of the
filesystem. Caught up in this changeset is an unrelated modification
to the directory listing routine in Radiant, which now sorts the
entries alphabetically.
- Author:
- rool
- Date:
- Sun Dec 10 19:28:59 +0000 2006
- Size:
- 29098 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/rool/devel/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() |