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