From e7b0a33988e6e160c40a10b184ccb38bd1cfa279 Mon Sep 17 00:00:00 2001 From: Alexander Medvednikov Date: Sat, 16 May 2026 16:24:28 +0300 Subject: [PATCH] repo: run language analysis in background after git push analyze_lang reads every file in the repo, which can take seconds to minutes on large repos and was blocking the git client on each push. Skip it in update_repo_from_fs's push path and run it in a background thread with its own DB connection instead, so new commits appear in the UI immediately while language stats catch up asynchronously. --- repo/git_routes.v | 5 +++++ repo/repo.v | 41 ++++++++++++++++++++++++++++++++++++++--- repo/repo_routes.v | 6 ++++-- 3 files changed, 47 insertions(+), 5 deletions(-) diff --git a/repo/git_routes.v b/repo/git_routes.v index 06535ae..4a4f695 100644 --- a/repo/git_routes.v +++ b/repo/git_routes.v @@ -72,6 +72,11 @@ fn (mut app App) handle_git_receive_pack(username string, git_repo_name string) return ctx.server_error('There was an error while updating the repo') } + // Language analysis reads every file in the repo and is slow; run it in + // a background thread with its own DB connection so the git client is + // not blocked. + spawn bg_recompute_lang_stats(repo.id, app.config) + // Trigger CI if .gitly-ci.yml exists in the repo spawn app.trigger_ci_if_configured(repo.id, branch_name) diff --git a/repo/repo.v b/repo/repo.v index c8dfff7..60b7a4d 100644 --- a/repo/repo.v +++ b/repo/repo.v @@ -7,6 +7,7 @@ import time import git import highlight import validation +import config struct Repo { id int @[primary; sql: serial] @@ -319,13 +320,18 @@ fn (mut app App) user_has_repo(user_id int, repo_name string) bool { return count >= 0 } -fn (mut app App) update_repo_from_fs(mut repo Repo) ! { +fn (mut app App) update_repo_from_fs(mut repo Repo, recompute_lang_stats bool) ! { println('UPDATE REPO FROM FS') repo_id := repo.id app.db.exec('BEGIN TRANSACTION')! - repo.analyze_lang(app)! + // Language analysis reads every file in the repo and is slow on large + // repos; callers on the git push hot path pass `false` and run it in a + // background thread instead, so the git client is not blocked. + if recompute_lang_stats { + repo.analyze_lang(app)! + } app.info(repo.nr_contributors.str()) app.fetch_branches(repo)! @@ -481,13 +487,42 @@ fn (mut app App) update_repo_branch_data(mut repo Repo, branch_name string) ! { } // TODO: tags and other stuff +// update_repo_after_push runs on the request thread after a git push so that +// new commits appear in the UI immediately. It skips language analysis, +// which is slow and runs in bg_recompute_lang_stats instead. fn (mut app App) update_repo_after_push(repo_id int, branch_name string) ! { mut repo := app.find_repo_by_id(repo_id) or { return } - app.update_repo_from_fs(mut repo)! + app.update_repo_from_fs(mut repo, false)! app.delete_repository_files_in_branch(repo_id, branch_name)! } +// bg_recompute_lang_stats recomputes language statistics for a repo in a +// background thread. It opens its own sqlite connection (matching the +// clone_repo / bg_fetch_files_info pattern) because the shared App.db +// handle is not safe for concurrent use across threads. +fn bg_recompute_lang_stats(repo_id int, conf config.Config) { + mut app := &App{ + db: connect_db(conf) or { + eprintln('bg_recompute_lang_stats: cannot open ${db_backend_name()} db: ${err}') + return + } + config: conf + } + app.load_settings() + defer { + app.db.close() or {} + } + + repo := app.find_repo_by_id(repo_id) or { + eprintln('bg_recompute_lang_stats: repo ${repo_id} not found') + return + } + repo.analyze_lang(app) or { + eprintln('bg_recompute_lang_stats: analyze_lang failed for repo ${repo_id}: ${err}') + } +} + fn (r &Repo) analyze_lang(app &App) ! { file_paths := r.get_all_file_paths() diff --git a/repo/repo_routes.v b/repo/repo_routes.v index 2c2a643..b8cf964 100644 --- a/repo/repo_routes.v +++ b/repo/repo_routes.v @@ -314,7 +314,7 @@ pub fn (mut app App) handle_new_repo(mut ctx Context, name string, clone_url str // Update only cloned repositories /* if !is_clone_url_empty { - app.update_repo_from_fs(mut new_repo) or { + app.update_repo_from_fs(mut new_repo, true) or { ctx.error('There was an error while cloning the repo') return app.new(mut ctx) } @@ -374,7 +374,9 @@ fn clone_repo(new_repo Repo, conf config.Config, import_issues bool, owner_user_ spawn bg_import_github_issues(cloned_repo.id, cloned_repo.clone_url, owner_user_id, conf) } // Index branches, commits, and language stats in the background. - app.update_repo_from_fs(mut cloned_repo) or { eprintln('cannot update repo from fs ${err}') } + app.update_repo_from_fs(mut cloned_repo, true) or { + eprintln('cannot update repo from fs ${err}') + } eprintln('background indexing complete') app.db.close() or {} } -- 2.39.5