Gitweb: how to display markdown file in html format automatically like github

5.8k Views Asked by At

Markdown is important for documentation, it is very nice to see README.md can be automatically show in html format in github like https://github.com/twitter/bootstrap/blob/master/README.md

gitweb is written in perl script, and there is plugin for markdown in perl already.

I want to check whether there is plugin/solution to let gitweb automatically show html files for markdown format.

4

There are 4 best solutions below

2
On BEST ANSWER

Here's something you can stick somewhere under sub git_summary in your gitweb.perl or gitweb.cgi. Note that it depends on an external markdown executable.

if (!$prevent_xss) {
    $file_name = "README.md";
    my $proj_head_hash = git_get_head_hash($project);
    my $readme_blob_hash = git_get_hash_by_path($proj_head_hash, "README.md", "blob");

    if ($readme_blob_hash) { # if README.md exists                                                                                                                                                      
        print "<div class=\"header\">readme</div>\n";
        print "<div class=\"readme page_body\">"; # TODO find/create a better CSS class than page_body                                                                                                  

        my $cmd_markdownify = $GIT . " " . git_cmd() . " cat-file blob " . $readme_blob_hash . " | markdown |";
        open FOO, $cmd_markdownify or die_error(500, "Open git-cat-file blob '$hash' failed");
        while (<FOO>) {
            print $_;
        }
        close(FOO);

        print "</div>";
    }
}

I don't really know Perl, so this is a dirty hack more than anything else, but it works.

1
On

I use following post-receive hook in my remote repositories which are made browsable with gitweb.

#!/bin/sh
#
# Post-receive hook script which generates README.html to git-dir from
# README.md found at the head of master branch in repository.
#
# Gitweb can read the README.html and embed it to the project summary page.

git cat-file blob HEAD:README.md | markdown > $GIT_DIR/README.html

That script is run when I push commits from my local work repositories to the remote bare ones. You could use that or something similar depending on your workflow/setup.

More information about git hooks: http://book.git-scm.com/5_git_hooks.html

0
On

I use this tampermonkey script in chrome to render README.md files as html, directly in gitweb: https://gist.github.com/nemoo/ee47cd9ad2a5b4fdddfa Works nicely even if you do not have access to the gitweb server.

2
On

Here are my modifications to gitweb.cgi based on the accepted answer, so that:

  • README.md in the project root is displayed (as in accepted answer)
  • Any *.md file is displayed if it is chosen as file from the tree view
  • local links among .md files in a project are preserved (i.e. (my parent)[../parent.md] would refer correctly); should work for images as well

Note that I'm using sudo apt-get install libtext-markdown-perl on Ubuntu to provide the required markdown executable.

Here are the modifications as a diff patch:

--- /usr/share/gitweb/gitweb.cgi.orig   2016-04-13 10:28:03.268872899 +0200
+++ /usr/share/gitweb/gitweb.cgi    2016-04-13 10:39:02.344875516 +0200
@@ -16,8 +16,9 @@
 use Encode;
 use Fcntl ':mode';
 use File::Find qw();
-use File::Basename qw(basename);
+use File::Basename qw(basename dirname);
 use Time::HiRes qw(gettimeofday tv_interval);
+use File::Spec; # hack
 binmode STDOUT, ':utf8';

 our $t0 = [ gettimeofday() ];
@@ -6585,6 +6586,20 @@
        print "\n</div>\n"; # class="readme"
    }

+   # hack
+   if (!$prevent_xss) {
+       $file_name = "README.md";
+       my $proj_head_hash = git_get_head_hash($project);
+       my $readme_blob_hash = git_get_hash_by_path($proj_head_hash, "README.md", "blob");
+
+       if ($readme_blob_hash) { # if README.md exists
+           print "<div class=\"header\">$file_name</div>\n";
+           print "<div class=\"readme page_body\">"; # TODO find/create a better CSS class than page_body
+           print get_markdown($file_name, $readme_blob_hash);
+           print "</div>";
+       }
+   }
+
    # we need to request one more than 16 (0..15) to check if
    # those 16 are all
    my @commitlist = $head ? parse_commits($head, 17) : ();
@@ -7059,6 +7074,9 @@
    $fd = run_highlighter($fd, $highlight, $syntax)
        if $syntax;

