cGit-UI for Git Repositories

cGit-UI – is a web interface for Git Repositories. cGit CGI script is writen in C and therefore it's fast enough

12 Commits   0 Branches   1 Tag
Index: git-shared.c
===================================================================
--- git-shared.c	(nonexistent)
+++ git-shared.c	(revision 5)
@@ -0,0 +1,1410 @@
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <sys/sysinfo.h>
+#include <sys/types.h>
+#include <stdint.h>
+#include <dirent.h>
+#include <sys/stat.h> /* chmod(2)    */
+#include <sys/file.h>
+#include <sys/mman.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <string.h>   /* strdup(3)   */
+#include <libgen.h>   /* basename(3) */
+#include <ctype.h>    /* tolower(3)  */
+#include <errno.h>
+#include <time.h>
+#include <sys/time.h>
+#include <pwd.h>
+#include <grp.h>
+#include <stdarg.h>
+#include <unistd.h>
+#include <magic.h>
+
+#include <git2.h>
+
+#include <nls.h>
+
+#include <defs.h>
+#include <fatal.h>
+#include <http.h>
+#include <html.h>
+#include <strbuf.h>
+#include <wrapper.h>
+#include <system.h>
+
+#include <ctx.h>
+#include <git-shared.h>
+
+
+#define CGIT_ERRMSG_SIZE 4096
+
+void cgit_error( const char *fmt, ... )
+{
+  va_list arg_ptr;
+  char  buf[CGIT_ERRMSG_SIZE];
+  char  msg[CGIT_ERRMSG_SIZE];
+  char *format = "%s: %s\n";
+
+  va_start( arg_ptr, fmt );
+
+  vsnprintf( msg, CGIT_ERRMSG_SIZE, (const void *)fmt, arg_ptr );
+
+  va_end( arg_ptr ); /* Reset variable arguments. */
+
+  snprintf( buf, CGIT_ERRMSG_SIZE, format, "cgit", msg );
+
+  (void)write( STDERR_FILENO, buf, strlen( buf ) );
+
+  exit( 1 );
+}
+
+cgit_errfunc cgit_fatal = cgit_error;
+
+
+
+int is_bare( const char *path )
+{
+  char *p = NULL;
+  int   ret = -1;
+
+  if( !path || !*path ) return ret;
+
+  p = rindex( path, '.' );
+  if( !p )
+  {
+    ret = 0;
+  }
+  else
+  {
+    if( !strncmp( p, ".git", 4 ) )
+      ret = 1;
+    else
+      ret = 0;
+  }
+
+  return ret;
+}
+
+git_repository *open_repository( const char *path )
+{
+  git_repository *repo = NULL;
+  int bare = 0;
+
+  if( (bare = is_bare( path )) < 0 ) return repo;
+
+  if( bare )
+  {
+    if( git_repository_open_bare( &repo, path ) < 0 ) return repo;
+  }
+  else
+  {
+    if( git_repository_open( &repo, path ) < 0 ) return repo;
+  }
+
+  return repo;
+}
+
+void close_repository( git_repository *repo )
+{
+  if( !repo ) return;
+  git_repository_free( repo );
+}
+
+/*
+  Get Last Commit:
+  (Only commit-ish refs are permitted)
+ */
+git_commit *get_commit_by_ref( git_repository *repo, const char *ref )
+{
+  int error = 0;
+  git_object *obj = NULL;
+  git_commit *commit = NULL;
+
+  char reference[PATH_MAX] = { 0 };
+
+  if( !repo ) return commit;
+  if( !ref ) ref = "HEAD^{commit}";
+
+  sprintf( (char *)&reference[0], "%s^{commit}", ref );
+
+  error = git_revparse_single( &obj, repo, (const char *)&reference[0] );
+  if( error < 0 )
+  {
+    git_error_clear();
+    return commit;
+  }
+
+  if( git_object_type(obj) != GIT_OBJECT_COMMIT )
+  {
+    git_object_free( obj );
+    return commit;
+  }
+
+  error = git_commit_lookup( &commit, repo, git_object_id( obj ) );
+  if( error < 0 )
+  {
+    git_error_clear();
+    git_object_free( obj );
+    return NULL;
+  }
+
+  git_object_free( obj );
+
+  return commit;
+}
+
+git_commit *get_commit_by_hex( git_repository *repo, const char *hex )
+{
+  git_oid     cid;
+  git_commit *commit = NULL;
+
+  if( !repo || !hex ) return commit;
+  if( git_oid_fromstr( &cid, hex ) < 0 ) return commit;
+
+  if( git_commit_lookup( &commit, repo, &cid ) < 0 ) return NULL;
+  return commit;
+}
+
+
+void fill_short_commit_info( struct short_commit_info *info, const char *path )
+{
+  git_repository *repo = NULL;
+  git_commit     *commit = NULL;
+
+  if( !info || !path ) return;
+
+  if( !(repo = open_repository( path )) ) return;
+
+  if( !(commit = get_commit_by_ref( repo, NULL )) )
+  {
+    close_repository( repo );
+    return;
+  }
+
+  git_oid_tostr( info->rev, GIT_OID_HEXSZ+1, git_commit_id( commit ) );
+  info->date   = git_commit_time( commit );
+  info->offset = git_commit_time_offset( commit );
+
+  info->offset = (info->offset % 60) + ((info->offset / 60) * 100);
+
+  git_commit_free( commit );
+
+  close_repository( repo );
+}
+
+struct found {
+  git_object_t    kind;
+  git_filemode_t  mode;
+  const git_oid  *oid;
+  git_object     *obj;
+};
+
+static void find_entry( struct found *ret, git_repository *repo, git_tree *tree, char *path )
+{
+  git_tree     *ntree = NULL;
+  int           i, cnt;
+  char         *s, *p = NULL, *n;
+
+  if( !ret || !repo || !tree ) return;
+
+  if( path && *path )
+  {
+    s = path;
+
+    while( *s &&  *s == '/' )
+      ++s;
+    n = p = s;
+
+    while( *p && *p != '/' )
+      ++p;
+    if( *p )
+    {
+      *p = '\0'; s = ++p;
+    }
+    else
+      s = p;
+
+    git_tree_dup( &ntree, tree );
+    cnt = git_tree_entrycount( ntree );
+
+    for( i = 0; i < cnt; ++i )
+    {
+      const git_tree_entry *entry;
+      entry = git_tree_entry_byindex( ntree, i );
+
+      if( !strcmp( git_tree_entry_name( entry ), n ) )
+      {
+        ret->kind = git_tree_entry_type( entry );
+        ret->mode = git_tree_entry_filemode( entry );
+        git_tree_entry_to_object( &ret->obj, repo, entry );
+        ret->oid = git_object_id( (const git_object *)ret->obj );
+
+        if( ret->kind == GIT_OBJECT_TREE && s && *s )
+        {
+          git_object *obj;
+          git_tree_entry_to_object( &obj, repo, entry );
+          find_entry( ret, repo, (git_tree *)obj, s );
+          git_object_free( obj );
+          break;
+        }
+        else
+        {
+          break;
+        }
+      }
+
+    }
+    git_tree_free( ntree );
+  }
+
+  return;
+}
+
+
+void fill_commit_info( struct cgit_info *info, const char *path, const char *rpath )
+{
+  git_repository *repo = NULL;
+  git_oid         cid;
+  git_commit     *commit = NULL;
+  git_tree       *tree = NULL;
+  const git_oid  *oid;
+  const git_signature *sign = NULL;
+
+  char *author;
+  int   len;
+
+  if( !info || !path || !info->revision[0] ) return;
+  if( git_oid_fromstr( &cid, (const char *)&info->revision[0] ) < 0 ) return;
+  if( !(repo = open_repository( path )) ) return;
+
+  git_commit_lookup( &commit, repo, &cid );
+  git_commit_tree( &tree, commit );
+
+  info->kind = git_object_type( (const git_object *)tree );
+  oid = git_tree_id( (const git_tree *)tree );
+  git_oid_fmt( (char *)&info->oid[0], oid );
+
+  if( rpath && *rpath )
+  {
+    struct found  ret = { .kind = GIT_OBJECT_TREE, .oid = NULL, .obj = NULL };
+    char relative_path[PATH_MAX] = { 0 };
+    sprintf( (char *)&relative_path[0], rpath );
+    find_entry( &ret, repo, tree, (char *)&relative_path[0] );
+    info->kind = ret.kind;
+    info->mode = ret.mode;
+    git_oid_fmt( (char *)&info->oid[0], ret.oid );
+    git_object_free( ret.obj );
+  }
+
+  git_tree_free( tree );
+
+  info->date = git_commit_time( commit );
+  info->offset = git_commit_time_offset( commit );
+
+  info->offset = (info->offset % 60) + ((info->offset / 60) * 100);
+
+  sign = git_commit_author( commit );
+  len = (int)strlen( sign->name ) + 1;
+  author = (char *)__sbrk( len );
+  strcpy( author, sign->name );
+  info->author = author;
+
+  git_commit_free( commit );
+  close_repository( repo );
+}
+
+
+static const char *mime_info( struct cgit_info *info, const char *buffer, size_t length )
+{
+  const char *mime = NULL;
+  magic_t     magic;
+
+  if( !info || !buffer || !length ) return mime;
+
+  magic = magic_open( MAGIC_MIME );
+  if( !magic )
+  {
+    html_fatal( "unable to initialize magic library" );
+    return mime;
+  }
+  if( magic_load( magic, NULL ) != 0 )
+  {
+    html_fatal( "cannot load magic database - %s\n", magic_error( magic ) );
+    magic_close( magic );
+    return mime;
+  }
+
+  mime = magic_buffer( magic, buffer, length );
+  if( mime )
+  {
+    int len = (int)strlen( mime ) + 1;
+    char *mime_type = (char *)__sbrk( len );
+    memcpy( (void *)mime_type, (const void *)mime, len );
+    info->mime = (const char *)mime_type;
+  }
+
+  magic_close( magic );
+  return mime;
+}
+
+void fill_mime_info( struct cgit_info *info, const char *path, const char *rpath )
+{
+  git_repository *repo = NULL;
+  git_oid         cid;
+  git_commit     *commit = NULL;
+  git_tree       *tree = NULL;
+  git_object     *obj = NULL;
+  git_object_t    kind;
+  git_blob       *blob = NULL;
+  int             blob_allocated = 0;
+  git_off_t       rawsize = 0;
+  const char     *raw = NULL;
+  char            mime_buf[1024] = { 0 };
+  size_t          len = 1024;
+
+  if( !info || !path || !info->revision[0] || info->kind != GIT_OBJECT_BLOB ) return;
+  if( git_oid_fromstr( &cid, (const char *)&info->revision[0] ) < 0 ) return;
+  if( !(repo = open_repository( path )) ) return;
+
+  git_commit_lookup( &commit, repo, &cid );
+  git_commit_tree( &tree, commit );
+
+  blob = (git_blob *)tree;
+  kind = git_object_type( (const git_object *)tree );
+  if( rpath && *rpath )
+  {
+    struct found  ret = { .kind = GIT_OBJECT_TREE, .oid = NULL, .obj = NULL };
+    char relative_path[PATH_MAX] = { 0 };
+    sprintf( (char *)&relative_path[0], rpath );
+    find_entry( &ret, repo, tree, (char *)&relative_path[0] );
+    kind = ret.kind;
+    if( kind == GIT_OBJECT_BLOB )
+    {
+      git_object_dup( &obj, ret.obj );
+      blob_allocated = 1;
+      blob = (git_blob *)obj;
+    }
+    git_object_free( ret.obj ); /* We have to free allocated object returned by find_entry(). */
+  }
+
+  if( kind != GIT_OBJECT_BLOB )
+  {
+    if( blob_allocated )
+      git_object_free( (git_object *)blob );
+    git_tree_free( tree );
+    git_commit_free( commit );
+    close_repository( repo );
+    return;
+  }
+
+  rawsize = git_blob_rawsize( blob );
+  raw = (const char *)git_blob_rawcontent( blob );
+  if( rawsize > 1024 )
+  {
+    memcpy( (void *)&mime_buf[0], (const void *)raw, len );
+  }
+  else
+  {
+    memcpy( (void *)&mime_buf[0], (const void *)raw, (size_t)rawsize );
+    len = (size_t)rawsize;
+  }
+
+  if( blob_allocated )
+    git_object_free( (git_object *)blob );
+  git_tree_free( tree );
+
+  mime_info( info, (const char *)&mime_buf[0], len );
+
+  git_commit_free( commit );
+  close_repository( repo );
+}
+
+
+size_t branches_number( const char *path, const char *skip )
+{
+  git_repository *repo = NULL;
+  git_reference_iterator *iter = NULL;
+  const char *name = NULL;
+  size_t ret = 0;
+
+  if( !path ) return ret;
+
+  if( !(repo = open_repository( path )) ) return ret;
+
+  if( git_reference_iterator_glob_new( &iter, repo, "refs/heads/*" ) < 0 )
+  {
+    close_repository( repo );
+    return ret;
+  }
+
+  while( !git_reference_next_name( &name, iter ) )
+  {
+    if( skip && *skip )
+    {
+      if( strcmp( (const char *)&name[11], skip ) )
+      {
+        ++ret;
+      }
+    }
+    else
+      ++ret;
+  }
+
+  git_reference_iterator_free( iter );
+  close_repository( repo );
+
+  return ret;
+}
+
+size_t tags_number( const char *path )
+{
+  git_repository *repo = NULL;
+  git_strarray    tags;
+  size_t ret = 0;
+
+  if( !path ) return ret;
+
+  if( !(repo = open_repository( path )) ) return ret;
+
+  if( git_tag_list( &tags, repo ) < 0 )
+  {
+    close_repository( repo );
+    return ret;
+  }
+
+  ret = tags.count;
+  git_strarray_free( &tags );
+
+  close_repository( repo );
+
+  return ret;
+}
+
+/**********************************
+  List of references functions:
+ */
+struct cgit_ref_names *cgit_ref_names_new( void )
+{
+  struct cgit_ref_names *names = NULL;
+
+  names = (struct cgit_ref_names *)xmalloc( sizeof(struct cgit_ref_names) );
+  return names;
+}
+
+void cgit_ref_names_allocate( struct cgit_ref_names **ref_names )
+{
+  struct cgit_ref_names *names = NULL;
+
+  if( !ref_names ) return;
+  names = (struct cgit_ref_names *)xmalloc( sizeof(struct cgit_ref_names) );
+  *ref_names = names;
+}
+
+void cgit_ref_names_add( struct cgit_ref_names *names, const char *name )
+{
+  if( !names ) return;
+
+  if( !names->name )
+  {
+    names->name = (char **)xmalloc( sizeof(char *) );
+    names->name[0] = strdup( name );
+    names->len = (size_t)1;
+  }
+  else
+  {
+    names->name = (char **)xrealloc( names->name, (names->len + 1) * sizeof(char *) );
+    names->name[names->len] = strdup( name );
+    ++names->len;
+  }
+}
+
+void cgit_ref_names_free( struct cgit_ref_names *names )
+{
+  if( !names ) return;
+
+  if( names->len && names->name )
+  {
+    size_t i;
+    for( i = 0; i < names->len; ++i )
+    {
+      if( names->name[i] )
+        free( names->name[i] );
+    }
+    free( names->name );
+  }
+  free( names );
+}
+/*
+  End of List of references functions.
+ **************************************/
+
+void lookup_branches_by_prefix( struct cgit_ref_names **ref_names, const char *prefix )
+{
+  const char *name = NULL, *git_root = NULL, *repo_root = NULL;
+  struct cgit_ref_names *names = NULL;
+
+  if( !ref_names || !prefix || !*prefix ) return;
+
+  name      = ctx.repo.name;
+  git_root  = ctx.repo.git_root;
+  repo_root = ctx.repo.repo_root;
+
+  if( name && git_root )
+  {
+    git_repository *repo = NULL;
+    char path[PATH_MAX] = { 0 };
+
+    sprintf( (char *)&path[0], "%s/", git_root );
+    if( repo_root && *repo_root )
+    {
+      strcat( (char *)&path[0], repo_root );
+      strcat( (char *)&path[0], "/" );
+    }
+    strcat( (char *)&path[0], name );
+
+    if( !is_bare( (char *)&path[0] ) )
+    {
+      strcat( (char *)&path[0], "/.git" );
+    }
+
+    if( !(repo = open_repository( (const char *)&path[0] )) ) return;
+
+    {
+      git_reference_iterator *iter = NULL;
+      const char *name = NULL;
+      const char *refs = "refs/heads/*";
+
+      if( git_reference_iterator_glob_new( &iter, repo, refs ) < 0 )
+      {
+        close_repository( repo );
+        return;
+      }
+
+      cgit_ref_names_allocate( &names );
+      *ref_names = names;
+
+      while( !git_reference_next_name( &name, iter ) )
+      {
+        size_t len = strlen( prefix );
+
+        if( !strncmp( prefix, (char *)&name[11], len ) )
+          cgit_ref_names_add( names, (const char *)&name[11] );
+      }
+      git_reference_iterator_free( iter );
+    }
+
+    close_repository( repo );
+  }
+
+  return;
+}
+
+void lookup_tags_by_prefix( struct cgit_ref_names **ref_names, const char *prefix )
+{
+  const char *name = NULL, *git_root = NULL, *repo_root = NULL;
+  struct cgit_ref_names *names = NULL;
+
+  if( !ref_names || !prefix || !*prefix ) return;
+
+  name      = ctx.repo.name;
+  git_root  = ctx.repo.git_root;
+  repo_root = ctx.repo.repo_root;
+
+  if( name && git_root )
+  {
+    git_repository *repo = NULL;
+    char path[PATH_MAX] = { 0 };
+
+    sprintf( (char *)&path[0], "%s/", git_root );
+    if( repo_root && *repo_root )
+    {
+      strcat( (char *)&path[0], repo_root );
+      strcat( (char *)&path[0], "/" );
+    }
+    strcat( (char *)&path[0], name );
+
+    if( !is_bare( (char *)&path[0] ) )
+    {
+      strcat( (char *)&path[0], "/.git" );
+    }
+
+    if( !(repo = open_repository( (const char *)&path[0] )) ) return;
+
+    {
+      git_reference_iterator *iter = NULL;
+      const char *name = NULL;
+      const char *refs = "refs/tags/*";
+
+      if( git_reference_iterator_glob_new( &iter, repo, refs ) < 0 )
+      {
+        close_repository( repo );
+        return;
+      }
+
+      cgit_ref_names_allocate( &names );
+      *ref_names = names;
+
+      while( !git_reference_next_name( &name, iter ) )
+      {
+        size_t len = strlen( prefix );
+
+        if( !strncmp( prefix, (char *)&name[10], len ) )
+          cgit_ref_names_add( names, (const char *)&name[10] );
+      }
+      git_reference_iterator_free( iter );
+    }
+
+    close_repository( repo );
+  }
+
+  return;
+}
+
+
+/***********************************
+  List of commits functions:
+ */
+struct cgit_hex_commits *cgit_hex_commits_new( void )
+{
+  struct cgit_hex_commits *commits = NULL;
+
+  commits = (struct cgit_hex_commits *)xmalloc( sizeof(struct cgit_hex_commits) );
+  return commits;
+}
+
+void cgit_hex_commits_allocate( struct cgit_hex_commits **hex_commits )
+{
+  struct cgit_hex_commits *commits = NULL;
+
+  if( !hex_commits ) return;
+  commits = (struct cgit_hex_commits *)xmalloc( sizeof(struct cgit_hex_commits) );
+  *hex_commits = commits;
+}
+
+void cgit_hex_commits_add( struct cgit_hex_commits *commits, const char *hex )
+{
+  if( !commits ) return;
+
+  if( !commits->hex )
+  {
+    commits->hex = (char **)xmalloc( sizeof(char *) );
+    commits->hex[0] = strdup( hex );
+    commits->len = (size_t)1;
+  }
+  else
+  {
+    commits->hex = (char **)xrealloc( commits->hex, (commits->len + 1) * sizeof(char *) );
+    commits->hex[commits->len] = strdup( hex );
+    ++commits->len;
+  }
+}
+
+void cgit_hex_commits_free( struct cgit_hex_commits *commits )
+{
+  if( !commits ) return;
+
+  if( commits->len && commits->hex )
+  {
+    size_t i;
+    for( i = 0; i < commits->len; ++i )
+    {
+      if( commits->hex[i] )
+        free( commits->hex[i] );
+    }
+    free( commits->hex );
+  }
+  free( commits );
+}
+/*
+  End of List of commits functions.
+ ***********************************/
+
+void parse_relative_path( char *ref, char *rpath, const char *relative_path )
+{
+  char *s, *p = NULL, *n, *path, *path_info;
+  int   len = 0;
+
+  *ref = '\0', *rpath = '\0';
+
+  if( relative_path && *relative_path )
+  {
+    path_info = xstrdup( relative_path );
+    s = path_info;
+
+    while( *s )
+    {
+      while( *s &&  *s == '/' )
+        ++s;
+      n = p = s;
+
+      while( *p && *p != '/' )
+        ++p;
+      if( *p )
+      {
+        *p = '\0'; s = ++p;
+      }
+      else
+        s = p;
+
+      if( !strcmp( n, "tags" ) )
+      {
+        sprintf( ref, "refs/%s/", n );
+        n = s;
+        while( *p && *p != '/' )
+          ++p;
+        if( *p )
+        {
+          *p = '\0'; s = ++p;
+        }
+        else
+         s = p;
+
+        if( n && *n )
+        {
+          int   found = 0;
+          char  tag[PATH_MAX] = { 0 };
+          char  reminder[PATH_MAX] = { 0 };
+          char *b, *r;
+
+          struct cgit_ref_names *names = NULL;
+
+          sprintf( reminder, "%s", s );
+          sprintf( tag, "%s", n );
+
+          r = (char *)&reminder[0];
+
+          /*********************************
+            Searching the real name of tag:
+           */
+          lookup_tags_by_prefix( &names, n );
+          while( *r )
+          {
+            b = r;
+            while( *r && *r != '/' )
+              ++r;
+            if( *r )
+            {
+              *r = '\0'; ++r;
+            }
+
+            if( b && *b )
+            {
+              char probe[PATH_MAX] = { 0 };
+              sprintf( probe, "%s/%s", tag, b );
+
+              found = 0;
+              {
+                size_t i, len = strlen( probe );
+                for( i = 0; i < names->len; ++i )
+                {
+                  if( !strncmp( probe, names->name[i], len ) )
+                  {
+                    ++found;
+                  }
+                }
+              }
+              if( found == 1 )
+              {
+                strcat( tag, "/" );
+                strcat( tag, b );
+                break;
+              }
+              if( found )
+              {
+                strcat( tag, "/" );
+                strcat( tag, b );
+              }
+            }
+          }
+          cgit_ref_names_free( names );
+
+          if( found )
+          {
+            strcat( ref, tag );
+            p += r - &reminder[0];
+            s = p;
+          }
+          else
+            strcat( ref, n );
+        }
+        else
+          sprintf( ref, "refs/heads/%s", ctx.repo.trunk );
+
+        break;
+      }
+      else if( !strcmp( n, "branches" ) )
+      {
+        sprintf( ref, "refs/heads/" );
+
+        n = s;
+        while( *p && *p != '/' )
+          ++p;
+        if( *p )
+        {
+          *p = '\0'; s = ++p;
+        }
+        else
+         s = p;
+
+        if( n && *n )
+        {
+          int   found = 0;
+          char  branch[PATH_MAX] = { 0 };
+          char  reminder[PATH_MAX] = { 0 };
+          char *b, *r;
+
+          struct cgit_ref_names *names = NULL;
+
+          sprintf( reminder, "%s", s );
+          sprintf( branch, "%s", n );
+
+          r = (char *)&reminder[0];
+
+          /************************************
+            Searching the real name of branch:
+           */
+          lookup_branches_by_prefix( &names, n );
+          while( *r )
+          {
+            b = r;
+            while( *r && *r != '/' )
+              ++r;
+            if( *r )
+            {
+              *r = '\0'; ++r;
+            }
+
+            if( b && *b )
+            {
+              char probe[PATH_MAX] = { 0 };
+              sprintf( probe, "%s/%s", branch, b );
+
+              found = 0;
+              {
+                size_t i, len = strlen( probe );
+                for( i = 0; i < names->len; ++i )
+                {
+                  if( !strncmp( probe, names->name[i], len ) )
+                  {
+                    ++found;
+                  }
+                }
+              }
+              if( found == 1 )
+              {
+                strcat( branch, "/" );
+                strcat( branch, b );
+                break;
+              }
+              if( found )
+              {
+                strcat( branch, "/" );
+                strcat( branch, b );
+              }
+            }
+          }
+          cgit_ref_names_free( names );
+
+          if( found )
+          {
+            strcat( ref, branch );
+            p += r - &reminder[0];
+            s = p;
+          }
+          else
+            strcat( ref, n );
+        }
+        else
+          sprintf( ref, "refs/heads/%s", ctx.repo.trunk );
+
+        break;
+      }
+      else if( !strcmp( n, "trunk" ) )
+      {
+        sprintf( ref, "refs/heads/%s", ctx.repo.trunk );
+        break;
+      }
+      else
+      {
+        /* return '/' */
+        *--p = '/'; p = n;
+        sprintf( ref, "refs/heads/%s", ctx.repo.trunk );
+        break;
+      }
+    }
+
+    path = p;
+
+    if( *path )
+    {
+      len = (int)strlen( path );
+
+      if( path[len-1] =='/' ) { path[len-1] = '\0'; --len; }
+      len += 1;
+
+      sprintf( rpath, "%s", path );
+    }
+
+    free( path_info );
+  }
+}
+
+static void fill_hex_list( struct cgit_hex_commits **hex_commits, char *buf )
+{
+  char *s, *p = NULL, *h;
+  struct cgit_hex_commits *commits = NULL;
+
+  if( !hex_commits || !buf ) return;
+
+  cgit_hex_commits_allocate( &commits );
+  *hex_commits = commits;
+
+  s = buf;
+  while( *s )
+  {
+    while( *s &&  *s == '\n' )
+      ++s;
+    h = p = s;
+
+    while( *p && *p != '\n' )
+      ++p;
+    if( *p )
+    {
+      *p = '\0'; s = ++p;
+    }
+    else
+      s = p;
+
+    cgit_hex_commits_add( commits, h );
+  }
+}
+
+
+void cgit_fill_commits_list( struct cgit_hex_commits **hex_commits, int ofs, const char *relative_path, const char *revision )
+{
+  const char *name = NULL, *git_root = NULL, *repo_root = NULL;
+
+  if( !hex_commits || !relative_path ) return;
+
+  name      = ctx.repo.name;
+  git_root  = ctx.repo.git_root;
+  repo_root = ctx.repo.repo_root;
+
+  if( name && git_root )
+  {
+    char ref[PATH_MAX] = { 0 };
+    char rpath[PATH_MAX] = { 0 };
+    int  psize = atoi( ctx.vars.page_size );
+
+    char path[PATH_MAX] = { 0 };
+    char cmd[PATH_MAX];
+    struct strbuf buf = STRBUF_INIT;
+    pid_t p = (pid_t) -1;
+    int   rc;
+
+    sprintf( (char *)&path[0], "%s/", git_root );
+    if( repo_root && *repo_root )
+    {
+      strcat( (char *)&path[0], repo_root );
+      strcat( (char *)&path[0], "/" );
+    }
+    strcat( (char *)&path[0], name );
+
+    if( !is_bare( (char *)&path[0] ) )
+    {
+      strcat( (char *)&path[0], "/.git" );
+    }
+
+    parse_relative_path( (char *)&ref[0], (char *)&rpath[0], relative_path );
+
+    /*
+      NOTE:
+      ====
+        The --follow option for blobs is not applicable here because the option --skip=n
+        doesn't works when option --follow is defined.
+
+        In other words we cannot use --follow here:
+
+        struct cgit_info  info = CGIT_INFO_INIT;
+        const char *follow = "";
+
+        if( revision && *revision )
+          strncpy( info.revision, revision, GIT_OID_HEXSZ+1 );
+        else
+          strncpy( info.revision, ctx.repo.info.revision, GIT_OID_HEXSZ+1 );
+
+        fill_commit_info( &info, (const char *)&path[0], (const char *)&rpath[0] );
+        if( info.kind == GIT_OBJECT_BLOB )
+        {
+          follow = "--follow";
+        }
+     */
+
+    ++psize;
+
+    if( revision && *revision )
+      snprintf( (char *)&cmd[0], 1024, "git --git-dir=%s log --pretty=format:'%%H' --skip=%d -%d %s -- %s 2>/dev/null", (char *)&path[0], ofs, psize, revision, (char *)&rpath[0] );
+    else
+      snprintf( (char *)&cmd[0], 1024, "git --git-dir=%s log --pretty=format:'%%H' --skip=%d -%d %s -- %s 2>/dev/null", (char *)&path[0], ofs, psize, (char *)&ref[0], (char *)&rpath[0] );
+    p = sys_exec_command( &buf, cmd );
+    rc = sys_wait_command( p, NULL );
+    if( rc != 0 )
+    {
+      strbuf_release( &buf );
+      return;
+    }
+
+    if( buf.buf[0] )
+    {
+      strbuf_trim( &buf );
+
+      fill_hex_list( hex_commits, (char *)&buf.buf[0] );
+    }
+
+    strbuf_release( &buf );
+  }
+
+  return;
+}
+
+git_repository *cgit_open_repository( void )
+{
+  const char     *name = NULL, *git_root = NULL, *repo_root = NULL;
+  git_repository *repo = NULL;
+
+  name      = ctx.repo.name;
+  git_root  = ctx.repo.git_root;
+  repo_root = ctx.repo.repo_root;
+
+  if( name && git_root )
+  {
+    char path[PATH_MAX] = { 0 };
+
+    sprintf( (char *)&path[0], "%s/", git_root );
+    if( repo_root && *repo_root )
+    {
+      strcat( (char *)&path[0], repo_root );
+      strcat( (char *)&path[0], "/" );
+    }
+    strcat( (char *)&path[0], name );
+
+    if( !is_bare( (char *)&path[0] ) )
+    {
+      strcat( (char *)&path[0], "/.git" );
+    }
+
+    return open_repository( (const char *)&path[0] );
+  }
+
+  return repo;
+}
+
+git_commit *lookup_commit_by_ref( const char *ref )
+{
+  const char *name = NULL, *git_root = NULL, *repo_root = NULL;
+  git_commit *commit = NULL;
+
+  if( !ref || !*ref ) return commit;
+
+  name      = ctx.repo.name;
+  git_root  = ctx.repo.git_root;
+  repo_root = ctx.repo.repo_root;
+
+  if( name && git_root )
+  {
+    git_repository *repo = NULL;
+    char path[PATH_MAX] = { 0 };
+
+    sprintf( (char *)&path[0], "%s/", git_root );
+    if( repo_root && *repo_root )
+    {
+      strcat( (char *)&path[0], repo_root );
+      strcat( (char *)&path[0], "/" );
+    }
+    strcat( (char *)&path[0], name );
+
+    if( !is_bare( (char *)&path[0] ) )
+    {
+      strcat( (char *)&path[0], "/.git" );
+    }
+
+    if( !(repo = open_repository( (const char *)&path[0] )) ) return commit;
+    commit = get_commit_by_ref( repo, ref );
+    close_repository( repo );
+  }
+
+  return commit;
+}
+
+git_commit *lookup_commit_by_hex( const char *hex )
+{
+  const char *name = NULL, *git_root = NULL, *repo_root = NULL;
+  git_commit *commit = NULL;
+
+  if( !hex || !*hex ) return commit;
+
+  name      = ctx.repo.name;
+  git_root  = ctx.repo.git_root;
+  repo_root = ctx.repo.repo_root;
+
+  if( name && git_root )
+  {
+    git_repository *repo = NULL;
+    char path[PATH_MAX] = { 0 };
+
+    sprintf( (char *)&path[0], "%s/", git_root );
+    if( repo_root && *repo_root )
+    {
+      strcat( (char *)&path[0], repo_root );
+      strcat( (char *)&path[0], "/" );
+    }
+    strcat( (char *)&path[0], name );
+
+    if( !is_bare( (char *)&path[0] ) )
+    {
+      strcat( (char *)&path[0], "/.git" );
+    }
+
+    if( !(repo = open_repository( (const char *)&path[0] )) ) return commit;
+    commit = get_commit_by_hex( repo, hex );
+    close_repository( repo );
+  }
+
+  return commit;
+}
+
+
+struct print_data {
+  struct strbuf *sb;
+  int *files;
+  int *insertions;
+  int *deletions;
+};
+
+static int printer( const git_diff_delta *delta, const git_diff_hunk *hunk, const git_diff_line *line, void *data )
+{
+  struct print_data *pdata = (struct print_data *)data;
+
+  (void)delta; (void)hunk;
+
+  switch( line->origin )
+  {
+    case GIT_DIFF_LINE_ADDITION: *pdata->insertions += 1; break;
+    case GIT_DIFF_LINE_DELETION: *pdata->deletions  += 1; break;
+    case GIT_DIFF_LINE_FILE_HDR: *pdata->files      += 1; break;
+    default: break;
+  }
+
+  if( line->origin == GIT_DIFF_LINE_CONTEXT  ||
+      line->origin == GIT_DIFF_LINE_ADDITION ||
+      line->origin == GIT_DIFF_LINE_DELETION   )
+    strbuf_addch( pdata->sb, line->origin );
+
+  strbuf_add( pdata->sb, (const void *)line->content, (size_t)line->content_len );
+
+  return 0;
+}
+
+void cgit_fill_diff_with_parent( struct strbuf *sb, char *parent_hex, size_t parent_len, int *files, int *insertions, int *deletions, const char *hex )
+{
+  const char *name = NULL, *git_root = NULL, *repo_root = NULL;
+
+  git_commit *commit = NULL, *parent = NULL;
+  git_tree   *commit_tree = NULL, *parent_tree = NULL;
+  git_diff   *diff = NULL;
+
+  struct print_data  data = { .sb = NULL, .files = NULL, .insertions = NULL, .deletions = NULL };
+
+  if( !parent_hex || !files || !insertions || !deletions || !hex || !*hex ) return;
+  if( parent_len != GIT_OID_HEXSZ+1 ) return;
+
+  name      = ctx.repo.name;
+  git_root  = ctx.repo.git_root;
+  repo_root = ctx.repo.repo_root;
+
+  if( name && git_root )
+  {
+    git_repository *repo = NULL;
+    char path[PATH_MAX] = { 0 };
+
+    sprintf( (char *)&path[0], "%s/", git_root );
+    if( repo_root && *repo_root )
+    {
+      strcat( (char *)&path[0], repo_root );
+      strcat( (char *)&path[0], "/" );
+    }
+    strcat( (char *)&path[0], name );
+
+    if( !is_bare( (char *)&path[0] ) )
+    {
+      strcat( (char *)&path[0], "/.git" );
+    }
+
+    if( !(repo = open_repository( (const char *)&path[0] )) ) return;
+
+    commit = get_commit_by_hex( repo, hex );
+    if( git_commit_parent( &parent, commit, 0 ) < 0 )
+    {
+      git_commit_free( commit );
+      close_repository( repo );
+      return;
+    }
+    if( git_oid_tostr( parent_hex, GIT_OID_HEXSZ+1, git_commit_id( parent ) ) < 0 )
+    {
+      git_commit_free( commit );
+      git_commit_free( parent );
+      close_repository( repo );
+      return;
+    }
+
+    if( git_commit_tree( &commit_tree, commit ) < 0 )
+    {
+      git_commit_free( commit );
+      git_commit_free( parent );
+      close_repository( repo );
+      return;
+    }
+    if( git_commit_tree( &parent_tree, parent ) < 0 )
+    {
+      git_tree_free( commit_tree );
+      git_commit_free( commit );
+      git_commit_free( parent );
+      close_repository( repo );
+      return;
+    }
+    if( git_diff_tree_to_tree( &diff, repo, parent_tree, commit_tree, NULL ) < 0 )
+    {
+      git_tree_free( commit_tree );
+      git_tree_free( parent_tree );
+      git_commit_free( commit );
+      git_commit_free( parent );
+      close_repository( repo );
+      return;
+    }
+
+
+    *files = 0, *insertions = 0, *deletions = 0;
+
+    data.sb         = sb;
+    data.files      = files;
+    data.insertions = insertions;
+    data.deletions  = deletions;
+
+    git_diff_print( diff, GIT_DIFF_FORMAT_PATCH, printer, &data );
+
+    *files = *data.files, *insertions = *data.insertions, *deletions = *data.deletions;
+
+    git_diff_free( diff );
+    git_tree_free( commit_tree );
+    git_tree_free( parent_tree );
+    git_commit_free( commit );
+    git_commit_free( parent );
+
+    close_repository( repo );
+  }
+}
+
+
+void cgit_diff_with_parent( struct strbuf *sb, char *parent_hex, size_t parent_len, int *files, int *insertions, int *deletions, const char *hex, const char *relative_path )
+{
+  const char *name = NULL, *git_root = NULL, *repo_root = NULL;
+
+  git_commit *commit = NULL, *parent = NULL;
+
+  if( !parent_hex || !files || !insertions || !deletions || !hex || !*hex || !relative_path || !*relative_path ) return;
+  if( parent_len != GIT_OID_HEXSZ+1 ) return;
+
+  name      = ctx.repo.name;
+  git_root  = ctx.repo.git_root;
+  repo_root = ctx.repo.repo_root;
+
+  *files = 0, *insertions = 0, *deletions = 0;
+
+  if( name && git_root )
+  {
+    char ref[PATH_MAX] = { 0 };
+    char rpath[PATH_MAX] = { 0 };
+
+    git_repository *repo = NULL;
+    char path[PATH_MAX] = { 0 };
+    char cmd[PATH_MAX];
+    struct strbuf buf = STRBUF_INIT;
+    pid_t p = (pid_t) -1;
+    int   rc;
+
+    sprintf( (char *)&path[0], "%s/", git_root );
+    if( repo_root && *repo_root )
+    {
+      strcat( (char *)&path[0], repo_root );
+      strcat( (char *)&path[0], "/" );
+    }
+    strcat( (char *)&path[0], name );
+
+    if( !is_bare( (char *)&path[0] ) )
+    {
+      strcat( (char *)&path[0], "/.git" );
+    }
+
+    if( !(repo = open_repository( (const char *)&path[0] )) ) return;
+
+    commit = get_commit_by_hex( repo, hex );
+    if( git_commit_parent( &parent, commit, 0 ) < 0 )
+    {
+      git_commit_free( commit );
+      close_repository( repo );
+      return;
+    }
+    if( git_oid_tostr( parent_hex, GIT_OID_HEXSZ+1, git_commit_id( parent ) ) < 0 )
+    {
+      git_commit_free( commit );
+      git_commit_free( parent );
+      close_repository( repo );
+      return;
+    }
+
+    git_commit_free( commit );
+    git_commit_free( parent );
+    close_repository( repo );
+
+    /* now we have two revisions */
+    parse_relative_path( (char *)&ref[0], (char *)&rpath[0], relative_path );
+
+    snprintf( (char *)&cmd[0], 1024, "git --git-dir=%s diff %s %s -- %s 2>/dev/null", (char *)&path[0], parent_hex, hex, (char *)&rpath[0] );
+    p = sys_exec_command( &buf, cmd );
+    rc = sys_wait_command( p, NULL );
+    if( rc != 0 )
+    {
+      strbuf_release( &buf );
+      return;
+    }
+
+    if( buf.buf[0] )
+    {
+      char *p = (char *)&buf.buf[0];
+
+      while( *p )
+      {
+        if( *p == '\n' && p[1] && p[2] && p[3] )
+        {
+          if( (p[1] == '+' && p[2] == '+' && p[3] == '+') ||
+              (p[1] == '-' && p[2] == '-' && p[3] == '-')   )
+            *files += 1;
+          if( p[1] == '+' && p[2] != '+' && p[3] != '+' ) { ++p; *insertions += 1; }
+          if( p[1] == '-' && p[2] != '-' && p[3] != '-' ) { ++p; *deletions  += 1; }
+        }
+        ++p;
+      }
+      *files /= 2;
+
+      strbuf_addbuf( sb, (const struct strbuf *)&buf );
+    }
+
+    strbuf_release( &buf );
+  }
+}