#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 );
}
}