+   # hack
+   my $ismarkdown = ($file_name =~ /md$/);
+
    git_header_html(undef, $expires);
    my $formats_nav = '';
    if (defined $hash_base && (my %co = parse_commit($hash_base))) {
@@ -7102,6 +7120,10 @@
              href(action=>"blob_plain", hash=>$hash,
                   hash_base=>$hash_base, file_name=>$file_name) .
              qq!" />\n!;
+   } elsif ($ismarkdown) {
+       print qq!<div class="readme page_body">\n!;
+       print get_markdown($file_name, $hash);
+       print qq!</div>\n!; #  $cmd_markdownify
    } else {
        my $nr;
        while (my $line = <$fd>) {
@@ -7119,6 +7141,79 @@
    git_footer_html();
 }

+# hack
+sub get_norm_rel_path { # http://www.perlmonks.org/bare/?node_id=11907
+   my $unnormpath = shift;
+   while ($unnormpath =~ m!/\.!) {
+       $unnormpath =~ s!/[^\/]+/\.\.!!;
+       # print "Path is now -+$unnormpath+-\n";
+   }
+   return $unnormpath;
+}
+sub get_markdown {
+   my $tfilename = shift;
+   my $thash = shift;
+   my $rethtmlstr = "";
+   use open ":encoding(utf8)"; # needed to have utf8 survive through the shell pipe
+   my $cmd_markdownify = $GIT . " " . git_cmd() . " cat-file blob " . $thash . " | perl -e 'my \$str = do { local \$/; <STDIN> }; \$str =~ s/<!--.*?--\s*>//gs; print \$str;' | markdown |";
+   open (FOO, $cmd_markdownify) or die_error(500, "Open git-cat-file blob '$thash' failed");
+   while (<FOO>) {
+       if ($_ =~ /(<img[^>]src=")(.*?)"/) {
+           my $origcut = "".$2;
+           my $testcut = "".$2;
+           my $is_anchor = ($testcut =~ /^#/);
+           my $is_absolute = ($testcut =~ /^http/);
+           my $is_relative_up = ($testcut =~ /^\.\./);
+           my $is_local_link = ((!$is_anchor) and (!$is_absolute));
+           my $tdir = dirname($tfilename);
+           my $is_tdir_proper = (($tdir ne "") and ($tdir ne "."));
+           #print "XX: $origcut ($is_anchor, $is_absolute - $is_local_link) ($is_relative_up, $is_tdir_proper, $tdir, $tfilename)\n"; # dbg
+           if ($is_local_link) {
+               if ($is_relative_up) { # normalize
+                   if ($is_tdir_proper) {
+                       # cheat with absolute path here:
+                       my $resolved = get_norm_rel_path( File::Spec->rel2abs ("$origcut", "/$tdir" ) );
+                       $resolved = substr $resolved, 1;
+                       #print "YY: $resolved\n";
+                       $_ =~ s!(<img[^>]src=")(.*?)"!$1?p=$project;a=blob_plain;f=$resolved"!gi;
+                   }
+               } else {
+                   $_ =~ s!(<img[^>]src=")(.*?)"!$1?p=$project;a=blob_plain;f=$2"!gi;
+                   #print "ZZ: $_\n";
+               }
+           }
+       }
+       if ($_ =~ /(<a[^>]href=")(.*?)"/) {
+           my $origcut = "".$2;
+           my $testcut = "".$2;
+           my $is_anchor = ($testcut =~ /^#/);
+           my $is_absolute = ($testcut =~ /^http/);
+           my $is_relative_up = ($testcut =~ /^\.\./);
+           my $is_local_link = ((!$is_anchor) and (!$is_absolute));
+           my $tdir = dirname($tfilename);
+           my $is_tdir_proper = (($tdir ne "") and ($tdir ne "."));
+           #print "XX: $origcut ($is_anchor, $is_absolute - $is_local_link) ($is_relative_up, $is_tdir_proper, $tdir, $tfilename)\n"; # dbg
+           if ($is_local_link) {
+               if ($is_relative_up) { # normalize
+                   if ($is_tdir_proper) {
+                       # cheat with absolute path here:
+                       my $resolved = get_norm_rel_path( File::Spec->rel2abs ("$origcut", "/$tdir" ) );
+                       $resolved = substr $resolved, 1;
+                       #print "YY: $resolved\n";
+                       $_ =~ s!(<a[^>]href=")(.*?)"!$1?p=$project;a=blob;f=$resolved"!gi;
+                   }
+               } else {
+                   $_ =~ s!(<a[^>]href=")(.*?)"!$1?p=$project;a=blob;f=$2"!gi;
+                   #print "ZZ: $_\n";
+               }
+           }
+       }
+       $rethtmlstr .= $_;
+   }
+   close(FOO);
+   return $rethtmlstr;
+}
+
 sub git_tree {
    if (!defined $hash_base) {
        $hash_base = "HEAD";