#ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include /* chmod(2) */ #include #include #include #include #include /* strdup(3) */ #include /* basename(3) */ #include /* tolower(3) */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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 ); } }