diff options
| author | kx <kx@radix.pro> | 2023-03-24 03:51:10 +0300 |
|---|---|---|
| committer | kx <kx@radix.pro> | 2023-03-24 03:51:10 +0300 |
| commit | 05d292b208dfe01324826b4c87bbc4da3389a0d5 (patch) | |
| tree | b10a2269e9320785f3b61189e75f6778fa167986 /cgitcgi/git-shared.c | |
| parent | 40ab18a661ff6ada40e73969be293918d346a2f5 (diff) | |
| download | cgit-ui-05d292b208dfe01324826b4c87bbc4da3389a0d5.tar.xz | |
Version 0.1.7
Diffstat (limited to 'cgitcgi/git-shared.c')
| -rw-r--r-- | cgitcgi/git-shared.c | 1410 |
1 files changed, 1410 insertions, 0 deletions
diff --git a/cgitcgi/git-shared.c b/cgitcgi/git-shared.c new file mode 100644 index 0000000..3c81c42 --- /dev/null +++ b/cgitcgi/git-shared.c @@ -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 ); + } +} |
