Refactor committags support, fix BUG(868) properly
[git/jnareb-git.git] / snapshot.cgi
blobc1980cbd1f65f5500d4147e07bab1b31340056bf
1 #!/usr/bin/python
2 # gitweb snapshot.cgi - generate snapshots of git repositories
3 # Copyright (C) 2005-2006 Anders Gustafsson, Sham Chukoury
5 # This program is free software; you can redistribute it and/or
6 # modify it under the terms of the GNU General Public License
7 # as published by the Free Software Foundation; either version 2
8 # of the License, or (at your option) any later version.
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU General Public License for more details.
15 import sys
16 import os
17 import cgi
18 import time
19 import bz2
21 # where the git tools are located
22 git_bin_dir = "/usr/bin"
24 # where the git repositories are located
25 git_base_dir = "/xmms2"
27 # where to put snapshots
28 snapshot_dir = "/tmp/snapshots"
30 # where to send users if an invalid snapshot is requested
31 snapshots_url = "http://git.xmms.se/"
33 # Note: in the context of this script, a 'tree' is a Git repository,
34 # not a Git tree.
36 def redirect(url):
37 print "Location: %s\n" % url
39 def send_text(msg):
40 print "Content-Type: text/plain\n"
41 print msg
43 class Snapshot:
44 def __init__(self, git_dir, treename, commitID):
45 self.git_dir = git_dir
46 self.treename = treename
47 self.dir = os.path.join(snapshot_dir, treename)
49 if (commitID == "HEAD"):
50 self.commitID = file("%s/HEAD" % git_dir).read().rstrip()
51 redirect("%s/%s-snapshot-%s.tar.bz2" % (
52 os.environ["SCRIPT_NAME"], treename, self.commitID))
53 sys.exit()
54 else:
55 self.commitID = commitID
57 # read commit data
58 os.putenv("GIT_DIR", git_dir)
59 f = os.popen("%s commit %s" % (
60 os.path.join(git_bin_dir, "git-cat-file"), self.commitID))
61 self.tree = f.readline().rstrip().split(" ")[-1]
62 f.readline() # parent
63 f.readline() # author
64 commit = f.readline().rstrip()
66 if commit == "":
67 # commit is empty, must be invalid hash
68 send_text("Invalid hash %s for tree %s" % (self.commitID, self.treename))
69 sys.exit()
70 # parse committer field, generate snapshot name
71 #committime = int(commit.split(" ")[-2])
72 self.name = "%s-snapshot-%s" % (treename, self.commitID)
73 self.filepath = "%s/%s.tar.bz2" % (self.dir, self.name)
74 self.tarpath = self.filepath[:-4]
76 # check whether snapshot dir exists, or create it
77 def check_dir(self):
78 if not os.access("%s/" % self.dir, os.F_OK):
79 os.mkdir("%s/" % self.dir)
81 def make_commithash(self):
82 # check whether commithash file exists
83 ch = ("%s/commithash-%s" % (self.dir, self.commitID))
84 if not os.access(ch, os.F_OK):
85 # make commithash file
86 chfile = open(ch, "w+")
87 chfile.write("%s\n\n" % self.commitID)
88 lstree = os.popen("git-ls-tree -r %s" % self.commitID)
89 for line in lstree:
90 chfile.write(line)
91 chfile.close()
92 lstree.close()
93 return ch
95 # check whether snapshot file exists, or build it
96 def build(self):
97 self.check_dir()
98 if not os.access(self.filepath, os.F_OK):
99 import tarfile
100 # todo: trap possible errors here
101 # create tarball
102 os.system("%s %s %s > %s" % (
103 os.path.join(git_bin_dir, "git-tar-tree"),
104 self.tree, self.name, self.tarpath))
106 # add commithash file to tarball
107 chFilename = self.make_commithash()
108 tfile = tarfile.TarFile(self.tarpath, "a")
109 tfile.add(chFilename, "%s/commithash" % self.name)
110 tfile.close()
111 # compress tarball
112 os.system("bzip2 %s" % self.tarpath)
114 # add to .htaccess
115 file("%s/.htaccess" % self.dir,"a").write('AddDescription "%s git snapshot (%s)" %s.tar.bz2\n' % (self.treename, self.commitID, self.name))
117 # open snapshot file
118 def get_file(self):
119 try:
120 retFile = file(self.filepath, "r")
121 except IOError:
122 retFile = None
123 return retFile
125 def send_bheaders(self, filename = None, size = None, lastmod = None):
126 if filename is None:
127 filename = self.name + ".tar.bz2"
128 sizestr = ""
129 if size is not None:
130 sizestr = "; size=%i" % size
131 print "Content-Type: application/x-bzip2"
132 print "Content-Encoding: x-bzip2"
133 print "Content-Disposition: inline; filename=%s%s" % (
134 filename, sizestr)
135 print "Accept-Ranges: none"
136 if size is not None:
137 print "Content-Length: %i" % size
138 if lastmod is not None:
139 print "Last-Modified: %s" % (
140 time.strftime("%a, %d %b %Y %H:%M:%S GMT", time.gmtime(lastmod)))
141 print ""
143 # send pre-made tarball (self.build, or otherwise)
144 def send_binary(self):
145 bfile = self.get_file()
146 if bfile is None:
147 send_text("Sorry, could not provide snapshot for tree %s, commit %s" % (self.treename, self.commitID))
148 else:
149 self.send_bheaders(size=os.stat(self.filepath)[6],
150 lastmod = os.stat(self.filepath)[8])
151 for line in bfile:
152 sys.stdout.write(line)
153 bfile.close()
155 # make snapshot tarball and send, on the fly
156 def on_the_fly(self):
157 def cache_chunk_send(chunk, cfile):
158 cfile.write(chunk)
159 sys.stdout.write(chunk)
161 # try to get file from disk if it exists, first
162 if os.access(self.filepath, os.F_OK):
163 self.send_binary()
164 else:
165 self.check_dir()
166 cachefile = file(self.filepath, "w")
167 tar = os.popen("%s %s %s" % (
168 os.path.join(git_bin_dir, "git-tar-tree"),
169 self.tree, self.name))
171 kompressor = bz2.BZ2Compressor()
172 self.send_bheaders()
173 for line in tar:
174 cache_chunk_send(kompressor.compress(line),
175 cachefile)
176 cache_chunk_send(kompressor.flush(), cachefile)
177 cachefile.close()
178 tar.close()
180 if not os.access(snapshot_dir, os.F_OK):
181 try:
182 os.mkdir(snapshot_dir)
183 except OSError:
184 send_text("Could not create snapshot dir '%s'" % snapshot_dir)
185 sys.exit()
187 def valid_hash(ahash):
188 retVal = True
190 if ahash != "HEAD":
191 if len(ahash) != 40:
192 retVal = False
193 for char in ahash:
194 if char not in "0123456789abcdef":
195 retVal = False
196 return retVal
198 def valid_pathinfo():
199 import re
200 retVal = ()
201 try:
202 path = os.environ["PATH_INFO"]
203 except KeyError:
204 return retVal
205 tree = None
206 commit = None
207 # path must be '/treename-snapshot-hash.tar.bz2'
208 match = re.compile(r"^/[\w\d\.-]+-snapshot").search(path)
209 if match is not None:
210 tree = match.group()[1:-len("-snapshot")]
211 match = re.compile(r"snapshot-(([\da-f]{40})|(HEAD))\.tar\.bz2$").search(path)
212 if match is not None:
213 commit = match.group()[len("snapshot-"):-len(".tar.bz2")]
214 if tree and commit:
215 retVal = (tree, commit)
216 return retVal
218 def send_snapshot(gitdir, tree, commit):
219 # validate tree name
220 if not os.access(gitdir, os.F_OK):
221 send_text("No such tree: %s" % tree)
222 sys.exit()
224 snap = Snapshot(gitdir, tree, commit)
225 snap.build()
226 snap.send_binary()
228 fs = cgi.FieldStorage()
229 pathArgs = valid_pathinfo()
230 if (fs.has_key("tree") and fs.has_key("commit")):
231 tree = fs["tree"].value
232 commit = fs["commit"].value
234 # validate commit hash
235 if not valid_hash(commit):
236 send_text("Invalid hash: %s" % commit)
237 sys.exit()
239 send_snapshot(os.path.join(git_base_dir, tree), tree, commit)
241 #elif fs.has_key("tree"):
242 # redirect("%s%s" % (snapshots_url, fs["tree"].value))
243 # #if os.access(os.path.join(snapshot_dir, fs["tree"].value), os.F_OK):
244 # # redirect("%s%s" % (snapshots_url, fs["tree"].value))
245 # #else:
246 # # send_text("No such tree: %s\n" % fs["tree"].value)
248 elif pathArgs:
249 tree = pathArgs[0]
250 commit = pathArgs[1]
252 send_snapshot(os.path.join(git_base_dir, tree), tree, commit)
253 else:
254 # user requested url directly, without a commit hash
255 # redirect to snapshots dir
256 redirect(snapshots_url)