Merge branch 'jc/diff-merge-base-multi'
[git] / git-instaweb.sh
1 #!/bin/sh
2 #
3 # Copyright (c) 2006 Eric Wong
4 #
5
6 PERL='@@PERL@@'
7 OPTIONS_KEEPDASHDASH=
8 OPTIONS_SPEC="\
9 git instaweb [options] (--start | --stop | --restart)
10 --
11 l,local        only bind on 127.0.0.1
12 p,port=        the port to bind to
13 d,httpd=       the command to launch
14 b,browser=     the browser to launch
15 m,module-path= the module path (only needed for apache2)
16  Action
17 stop           stop the web server
18 start          start the web server
19 restart        restart the web server
20 "
21
22 . git-sh-setup
23
24 fqgitdir="$GIT_DIR"
25 local="$(git config --bool --get instaweb.local)"
26 httpd="$(git config --get instaweb.httpd)"
27 root="$(git config --get instaweb.gitwebdir)"
28 port=$(git config --get instaweb.port)
29 module_path="$(git config --get instaweb.modulepath)"
30
31 conf="$GIT_DIR/gitweb/httpd.conf"
32
33 # Defaults:
34
35 # if installed, it doesn't need further configuration (module_path)
36 test -z "$httpd" && httpd='lighttpd -f'
37
38 # Default is @@GITWEBDIR@@
39 test -z "$root" && root='@@GITWEBDIR@@'
40
41 # any untaken local port will do...
42 test -z "$port" && port=1234
43
44 resolve_full_httpd () {
45         case "$httpd" in
46         *apache2*|*lighttpd*)
47                 # ensure that the apache2/lighttpd command ends with "-f"
48                 if ! echo "$httpd" | sane_grep -- '-f *$' >/dev/null 2>&1
49                 then
50                         httpd="$httpd -f"
51                 fi
52                 ;;
53         *plackup*)
54                 # server is started by running via generated gitweb.psgi in $fqgitdir/gitweb
55                 full_httpd="$fqgitdir/gitweb/gitweb.psgi"
56                 httpd_only="${httpd%% *}" # cut on first space
57                 return
58                 ;;
59         esac
60
61         httpd_only="$(echo $httpd | cut -f1 -d' ')"
62         if case "$httpd_only" in /*) : ;; *) which $httpd_only >/dev/null 2>&1;; esac
63         then
64                 full_httpd=$httpd
65         else
66                 # many httpds are installed in /usr/sbin or /usr/local/sbin
67                 # these days and those are not in most users $PATHs
68                 # in addition, we may have generated a server script
69                 # in $fqgitdir/gitweb.
70                 for i in /usr/local/sbin /usr/sbin "$root" "$fqgitdir/gitweb"
71                 do
72                         if test -x "$i/$httpd_only"
73                         then
74                                 full_httpd=$i/$httpd
75                                 return
76                         fi
77                 done
78
79                 echo >&2 "$httpd_only not found. Install $httpd_only or use" \
80                      "--httpd to specify another httpd daemon."
81                 exit 1
82         fi
83 }
84
85 start_httpd () {
86         if test -f "$fqgitdir/pid"; then
87                 say "Instance already running. Restarting..."
88                 stop_httpd
89         fi
90
91         # here $httpd should have a meaningful value
92         resolve_full_httpd
93
94         # don't quote $full_httpd, there can be arguments to it (-f)
95         case "$httpd" in
96         *mongoose*|*plackup*)
97                 #These servers don't have a daemon mode so we'll have to fork it
98                 $full_httpd "$fqgitdir/gitweb/httpd.conf" &
99                 #Save the pid before doing anything else (we'll print it later)
100                 pid=$!
101
102                 if test $? != 0; then
103                         echo "Could not execute http daemon $httpd."
104                         exit 1
105                 fi
106
107                 cat > "$fqgitdir/pid" <<EOF
108 $pid
109 EOF
110                 ;;
111         *)
112                 $full_httpd "$fqgitdir/gitweb/httpd.conf"
113                 if test $? != 0; then
114                         echo "Could not execute http daemon $httpd."
115                         exit 1
116                 fi
117                 ;;
118         esac
119 }
120
121 stop_httpd () {
122         test -f "$fqgitdir/pid" && kill $(cat "$fqgitdir/pid")
123         rm -f "$fqgitdir/pid"
124 }
125
126 httpd_is_ready () {
127         "$PERL" -MIO::Socket::INET -e "
128 local \$| = 1; # turn on autoflush
129 exit if (IO::Socket::INET->new('127.0.0.1:$port'));
130 print 'Waiting for \'$httpd\' to start ..';
131 do {
132         print '.';
133         sleep(1);
134 } until (IO::Socket::INET->new('127.0.0.1:$port'));
135 print qq! (done)\n!;
136 "
137 }
138
139 while test $# != 0
140 do
141         case "$1" in
142         --stop|stop)
143                 stop_httpd
144                 exit 0
145                 ;;
146         --start|start)
147                 start_httpd
148                 exit 0
149                 ;;
150         --restart|restart)
151                 stop_httpd
152                 start_httpd
153                 exit 0
154                 ;;
155         -l|--local)
156                 local=true
157                 ;;
158         -d|--httpd)
159                 shift
160                 httpd="$1"
161                 ;;
162         -b|--browser)
163                 shift
164                 browser="$1"
165                 ;;
166         -p|--port)
167                 shift
168                 port="$1"
169                 ;;
170         -m|--module-path)
171                 shift
172                 module_path="$1"
173                 ;;
174         --)
175                 ;;
176         *)
177                 usage
178                 ;;
179         esac
180         shift
181 done
182
183 mkdir -p "$GIT_DIR/gitweb/tmp"
184 GIT_EXEC_PATH="$(git --exec-path)"
185 GIT_DIR="$fqgitdir"
186 GITWEB_CONFIG="$fqgitdir/gitweb/gitweb_config.perl"
187 export GIT_EXEC_PATH GIT_DIR GITWEB_CONFIG
188
189 webrick_conf () {
190         # generate a standalone server script in $fqgitdir/gitweb.
191         cat >"$fqgitdir/gitweb/$httpd.rb" <<EOF
192 require 'webrick'
193 require 'yaml'
194 options = YAML::load_file(ARGV[0])
195 options[:StartCallback] = proc do
196   File.open(options[:PidFile],"w") do |f|
197     f.puts Process.pid
198   end
199 end
200 options[:ServerType] = WEBrick::Daemon
201 server = WEBrick::HTTPServer.new(options)
202 ['INT', 'TERM'].each do |signal|
203   trap(signal) {server.shutdown}
204 end
205 server.start
206 EOF
207         # generate a shell script to invoke the above ruby script,
208         # which assumes _ruby_ is in the user's $PATH. that's _one_
209         # portable way to run ruby, which could be installed anywhere,
210         # really.
211         cat >"$fqgitdir/gitweb/$httpd" <<EOF
212 #!/bin/sh
213 exec ruby "$fqgitdir/gitweb/$httpd.rb" \$*
214 EOF
215         chmod +x "$fqgitdir/gitweb/$httpd"
216
217         cat >"$conf" <<EOF
218 :Port: $port
219 :DocumentRoot: "$root"
220 :DirectoryIndex: ["gitweb.cgi"]
221 :PidFile: "$fqgitdir/pid"
222 EOF
223         test "$local" = true && echo ':BindAddress: "127.0.0.1"' >> "$conf"
224 }
225
226 lighttpd_conf () {
227         cat > "$conf" <<EOF
228 server.document-root = "$root"
229 server.port = $port
230 server.modules = ( "mod_setenv", "mod_cgi" )
231 server.indexfiles = ( "gitweb.cgi" )
232 server.pid-file = "$fqgitdir/pid"
233 server.errorlog = "$fqgitdir/gitweb/$httpd_only/error.log"
234
235 # to enable, add "mod_access", "mod_accesslog" to server.modules
236 # variable above and uncomment this
237 #accesslog.filename = "$fqgitdir/gitweb/$httpd_only/access.log"
238
239 setenv.add-environment = ( "PATH" => env.PATH, "GITWEB_CONFIG" => env.GITWEB_CONFIG )
240
241 cgi.assign = ( ".cgi" => "" )
242
243 # mimetype mapping
244 mimetype.assign             = (
245   ".pdf"          =>      "application/pdf",
246   ".sig"          =>      "application/pgp-signature",
247   ".spl"          =>      "application/futuresplash",
248   ".class"        =>      "application/octet-stream",
249   ".ps"           =>      "application/postscript",
250   ".torrent"      =>      "application/x-bittorrent",
251   ".dvi"          =>      "application/x-dvi",
252   ".gz"           =>      "application/x-gzip",
253   ".pac"          =>      "application/x-ns-proxy-autoconfig",
254   ".swf"          =>      "application/x-shockwave-flash",
255   ".tar.gz"       =>      "application/x-tgz",
256   ".tgz"          =>      "application/x-tgz",
257   ".tar"          =>      "application/x-tar",
258   ".zip"          =>      "application/zip",
259   ".mp3"          =>      "audio/mpeg",
260   ".m3u"          =>      "audio/x-mpegurl",
261   ".wma"          =>      "audio/x-ms-wma",
262   ".wax"          =>      "audio/x-ms-wax",
263   ".ogg"          =>      "application/ogg",
264   ".wav"          =>      "audio/x-wav",
265   ".gif"          =>      "image/gif",
266   ".jpg"          =>      "image/jpeg",
267   ".jpeg"         =>      "image/jpeg",
268   ".png"          =>      "image/png",
269   ".xbm"          =>      "image/x-xbitmap",
270   ".xpm"          =>      "image/x-xpixmap",
271   ".xwd"          =>      "image/x-xwindowdump",
272   ".css"          =>      "text/css",
273   ".html"         =>      "text/html",
274   ".htm"          =>      "text/html",
275   ".js"           =>      "text/javascript",
276   ".asc"          =>      "text/plain",
277   ".c"            =>      "text/plain",
278   ".cpp"          =>      "text/plain",
279   ".log"          =>      "text/plain",
280   ".conf"         =>      "text/plain",
281   ".text"         =>      "text/plain",
282   ".txt"          =>      "text/plain",
283   ".dtd"          =>      "text/xml",
284   ".xml"          =>      "text/xml",
285   ".mpeg"         =>      "video/mpeg",
286   ".mpg"          =>      "video/mpeg",
287   ".mov"          =>      "video/quicktime",
288   ".qt"           =>      "video/quicktime",
289   ".avi"          =>      "video/x-msvideo",
290   ".asf"          =>      "video/x-ms-asf",
291   ".asx"          =>      "video/x-ms-asf",
292   ".wmv"          =>      "video/x-ms-wmv",
293   ".bz2"          =>      "application/x-bzip",
294   ".tbz"          =>      "application/x-bzip-compressed-tar",
295   ".tar.bz2"      =>      "application/x-bzip-compressed-tar",
296   ""              =>      "text/plain"
297  )
298 EOF
299         test x"$local" = xtrue && echo 'server.bind = "127.0.0.1"' >> "$conf"
300 }
301
302 apache2_conf () {
303         test -z "$module_path" && module_path=/usr/lib/apache2/modules
304         bind=
305         test x"$local" = xtrue && bind='127.0.0.1:'
306         echo 'text/css css' > "$fqgitdir/mime.types"
307         cat > "$conf" <<EOF
308 ServerName "git-instaweb"
309 ServerRoot "$root"
310 DocumentRoot "$root"
311 ErrorLog "$fqgitdir/gitweb/$httpd_only/error.log"
312 CustomLog "$fqgitdir/gitweb/$httpd_only/access.log" combined
313 PidFile "$fqgitdir/pid"
314 Listen $bind$port
315 EOF
316
317         for mod in mime dir; do
318                 if test -e $module_path/mod_${mod}.so; then
319                         echo "LoadModule ${mod}_module " \
320                              "$module_path/mod_${mod}.so" >> "$conf"
321                 fi
322         done
323         cat >> "$conf" <<EOF
324 TypesConfig "$fqgitdir/mime.types"
325 DirectoryIndex gitweb.cgi
326 EOF
327
328         # check to see if Dennis Stosberg's mod_perl compatibility patch
329         # (<20060621130708.Gcbc6e5c@leonov.stosberg.net>) has been applied
330         if test -f "$module_path/mod_perl.so" &&
331            sane_grep 'MOD_PERL' "$root/gitweb.cgi" >/dev/null
332         then
333                 # favor mod_perl if available
334                 cat >> "$conf" <<EOF
335 LoadModule perl_module $module_path/mod_perl.so
336 PerlPassEnv GIT_DIR
337 PerlPassEnv GIT_EXEC_DIR
338 PerlPassEnv GITWEB_CONFIG
339 <Location /gitweb.cgi>
340         SetHandler perl-script
341         PerlResponseHandler ModPerl::Registry
342         PerlOptions +ParseHeaders
343         Options +ExecCGI
344 </Location>
345 EOF
346         else
347                 # plain-old CGI
348                 resolve_full_httpd
349                 list_mods=$(echo "$full_httpd" | sed 's/-f$/-l/')
350                 $list_mods | sane_grep 'mod_cgi\.c' >/dev/null 2>&1 || \
351                 if test -f "$module_path/mod_cgi.so"
352                 then
353                         echo "LoadModule cgi_module $module_path/mod_cgi.so" >> "$conf"
354                 else
355                         $list_mods | grep 'mod_cgid\.c' >/dev/null 2>&1 || \
356                         if test -f "$module_path/mod_cgid.so"
357                         then
358                                 echo "LoadModule cgid_module $module_path/mod_cgid.so" \
359                                         >> "$conf"
360                         else
361                                 echo "You have no CGI support!"
362                                 exit 2
363                         fi
364                         echo "ScriptSock logs/gitweb.sock" >> "$conf"
365                 fi
366                 cat >> "$conf" <<EOF
367 AddHandler cgi-script .cgi
368 <Location /gitweb.cgi>
369         Options +ExecCGI
370 </Location>
371 EOF
372         fi
373 }
374
375 mongoose_conf() {
376         cat > "$conf" <<EOF
377 # Mongoose web server configuration file.
378 # Lines starting with '#' and empty lines are ignored.
379 # For detailed description of every option, visit
380 # http://code.google.com/p/mongoose/wiki/MongooseManual
381
382 root            $root
383 ports           $port
384 index_files     gitweb.cgi
385 #ssl_cert       $fqgitdir/gitweb/ssl_cert.pem
386 error_log       $fqgitdir/gitweb/$httpd_only/error.log
387 access_log      $fqgitdir/gitweb/$httpd_only/access.log
388
389 #cgi setup
390 cgi_env         PATH=$PATH,GIT_DIR=$GIT_DIR,GIT_EXEC_PATH=$GIT_EXEC_PATH,GITWEB_CONFIG=$GITWEB_CONFIG
391 cgi_interp      $PERL
392 cgi_ext         cgi,pl
393
394 # mimetype mapping
395 mime_types      .gz=application/x-gzip,.tar.gz=application/x-tgz,.tgz=application/x-tgz,.tar=application/x-tar,.zip=application/zip,.gif=image/gif,.jpg=image/jpeg,.jpeg=image/jpeg,.png=image/png,.css=text/css,.html=text/html,.htm=text/html,.js=text/javascript,.c=text/plain,.cpp=text/plain,.log=text/plain,.conf=text/plain,.text=text/plain,.txt=text/plain,.dtd=text/xml,.bz2=application/x-bzip,.tbz=application/x-bzip-compressed-tar,.tar.bz2=application/x-bzip-compressed-tar
396 EOF
397 }
398
399 plackup_conf () {
400         # generate a standalone 'plackup' server script in $fqgitdir/gitweb
401         # with embedded configuration; it does not use "$conf" file
402         cat > "$fqgitdir/gitweb/gitweb.psgi" <<EOF
403 #!$PERL
404
405 # gitweb - simple web interface to track changes in git repositories
406 #          PSGI wrapper and server starter (see http://plackperl.org)
407
408 use strict;
409
410 use IO::Handle;
411 use Plack::MIME;
412 use Plack::Builder;
413 use Plack::App::WrapCGI;
414 use CGI::Emulate::PSGI 0.07; # minimum version required to work with gitweb
415
416 # mimetype mapping (from lighttpd_conf)
417 Plack::MIME->add_type(
418         ".pdf"          =>      "application/pdf",
419         ".sig"          =>      "application/pgp-signature",
420         ".spl"          =>      "application/futuresplash",
421         ".class"        =>      "application/octet-stream",
422         ".ps"           =>      "application/postscript",
423         ".torrent"      =>      "application/x-bittorrent",
424         ".dvi"          =>      "application/x-dvi",
425         ".gz"           =>      "application/x-gzip",
426         ".pac"          =>      "application/x-ns-proxy-autoconfig",
427         ".swf"          =>      "application/x-shockwave-flash",
428         ".tar.gz"       =>      "application/x-tgz",
429         ".tgz"          =>      "application/x-tgz",
430         ".tar"          =>      "application/x-tar",
431         ".zip"          =>      "application/zip",
432         ".mp3"          =>      "audio/mpeg",
433         ".m3u"          =>      "audio/x-mpegurl",
434         ".wma"          =>      "audio/x-ms-wma",
435         ".wax"          =>      "audio/x-ms-wax",
436         ".ogg"          =>      "application/ogg",
437         ".wav"          =>      "audio/x-wav",
438         ".gif"          =>      "image/gif",
439         ".jpg"          =>      "image/jpeg",
440         ".jpeg"         =>      "image/jpeg",
441         ".png"          =>      "image/png",
442         ".xbm"          =>      "image/x-xbitmap",
443         ".xpm"          =>      "image/x-xpixmap",
444         ".xwd"          =>      "image/x-xwindowdump",
445         ".css"          =>      "text/css",
446         ".html"         =>      "text/html",
447         ".htm"          =>      "text/html",
448         ".js"           =>      "text/javascript",
449         ".asc"          =>      "text/plain",
450         ".c"            =>      "text/plain",
451         ".cpp"          =>      "text/plain",
452         ".log"          =>      "text/plain",
453         ".conf"         =>      "text/plain",
454         ".text"         =>      "text/plain",
455         ".txt"          =>      "text/plain",
456         ".dtd"          =>      "text/xml",
457         ".xml"          =>      "text/xml",
458         ".mpeg"         =>      "video/mpeg",
459         ".mpg"          =>      "video/mpeg",
460         ".mov"          =>      "video/quicktime",
461         ".qt"           =>      "video/quicktime",
462         ".avi"          =>      "video/x-msvideo",
463         ".asf"          =>      "video/x-ms-asf",
464         ".asx"          =>      "video/x-ms-asf",
465         ".wmv"          =>      "video/x-ms-wmv",
466         ".bz2"          =>      "application/x-bzip",
467         ".tbz"          =>      "application/x-bzip-compressed-tar",
468         ".tar.bz2"      =>      "application/x-bzip-compressed-tar",
469         ""              =>      "text/plain"
470 );
471
472 my \$app = builder {
473         # to be able to override \$SIG{__WARN__} to log build time warnings
474         use CGI::Carp; # it sets \$SIG{__WARN__} itself
475
476         my \$logdir = "$fqgitdir/gitweb/$httpd_only";
477         open my \$access_log_fh, '>>', "\$logdir/access.log"
478                 or die "Couldn't open access log '\$logdir/access.log': \$!";
479         open my \$error_log_fh,  '>>', "\$logdir/error.log"
480                 or die "Couldn't open error log '\$logdir/error.log': \$!";
481
482         \$access_log_fh->autoflush(1);
483         \$error_log_fh->autoflush(1);
484
485         # redirect build time warnings to error.log
486         \$SIG{'__WARN__'} = sub {
487                 my \$msg = shift;
488                 # timestamp warning like in CGI::Carp::warn
489                 my \$stamp = CGI::Carp::stamp();
490                 \$msg =~ s/^/\$stamp/gm;
491                 print \$error_log_fh \$msg;
492         };
493
494         # write errors to error.log, access to access.log
495         enable 'AccessLog',
496                 format => "combined",
497                 logger => sub { print \$access_log_fh @_; };
498         enable sub {
499                 my \$app = shift;
500                 sub {
501                         my \$env = shift;
502                         \$env->{'psgi.errors'} = \$error_log_fh;
503                         \$app->(\$env);
504                 }
505         };
506         # gitweb currently doesn't work with $SIG{CHLD} set to 'IGNORE',
507         # because it uses 'close $fd or die...' on piped filehandle $fh
508         # (which causes the parent process to wait for child to finish).
509         enable_if { \$SIG{'CHLD'} eq 'IGNORE' } sub {
510                 my \$app = shift;
511                 sub {
512                         my \$env = shift;
513                         local \$SIG{'CHLD'} = 'DEFAULT';
514                         local \$SIG{'CLD'}  = 'DEFAULT';
515                         \$app->(\$env);
516                 }
517         };
518         # serve static files, i.e. stylesheet, images, script
519         enable 'Static',
520                 path => sub { m!\.(js|css|png)\$! && s!^/gitweb/!! },
521                 root => "$root/",
522                 encoding => 'utf-8'; # encoding for 'text/plain' files
523         # convert CGI application to PSGI app
524         Plack::App::WrapCGI->new(script => "$root/gitweb.cgi")->to_app;
525 };
526
527 # make it runnable as standalone app,
528 # like it would be run via 'plackup' utility
529 if (__FILE__ eq \$0) {
530         require Plack::Runner;
531
532         my \$runner = Plack::Runner->new();
533         \$runner->parse_options(qw(--env deployment --port $port),
534                                "$local" ? qw(--host 127.0.0.1) : ());
535         \$runner->run(\$app);
536 }
537 __END__
538 EOF
539
540         chmod a+x "$fqgitdir/gitweb/gitweb.psgi"
541         # configuration is embedded in server script file, gitweb.psgi
542         rm -f "$conf"
543 }
544
545 gitweb_conf() {
546         cat > "$fqgitdir/gitweb/gitweb_config.perl" <<EOF
547 #!/usr/bin/perl
548 our \$projectroot = "$(dirname "$fqgitdir")";
549 our \$git_temp = "$fqgitdir/gitweb/tmp";
550 our \$projects_list = \$projectroot;
551 EOF
552 }
553
554 gitweb_conf
555
556 resolve_full_httpd
557 mkdir -p "$fqgitdir/gitweb/$httpd_only"
558
559 case "$httpd" in
560 *lighttpd*)
561         lighttpd_conf
562         ;;
563 *apache2*)
564         apache2_conf
565         ;;
566 webrick)
567         webrick_conf
568         ;;
569 *mongoose*)
570         mongoose_conf
571         ;;
572 *plackup*)
573         plackup_conf
574         ;;
575 *)
576         echo "Unknown httpd specified: $httpd"
577         exit 1
578         ;;
579 esac
580
581 start_httpd
582 url=http://127.0.0.1:$port
583
584 if test -n "$browser"; then
585         httpd_is_ready && git web--browse -b "$browser" $url || echo $url
586 else
587         httpd_is_ready && git web--browse -c "instaweb.browser" $url || echo $url
588 fi