From 8f0e771bc9f2b6856d7034474716b39a5e96c171 Mon Sep 17 00:00:00 2001 From: Alexander Medvednikov Date: Sat, 2 May 2026 07:42:27 +0300 Subject: [PATCH] pg/sqlite orm --- README.md | 26 +++++++++++++++--- build.vsh | 11 +++++--- commit/commit_routes.v | 14 +++++----- config/loader.v | 17 ++++++++++++ database.v | 13 +++++++++ database_d_sqlite.v | 35 ++++++++++++++++++++++++ database_notd_sqlite.v | 61 ++++++++++++++++++++++++++++++++++++++++++ database_test.v | 14 ++++++++++ feed.v | 17 ++++++++---- gitly.v | 41 +++++++++++++++------------- highlight/markdown.v | 22 +++++---------- issue_routes.v | 41 +++++++++++++++++++--------- repo/file_routes.v | 12 ++++++--- repo/release_routes.v | 14 +++++++--- repo/repo.v | 33 +++++++++++++---------- repo/repo_routes.v | 39 +++++++++++---------------- user/user.v | 15 ++++++----- 17 files changed, 308 insertions(+), 117 deletions(-) create mode 100644 database.v create mode 100644 database_d_sqlite.v create mode 100644 database_notd_sqlite.v create mode 100644 database_test.v diff --git a/README.md b/README.md index 67ed7c7..365f5bc 100644 --- a/README.md +++ b/README.md @@ -27,18 +27,35 @@ v build.vsh ./gitly ``` +Gitly builds against PostgreSQL by default. Create the default PostgreSQL role/database with: + +```sh +v run setup_db.vsh +``` + +To build a SQLite-backed binary instead, use: + +```sh +v -d sqlite . +./gitly +``` + +The SQLite database path defaults to `gitly.sqlite` and can be changed with `sqlite.path` in +`config.json` or `GITLY_SQLITE_PATH`. PostgreSQL settings can be changed with `pg` in +`config.json`, `GITLY_DB_*` environment variables, or the usual `PG*` environment variables. + If you don't want to install `sassc`, you can simply run ``` -curl https://gitly.org/css/gitly.css --output static/css/gitly.css +curl https://gitly.org/css/gitly.css --output static/static/css/gitly.css ``` Required dependencies: * V 0.4.2 93ff40a (https://vlang.io) * SQLite (Ubuntu/Debian: `libsqlite3-dev`) +* PostgreSQL client library (Ubuntu/Debian: `libpq-dev`, macOS: `brew install libpq`) * Markdown (`v install markdown`) -* PCRE (`v install pcre`) * sassc * libgit2 @@ -48,6 +65,9 @@ You can install libgit2 with: * macOS: `brew install libgit2` -Gitly will support Postgres and MySQL in the future (once V ORM does). +### Features + +- Track performance in CI +- "Top files" mode ![](https://user-images.githubusercontent.com/687996/85933714-b195fe80-b8da-11ea-9ddd-09cadc2103e4.png) diff --git a/build.vsh b/build.vsh index 4b6ad85..7327224 100644 --- a/build.vsh +++ b/build.vsh @@ -1,11 +1,16 @@ import net.http -path := 'static/css/gitly.css' +static_root := if exists('static/static') && !exists('static/assets') { + 'static/static' +} else { + 'static' +} +path := '${static_root}/css/gitly.css' if !exists(path) { - ret := system('sassc static/css/gitly.scss > static/css/gitly.css') + ret := system('sassc ${static_root}/css/gitly.scss > ${path}') if ret != 0 { http.download_file('https://gitly.org/css/gitly.css', path)! - println("No sassc detected on this system, gitly.css has been downloaded from gitly.org.") + println('No sassc detected on this system, gitly.css has been downloaded from gitly.org.') } } diff --git a/commit/commit_routes.v b/commit/commit_routes.v index a321b33..2b45eb3 100644 --- a/commit/commit_routes.v +++ b/commit/commit_routes.v @@ -7,8 +7,7 @@ import api @['/api/v1/:user/:repo_name/:branch_name/commits/count'] fn (mut app App) handle_commits_count(mut ctx Context, username string, repo_name string, branch_name string) veb.Result { - has_access := app.has_user_repo_read_access_by_repo_name(ctx, ctx.user.id, username, - repo_name) + has_access := app.has_user_repo_read_access_by_repo_name(ctx, ctx.user.id, username, repo_name) if !has_access { return ctx.json_error('Not found') @@ -18,7 +17,6 @@ fn (mut app App) handle_commits_count(mut ctx Context, username string, repo_nam return ctx.json_error('Not found') } - branch := app.find_repo_branch_by_name(repo.id, branch_name) count := app.get_repo_commit_count(repo.id, branch.id) @@ -95,11 +93,15 @@ pub fn (mut app App) commit(mut ctx Context, username string, repo_name string, mut all_adds := 0 mut all_dels := 0 mut sources := map[string]veb.RawHtml{} - for change in changes { + mut change := Change{} + mut highlighted_src := '' + mut i := 0 + for i = 0; i < changes.len; i++ { + change = changes[i] all_adds += change.additions all_dels += change.deletions - src, _, _ := highlight.highlight_text(change.message, change.file, true) - sources[change.file] = veb.RawHtml(src) + highlighted_src, _, _ = highlight.highlight_text(change.message, change.file, true) + sources[change.file] = veb.RawHtml(highlighted_src) } return $veb.html() diff --git a/config/loader.v b/config/loader.v index 63b4e57..7c36ad4 100644 --- a/config/loader.v +++ b/config/loader.v @@ -11,6 +11,23 @@ pub: hostname string ci_service_url string port int + pg PgConfig + sqlite SqliteConfig +} + +pub struct PgConfig { +pub: + host string = 'localhost' + port int = 5432 + dbname string = 'gitly' + user string = 'gitly' + password string = 'gitly' + conninfo string +} + +pub struct SqliteConfig { +pub: + path string = 'gitly.sqlite' } pub fn read_config(path string) !Config { diff --git a/database.v b/database.v new file mode 100644 index 0000000..bda038d --- /dev/null +++ b/database.v @@ -0,0 +1,13 @@ +module main + +fn sql_table(name string) string { + return '"' + name.to_lower().replace('"', '""') + '"' +} + +fn sql_literal(value string) string { + return "'" + value.replace("'", "''") + "'" +} + +fn sql_like_pattern(value string) string { + return sql_literal('%' + value + '%') +} diff --git a/database_d_sqlite.v b/database_d_sqlite.v new file mode 100644 index 0000000..cd888d5 --- /dev/null +++ b/database_d_sqlite.v @@ -0,0 +1,35 @@ +module main + +import config +import db.sqlite +import os + +type GitlyDb = sqlite.DB + +fn connect_db(conf config.Config) !GitlyDb { + path := first_env(['GITLY_SQLITE_PATH', 'GITLY_DB_PATH'], conf.sqlite.path) + return GitlyDb(sqlite.connect(path)!) +} + +fn db_backend_name() string { + return 'sqlite' +} + +fn db_exec_values(db &GitlyDb, query string) ![][]string { + rows := db.exec(query)! + mut values := [][]string{cap: rows.len} + for row in rows { + values << row.vals.clone() + } + return values +} + +fn first_env(keys []string, fallback string) string { + for key in keys { + value := os.getenv(key) + if value != '' { + return value + } + } + return fallback +} diff --git a/database_notd_sqlite.v b/database_notd_sqlite.v new file mode 100644 index 0000000..bb90d52 --- /dev/null +++ b/database_notd_sqlite.v @@ -0,0 +1,61 @@ +module main + +import config +import db.pg +import os + +type GitlyDb = pg.DB + +fn connect_db(conf config.Config) !GitlyDb { + if conninfo := first_env_opt(['GITLY_DB_CONNINFO', 'DATABASE_URL'], conf.pg.conninfo) { + return GitlyDb(pg.connect_with_conninfo(conninfo)!) + } + return GitlyDb(pg.connect( + host: first_env(['GITLY_DB_HOST', 'PGHOST'], conf.pg.host) + port: first_int_env(['GITLY_DB_PORT', 'PGPORT'], conf.pg.port) + dbname: first_env(['GITLY_DB_NAME', 'PGDATABASE'], conf.pg.dbname) + user: first_env(['GITLY_DB_USER', 'PGUSER'], conf.pg.user) + password: first_env(['GITLY_DB_PASSWORD', 'PGPASSWORD'], conf.pg.password) + )!) +} + +fn db_backend_name() string { + return 'postgres' +} + +fn db_exec_values(db &GitlyDb, query string) ![][]string { + rows := db.exec_no_null(query)! + mut values := [][]string{cap: rows.len} + for row in rows { + values << row.vals.clone() + } + return values +} + +fn first_env(keys []string, fallback string) string { + for key in keys { + value := os.getenv(key) + if value != '' { + return value + } + } + return fallback +} + +fn first_env_opt(keys []string, fallback string) ?string { + value := first_env(keys, fallback) + if value == '' { + return none + } + return value +} + +fn first_int_env(keys []string, fallback int) int { + for key in keys { + value := os.getenv(key) + if value != '' { + return value.int() + } + } + return fallback +} diff --git a/database_test.v b/database_test.v new file mode 100644 index 0000000..c4dfe52 --- /dev/null +++ b/database_test.v @@ -0,0 +1,14 @@ +module main + +fn test_sql_table_quotes_identifiers() { + assert sql_table('Commit') == '"commit"' + assert sql_table('weird"name') == '"weird""name"' +} + +fn test_sql_literal_escapes_single_quotes() { + assert sql_literal("bob's repo") == "'bob''s repo'" +} + +fn test_sql_like_pattern_wraps_and_escapes_query() { + assert sql_like_pattern("bob's") == "'%bob''s%'" +} diff --git a/feed.v b/feed.v index 6a44f82..1280fd4 100644 --- a/feed.v +++ b/feed.v @@ -18,10 +18,13 @@ const feed_items_per_page = 30 fn (mut app App) build_user_feed_as_page(user_id int, offset int) []FeedItem { mut feed := []FeedItem{} repo_ids := app.find_watching_repo_ids(user_id) + if repo_ids.len == 0 { + return [] + } where_repo_ids := repo_ids.map(it.str()).join(', ') - commits := app.db.exec_no_null(' - select author, hash, created_at, repo_id, branch_id, message from `Commit` + commits := db_exec_values(app.db, ' + select author, hash, created_at, repo_id, branch_id, message from ${sql_table('Commit')} where repo_id in (${where_repo_ids}) order by created_at desc limit ${feed_items_per_page} offset ${offset}') or { return [] @@ -31,7 +34,7 @@ fn (mut app App) build_user_feed_as_page(user_id int, offset int) []FeedItem { println(commits) for commit in commits { - vals := commit.vals + vals := commit author_name := vals[0] commit_hash := vals[1] created_at_unix := vals[2].int() @@ -62,11 +65,15 @@ fn (mut app App) build_user_feed_as_page(user_id int, offset int) []FeedItem { fn (mut app App) get_feed_items_count(user_id int) int { repo_ids := app.find_watching_repo_ids(user_id) + if repo_ids.len == 0 { + return 0 + } where_repo_ids := repo_ids.map(it.str()).join(', ') - count_result := app.db.exec_no_null('select count(id) from `Commit` where repo_id in (${where_repo_ids})') or { + count_result := db_exec_values(app.db, + 'select count(id) from ${sql_table('Commit')} where repo_id in (${where_repo_ids})') or { return 0 } - return count_result.first().vals.first().int() + return count_result.first().first().int() } diff --git a/gitly.v b/gitly.v index 1291dc7..80c1017 100644 --- a/gitly.v +++ b/gitly.v @@ -6,8 +6,6 @@ import veb import time import os import log -// import db.sqlite -import db.pg import api import config @@ -21,13 +19,20 @@ const max_repo_name_len = 100 const max_namechanges = 3 const namechange_period = time.hour * 24 +fn static_root_path() string { + if os.exists(os.join_path('static', 'static')) && !os.exists(os.join_path('static', 'assets')) { + return os.join_path('static', 'static') + } + return 'static' +} + @[heap] pub struct App { veb.StaticHandler veb.Middleware[Context] started_at i64 pub mut: - db pg.DB + db GitlyDb mut: version string logger log.Log @@ -53,17 +58,14 @@ mut: fn new_app() !&App { // C.sqlite3_config(3) + conf := config.read_config('./config.json') or { + panic('Config not found or has syntax errors') + } mut app := &App{ // db: sqlite.connect('gitly.sqlite') or { panic(err) } - db: pg.connect( - // host: conf.pg.host - dbname: 'gitly' - user: 'gitly' - password: 'gitly' - // port: conf.pg.port - )! - + db: connect_db(conf)! + config: conf started_at: time.now().unix() } @@ -75,20 +77,25 @@ fn new_app() !&App { app.setup_logger() - mut version := os.read_file('static/assets/version') or { 'unknown' } + static_root := static_root_path() + version_path := os.join_path(static_root, 'assets', 'version') + create_directory_if_not_exists(os.dir(version_path)) + + stored_version := os.read_file(version_path) or { 'unknown' } + mut version := stored_version git_result := os.execute('git rev-parse --short HEAD') if git_result.exit_code == 0 && !git_result.output.contains('fatal') { version = git_result.output.trim_space() } - if version != app.version { - os.write_file('static/assets/version', app.version) or { panic(err) } + if version != stored_version { + os.write_file(version_path, version) or { panic(err) } } app.version = version - app.handle_static('static', true)! + app.handle_static(static_root, true)! if !os.exists('avatars') { os.mkdir('avatars')! } @@ -96,10 +103,6 @@ fn new_app() !&App { app.load_settings() - app.config = config.read_config('./config.json') or { - panic('Config not found or has syntax errors') - } - create_directory_if_not_exists(app.config.repo_storage_path) create_directory_if_not_exists(app.config.archive_path) create_directory_if_not_exists(app.config.avatars_path) diff --git a/highlight/markdown.v b/highlight/markdown.v index ca68e28..3a0e63e 100644 --- a/highlight/markdown.v +++ b/highlight/markdown.v @@ -1,7 +1,7 @@ module highlight import markdown -import pcre +import regex.pcre const allowed_tags = [ 'a', @@ -89,7 +89,7 @@ pub fn sanitize_markdown_code(code string) string { fn sanitize_html_tags(code string) string { mut result := code // tag name, attributes, tag content(optional) - paired_tags_re := r'<[\s]*?(?[a-zA-Z0-9]*)(.*?)>([\s\S]*?)<\/\s*?\g{tag}*?\s*?>' + paired_tags_re := r'<[\s]*?([a-zA-Z0-9]+)(.*?)>(.*?)<\/\s*?[a-zA-Z0-9]+\s*?>' unpaired_tags_re := r'<(\w*)\s+(.*?)()>' result = sanitize_html_tags_with_re(paired_tags_re, result) @@ -145,15 +145,13 @@ fn sanitize_html_tags_with_re(re string, code string) string { } } - tags_re.free() - return result } fn sanitize_html_attributes(attributes string) string { mut result := attributes - attributes_query := r'(\w+)[\s\S]*?=["]([\s\S]*?)["]' + attributes_query := r'(\w+).*?=["](.*?)["]' attributes_re := pcre.new_regex(attributes_query, 0) or { println(err) return result @@ -187,19 +185,11 @@ fn sanitize_html_attributes(attributes string) string { fn remove_comments(code string) string { mut result := code - remove_comments_query := r'' - remove_comments_re := pcre.new_regex(remove_comments_query, 0) or { - println(err) - return result - } - for { - matched := remove_comments_re.match_str(result, 0, 0) or { break } - comment := matched.get(0) or { break } - result = result.replace(comment, '') + start := result.index('', start + 4) or { return result[..start] } + result = result[..start] + result[end + 3..] } - remove_comments_re.free() - return result } diff --git a/issue_routes.v b/issue_routes.v index 0789cbf..b764691 100644 --- a/issue_routes.v +++ b/issue_routes.v @@ -14,8 +14,7 @@ type CommentWithUser = ItemWithUser[Comment] @['/api/v1/:username/:repo_name/issues/count'] fn (mut app App) handle_issues_count(username string, repo_name string) veb.Result { - has_access := app.has_user_repo_read_access_by_repo_name(ctx, ctx.user.id, username, - repo_name) + has_access := app.has_user_repo_read_access_by_repo_name(ctx, ctx.user.id, username, repo_name) if !has_access { return ctx.json_error('Not found') } @@ -77,8 +76,13 @@ pub fn (mut app App) issues(mut ctx Context, username string, repo_name string, repo := app.find_repo_by_name_and_username(repo_name, username) or { return ctx.not_found() } page_i := page.int() mut issues_with_users := []IssueWithUser{} - for issue in app.find_repo_issues_as_page(repo.id, page_i) { - user := app.get_user_by_id(issue.author_id) or { continue } + mut issue := Issue{} + mut user := User{} + repo_issues := app.find_repo_issues_as_page(repo.id, page_i) + mut i := 0 + for i = 0; i < repo_issues.len; i++ { + issue = repo_issues[i] + user = app.get_user_by_id(issue.author_id) or { continue } issues_with_users << IssueWithUser{ item: issue user: user @@ -111,11 +115,16 @@ pub fn (mut app App) issue(mut ctx Context, username string, repo_name string, i issue := app.find_issue_by_id(id.int()) or { return ctx.not_found() } issue_author := app.get_user_by_id(issue.author_id) or { return ctx.not_found() } mut comments_with_users := []CommentWithUser{} - for comment in app.get_all_issue_comments(issue.id) { - user := app.get_user_by_id(comment.author_id) or { continue } + mut comment := Comment{} + mut comment_author := User{} + issue_comments := app.get_all_issue_comments(issue.id) + mut i := 0 + for i = 0; i < issue_comments.len; i++ { + comment = issue_comments[i] + comment_author = app.get_user_by_id(comment.author_id) or { continue } comments_with_users << CommentWithUser{ item: comment - user: user + user: comment_author } } return $veb.html() @@ -137,10 +146,14 @@ pub fn (mut app App) user_issues(mut ctx Context, username string, page string) mut issues := app.find_user_issues(user.id) mut first := false mut last := false - for i, issue in issues { - repo := app.find_repo_by_id(issue.repo_id) or { continue } - issues[i].repo_author = repo.user_name - issues[i].repo_name = repo.name + mut issue := Issue{} + mut issue_repo := Repo{} + mut i := 0 + for i = 0; i < issues.len; i++ { + issue = issues[i] + issue_repo = app.find_repo_by_id(issue.repo_id) or { continue } + issues[i].repo_author = issue_repo.user_name + issues[i].repo_name = issue_repo.name } if issues.len > commits_per_page { offset := page_i * commits_per_page @@ -157,8 +170,10 @@ pub fn (mut app App) user_issues(mut ctx Context, username string, page string) first = true } mut issues_with_users := []IssueWithUser{} - for issue in issues { - issue_author := app.get_user_by_id(issue.author_id) or { continue } + mut issue_author := User{} + for i = 0; i < issues.len; i++ { + issue = issues[i] + issue_author = app.get_user_by_id(issue.author_id) or { continue } issues_with_users << IssueWithUser{ item: issue user: issue_author diff --git a/repo/file_routes.v b/repo/file_routes.v index 5d930ca..1d54342 100644 --- a/repo/file_routes.v +++ b/repo/file_routes.v @@ -68,6 +68,7 @@ pub fn (mut app App) handle_update_file(username string, repo_name string) veb.R file_content := ctx.form['file_content'] branch_name := ctx.form['branch'] commit_message := ctx.form['commit_message'] + mut actual_branch := '' if commit_message == '' { ctx.error('Commit message is required') @@ -75,7 +76,7 @@ pub fn (mut app App) handle_update_file(username string, repo_name string) veb.R return $veb.html('templates/edit_file.html') } - mut actual_branch := branch_name + actual_branch = branch_name if actual_branch == '' { actual_branch = repo.primary_branch } @@ -121,6 +122,7 @@ pub fn (mut app App) handle_create_file(username string, repo_name string) veb.R file_content := ctx.form['file_content'] branch_name := ctx.form['branch'] commit_message := ctx.form['commit_message'] + mut actual_branch := '' if file_path == '' { ctx.error('File path is required') @@ -144,7 +146,7 @@ pub fn (mut app App) handle_create_file(username string, repo_name string) veb.R return $veb.html('templates/new_file.html') } - mut actual_branch := branch_name + actual_branch = branch_name if actual_branch == '' { actual_branch = repo.primary_branch } @@ -216,14 +218,16 @@ fn (mut app App) create_file_in_bare_repo(mut repo Repo, branch string, file_pat } // Read existing tree into temp index - r1 := os.execute('/bin/sh -c \'GIT_INDEX_FILE=${tmp_index} git -C ${git_dir} read-tree ${existing_tree}\'') + r1 := + os.execute('/bin/sh -c \'GIT_INDEX_FILE=${tmp_index} git -C ${git_dir} read-tree ${existing_tree}\'') if r1.exit_code != 0 { app.warn('read-tree failed: ${r1.output}') return false } // Add the new blob to the index - r2 := os.execute('/bin/sh -c \'GIT_INDEX_FILE=${tmp_index} git -C ${git_dir} update-index --add --cacheinfo 100644,${blob_hash},${file_path}\'') + r2 := + os.execute('/bin/sh -c \'GIT_INDEX_FILE=${tmp_index} git -C ${git_dir} update-index --add --cacheinfo 100644,${blob_hash},${file_path}\'') if r2.exit_code != 0 { app.warn('update-index failed: ${r2.output}') return false diff --git a/repo/release_routes.v b/repo/release_routes.v index 10b1173..e21af9f 100644 --- a/repo/release_routes.v +++ b/repo/release_routes.v @@ -32,11 +32,18 @@ pub fn (mut app App) releases(mut ctx Context, username string, repo_name string download_archive_prefix := '/${username}/${repo_name}/tag' - for rel in rels { + mut rel := Release{} + mut tag := Tag{} + mut user := User{} + mut i := 0 + mut j := 0 + for i = 0; i < rels.len; i++ { + rel = rels[i] release.notes = rel.notes mut user_id := 0 - for tag in tags { + for j = 0; j < tags.len; j++ { + tag = tags[j] if tag.id == rel.tag_id { release.tag_name = tag.name release.tag_hash = tag.hash @@ -45,7 +52,8 @@ pub fn (mut app App) releases(mut ctx Context, username string, repo_name string break } } - for user in users { + for j = 0; j < users.len; j++ { + user = users[j] if user.id == user_id { release.user = user.username break diff --git a/repo/repo.v b/repo/repo.v index 901bc87..c8a07a5 100644 --- a/repo/repo.v +++ b/repo/repo.v @@ -133,23 +133,24 @@ fn (mut app App) find_user_public_repos(user_id int) []Repo { } fn (app &App) search_public_repos(query string) []Repo { - repo_rows := app.db.exec_no_null('select id, name, user_id, description, stars_count from `Repo` where is_public is true and name like "%${query}%"') or { + repo_rows := db_exec_values(app.db, + 'select id, name, user_id, description, stars_count from ${sql_table('Repo')} where is_public is true and name like ${sql_like_pattern(query)}') or { return [] } mut repos := []Repo{} for row in repo_rows { - user_id := row.vals[2].int() + user_id := row[2].int() user := app.get_user_by_id(user_id) or { User{} } repos << Repo{ - id: row.vals[0].int() + id: row[0].int() git_repo: unsafe { nil } - name: row.vals[1] + name: row[1] user_name: user.username - description: row.vals[3] - nr_stars: row.vals[4].int() + description: row[3] + nr_stars: row[4].int() } } @@ -312,7 +313,8 @@ fn (mut app App) update_repo_branch_from_fs(mut repo Repo, branch_name string) ! return } - data := repo.git('--no-pager log ${branch_name} --abbrev-commit --abbrev=7 --pretty="%h${log_field_separator}%aE${log_field_separator}%cD${log_field_separator}%s${log_field_separator}%aN"') + data := + repo.git('--no-pager log ${branch_name} --abbrev-commit --abbrev=7 --pretty="%h${log_field_separator}%aE${log_field_separator}%cD${log_field_separator}%s${log_field_separator}%aN"') for line in data.split_into_lines() { args := line.split(log_field_separator) @@ -343,8 +345,8 @@ fn (mut app App) update_repo_branch_from_fs(mut repo Repo, branch_name string) ! commit_author_id = user.id } - app.add_commit(repo_id, branch.id, commit_hash, commit_author, - commit_author_id, commit_message, int(commit_date.unix()))! + app.add_commit(repo_id, branch.id, commit_hash, commit_author, commit_author_id, + commit_message, int(commit_date.unix()))! } } } @@ -392,7 +394,8 @@ fn (mut app App) update_repo_branch_data(mut repo Repo, branch_name string) ! { return } - data := repo.git('--no-pager log ${branch_name} --abbrev-commit --abbrev=7 --pretty="%h${log_field_separator}%aE${log_field_separator}%cD${log_field_separator}%s${log_field_separator}%aN"') + data := + repo.git('--no-pager log ${branch_name} --abbrev-commit --abbrev=7 --pretty="%h${log_field_separator}%aE${log_field_separator}%cD${log_field_separator}%s${log_field_separator}%aN"') for line in data.split_into_lines() { args := line.split(log_field_separator) @@ -421,8 +424,8 @@ fn (mut app App) update_repo_branch_data(mut repo Repo, branch_name string) ! { commit_author_id = user.id } - app.add_commit(repo_id, branch.id, commit_hash, commit_author, - commit_author_id, commit_message, int(commit_date.unix()))! + app.add_commit(repo_id, branch.id, commit_hash, commit_author, commit_author_id, + commit_message, int(commit_date.unix()))! } } } @@ -633,7 +636,8 @@ fn (mut app App) cache_repository_items(mut r Repo, branch string, path string) } else { directory_path := if path == '' { path } else { '${path}/' } format := '%(objectmode) %(objecttype) %(objectname) %(objectsize) %(path)' - repository_ls = r.git('ls-tree --full-name --format="${format}" ${branch} ${directory_path}') + repository_ls = + r.git('ls-tree --full-name --format="${format}" ${branch} ${directory_path}') } // mode type name path @@ -779,7 +783,8 @@ fn (mut app App) fetch_file_info(r &Repo, file &File) ! { file_id := file.id sql app.db { - update File set last_msg = last_msg, last_time = last_time, last_hash = last_hash where id == file_id + update File set last_msg = last_msg, last_time = last_time, last_hash = last_hash + where id == file_id }! } diff --git a/repo/repo_routes.v b/repo/repo_routes.v index 8103960..cc57fba 100644 --- a/repo/repo_routes.v +++ b/repo/repo_routes.v @@ -7,7 +7,7 @@ import os import highlight import validation import git -import db.pg +import config @['/:username/repos'] pub fn (mut app App) user_repos(username string) veb.Result { @@ -255,7 +255,7 @@ pub fn (mut app App) handle_new_repo(mut ctx Context, name string, clone_url str // t := time.now() new_repo.status = .cloning - spawn clone_repo(mut new_repo) + spawn clone_repo(mut new_repo, app.config) // new_repo.clone() // println(time.since(t)) } @@ -297,17 +297,14 @@ pub fn (mut app App) handle_new_repo(mut ctx Context, name string, clone_url str return ctx.redirect('/${ctx.user.username}/${new_repo.name}') } -fn bg_fetch_files_info(repo_ Repo, branch string, path string) { +fn bg_fetch_files_info(repo_ Repo, branch string, path string, conf config.Config) { mut repo := repo_ mut app := &App{ - db: pg.connect( - dbname: 'gitly' - user: 'gitly' - password: 'gitly' - ) or { - eprintln('cannot open db connection for bg_fetch thread: ${err}') + db: connect_db(conf) or { + eprintln('cannot open ${db_backend_name()} db connection for bg_fetch thread: ${err}') return } + config: conf } app.slow_fetch_files_info(mut repo, branch, path) or { eprintln('bg_fetch_files_info error: ${err}') @@ -315,19 +312,16 @@ fn bg_fetch_files_info(repo_ Repo, branch string, path string) { app.db.close() or {} } -fn clone_repo(mut new_repo Repo) { +fn clone_repo(mut new_repo Repo, conf config.Config) { new_repo.clone() // Use a dedicated DB connection for the clone thread to avoid - // corrupting the main connection's PostgreSQL protocol state. + // sharing a connection across threads. mut app := &App{ - db: pg.connect( - dbname: 'gitly' - user: 'gitly' - password: 'gitly' - ) or { - eprintln('cannot open db connection for clone thread: ${err}') + db: connect_db(conf) or { + eprintln('cannot open ${db_backend_name()} db connection for clone thread: ${err}') return } + config: conf } // Mark repo as done immediately so the user can browse it. // The tree page will fetch files from git on demand. @@ -409,10 +403,10 @@ pub fn (mut app App) tree(mut ctx Context, username string, repo_name string, br []File{} } // Fetch commit info in background — don't block the page - spawn bg_fetch_files_info(repo, branch_name, ctx.current_path) + spawn bg_fetch_files_info(repo, branch_name, ctx.current_path, app.config) } else if items.any(it.last_msg == '') { // Some files still need commit info — fetch in background - spawn bg_fetch_files_info(repo, branch_name, ctx.current_path) + spawn bg_fetch_files_info(repo, branch_name, ctx.current_path, app.config) } // Fetch last commit message for this directory, printed at the top of the tree @@ -449,8 +443,7 @@ pub fn (mut app App) tree(mut ctx Context, username string, repo_name string, br if readme_file.id != 0 { readme_path := '${path}/${readme_file.name}' readme_content := repo.read_file(branch_name, readme_path) - highlighted_readme, _, _ := highlight.highlight_text(readme_content, readme_path, - false) + highlighted_readme, _, _ := highlight.highlight_text(readme_content, readme_path, false) readme = veb.RawHtml(highlighted_readme) } @@ -470,9 +463,7 @@ pub fn (mut app App) tree(mut ctx Context, username string, repo_name string, br // CI status for last commit ci_status := app.find_ci_status_for_commit(repo_id, last_commit.hash) or { - app.find_ci_status_for_branch(repo_id, branch_name) or { - CiStatus{} - } + app.find_ci_status_for_branch(repo_id, branch_name) or { CiStatus{} } } has_ci := ci_status.id != 0 diff --git a/user/user.v b/user/user.v index 8b2a851..c2cfb93 100644 --- a/user/user.v +++ b/user/user.v @@ -281,16 +281,17 @@ pub fn (mut app App) get_all_registered_user_count() int { } fn (app App) search_users(query string) []User { - q := 'select id, full_name, username, avatar from `User` where is_blocked is false and ' + - '(username like "%${query}%" or full_name like "%${query}%")' - repo_rows := app.db.exec_no_null(q) or { return [] } + q := + 'select id, full_name, username, avatar from ${sql_table('User')} where is_blocked is false and ' + + '(username like ${sql_like_pattern(query)} or full_name like ${sql_like_pattern(query)})' + repo_rows := db_exec_values(app.db, q) or { return [] } mut users := []User{} for row in repo_rows { users << User{ - id: row.vals[0].int() - full_name: row.vals[1] - username: row.vals[2] - avatar: row.vals[3] + id: row[0].int() + full_name: row[1] + username: row[2] + avatar: row[3] } } return users -- 2.39.5