darcsweb-1.1/0000755000175000017500000000000011101631641011215 5ustar albalbdarcsweb-1.1/minidarcs.png0000644000175000017500000001352011101631641013675 0ustar albalbPNG  IHDR>>sDbKGD pHYs  tIME trtEXtCommentCreated with The GIMPd%nIDATh{Օ?c@1d̀nYY?e#vf#.ɮ HD0@ mnwݯc~AH[*W={ιǵ~{ځ^>(4PzgO/I,w`'p:kmH""$*B &y?:}(,S溮hooL&y8$IR8T* ŢZڋ9cǎ׏9Rg|vv6Oy9s|>/l–-[شi7nR!@kM$A@Xd||QFGGx"/_&RWWÇΝ;gT*_婧7'NuݶAvw͛ikk#"Z[31r,.\7ٳLLLD~__~pBïS`ݻٿ?r ===R:إ ͘u!a( /^,ݻ׾/nwر=x w`GQü?~&I|+vf~~ƻ{w["ĘuҕZS֬)%J)N:ѣG9sǯ^;'|;;;җOOOG2g6xm 'q0R,୵H)禛nB{x>sa8Ͽ}>_g2|Aݻ́Y  7ii#\W Q%QHu |m۶T*zg|~ȑ#QrWrnn7 ~_f޽(iVL2ibsPegQCbgm'h(1ܜmSU Ē+rǿ{/N<={hW򩧞o=܃5ƘY" 8+4HL'x]N.ձEE^sТc@# FfmēR HӿO>X .twL{cA܎j 8c,Z[D+|A]h܎xuXOkMoo/'Nx矿u]?66vqUk ZN `Ы|H%1FCpU6̳h2W%q M# Ua$I®]?2== ĉݿկܼy|~d4m ObmBcnx6*1yZ xV6U뢁h,(}<_|5۫{?IS5^ji6*=+87F g4gkϮ6x4رngrF.4>99z=p^/ն1 IejcA#+DՑ%xV` tjkEZ8z+CCC?y5{뭷ۻ൩50 vVȌ,jdmKnAeoN6K/ -kHw8kKfkիx"c  3%Xb=]q֐Edc fUC咃 ,6mrO>ҢgffGyl6+PJ]3%P|ۦpRF Kj1|MuEX/^w@RJcbQ|21ZjhPE誇h +֭[yntR?0Z>22abbbw{{;+Fiت Q S $bCȆձ- 5Կ өx1I&76$XPJ\l 8ƇFz4gX<{uM^T*oݺT*P 6X-])BT*hUJp7,>̒RlMH43DYNDx#B<冇o^jirXkQ٩HϐM A Z|2X,E!i&8[1H4=*Uj9hϒ8-ã|6}r߿ɹl68ŅEAz{̳CoL0"AȆQ dUI9 ѤG|n^.ZEiK誉7'=C~G?amE۝ gvV8RiST|~[n[[e+cYj$dEBYtYVATO`j̫y|TD)%!$Om^[;%)lMN֐>XbQNJA}`\n`򓓓}]i]X@9'*bəL:@hPBy 7)*vK֜~'S-dt=!2t6K%M+WPJN2̢[[[GF)%hd o;'IƸyՂr芏5I5nZWpQRm[tXbZ[@[]i4)[>٭!2eP.O4cjR@nj/h$-j|)Zk)eevf1NNW@F` HגhW$Vy*)arwWRXu(ZwUz8(61F8`ARD3ոV2YNNNR(ԧ>uɹ bؾМ\'f-Γ"jZd]AL]+ eV^cS>qCᅵ-.V7.(B84FFF)e&6m==77SkMNn鹛Ӥzۯ}uQqhV0s%'cz%o<9#Bkop򸠦RZnY㝝ɷWΞ={Vb||SU~ '$^#s(UK"5K09G43CimVL "8ڋsi]0rk-ټy ̾}^9z8{Rt̖y9&*30nւkWAگE'\Ȱ1ߒ떦366n텮. 79}RJ -.(r1 rDaTѲ)b\Kf]`Zk "kz-HM1Ecm~ibcWWWO .|Nc}r\sh!QT^;(WQnU-c,3-Ҩߪڹs系wpպ;rbqBpe<ߍp:`~\Q=,`gWWKAᐠO^7%CW"5hĂ[!\.sʕzXS_Iٳgw7Z/>* &CNJfeCQan0}R!a_7Mϟ c[ͧH*S߲&S'4(X,211A$Į]߾fÿ{SfJA@Gc&J?me~׶EgE  QFq,07b6ʓ:Tbrr8EFuG:oy_>XkpU*RdY̜tsOvs '0d,Ow1:Uj  vhC`I]?mR[.᣽?Iˍr?E"B)E\ h͝w?Cͮx___?o~s>)eBqVH `Z{UA$:5KKz/ف FqZ6^.d2<ٳglիgo߾ond хR IJ5D'. JcQ M̻>QѠl4h/p'R(ѣG[ONvr.[kXU/,:Xϯzν$sn5]'_xۧOVz*&VT*/(BL#ʅ N \QUv/.Kih7. ?sT5\N4v6\&£X>*m4,V NVw)ܜ$d%)ޗzmtңJzH뺤R)mԸA(rJڋlVq:?%zqQIԜKhk[qQĉ/( Rld2<ȏ}!ڿ q%IZ,hxgɻEDX|q$J]'-1OUre~~Klvv?r+qga~yKa~EѕZ,蕀kJZ p] 6q66333EQ4,T^ Ag m;+Fq|KK˞|w|,H1ߋ6QJ]T* (xjIy5zeXjK5|C7Juerl68NHUӫHb(RA/˧0 ǭ^ VW{0k_qa,,WѼX!L;}qN!D뺾1FqXkl3Z:hh%:R/toY9abjƉ]cU4Vߨ?_޸t&ۓIENDB`darcsweb-1.1/config.py.sample0000644000175000017500000001601711101631641014321 0ustar albalb # base configuration, common to all repos class base: # location of the darcs logo darcslogo = "darcs.png" # location of the darcs favicon darcsfav = "minidarcs.png" # the CSS file to use cssfile = 'style.css' # this script's name, usually just "darcsweb.cgi" unless you rename # it; if you leave this commented it will be detected automatically #myname = "darcsweb.cgi" # our url, used only to generate RSS links, without the script name; # if you leave this commented it will be detected automatically #myurl = "http://example.com/darcsweb" # optionally, you can specify the path to the darcs executable; if you # leave this commented, the one on $PATH will be used (this is # normally what you want) #darcspath = "/home/me/bin/" # the text to appear in the top of repo list; this is also optional, # and html-formatted #summary = "I love darcs!" # in case you want to change the beautiful default, you can specify an # alternative footer here; it's optional, of course #footer = "I don't like shoes" # It is possible to have a cache where darcsweb will store the pages # it generates; entries are automatically updated when the repository # changes. This will speed things up significatively, specially for # popular sites. # It's recommended that you clean the directory with some regularity, # to avoid having too many unused files. A simple rm will do just # fine. # If you leave the entry commented, no cache will be ever used; # otherwise the directory is assumed to exist and be writeable. # If you use this option you must set the "myname" and "myurl" # variables. #cachedir = '/tmp/darcsweb-cache' # By default, darcsweb's search looks in the last 100 commits; you can # change that number by specifying it here. # Note that search are not cached, so if you have tons of commits and # set the limit to a very high number, they will take time. #searchlimit = 100 # If you want to log the times it took darcsweb to present a page, # uncomment this option. The value should be a file writeable by # darcsweb. #logtimes = "/tmp/darcsweb_times" # If you want darcsweb to automatically detect embedded URLs, # define them here, using python-style regexps like the examples # below. They will be replaced in summaries, logs, and commits. # The following variables are replaced: # myreponame: repository link (darcsweb.cgi?r=repo) # reponame: repository name (repo) # #url_links = ( # Format is: (regexp, replacement) # Some examples: # # Detect '#NNN' as a reference to bug database #(r'#([0-9]+)', # r'#\1'), # # Replace hashes with commit-links. #(r'(\d{14}-[0-9a-f]{5}-[0-9a-f]{40}\.gz)', # r'\1'), #) # If you want to generate links from patch author names, define the url # here. Example for CIA: #author_links = "http://cia.navi.cx/stats/author/%(author)s" # If you want to disable the annotate feature (for performance reasons, # the http connection will time out on slow machines), uncomment this # option. #disable_annotate = True # # From now on, every class is a repo configuration, with the same format # There are no restrictions on the class' name, except that it can't be named # "base" (because it's the name of the one above). # # If you have a lot of repos and/or you're too lazy to do this by hand, you # can use the configuration generator that comes with darcsweb, called # "mkconfig.py". # class repo1: # the descriptive name reponame = 'repo1' # a brief description repodesc = 'Example repository' # the real path to the repository repodir = '/usr/src/repo1' # an url so people know where to do "darcs get" from repourl = 'http://example.com/repos/repo1/' # the encoding used in the repo # NOTE: if you use utf8, you _must_ write 'utf8' (and not the variants # like 'utf-8' or 'UTF8') if you expect darcsweb to work properly. # This is because to workaround a bug in darcs we need to do some # codec mangling and it needs special cases for UTF8. # You can, optionally, specify multiple encodings; they're tried in # order, and if one fails to decode a string, the next one is tried. # Example: repoencoding = "utf8", "latin1" repoencoding = "latin1" # as with the base configuration, the footer is also optional, and it # affects only this repository; if you don't specify, the one # specified in base is used (and if you don't specify one there # either, a default one is used) #footer = "I don't like being cold" # Each repository may show a link to some website associated with it. # This is typically useful if you've got a website describing the # software in your repository. #repoprojurl = 'http://example.com/projects/repo1/' class repo2: reponame = 'repo2' repodesc = 'Second example repository' repodir = '/usr/src/repo2' repourl = 'http://example.com/repos/repo2/' repoencoding = 'latin1' # # If you have several repositories in a single directory and don't want to # create a configuration entry for each one, you can use a "multidir" entry, # which serves as a "template" for all the repositories in that directory. # The name is taken from the directory, and inside the variables the string # "%(name)s" gets expanded to the it. If displayname is set, "%(dname)s" gets # expanded to it; otherwise it's the same as "%(name)s". # # If you set multidir_deep to True (note the capitalization) then all # subdirectories are searched for darcs repositories. Subdirectories starting # with a dot (.) are not searched. This may be slow, if huge directory trees # must be searched. It's unnecesary unless you have a multidir with several # nested repositories. It defaults to False, and it's optional. # class multi1: multidir = '/usr/local/src' #multidir_deep = False repodesc = 'Repository for %(name)s' repourl = 'http://example.com/repos/%(name)s/' repoencoding = 'latin1' # if you want to change the display name of the repositories (i.e. the # name it will have on the listings, urls, etc.), you can set it here. # You can use "%(name)s" expansion, see above. #displayname = "local/%(name)s" # optional, see above #repoprojurl = 'http://example.com/projects/%(name)s/' # if you want to exclude some directories, add them to this list (note # they're relative to multidir, not absolute) #exclude = 'dir1', 'dir2' # if you want the descriptions to be picked up automatically from the # file named "_darcs/third_party/darcsweb/desc" (one line only), set # this to True. It defaults to False #autodesc = True # if you want to exclude all the repositories which do NOT have a # directory named "_darcs/third_party/darcsweb/" inside, set this to # True. It defaults to False. #autoexclude = True # if you want urls to be picked up automatically from the file named # "_darcs/third_party/darcsweb/url" (one line only), set this to # True. It defaults to False. #autourl = True # if you want the projects urls to be picked up automatically from the # file named "_darcs/third_party/darcsweb/projurl" (one line only), set # this to True. It defaults to False. #autoprojurl = True darcsweb-1.1/LICENSE0000644000175000017500000000247611101631641012233 0ustar albalb I don't like licenses, because I don't like having to worry about all this legal stuff just for a simple piece of software I don't really mind anyone using. But I also believe that it's important that people share and give back; so I'm placing darcsweb under the following license, to make you feel guilty if you don't ;) BOLA - Buena Onda License Agreement ----------------------------------- This work is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this work. To all effects and purposes, this work is to be considered Public Domain. However, if you want to be "Buena onda", you should: 1. Not take credit for it, and give proper recognition to the authors. 2. Share your modifications, so everybody benefits from them. 4. Do something nice for the authors. 5. Help someone who needs it: sign up for some volunteer work or help your neighbour paint the house. 6. Don't waste. Anything, but specially energy that comes from natural non-renewable resources. Extra points if you discover or invent something to replace them. 7. Be tolerant. Everything that's good in nature comes from cooperation. The order is important, and the further you go the more "Buena onda" you are. Make the world a better place: be "Buena onda". darcsweb-1.1/mkconfig.py0000644000175000017500000000373211101631641013371 0ustar albalb#!/usr/bin/env python """ A darcsweb configuration generator ---------------------------------- This is a small utility that generates configuration files for darcsweb, in case you're lazy and/or have many repositories. It receives four parameters: the base URL for your repositories, the base description, the encoding and the directory to get the repositories from. It replaces the string "NAME" with the name of the directory that holds the repository, so you can specify urls and descriptions with the name in them. Then, it generates an appropiate configuration for each repository in the directory. It outputs the configuration to stdout, so you can redirect it to config.py. For example: $ mkconf.py "http://example.com/darcs/NAME" "Repo for NAME" latin1 \\ ~/devel/repos/ >> config.py Remember that you still need to do the base configuration by hand. You can do that by copying the sample included with darcsweb. """ import sys import os import string import urllib def help(): print "Error: wrong parameter count" print __doc__ def filter_class(s): "Filter s so the new string can be used as a class name." allowed = string.ascii_letters + string.digits + '_' l = [c for c in s if c in allowed] return string.join(l, "") def filter_url(s): "Filter s so the new string can be used in a raw url." return urllib.quote_plus(s, ':/') # check parameters if len(sys.argv) != 5: help() sys.exit(0) myself, baseurl, basedesc, baseencoding, basepath = sys.argv dirs = os.listdir(basepath) for d in dirs: path = basepath + '/' + d if not os.path.isdir(path + '/_darcs'): # not a repo, skip continue s = \ """ class %(classname)s: reponame = '%(name)s' repodesc = '%(desc)s' repodir = '%(dir)s' repourl = '%(url)s' repoencoding = '%(encoding)s' """ % { 'classname': filter_class(d), 'name': d, 'desc': basedesc.replace('NAME', d), 'dir': os.path.abspath(basepath + '/' + d), 'url': filter_url(baseurl.replace('NAME', d)), 'encoding': baseencoding, } print s darcsweb-1.1/style.css0000644000175000017500000001645411101631641013101 0ustar albalb /* darcsweb CSS * Alberto Bertogli (albertito@blitiri.com.ar) * * Copied almost entirely from gitweb's, written by Kay Sievers * and Christian Gierke . */ body { font-family: sans-serif; font-size: 12px; margin:0px; border:solid #d9d8d1; border-width:1px; margin:10px; } a { color:#0000cc; } a:hover, a:visited, a:active { color:#880000; } div.page_header { height:25px; padding:8px; font-size:18px; font-weight:bold; background-color:#d9d8d1; } div.page_header a:visited { color:#0000cc; } div.page_header a:hover { color:#880000; } div.page_nav { padding:8px; } div.page_nav a:visited { color:#0000cc; } div.page_path { padding:8px; border:solid #d9d8d1; border-width:0px 0px 1px } div.page_footer { height:17px; padding:4px 8px; background-color: #d9d8d1; } div.page_footer_text { float:left; color:#555555; font-style:italic; } div.page_body { padding:8px; } div.search_box { float:right; text-align:right; } input.search_text { font-size:xx-small; background-color: #edece6; vertical-align: top; } input.search_button { font-size:xx-small; vertical-align: top; } div.title, a.title { display:block; padding:6px 8px; font-weight:bold; background-color:#edece6; text-decoration:none; color:#000000; } a.title:hover { background-color: #d9d8d1; } div.title_text { padding:6px 0px; border: solid #d9d8d1; border-width:0px 0px 1px; } div.log_body { padding:8px 8px 8px 150px; } span.age { position:relative; float:left; width:142px; font-style:italic; } div.log_link { padding:0px 8px; font-size:10px; font-family:sans-serif; font-style:normal; position:relative; float:left; width:136px; } div.list_head { padding:6px 8px 4px; border:solid #d9d8d1; border-width:1px 0px 0px; font-style:italic; } a.list { text-decoration:none; color:#000000; } a.list:hover { text-decoration:underline; color:#880000; } a.line { text-decoration:none; color:#000000; } a.line:hover { text-decoration:none; color:#880000; } table { /*clear:both;*/ padding:8px 4px; } th { padding:2px 5px; font-size:12px; text-align:left; } .light:hover { background-color:#edece6; } .dark { background-color:#f6f6f0; } .dark:hover { background-color:#edece6; } .tag { background-color:#f0f0ff; } .tag:hover { background-color:#e0e0ff; } td { padding:2px 5px; font-size:12px; vertical-align:top; } td.link { padding:2px 5px; font-family:sans-serif; font-size:10px; } div.pre { font-family:monospace; font-size:12px; white-space:pre; } div.diff_info { font-family:monospace; color:#000099; background-color:#edece6; font-style:italic; } div.index_include { border:solid #d9d8d1; border-width:0px 0px 1px; padding:12px 8px; } div.search { margin:4px 8px; position:absolute; top:56px; right:12px; } a.linenr, .linenos { color:#999999; text-decoration:none; } a.annotate_desc { color:#999999; text-decoration:none; font-size:11px; } a.annotate_desc:hover { color:#880000; } a.rss_logo { float:right; padding:3px 0px; width:35px; line-height:10px; border:1px solid; border-color:#fcc7a5 #7d3302 #3e1a01 #ff954e; color:#ffffff; background-color:#ff6600; font-weight:bold; font-family:sans-serif; font-size:10px; text-align:center; text-decoration:none; } a.rss_logo:hover { background-color:#ee5500; } img.logo { border-width:0px; vertical-align:top; margin-left:12pt; margin-right:5pt; } /* Syntax highlighting related styles. This is highly dependent on what * python-pygments generates. * This was generated using the following commands in a python interpreter and * slightly modified after. * >>> import pygments * >>> pygments.formatters.HtmlFormatter().get_style_defs('.page_body') */ .page_body table { margin: 0; padding: 0;} .page_body pre { margin : 0; padding: 0; } .page_body .c { color: #008800; font-style: italic } /* Comment */ .page_body .err { border: 1px solid #FF0000 } /* Error */ .page_body .k { color: #AA22FF; font-weight: bold } /* Keyword */ .page_body .o { color: #666666 } /* Operator */ .page_body .cm { color: #008800; font-style: italic } /* Comment.Multiline */ .page_body .cp { color: #008800 } /* Comment.Preproc */ .page_body .c1 { color: #008800; font-style: italic } /* Comment.Single */ .page_body .gd { color: #A00000 } /* Generic.Deleted */ .page_body .ge { font-style: italic } /* Generic.Emph */ .page_body .gr { color: #FF0000 } /* Generic.Error */ .page_body .gh { color: #000080; font-weight: bold } /* Generic.Heading */ .page_body .gi { color: #00A000 } /* Generic.Inserted */ .page_body .go { color: #808080 } /* Generic.Output */ .page_body .gp { color: #000080; font-weight: bold } /* Generic.Prompt */ .page_body .gs { font-weight: bold } /* Generic.Strong */ .page_body .gu { color: #800080; font-weight: bold } /* Generic.Subheading */ .page_body .gt { color: #0040D0 } /* Generic.Traceback */ .page_body .kc { color: #AA22FF; font-weight: bold } /* Keyword.Constant */ .page_body .kd { color: #AA22FF; font-weight: bold } /* Keyword.Declaration */ .page_body .kp { color: #AA22FF } /* Keyword.Pseudo */ .page_body .kr { color: #AA22FF; font-weight: bold } /* Keyword.Reserved */ .page_body .kt { color: #AA22FF; font-weight: bold } /* Keyword.Type */ .page_body .m { color: #666666 } /* Literal.Number */ .page_body .s { color: #BB4444 } /* Literal.String */ .page_body .na { color: #BB4444 } /* Name.Attribute */ .page_body .nb { color: #AA22FF } /* Name.Builtin */ .page_body .nc { color: #0000FF } /* Name.Class */ .page_body .no { color: #880000 } /* Name.Constant */ .page_body .nd { color: #AA22FF } /* Name.Decorator */ .page_body .ni { color: #999999; font-weight: bold } /* Name.Entity */ .page_body .ne { color: #D2413A; font-weight: bold } /* Name.Exception */ .page_body .nf { color: #00A000 } /* Name.Function */ .page_body .nl { color: #A0A000 } /* Name.Label */ .page_body .nn { color: #0000FF; font-weight: bold } /* Name.Namespace */ .page_body .nt { color: #008000; font-weight: bold } /* Name.Tag */ .page_body .nv { color: #B8860B } /* Name.Variable */ .page_body .ow { color: #AA22FF; font-weight: bold } /* Operator.Word */ .page_body .mf { color: #666666 } /* Literal.Number.Float */ .page_body .mh { color: #666666 } /* Literal.Number.Hex */ .page_body .mi { color: #666666 } /* Literal.Number.Integer */ .page_body .mo { color: #666666 } /* Literal.Number.Oct */ .page_body .sb { color: #BB4444 } /* Literal.String.Backtick */ .page_body .sc { color: #BB4444 } /* Literal.String.Char */ .page_body .sd { color: #BB4444; font-style: italic } /* Literal.String.Doc */ .page_body .s2 { color: #BB4444 } /* Literal.String.Double */ .page_body .se { color: #BB6622; font-weight: bold } /* Literal.String.Escape */ .page_body .sh { color: #BB4444 } /* Literal.String.Heredoc */ .page_body .si { color: #BB6688; font-weight: bold } /* Literal.String.Interpol */ .page_body .sx { color: #008000 } /* Literal.String.Other */ .page_body .sr { color: #BB6688 } /* Literal.String.Regex */ .page_body .s1 { color: #BB4444 } /* Literal.String.Single */ .page_body .ss { color: #B8860B } /* Literal.String.Symbol */ .page_body .bp { color: #AA22FF } /* Name.Builtin.Pseudo */ .page_body .vc { color: #B8860B } /* Name.Variable.Class */ .page_body .vg { color: #B8860B } /* Name.Variable.Global */ .page_body .vi { color: #B8860B } /* Name.Variable.Instance */ .page_body .il { color: #666666 } /* Literal.Number.Integer.Long */ darcsweb-1.1/darcsweb.cgi0000644000175000017500000020344411101631641013502 0ustar albalb#!/usr/bin/env python """ darcsweb - A web interface for darcs Alberto Bertogli (albertito@blitiri.com.ar) Inspired on gitweb (as of 28/Jun/2005), which is written by Kay Sievers and Christian Gierke """ import time time_begin = time.time() import sys import os import string import stat import cgi import cgitb; cgitb.enable() import urllib import xml.sax from xml.sax.saxutils import escape as xml_escape time_imports = time.time() - time_begin iso_datetime = '%Y-%m-%dT%H:%M:%SZ' # In order to be able to store the config file in /etc/darcsweb, it has to be # added to sys.path. It's mainly used by distributions, which place the # default configuration there. Add it second place, so it goes after '.' but # before the normal path. This allows per-directory config files (desirable # for multiple darcsweb installations on the same machin), and avoids name # clashing if there's a config.py in the standard path. sys.path.insert(1, '/etc/darcsweb') # Similarly, when hosting multiple darcsweb instrances on the same # server, you can just 'SetEnv DARCSWEB_CONFPATH' in the httpd config, # and this will have a bigger priority than the system-wide # configuration file. if 'DARCSWEB_CONFPATH' in os.environ: sys.path.insert(1, os.environ['DARCSWEB_CONFPATH']) # empty configuration class, we will fill it in later depending on the repo class config: pass # list of run_darcs() invocations, for performance measures darcs_runs = [] # exception handling def exc_handle(t, v, tb): try: cache.cancel() except: pass cgitb.handler((t, v, tb)) sys.excepthook = exc_handle # # utility functions # def filter_num(s): l = [c for c in s if c in string.digits] return ''.join(l) allowed_in_action = string.ascii_letters + string.digits + '_' def filter_act(s): l = [c for c in s if c in allowed_in_action] return ''.join(l) allowed_in_hash = string.ascii_letters + string.digits + '-.' def filter_hash(s): l = [c for c in s if c in allowed_in_hash] return ''.join(l) def filter_file(s): if '..' in s or '"' in s: raise 'FilterFile FAILED' if s == '/': return s # remove extra "/"s r = s[0] last = s[0] for c in s[1:]: if c == last and c == '/': continue r += c last = c return r def printd(*params): print ' '.join(params), '
' # I _hate_ this. def fixu8(s): """Calls _fixu8(), which does the real work, line by line. Otherwise we choose the wrong encoding for big buffers and end up messing output.""" n = [] for i in s.split('\n'): n.append(_fixu8(i)) return '\n'.join(n) def _fixu8(s): if type(s) == unicode: return s.encode('utf8', 'replace') for e in config.repoencoding: try: return s.decode(e).encode('utf8', 'replace') except UnicodeDecodeError: pass raise 'DecodingError', config.repoencoding def escape(s): s = xml_escape(s) s = s.replace('"', '"') return s def how_old(epoch): if config.cachedir: # when we have a cache, the how_old() becomes a problem since # the cached entries will have old data; so in this case just # return a nice string t = time.localtime(epoch) s = time.strftime("%d %b %H:%M", t) return s age = int(time.time()) - int(epoch) if age > 60*60*24*365*2: s = str(age/60/60/24/365) s += " years ago" elif age > 60*60*24*(365/12)*2: s = str(age/60/60/24/(365/12)) s += " months ago" elif age > 60*60*24*7*2: s = str(age/60/60/24/7) s += " weeks ago" elif age > 60*60*24*2: s = str(age/60/60/24) s += " days ago" elif age > 60*60*2: s = str(age/60/60) s += " hours ago" elif age > 60*2: s = str(age/60) s += " minutes ago" elif age > 2: s = str(age) s += " seconds ago" else: s = "right now" return s def shorten_str(s, max = 60): if len(s) > max: s = s[:max - 4] + ' ...' return s def replace_tabs(s): pos = s.find("\t") while pos != -1: count = 8 - (pos % 8) if count: spaces = ' ' * count s = s.replace('\t', spaces, 1) pos = s.find("\t") return s def replace_links(s): """Replace user defined strings with links, as specified in the configuration file.""" import re vardict = { "myreponame": config.myreponame, "reponame": config.reponame, } for link_pat, link_dst in config.url_links: s = re.sub(link_pat, link_dst % vardict, s) return s def highlight(s, l): "Highlights appearences of s in l" import re # build the regexp by leaving "(s)", replacing '(' and ') first s = s.replace('\\', '\\\\') s = s.replace('(', '\\(') s = s.replace(')', '\\)') s = '(' + escape(s) + ')' try: pat = re.compile(s, re.I) repl = '\\1' l = re.sub(pat, repl, l) except: pass return l def fperms(fname): m = os.stat(fname)[stat.ST_MODE] m = m & 0777 s = [] if os.path.isdir(fname): s.append('d') else: s.append('-') if m & 0400: s.append('r') else: s.append('-') if m & 0200: s.append('w') else: s.append('-') if m & 0100: s.append('x') else: s.append('-') if m & 0040: s.append('r') else: s.append('-') if m & 0020: s.append('w') else: s.append('-') if m & 0010: s.append('x') else: s.append('-') if m & 0004: s.append('r') else: s.append('-') if m & 0002: s.append('w') else: s.append('-') if m & 0001: s.append('x') else: s.append('-') return ''.join(s) def fsize(fname): s = os.stat(fname)[stat.ST_SIZE] if s < 1024: return "%s" % s elif s < 1048576: return "%sK" % (s / 1024) elif s < 1073741824: return "%sM" % (s / 1048576) def isbinary(fname): import re bins = open(config.repodir + '/_darcs/prefs/binaries').readlines() bins = [b[:-1] for b in bins if b and b[0] != '#'] for b in bins: if re.compile(b).search(fname): return 1 return 0 def realpath(fname): realf = filter_file(config.repodir + '/_darcs/pristine/' + fname) if os.path.exists(realf): return realf realf = filter_file(config.repodir + '/_darcs/current/' + fname) if os.path.exists(realf): return realf realf = filter_file(config.repodir + '/' + fname) return realf def log_times(cache_hit, repo = None, event = None): if not config.logtimes: return time_total = time.time() - time_begin processing = time_total - time_imports if not event: event = action if cache_hit: event = event + " (hit)" s = '%s\n' % event if repo: s += '\trepo: %s\n' % repo s += """\ total: %.3f processing: %.3f imports: %.3f\n""" % (time_total, processing, time_imports) if darcs_runs: s += "\truns:\n" for params in darcs_runs: s += '\t\t%s\n' % params s += '\n' lf = open(config.logtimes, 'a') lf.write(s) lf.close() def parse_darcs_time(s): "Try to convert a darcs' time string into a Python time tuple." try: return time.strptime(s, "%Y%m%d%H%M%S") except ValueError: # very old darcs commits use a different format, for example: # "Wed May 21 19:39:10 CEST 2003" # we can't parse the "CEST" part reliably, so we leave it out fmt = "%a %b %d %H:%M:%S %Y" parts = s.split() ns = ' '.join(parts[0:4]) + ' ' + parts[5] return time.strptime(ns, fmt) # # generic html functions # def print_header(): print "Content-type: text/html; charset=utf-8" print """ darcs - %(reponame)s """ % { 'reponame': config.reponame, 'css': config.cssfile, 'url': config.myurl + '/' + config.myreponame, 'fav': config.darcsfav, 'logo': config.darcslogo, 'myname': config.myname, 'myreponame': config.myreponame, 'action': action } def print_footer(put_rss = 1): print """ \n\n" def print_navbar(h = "", f = ""): print """ ' def print_plain_header(): print "Content-type: text/plain; charset=utf-8\n" def print_binary_header(fname = None): import mimetypes if fname : (mime, enc) = mimetypes.guess_type(fname) else : mime = None if mime : print "Content-type: %s" % mime else : print "Content-type: application/octet-stream" if fname: print "Content-Disposition:attachment;filename=%s" % fname print def gen_authorlink(author, shortauthor=None): if not config.author_links: if shortauthor: return shortauthor else: return author if not shortauthor: shortauthor = author return '%s' % shortauthor # # basic caching # class Cache: def __init__(self, basedir, url): import sha self.basedir = basedir self.url = url self.fname = sha.sha(repr(url)).hexdigest() self.file = None self.mode = None self.real_stdout = sys.stdout def open(self): "Returns 1 on hit, 0 on miss" fname = self.basedir + '/' + self.fname if not os.access(fname, os.R_OK): # the file doesn't exist, direct miss pid = str(os.getpid()) fname = self.basedir + '/.' + self.fname + '-' + pid self.file = open(fname, 'w') self.mode = 'w' os.chmod(fname, stat.S_IRUSR | stat.S_IWUSR) # step over stdout so when "print" tries to write # output, we get it first sys.stdout = self return 0 inv = config.repodir + '/_darcs/patches' cache_lastmod = os.stat(fname).st_mtime repo_lastmod = os.stat(inv).st_mtime dw_lastmod = os.stat(sys.argv[0]).st_mtime if repo_lastmod > cache_lastmod or dw_lastmod > cache_lastmod: # the entry is too old, remove it and return a miss os.unlink(fname) pid = str(os.getpid()) fname = self.basedir + '/.' + self.fname + '-' + pid self.file = open(fname, 'w') self.mode = 'w' sys.stdout = self return 0 # the entry is still valid, hit! self.file = open(fname, 'r') self.mode = 'r' return 1 def dump(self): for l in self.file: self.real_stdout.write(l) def write(self, s): # this gets called from print, because we replaced stdout with # ourselves self.file.write(s) self.real_stdout.write(s) def close(self): if self.file: self.file.close() sys.stdout = self.real_stdout if self.mode == 'w': pid = str(os.getpid()) fname1 = self.basedir + '/.' + self.fname + '-' + pid fname2 = self.basedir + '/' + self.fname os.rename(fname1, fname2) self.mode = 'c' def cancel(self): "Like close() but don't save the entry." if self.file: self.file.close() sys.stdout = self.real_stdout if self.mode == 'w': pid = str(os.getpid()) fname = self.basedir + '/.' + self.fname + '-' + pid os.unlink(fname) self.mode = 'c' # # darcs repo manipulation # def repo_get_owner(): try: fd = open(config.repodir + '/_darcs/prefs/author') author = fd.readlines()[0].strip() except: author = None return author def run_darcs(params): """Runs darcs on the repodir with the given params, return a file object with its output.""" os.chdir(config.repodir) try: original_8bit_setting = os.environ['DARCS_DONT_ESCAPE_8BIT'] except KeyError: original_8bit_setting = None os.environ['DARCS_DONT_ESCAPE_8BIT'] = '1' cmd = config.darcspath + "darcs " + params inf, outf = os.popen4(cmd, 't') darcs_runs.append(params) if original_8bit_setting == None: del(os.environ['DARCS_DONT_ESCAPE_8BIT']) else: os.environ['DARCS_DONT_ESCAPE_8BIT'] = original_8bit_setting return outf class Patch: "Represents a single patch/record" def __init__(self): self.hash = '' self.author = '' self.shortauthor = '' self.date = 0 self.local_date = 0 self.name = '' self.comment = '' self.inverted = False; self.adds = [] self.removes = [] self.modifies = {} self.diradds = [] self.dirremoves = [] self.replaces = {} self.moves = {} def tostr(self): s = "%s\n\tAuthor: %s\n\tDate: %s\n\tHash: %s\n" % \ (self.name, self.author, self.date, self.hash) return s def getdiff(self): """Returns a list of lines from the diff -u corresponding with the patch.""" params = 'diff -u --match "hash %s"' % self.hash f = run_darcs(params) return f.readlines() def matches(self, s): "Defines if the patch matches a given string" if s.lower() in self.comment.lower(): return self.comment elif s.lower() in self.name.lower(): return self.name elif s.lower() in self.author.lower(): return self.author elif s == self.hash: return self.hash s = s.lower() for l in (self.adds, self.removes, self.modifies, self.diradds, self.dirremoves, self.replaces.keys(), self.moves.keys(), self.moves.keys() ): for i in l: if s in i.lower(): return i return '' class XmlInputWrapper: def __init__(self, fd): self.fd = fd self.times = 0 self._read = self.read def read(self, *args, **kwargs): self.times += 1 if self.times == 1: return '\n' s = self.fd.read(*args, **kwargs) if not s: return s return fixu8(s) def close(self, *args, **kwargs): return self.fd.close(*args, **kwargs) # patch parsing, we get them through "darcs changes --xml-output" class BuildPatchList(xml.sax.handler.ContentHandler): def __init__(self): self.db = {} self.list = [] self.cur_hash = '' self.cur_elem = None self.cur_val = '' self.cur_file = '' def startElement(self, name, attrs): # When you ask for changes to a given file, the xml output # begins with the patch that creates it is enclosed in a # "created_as" tag; then, later, it gets shown again in its # usual place. The following two "if"s take care of ignoring # everything inside the "created_as" tag, since we don't care. if name == 'created_as': self.cur_elem = 'created_as' return if self.cur_elem == 'created_as': return # now parse the tags normally if name == 'patch': p = Patch() p.hash = fixu8(attrs.get('hash')) au = attrs.get('author', None) p.author = fixu8(escape(au)) if au.find('<') != -1: au = au[:au.find('<')].strip() p.shortauthor = fixu8(escape(au)) td = parse_darcs_time(attrs.get('date', None)) p.date = time.mktime(td) p.date_str = time.strftime("%a, %d %b %Y %H:%M:%S", td) td = time.strptime(attrs.get('local_date', None), "%a %b %d %H:%M:%S %Z %Y") p.local_date = time.mktime(td) p.local_date_str = \ time.strftime("%a, %d %b %Y %H:%M:%S", td) inverted = attrs.get('inverted', None) if inverted and inverted == 'True': p.inverted = True self.db[p.hash] = p self.current = p.hash self.list.append(p.hash) elif name == 'name': self.db[self.current].name = '' self.cur_elem = 'name' elif name == 'comment': self.db[self.current].comment = '' self.cur_elem = 'comment' elif name == 'add_file': self.cur_elem = 'add_file' elif name == 'remove_file': self.cur_elem = 'remove_file' elif name == 'add_directory': self.cur_elem = 'add_directory' elif name == 'remove_directory': self.cur_elem = 'remove_dir' elif name == 'modify_file': self.cur_elem = 'modify_file' elif name == 'removed_lines': if self.cur_val: self.cur_file = fixu8(self.cur_val.strip()) cf = self.cur_file p = self.db[self.current] # the current value holds the file name at this point if not p.modifies.has_key(cf): p.modifies[cf] = { '+': 0, '-': 0 } p.modifies[cf]['-'] = int(attrs.get('num', None)) elif name == 'added_lines': if self.cur_val: self.cur_file = fixu8(self.cur_val.strip()) cf = self.cur_file p = self.db[self.current] if not p.modifies.has_key(cf): p.modifies[cf] = { '+': 0, '-': 0 } p.modifies[cf]['+'] = int(attrs.get('num', None)) elif name == 'move': src = fixu8(attrs.get('from', None)) dst = fixu8(attrs.get('to', None)) p = self.db[self.current] p.moves[src] = dst elif name == 'replaced_tokens': if self.cur_val: self.cur_file = fixu8(self.cur_val.strip()) cf = self.cur_file p = self.db[self.current] if not p.replaces.has_key(cf): p.replaces[cf] = 0 p.replaces[cf] = int(attrs.get('num', None)) else: self.cur_elem = None def characters(self, s): if not self.cur_elem: return self.cur_val += s def endElement(self, name): # See the comment in startElement() if name == 'created_as': self.cur_elem = None self.cur_val = '' return if self.cur_elem == 'created_as': return if name == 'replaced_tokens': return if name == 'name': p = self.db[self.current] p.name = fixu8(self.cur_val) if p.inverted: p.name = 'UNDO: ' + p.name elif name == 'comment': self.db[self.current].comment = fixu8(self.cur_val) elif name == 'add_file': scv = fixu8(self.cur_val.strip()) self.db[self.current].adds.append(scv) elif name == 'remove_file': scv = fixu8(self.cur_val.strip()) self.db[self.current].removes.append(scv) elif name == 'add_directory': scv = fixu8(self.cur_val.strip()) self.db[self.current].diradds.append(scv) elif name == 'remove_directory': scv = fixu8(self.cur_val.strip()) self.db[self.current].dirremoves.append(scv) elif name == 'modify_file': if not self.cur_file: # binary modification appear without a line # change summary, so we add it manually here f = fixu8(self.cur_val.strip()) p = self.db[self.current] p.modifies[f] = { '+': 0, '-': 0, 'b': 1 } self.cur_file = '' self.cur_elem = None self.cur_val = '' def get_list(self): plist = [] for h in self.list: plist.append(self.db[h]) return plist def get_db(self): return self.db def get_list_db(self): return (self.list, self.db) def get_changes_handler(params): "Returns a handler for the changes output, run with the given params" parser = xml.sax.make_parser() handler = BuildPatchList() parser.setContentHandler(handler) # get the xml output and parse it xmlf = run_darcs("changes --xml-output " + params) parser.parse(XmlInputWrapper(xmlf)) xmlf.close() return handler def get_last_patches(last = 15, topi = 0, fname = None): """Gets the last N patches from the repo, returns a patch list. If "topi" is specified, then it will return the N patches that preceeded the patch number topi in the list. It sounds messy but it's quite simple. You can optionally pass a filename and only changes that affect it will be returned. FIXME: there's probably a more efficient way of doing this.""" # darcs calculate last first, and then filters the filename, # so it's not so simple to combine them; that's why we do so much # special casing here toget = last + topi if fname: if fname[0] == '/': fname = fname[1:] s = '-s "%s"' % fname else: s = "-s --last=%d" % toget handler = get_changes_handler(s) # return the list of all the patch objects return handler.get_list()[topi:toget] def get_patch(hash): handler = get_changes_handler('-s --match "hash %s"' % hash) patch = handler.db[handler.list[0]] return patch def get_diff(hash): return run_darcs('diff -u --match "hash %s"' % hash) def get_file_diff(hash, fname): return run_darcs('diff -u --match "hash %s" "%s"' % (hash, fname)) def get_file_headdiff(hash, fname): return run_darcs('diff -u --from-match "hash %s" "%s"' % (hash, fname)) def get_patch_headdiff(hash): return run_darcs('diff -u --from-match "hash %s"' % hash) def get_raw_diff(hash): import gzip realf = filter_file(config.repodir + '/_darcs/patches/' + hash) if not os.path.isfile(realf): return None file = open(realf, 'rb') if file.read(2) == '\x1f\x8b': # file begins with gzip magic file.close() dsrc = gzip.open(realf) else: file.seek(0) dsrc = file return dsrc def get_darcs_diff(hash, fname = None): cmd = 'changes -v --matches "hash %s"' % hash if fname: cmd += ' "%s"' % fname return run_darcs(cmd) def get_darcs_headdiff(hash, fname = None): cmd = 'changes -v --from-match "hash %s"' % hash if fname: cmd += ' "%s"' % fname return run_darcs(cmd) class Annotate: def __init__(self): self.fname = "" self.creator_hash = "" self.created_as = "" self.lastchange_hash = "" self.lastchange_author = "" self.lastchange_name = "" self.lastchange_date = None self.firstdate = None self.lastdate = None self.lines = [] self.patches = {} class Line: def __init__(self): self.text = "" self.phash = None self.pauthor = None self.pdate = None def parse_annotate(src): import xml.dom.minidom annotate = Annotate() # FIXME: convert the source to UTF8; it _has_ to be a way to let # minidom know the source encoding s = "" for i in src: s += fixu8(i) dom = xml.dom.minidom.parseString(s) file = dom.getElementsByTagName("file")[0] annotate.fname = fixu8(file.getAttribute("name")) createinfo = dom.getElementsByTagName("created_as")[0] annotate.created_as = fixu8(createinfo.getAttribute("original_name")) creator = createinfo.getElementsByTagName("patch")[0] annotate.creator_hash = fixu8(creator.getAttribute("hash")) mod = dom.getElementsByTagName("modified")[0] lastpatch = mod.getElementsByTagName("patch")[0] annotate.lastchange_hash = fixu8(lastpatch.getAttribute("hash")) annotate.lastchange_author = fixu8(lastpatch.getAttribute("author")) lastname = lastpatch.getElementsByTagName("name")[0] lastname = lastname.childNodes[0].wholeText annotate.lastchange_name = fixu8(lastname) lastdate = parse_darcs_time(lastpatch.getAttribute("date")) annotate.lastchange_date = lastdate annotate.patches[annotate.lastchange_hash] = annotate.lastchange_date # these will be overriden by the real dates later annotate.firstdate = lastdate annotate.lastdate = 0 file = dom.getElementsByTagName("file")[0] for l in file.childNodes: # we're only intrested in normal and added lines if l.nodeName not in ["normal_line", "added_line"]: continue line = Annotate.Line() if l.nodeName == "normal_line": patch = l.getElementsByTagName("patch")[0] phash = patch.getAttribute("hash") pauthor = patch.getAttribute("author") pdate = patch.getAttribute("date") pdate = parse_darcs_time(pdate) else: # added lines inherit the creation from the annotate # patch phash = annotate.lastchange_hash pauthor = annotate.lastchange_author pdate = annotate.lastchange_date text = "" for node in l.childNodes: if node.nodeType == node.TEXT_NODE: text += node.wholeText # strip all "\n"s at the beginning; because the way darcs # formats the xml output it makes the DOM parser to add "\n"s # in front of it text = text.lstrip("\n") line.text = fixu8(text) line.phash = fixu8(phash) line.pauthor = fixu8(pauthor) line.pdate = pdate annotate.lines.append(line) annotate.patches[line.phash] = line.pdate if pdate > annotate.lastdate: annotate.lastdate = pdate if pdate < annotate.firstdate: annotate.firstdate = pdate return annotate def get_annotate(fname, hash = None): if config.disable_annotate: return None cmd = 'annotate --xml-output' if hash: cmd += ' --match="hash %s"' % hash if fname.startswith('/'): # darcs 2 doesn't like files starting with /, and darcs 1 # doesn't really care fname = fname[1:] cmd += ' "%s"' % fname return parse_annotate(run_darcs(cmd)) # # specific html functions # def print_diff(dsrc): for l in dsrc: l = fixu8(l) # remove the trailing newline if len(l) > 1: l = l[:-1] if l.startswith('diff'): # file lines, they have their own class print '
%s
' % escape(l) continue color = "" if l[0] == '+': color = 'style="color:#008800;"' elif l[0] == '-': color = 'style="color:#cc0000;"' elif l[0] == '@': color = 'style="color:#990099; ' color += 'border: solid #ffe0ff; ' color += 'border-width: 1px 0px 0px 0px; ' color += 'margin-top: 2px;"' elif l.startswith('Files'): # binary differences color = 'style="color:#666;"' print '
' % color + escape(l) + '
' def print_darcs_diff(dsrc): for l in dsrc: l = fixu8(l) if not l.startswith(" "): # comments and normal stuff print '
' + escape(l) + "
" continue l = l.strip() if not l: continue if l[0] == '+': cl = 'class="pre" style="color:#008800;"' elif l[0] == '-': cl = 'class="pre" style="color:#cc0000;"' else: cl = 'class="diff_info"' print '
' % cl + escape(l) + '
' def print_shortlog(last = 50, topi = 0, fname = None): ps = get_last_patches(last, topi, fname) if fname: title = '' % \ (config.myreponame, fname) title += 'History for path %s' % escape(fname) title += '' else: title = 'shortlog' \ % config.myreponame print '
%s
' % title print '' if topi != 0: # put a link to the previous page ntopi = topi - last if ntopi < 0: ntopi = 0 print '' alt = True for p in ps: if p.name.startswith("TAG "): print '' elif alt: print '' else: print '' alt = not alt print """ """ % { 'age': how_old(p.local_date), 'author': gen_authorlink(p.author, shorten_str(p.shortauthor, 26)), 'myrname': config.myreponame, 'hash': p.hash, 'name': escape(shorten_str(p.name)), 'fullname': escape(p.name), } print "" if len(ps) >= last: # only show if we've not shown them all already print '' print "
' if fname: print '...' \ % (config.myreponame, ntopi, fname) else: print '...' \ % (config.myreponame, ntopi) print '
%(age)s %(author)s %(name)s
' if fname: print '...' \ % (config.myreponame, topi + last, fname) else: print '...' \ % (config.myreponame, topi + last) print '
" def print_log(last = 50, topi = 0): ps = get_last_patches(last, topi) if topi != 0: # put a link to the previous page ntopi = topi - last if ntopi < 0: ntopi = 0 print '

<- Prev

' % \ (config.myreponame, ntopi) for p in ps: if p.comment: comment = replace_links(escape(p.comment)) fmt_comment = comment.replace('\n', '
') + '\n' fmt_comment += '

' else: fmt_comment = '' print """

%(author)s [%(date)s]
%(desc)s

%(comment)s
""" % { 'myreponame': config.myreponame, 'age': how_old(p.local_date), 'date': p.local_date_str, 'author': gen_authorlink(p.author, p.shortauthor), 'hash': p.hash, 'desc': escape(p.name), 'comment': fmt_comment } if len(ps) >= last: # only show if we've not shown them all already print '

Next ->

' % \ (config.myreponame, topi + last) def print_blob(fname): print '
%s
' % escape(fname) if isbinary(fname): print """
This is a binary file and its contents will not be displayed.
""" return try: import pygments except ImportError: pygments = False if not pygments: print_blob_simple(fname) return else: try: print_blob_highlighted(fname) except ValueError: # pygments couldn't guess a lexer to highlight the code, try # another method with sampling the file contents. try: print_blob_highlighted(fname, sample_code=True) except ValueError: # pygments really could not find any lexer for this file. print_blob_simple(fname) def print_blob_simple(fname): print '
' f = open(realpath(fname), 'r') count = 1 for l in f: l = fixu8(escape(l)) if l and l[-1] == '\n': l = l[:-1] l = replace_tabs(l) print """\
\ %(c)4d %(l)s\
\ """ % { 'c': count, 'l': l } count += 1 print '
' def print_blob_highlighted(fname, sample_code=False): import pygments import pygments.lexers import pygments.formatters code = open(realpath(fname), 'r').read() if sample_code: lexer = pygments.lexers.guess_lexer(code[:200], encoding=config.repoencoding[0]) else: lexer = pygments.lexers.guess_lexer_for_filename(fname, code[:200], encoding=config.repoencoding[0]) pygments_version = map(int, pygments.__version__.split('.')) if pygments_version >= [0, 7]: linenos_method = 'inline' else: linenos_method = True formatter = pygments.formatters.HtmlFormatter(linenos=linenos_method, cssclass='page_body') print pygments.highlight(code, lexer, formatter) def print_annotate(ann, style): print '
' if isbinary(ann.fname): print """ This is a binary file and its contents will not be displayed.
""" return if style == 'shade': # here's the idea: we will assign to each patch a shade of # color from its date (newer gets darker) max = 0xff min = max - 80 # to do that, we need to get a list of the patch hashes # ordered by their dates l = [ (date, hash) for (hash, date) in ann.patches.items() ] l.sort() l = [ hash for (date, hash) in l ] # now we have to map each element to a number in the range # min-max, with max being close to l[0] and min l[len(l) - 1] lenn = max - min lenl = len(l) shadetable = {} for i in range(0, lenl): hash = l[i] n = float(i * lenn) / lenl n = max - int(round(n)) shadetable[hash] = n elif style == "zebra": lineclass = 'dark' count = 1 prevhash = None for l in ann.lines: text = escape(l.text) text = text.rstrip() text = replace_tabs(text) plongdate = time.strftime("%Y-%m-%d %H:%M:%S", l.pdate) title = "%s by %s" % (plongdate, escape(l.pauthor) ) link = "%(myrname)s;a=commit;h=%(hash)s" % { 'myrname': config.myreponame, 'hash': l.phash } if style == "shade": linestyle = 'style="background-color:#ffff%.2x"' % \ shadetable[l.phash] lineclass = '' elif style == "zebra": linestyle = '' if l.phash != prevhash: if lineclass == 'dark': lineclass = 'light' else: lineclass = 'dark' else: linestyle = '' lineclass = '' if l.phash != prevhash: pdate = time.strftime("%Y-%m-%d", l.pdate) left = l.pauthor.find('<') right = l.pauthor.find('@') if left != -1 and right != -1: shortau = l.pauthor[left + 1:right] elif l.pauthor.find(" ") != -1: shortau = l.pauthor[:l.pauthor.find(" ")] elif right != -1: shortau = l.pauthor[:right] else: shortau = l.pauthor desc = "%12.12s" % shortau date = "%-10.10s" % pdate prevhash = l.phash line = 1 else: if line == 1 and style in ["shade", "zebra"]: t = "%s " % time.strftime("%H:%M:%S", l.pdate) desc = "%12.12s" % "'" date = "%-10.10s" % t else: desc = "%12.12s" % "'" date = "%-10.10s" % "" line += 1 print """\ \ """ % { 'class': lineclass, 'style': linestyle, 'date': date, 'desc': escape(desc), 'c': count, 'text': text, 'title': title, 'link': link } count += 1 print '' # # available actions # def do_summary(): print_header() print_navbar() owner = repo_get_owner() # we should optimize this, it's a pity to go in such a mess for just # one hash ps = get_last_patches(1) print '
 
' print '' print ' ' % \ escape(config.repodesc) if owner: print ' ' % escape(owner) if len(ps) > 0: print ' ' % \ ps[0].local_date_str print ' ' %\ { 'url': config.repourl } if config.repoprojurl: print ' ' print ' ' % \ { 'url': config.repoprojurl } print '
description%s
owner%s
last change%s
url%(url)s
project url%(url)s
' print_shortlog(15) print_footer() def do_commitdiff(phash): print_header() print_navbar(h = phash) p = get_patch(phash) print """ """ % { 'myreponame': config.myreponame, 'hash': p.hash, 'name': escape(p.name), } dsrc = p.getdiff() print_diff(dsrc) print_footer() def do_plain_commitdiff(phash): print_plain_header() dsrc = get_diff(phash) for l in dsrc: sys.stdout.write(fixu8(l)) def do_darcs_commitdiff(phash): print_header() print_navbar(h = phash) p = get_patch(phash) print """ """ % { 'myreponame': config.myreponame, 'hash': p.hash, 'name': escape(p.name), } dsrc = get_darcs_diff(phash) print_darcs_diff(dsrc) print_footer() def do_raw_commitdiff(phash): print_plain_header() dsrc = get_raw_diff(phash) if not dsrc: print "Error opening file!" return for l in dsrc: sys.stdout.write(l) def do_headdiff(phash): print_header() print_navbar(h = phash) p = get_patch(phash) print """ """ % { 'myreponame': config.myreponame, 'hash': p.hash, 'name': escape(p.name), } dsrc = get_patch_headdiff(phash) print_diff(dsrc) print_footer() def do_plain_headdiff(phash): print_plain_header() dsrc = get_patch_headdiff(phash) for l in dsrc: sys.stdout.write(fixu8(l)) def do_darcs_headdiff(phash): print_header() print_navbar(h = phash) p = get_patch(phash) print """ """ % { 'myreponame': config.myreponame, 'hash': p.hash, 'name': escape(p.name), } dsrc = get_darcs_headdiff(phash) print_darcs_diff(dsrc) print_footer() def do_raw_headdiff(phash): print_plain_header() dsrc = get_darcs_headdiff(phash) for l in dsrc: sys.stdout.write(l) def do_filediff(phash, fname): print_header() print_navbar(h = phash, f = fname) p = get_patch(phash) dsrc = get_file_diff(phash, fname) print """
%(fname)s
""" % { 'myreponame': config.myreponame, 'hash': p.hash, 'name': escape(p.name), 'fname': escape(fname), } print_diff(dsrc) print_footer() def do_plain_filediff(phash, fname): print_plain_header() dsrc = get_file_diff(phash, fname) for l in dsrc: sys.stdout.write(fixu8(l)) def do_darcs_filediff(phash, fname): print_header() print_navbar(h = phash, f = fname) p = get_patch(phash) print """
%(fname)s
""" % { 'myreponame': config.myreponame, 'hash': p.hash, 'name': escape(p.name), 'fname': escape(fname), } dsrc = get_darcs_diff(phash, fname) print_darcs_diff(dsrc) print_footer() def do_file_headdiff(phash, fname): print_header() print_navbar(h = phash, f = fname) p = get_patch(phash) dsrc = get_file_headdiff(phash, fname) print """
%(fname)s
""" % { 'myreponame': config.myreponame, 'hash': p.hash, 'name': escape(p.name), 'fname': escape(fname), } print_diff(dsrc) print_footer() def do_plain_fileheaddiff(phash, fname): print_plain_header() dsrc = get_file_headdiff(phash, fname) for l in dsrc: sys.stdout.write(fixu8(l)) def do_darcs_fileheaddiff(phash, fname): print_header() print_navbar(h = phash, f = fname) p = get_patch(phash) print """
%(fname)s
""" % { 'myreponame': config.myreponame, 'hash': p.hash, 'name': escape(p.name), 'fname': escape(fname), } dsrc = get_darcs_headdiff(phash, fname) print_darcs_diff(dsrc) print_footer() print_plain_header() print "Not yet implemented" def do_commit(phash): print_header() print_navbar(h = phash) p = get_patch(phash) print """
author%(author)s
local date%(local_date)s
date%(date)s
hash%(hash)s
""" % { 'myreponame': config.myreponame, 'author': gen_authorlink(p.author), 'local_date': p.local_date_str, 'date': p.date_str, 'hash': p.hash, 'name': escape(p.name), } if p.comment: comment = replace_links(escape(p.comment)) c = comment.replace('\n', '
\n') print '
' print replace_links(escape(p.name)), '

' print c print '
' changed = p.adds + p.removes + p.modifies.keys() + p.moves.keys() + \ p.diradds + p.dirremoves + p.replaces.keys() if changed or p.moves: n = len(changed) print '
%d file(s) changed:
' % n print '' changed.sort() alt = True for f in changed: if alt: print '' else: print '' alt = not alt show_diff = 1 if p.moves.has_key(f): # don't show diffs for moves, they're broken as of # darcs 1.0.3 show_diff = 0 if show_diff: print """ """ % { 'myreponame': config.myreponame, 'hash': p.hash, 'file': urllib.quote(f), 'fname': escape(f), } else: print "" % f show_diff = 1 if f in p.adds: print '' elif f in p.diradds: print '' elif f in p.removes: print '' elif f in p.dirremoves: print '' elif p.replaces.has_key(f): print '' elif p.moves.has_key(f): print '' show_diff = 0 else: print '' if show_diff: print """ """ % { 'myreponame': config.myreponame, 'hash': p.hash, 'file': urllib.quote(f) } print '' print '
%(fname)s %s', print '[added]', print '', print '[added dir]', print '', print '[removed]', print '', print '[removed dir]', print '', print '[replaced %d tokens]' % p.replaces[f], print '', print '[moved to "%s"]' % p.moves[f] print '', if p.modifies[f].has_key('b'): # binary modification print '(binary)' else: print '+%(+)d -%(-)d' % p.modifies[f], print '
' print_footer() def do_tree(dname): print_header() print_navbar() # the head print """
""" % config.myreponame # and the linked, with links parts = dname.split('/') print '/ ' sofar = '/' for p in parts: if not p: continue sofar += '/' + p print '%s /' % \ (config.myreponame, urllib.quote(sofar), p) print """
""" path = realpath(dname) + '/' alt = True files = os.listdir(path) files.sort() # list directories first dlist = [] flist = [] for f in files: if f == "_darcs": continue realfile = path + f if os.path.isdir(realfile): dlist.append(f) else: flist.append(f) files = dlist + flist for f in files: if alt: print '' else: print '' alt = not alt realfile = path + f fullf = filter_file(dname + '/' + f) print '' print '' if f in dlist: print """ """ % { 'myrname': config.myreponame, 'f': escape(f), 'fullf': urllib.quote(fullf), } else: print """ """ % { 'myrname': config.myreponame, 'f': escape(f), 'fullf': urllib.quote(fullf), } print '' print '
', fperms(realfile), print '', fsize(realfile), print ' %(f)s/ %(f)s
' print_footer() def do_headblob(fname): print_header() print_navbar(f = fname) filepath = os.path.dirname(fname) if filepath == '/': print '' % \ (config.myreponame) else: print '
' # and the linked, with links parts = filepath.split('/') print '/ ' sofar = '/' for p in parts: if not p: continue sofar += '/' + p print '%s /' % \ (config.myreponame, sofar, p) print '
' print_blob(fname) print_footer() def do_plainblob(fname): f = open(realpath(fname), 'r') if isbinary(fname): print_binary_header(os.path.basename(fname)) for l in f: sys.stdout.write(l) else: print_plain_header() for l in f: sys.stdout.write(fixu8(l)) def do_annotate(fname, phash, style): print_header() ann = get_annotate(fname, phash) if not ann: print """ The annotate feature has been disabled """ print_footer() return print_navbar(f = fname, h = ann.lastchange_hash) print """
Annotate for file %(fname)s
""" % { 'myreponame': config.myreponame, 'hash': ann.lastchange_hash, 'name': escape(ann.lastchange_name), 'fname': escape(fname), } print_annotate(ann, style) print_footer() def do_annotate_plain(fname, phash): print_plain_header() ann = get_annotate(fname, phash) for l in ann.lines: sys.stdout.write(l.text) def do_shortlog(topi): print_header() print_navbar() print_shortlog(topi = topi) print_footer() def do_filehistory(topi, f): print_header() print_navbar(f = fname) print_shortlog(topi = topi, fname = fname) print_footer() def do_log(topi): print_header() print_navbar() print_log(topi = topi) print_footer() def do_atom(): print "Content-type: application/atom+xml; charset=utf-8\n" print '' inv = config.repodir + '/_darcs/patches' repo_lastmod = os.stat(inv).st_mtime str_lastmod = time.strftime(iso_datetime, time.localtime(repo_lastmod)) print """ %(reponame)s darcs repository %(url)s darcs repository (several authors) darcsweb.cgi %(lastmod)s %(desc)s """ % { 'reponame': config.reponame, 'url': config.myurl + '/' + config.myreponame, 'desc': escape(config.repodesc), 'lastmod': str_lastmod, } ps = get_last_patches(20) for p in ps: title = time.strftime('%d %b %H:%M', time.localtime(p.date)) title += ' - ' + p.name pdate = time.strftime(iso_datetime, time.localtime(p.date)) link = '%s/%s;a=commit;h=%s' % (config.myurl, config.myreponame, p.hash) import email.Utils addr, author = email.Utils.parseaddr(p.author) if not addr: addr = "unknown_email@example.com" if not author: author = addr print """ %(title)s %(author)s %(email)s %(pdate)s %(link)s %(desc)s

""" % { 'title': escape(title), 'author': author, 'email': addr, 'url': config.myurl + '/' + config.myreponame, 'pdate': pdate, 'myrname': config.myreponame, 'hash': p.hash, 'pname': escape(p.name), 'link': link, 'desc': escape(p.name), } # TODO: allow to get plain text, not HTML? print escape(p.name) + '
' if p.comment: print '
' print escape(p.comment).replace('\n', '
\n') print '
' print '
' changed = p.adds + p.removes + p.modifies.keys() + \ p.moves.keys() + p.diradds + p.dirremoves + \ p.replaces.keys() for i in changed: # TODO: link to the file print '%s
' % i print '

' print '
' print '
' def do_rss(): print "Content-type: text/xml; charset=utf-8\n" print '' print """ %(reponame)s %(url)s %(desc)s en """ % { 'reponame': config.reponame, 'url': config.myurl + '/' + config.myreponame, 'desc': escape(config.repodesc), } ps = get_last_patches(20) for p in ps: title = time.strftime('%d %b %H:%M', time.localtime(p.date)) title += ' - ' + p.name pdate = time.strftime("%a, %d %b %Y %H:%M:%S +0000", time.localtime(p.date)) link = '%s/%s;a=commit;h=%s' % (config.myurl, config.myreponame, p.hash) # the author field is tricky because the standard requires it # has an email address; so we need to check that and lie # otherwise; there's more info at # http://feedvalidator.org/docs/error/InvalidContact.html if "@" in p.author: author = p.author else: author = "%s <unknown@email>" % p.author print """ %(title)s %(author)s %(pdate)s %(link)s %(desc)s """ % { 'title': escape(title), 'author': author, 'pdate': pdate, 'link': link, 'desc': escape(p.name), } print ' ' if p.comment: print '
' print escape(p.comment).replace('\n', '
\n') print '
' print '
' changed = p.adds + p.removes + p.modifies.keys() + \ p.moves.keys() + p.diradds + p.dirremoves + \ p.replaces.keys() for i in changed: print '%s
' % i print ']]>' print '
' print '
' def do_search(s): print_header() print_navbar() ps = get_last_patches(config.searchlimit) print '
Search last %d commits for "%s"
' \ % (config.searchlimit, escape(s)) print '' alt = True for p in ps: match = p.matches(s) if not match: continue if alt: print '' else: print '' alt = not alt print """ """ % { 'age': how_old(p.local_date), 'author': gen_authorlink(p.author, shorten_str(p.shortauthor, 26)), 'myrname': config.myreponame, 'hash': p.hash, 'name': escape(shorten_str(p.name)), 'fullname': escape(p.name), 'match': highlight(s, shorten_str(match)), } print "" print '
%(age)s %(author)s %(name)s
%(match)s
' print_footer() def do_die(): print_header() print "

Error! Malformed query

" print_footer() def do_listrepos(): import config as all_configs expand_multi_config(all_configs) # the header here is special since we don't have a repo print "Content-type: text/html; charset=utf-8\n" print '' print """ darcs - Repositories
%(summary)s
""" % { 'myname': config.myname, 'css': config.cssfile, 'fav': config.darcsfav, 'logo': config.darcslogo, 'summary': config.summary } # some python magic alt = True for conf in dir(all_configs): if conf.startswith('__'): continue c = all_configs.__getattribute__(conf) if 'reponame' not in dir(c): continue name = escape(c.reponame) desc = escape(c.repodesc) if alt: print '' else: print '' alt = not alt print """ """ % { 'myname': config.myname, 'dname': name, 'name': urllib.quote(name), 'desc': shorten_str(desc, 60) } print "
Project Description
%(dname)s %(desc)s
" print_footer(put_rss = 0) def expand_multi_config(config): """Expand configuration entries that serve as "template" to others; this make it easier to have a single directory with all the repos, because they don't need specific entries in the configuration anymore. """ for conf in dir(config): if conf.startswith('__'): continue c = config.__getattribute__(conf) if 'multidir' not in dir(c): continue if not os.path.isdir(c.multidir): continue if 'exclude' not in dir(c): c.exclude = [] entries = [] if 'multidir_deep' in dir(c) and c.multidir_deep: for (root, dirs, files) in os.walk(c.multidir): # do not visit hidden directories dirs[:] = [d for d in dirs \ if not d.startswith('.')] if '_darcs' in dirs: p = root[1 + len(c.multidir):] entries.append(p) else: entries = os.listdir(c.multidir) entries.sort() for name in entries: name = name.replace('\\', '/') if name.startswith('.'): continue fulldir = c.multidir + '/' + name if not os.path.isdir(fulldir + '/_darcs'): continue if name in c.exclude: continue # set the display name at the beginning, so it can be # used by the other replaces if 'displayname' in dir(c): dname = c.displayname % { 'name': name } else: dname = name rep_dict = { 'name': name, 'dname': dname } if 'autoexclude' in dir(c) and c.autoexclude: dpath = fulldir + \ '/_darcs/third_party/darcsweb' if not os.path.isdir(dpath): continue if 'autodesc' in dir(c) and c.autodesc: dpath = fulldir + \ '/_darcs/third_party/darcsweb/desc' if os.access(dpath, os.R_OK): desc = open(dpath).readline().rstrip("\n") else: desc = c.repodesc % rep_dict else: desc = c.repodesc % rep_dict if 'autourl' in dir(c) and c.autourl: dpath = fulldir + \ '/_darcs/third_party/darcsweb/url' if os.access(dpath, os.R_OK): url = open(dpath).readline().rstrip("\n") else: url = c.repourl % rep_dict else: url = c.repourl % rep_dict if 'autoprojurl' in dir(c) and c.autoprojurl: dpath = fulldir + \ '/_darcs/third_party/darcsweb/projurl' if os.access(dpath, os.R_OK): projurl = open(dpath).readline().rstrip("\n") elif 'repoprojurl' in dir(c): projurl = c.repoprojurl % rep_dict else: projurl = None elif 'repoprojurl' in dir(c): projurl = c.repoprojurl % rep_dict else: projurl = None rdir = fulldir class tmp_config: reponame = dname repodir = rdir repodesc = desc repourl = url repoencoding = c.repoencoding repoprojurl = projurl if 'footer' in dir(c): footer = c.footer # index by display name to avoid clashes config.__setattr__(dname, tmp_config) def fill_config(name = None): import config as all_configs expand_multi_config(all_configs) if name: # we only care about setting some configurations if a repo was # specified; otherwise we only set the common configuration # directives for conf in dir(all_configs): if conf.startswith('__'): continue c = all_configs.__getattribute__(conf) if 'reponame' not in dir(c): continue if c.reponame == name: break else: # not found raise "RepoNotFound", name # fill the configuration base = all_configs.base if 'myname' not in dir(base): # SCRIPT_NAME has the full path, we only take the file name config.myname = os.path.basename(os.environ['SCRIPT_NAME']) else: config.myname = base.myname if 'myurl' not in dir(base) and 'cachedir' not in dir(base): n = os.environ['SERVER_NAME'] p = os.environ['SERVER_PORT'] s = os.path.dirname(os.environ['SCRIPT_NAME']) u = os.environ.get('HTTPS', 'off') in ('on', '1') if not u and p == '80' or u and p == '443': p = '' else: p = ':' + p config.myurl = 'http%s://%s%s%s' % (u and 's' or '', n, p, s) else: config.myurl = base.myurl config.darcslogo = base.darcslogo config.darcsfav = base.darcsfav config.cssfile = base.cssfile if name: config.myreponame = config.myname + '?r=' + urllib.quote(name) config.reponame = c.reponame config.repodesc = c.repodesc config.repodir = c.repodir config.repourl = c.repourl config.repoprojurl = None if 'repoprojurl' in dir(c): config.repoprojurl = c.repoprojurl # repoencoding must be a tuple if isinstance(c.repoencoding, str): config.repoencoding = (c.repoencoding, ) else: config.repoencoding = c.repoencoding # optional parameters if "darcspath" in dir(base): config.darcspath = base.darcspath + '/' else: config.darcspath = "" if "summary" in dir(base): config.summary = base.summary else: config.summary = """ This is the repository index for a darcsweb site.
These are all the available repositories.
""" if "cachedir" in dir(base): config.cachedir = base.cachedir else: config.cachedir = None if "searchlimit" in dir(base): config.searchlimit = base.searchlimit else: config.searchlimit = 100 if "logtimes" in dir(base): config.logtimes = base.logtimes else: config.logtimes = None if "url_links" in dir(base): config.url_links = base.url_links else: config.url_links = () if name and "footer" in dir(c): config.footer = c.footer elif "footer" in dir(base): config.footer = base.footer else: config.footer = "Crece desde el pueblo el futuro / " \ + "crece desde el pie" if "author_links" in dir(base): config.author_links = base.author_links else: config.author_links = None if "disable_annotate" in dir(base): config.disable_annotate = base.disable_annotate else: config.disable_annotate = False # # main # if sys.version_info < (2, 3): print "Sorry, but Python 2.3 or above is required to run darcsweb." sys.exit(1) form = cgi.FieldStorage() # if they don't specify a repo, print the list and exit if not form.has_key('r'): fill_config() do_listrepos() log_times(cache_hit = 0, event = 'index') sys.exit(0) # get the repo configuration and fill the config class current_repo = urllib.unquote(form['r'].value) fill_config(current_repo) # get the action, or default to summary if not form.has_key("a"): action = "summary" else: action = filter_act(form["a"].value) # check if we have the page in the cache if config.cachedir: url_request = os.environ['QUERY_STRING'] # create a string representation of the request, ignoring all the # unused parameters to avoid DoS params = ['r', 'a', 'f', 'h', 'topi'] params = [ x for x in form.keys() if x in params ] url_request = [ (x, form[x].value) for x in params ] url_request.sort() cache = Cache(config.cachedir, url_request) if cache.open(): # we have a hit, dump and run cache.dump() cache.close() log_times(cache_hit = 1, repo = config.reponame) sys.exit(0) # if there is a miss, the cache will step over stdout, intercepting # all "print"s and writing them to the cache file automatically # see what should we do according to the received action if action == "summary": do_summary() elif action == "commit": phash = filter_hash(form["h"].value) do_commit(phash) elif action == "commitdiff": phash = filter_hash(form["h"].value) do_commitdiff(phash) elif action == "plain_commitdiff": phash = filter_hash(form["h"].value) do_plain_commitdiff(phash) elif action == "darcs_commitdiff": phash = filter_hash(form["h"].value) do_darcs_commitdiff(phash) elif action == "raw_commitdiff": phash = filter_hash(form["h"].value) do_raw_commitdiff(phash) elif action == 'headdiff': phash = filter_hash(form["h"].value) do_headdiff(phash) elif action == "plain_headdiff": phash = filter_hash(form["h"].value) do_plain_headdiff(phash) elif action == "darcs_headdiff": phash = filter_hash(form["h"].value) do_darcs_headdiff(phash) elif action == "filediff": phash = filter_hash(form["h"].value) fname = filter_file(form["f"].value) do_filediff(phash, fname) elif action == "plain_filediff": phash = filter_hash(form["h"].value) fname = filter_file(form["f"].value) do_plain_filediff(phash, fname) elif action == "darcs_filediff": phash = filter_hash(form["h"].value) fname = filter_file(form["f"].value) do_darcs_filediff(phash, fname) elif action == 'headfilediff': phash = filter_hash(form["h"].value) fname = filter_file(form["f"].value) do_file_headdiff(phash, fname) elif action == "plain_headfilediff": phash = filter_hash(form["h"].value) fname = filter_file(form["f"].value) do_plain_fileheaddiff(phash, fname) elif action == "darcs_headfilediff": phash = filter_hash(form["h"].value) fname = filter_file(form["f"].value) do_darcs_fileheaddiff(phash, fname) elif action == "annotate_normal": fname = filter_file(form["f"].value) if form.has_key("h"): phash = filter_hash(form["h"].value) else: phash = None do_annotate(fname, phash, "normal") elif action == "annotate_plain": fname = filter_file(form["f"].value) if form.has_key("h"): phash = filter_hash(form["h"].value) else: phash = None do_annotate_plain(fname, phash) elif action == "annotate_zebra": fname = filter_file(form["f"].value) if form.has_key("h"): phash = filter_hash(form["h"].value) else: phash = None do_annotate(fname, phash, "zebra") elif action == "annotate_shade": fname = filter_file(form["f"].value) if form.has_key("h"): phash = filter_hash(form["h"].value) else: phash = None do_annotate(fname, phash, "shade") elif action == "shortlog": if form.has_key("topi"): topi = int(filter_num(form["topi"].value)) else: topi = 0 do_shortlog(topi) elif action == "filehistory": if form.has_key("topi"): topi = int(filter_num(form["topi"].value)) else: topi = 0 fname = filter_file(form["f"].value) do_filehistory(topi, fname) elif action == "log": if form.has_key("topi"): topi = int(filter_num(form["topi"].value)) else: topi = 0 do_log(topi) elif action == 'headblob': fname = filter_file(form["f"].value) do_headblob(fname) elif action == 'plainblob': fname = filter_file(form["f"].value) do_plainblob(fname) elif action == 'tree': if form.has_key('f'): fname = filter_file(form["f"].value) else: fname = '/' do_tree(fname) elif action == 'rss': do_rss() elif action == 'atom': do_atom() elif action == 'search': if form.has_key('s'): s = form["s"].value else: s = '' do_search(s) if config.cachedir: cache.cancel() else: action = "invalid query" do_die() if config.cachedir: cache.cancel() if config.cachedir: cache.close() log_times(cache_hit = 0, repo = config.reponame) darcsweb-1.1/README0000644000175000017500000000075011101631641012077 0ustar albalb darcsweb - A web interface for darcs Alberto Bertogli (albertito@blitiri.com.ar) --------------------------------------------- This is a very simple web interface for darcs, inspired in gitweb (written by Kay Sievers and Christian Gierke). To configure, copy the "config.py.sample" file to "config.py" and edit it; you will configure your repositories there. Then just browse to the cgi file. If you have any question, suggestions or comments, please let me know. Thanks, Alberto darcsweb-1.1/darcs.png0000644000175000017500000001445111101631641013024 0ustar albalbPNG  IHDR>F'bKGD pHYs  tIME 7 (tEXtCommentCreated with The GIMPd%nIDATxڵ{Օ?431F$-da²qHe-)/eS_REśW8ĵv*.ۋS6'i !Fݿ}~!S~ݿ{sy;Cկ~׿u} dN )$%`@QO$<l۶msKTbxxCB`YB.Z`1foicq-( !. !ׄ' @kZ_1_d0 =__~SN111;~鮮\1pc gϞ~/ CCC۷;vvZD쇝ؚA(:/"gϞܹG_җcǎf.矿[ߌ޾e[na޽rՃmY~Ν; /3v7~gΝW&eNi.Z3Z:_~<< 7+sȑ@~ر188/~}ccZy0811K a2ѫWk8lڴN穧ӧݻ_JC|>:00 _CCC(Z/^(p vT;.nɲQ\^F fΓO> F-Vhu9Kpĉ|;vuum뮻ELƀ k6:%ذ@c^ahm-|߮ҰZZ#o& öG}oٲ݃ ѣ6]wΝ;R.ָ+VIY9Y/KH֕Y)/s2CbyUB#;o&7}M6y_l>z#+ž}n J)j+-zt JƸ=!v&UAZ"@# R 4lI&CX3g<:SV1?tlll'jRW !QK60JӬ_yT*c6Uuೳ믿t:-֭[rDAvIiHJA0k'ĸ1p{}by!"Zh)@P `ȂCxEU),6<䓻ϟ?߿m۶:𑑑NZFi Q -1 -ĄN"1/A"Bn~k$)7".8Xdnw$rT#|HQIl,Yc>NںKb D} vI.%)/,KNO}ڢ/V{Lw%(INX6xk#1ѡE4M&^dY\͜;w::s j3 \X&' -mRH,m5m ;^`:*q閧+6%Vr1hא\y VKTӋ]%Ɉw} H&uUߠ=[v >n_UmwU1jªLM\y@8R>$1Udx=1HecUQ,W ha"Izj[ٶMX\ȩWnSa+J+xb"4dU@;GW6sxC܎3+diMjo]r9)9,H[m&AL&rai)%q_IdGGf]`gnB;jӆX?MρsAEA+\&ix2Y_pkң^www0 PbKQAnϰx "_LtY{mDXk.#)LEDQ15Ƹ뛆abYVfUpV("h/zc vIWصrRtՐ^^0:m`46 |0QIтp=rX ʄF/d /2\mLPMۂ366Vݻ-ӧV0::J__"VoiKg2SbF۾<1ȟ}950$_a z ֚1<ײz?Z3>>yd2*@ax/u)/!틘6̼Q__-2cfffꧪ',io߾'V(ւ .n:\׭ػOCW`yˬR0?)SsEL$fp_'Z[ֵp^w BP*xbfyޥ|3?Lf m۶MxG\fbbQԛ3'21&)37~"?:??i_ԟwCtoYei̼':Q!B 8 o8ڲ;)pG"O$gu1V^PqRq̏iYt6*ϓQH/)˜^^RfnpJҿXdrr(EFqF:t+^DD.+W_}@VBƿh'q%V&BI4ӯ8^f^Kteoc+PhfCsYdPZQ;DHG(- _g8{w=Vui׊UiooH$Vw0СCt8q/ǩ_ Dr]ӽxYn aE\^I)% dm6HF8m Y /y:aݦk*w{f'"r\t}x`کZVB@ TkMZa/ a#TԴmCPlVFx0JyqhhhlUGAFFF8~xmsk֬||!`cmZD|8UL_ (A{WY0GXЕXhi3U|>c=Ld2شie9~/155yY.;!FWtFMd[Wk?l{/wدxffϟ?" <dq+jX,1կhO__ׅ]W𷒪#{=n^~eєYrrabFVB*B|8Ƙ鿽pI) [DǏW_M@fVX<oRzu7֕H_fU[jA !711hnV쉉:7ܝ0 ' mljDbeY&^{~~쯌1"E i76W)ͽku{ cccG0Xuˁnܬ⭤n5qOwwwY:eY5ZcLə<D]074tolDCRc/#mضmkkl6mB$hZ6R^,˯. hڼhyD4GUo$t:}M&X:uݫm;$A^buYJ9'}tTzT* `6X t3XUكYxil-!y$aYVҶy]mBd\M:iUE1WJFQ4**VجB͠%xm/^=&abjRƉY%$._|&oF @6> VD+/KB>9Poq?6!3+IENDB`