1# stagit
2
3static git page generator.
4
5It generates static HTML pages for a git repository.
6
7## Changes
8This is my personal fork of stagit.
9It has been modified to fit my needs and some refs points
10to cli apps on my web-server.
11
12* Title of files while viewing is now a link to download the file
13* The index page now links to the repository directory
14* md4c has been added to parse markdown files
15* Added classes and ids to some html tags to easier style them
16* Changed style.css to a modern looking nord theme as well as markdown styling
17
18The link to download the file points to `/raw/?repository=&file=`
19where file is the full path to the file relative to the repository.
20It is also possible to specify which branch to fetch the file from.
21
22Make necessary changes to the web-server to serve the file
23
24To view a file in a bare repository:
25
26 $cd path/to/repo
27 $git show HEAD:path/to/file
28
29Simple python cgi to serve the file:
30
31```
32#!/bin/python3
33
34import cgi
35import subprocess
36import mimetypes
37import sys
38
39def getFile(repo, filepath, branch):
40 gitcmd = subprocess.run(['git', 'show', f'{branch}:{filepath}'],
41 cwd=f'/home/pi/repositories/{repo}',
42 capture_output=True)
43 return gitcmd.stdout
44
45
46def guessType(filepath, extensions_map):
47 ext = '.' + filepath.split('.')[-1]
48 if ext in extensions_map:
49 return extensions_map[ext]
50 ext = ext.lower()
51 if ext in extensions_map:
52 return extensions_map[ext]
53 return extensions_map['']
54
55
56if not mimetypes.inited:
57 mimetypes.init()
58extensions_map = mimetypes.types_map.copy()
59extensions_map[''] = 'application/octet-stream'
60
61form = cgi.FieldStorage()
62repo = form.getvalue('repository')
63filepath = form.getvalue('file')
64if (form.getvalue('branch')):
65 branch = form.getvalue('branch')
66else:
67 branch = 'HEAD'
68
69ctype = guessType(filepath, extensions_map)
70response = getFile(repo, filepath, branch)
71
72if response:
73 print(f"Content-Type: {ctype}")
74 print(f"Conent-Length: {str(len(response))}")
75 print()
76 sys.stdout.flush()
77 sys.stdout.buffer.write(response)
78else:
79 print("Content-Type: text/plain")
80 print()
81 print("404: File not found")
82
83```
84
85The index page linking to the directory will not change the default
86behavior of stagit if the html files have been created with the [provided
87shell script](example_create.sh.html), since log.html is symlinked
88to index.html.
89
90This change was made so that the web-server could check if the
91request is for a directory. If it is check if there is a file called
92README.md.html in the sub-directory file, if it exists serve that
93file instead of log.html.
94
95Lighttpd conf to implement this:
96
97 url.redirect = ( "^\/stagit\/([a-zA-Z0-9-_.,]\/$" => "/stagit/$1/file/README.md" )
98 url.rewrite-if-not-file = ( "^\/stagit/([a-zA-Z0-9-_,.]*)\/file\/README.md.html$" => /redirect/$1" )
99 url.redirect += ( "^\/redirect/\([a-zA-Z0-9-_,.]*)" => "/stagit/$1/log.html" )
100
101### md4c
102md4c is used to parse and convert markdown documents into html. If a file
103ends with `.md` it will be parsed by md4c rather than the regular way
104stagit parses plain text files.
105
106https://github.com/mity/md4c
107
108## Usage
109
110Make files per repository:
111
112 $ mkdir -p htmldir && cd htmldir
113 $ stagit path-to-repo
114
115Make index file for repositories:
116
117 $ stagit-index repodir1 repodir2 repodir3 > index.html
118
119
120## Build and install
121
122 $ make
123 # make install
124
125
126## Dependencies
127
128- C compiler (C99).
129- libc (tested with OpenBSD, FreeBSD, NetBSD, Linux: glibc and musl).
130- libgit2 (v0.22+).
131- POSIX make (optional).
132- md4c (markdown parsing to html)
133
134## Documentation
135
136See man pages: stagit(1) and stagit-index(1).
137
138
139### Building a static binary
140
141It may be useful to build static binaries, for example to run in a chroot.
142
143It can be done like this at the time of writing (v0.24):
144
145 cd libgit2-src
146
147 # change the options in the CMake file: CMakeLists.txt
148 BUILD_SHARED_LIBS to OFF (static)
149 CURL to OFF (not needed)
150 USE_SSH OFF (not needed)
151 THREADSAFE OFF (not needed)
152 USE_OPENSSL OFF (not needed, use builtin)
153
154 mkdir -p build && cd build
155 cmake ../
156 make
157 make install
158
159
160### Extract owner field from git config
161
162A way to extract the gitweb owner for example in the format:
163
164 [gitweb]
165 owner = Name here
166
167Script:
168
169 #!/bin/sh
170 awk '/^[ ]*owner[ ]=/ {
171 sub(/^[^=]*=[ ]*/, "");
172 print $0;
173 }'
174
175
176### Set clone url for a directory of repos
177
178 #!/bin/sh
179 cd "$dir"
180 for i in *; do
181 test -d "$i" && echo "git://git.codemadness.org/$i" > "$i/url"
182 done
183
184
185### Update files on git push
186
187Using a post-receive hook the static files can be automatically updated.
188Keep in mind git push -f can change the history and the commits may need
189to be recreated. This is because stagit checks if a commit file already
190exists. It also has a cache (-c) option which can conflict with the new
191history. See stagit(1).
192
193git post-receive hook (repo/.git/hooks/post-receive):
194
195 #!/bin/sh
196 # detect git push -f
197 force=0
198 while read -r old new ref; do
199 hasrevs=$(git rev-list "$old" "^$new" | sed 1q)
200 if test -n "$hasrevs"; then
201 force=1
202 break
203 fi
204 done
205
206 # remove commits and .cache on git push -f
207 #if test "$force" = "1"; then
208 # ...
209 #fi
210
211 # see example_create.sh for normal creation of the files.
212
213
214### Create .tar.gz archives by tag
215
216 #!/bin/sh
217 name="stagit"
218 mkdir -p archives
219 git tag -l | while read -r t; do
220 f="archives/${name}-$(echo "${t}" | tr '/' '_').tar.gz"
221 test -f "${f}" && continue
222 git archive \
223 --format tar.gz \
224 --prefix "${t}/" \
225 -o "${f}" \
226 -- \
227 "${t}"
228 done
229
230
231## Features
232
233- Log of all commits from HEAD.
234- Log and diffstat per commit.
235- Show file tree with linkable line numbers.
236- Show references: local branches and tags.
237- Detect README and LICENSE file from HEAD and link it as a webpage.
238- Detect submodules (.gitmodules file) from HEAD and link it as a webpage.
239- Atom feed log (atom.xml).
240- Make index page for multiple repositories with stagit-index.
241- After generating the pages (relatively slow) serving the files is very fast,
242 simple and requires little resources (because the content is static), only
243 a HTTP file server is required.
244- Usable with text-browsers such as dillo, links, lynx and w3m.
245
246
247## Cons
248
249- Not suitable for large repositories (2000+ commits), because diffstats are
250an expensive operation, the cache (-c flag) is a workaround for this in
251some cases.
252- Not suitable for large repositories with many files, because all files
253are written for each execution of stagit. This is because stagit shows the
254lines of textfiles and there is no "cache" for file metadata
255(this would add more complexity to the code).
256- Not suitable for repositories with many branches, a quite linear
257history is assumed (from HEAD).
258
259 In these cases it is better to just use cgit or possibly change stagit to
260 run as a CGI program.
261
262- Relatively slow to run the first time (about 3 seconds for sbase,
2631500+ commits), incremental updates are faster.
264- Does not support some of the dynamic features cgit has, like:
265 - Snapshot tarballs per commit.
266 - File tree per commit.
267 - History log of branches diverged from HEAD.
268 - Stats (git shortlog -s).
269
270 This is by design, just use git locally.
271- After adding md4c for markdown rendering the stagit-index links are pointed
272 to /file/README.md.html instead of log.html this in turn requires every repo
273 to contain a README file in md. (Not really an issue for my personal use case).
274