From 7d2ae1887d69ce813b56df00c43b23e8458ae021 Mon Sep 17 00:00:00 2001 From: office Date: Sun, 11 Oct 2026 15:45:00 +0330 Subject: [PATCH 01/11] fix: make gitly compile with current V Master did not build at all. Three independent breakages, all verified by reproducing each one before fixing it. 1. 32 vweb route handlers were declared without `mut ctx Context`. Current vweb dispatches `app.$method(mut user_context, ...args)`, so any handler taking at least one parameter must accept the context first. Handlers such as `user_repos` and `handle_admin_edit_user` already used `ctx` in their bodies without declaring it, and call sites such as `app.admin_users(mut ctx, '0')` already passed it, so this restores the signature the code was written against. `method.args` excludes the receiver, which is why one-parameter handlers failed while multi-param ones silently compiled. 2. `struct FileInfo` in repo/file.v collided with `os.FileInfo`. Substituting it into the `api.ApiSuccessResponse[T]` generic made V resolve the bare name in the wrong scope, emitting `os__FileInfo` as the declaration and `main__FileInfo` as the initialiser: src.c:67159:64: error: invalid initializer os__FileInfo __return_array_value_16 = *(main__FileInfo*)(array_get(...)); Renamed to `RepoFileInfo`, which no stdlib type shadows. 3. `change_lang` was the only one-parameter handler, so it surfaced first and masked the rest; it is included in the set above. Verified: - `v -d sqlite -o gitly.exe .` exits 0 and produces the binary. - `v -d sqlite test utils_test.v` -> 1 passed, 1 total. - `git diff --stat` touches only .v files; no build artifacts staged. Note that `v test` compiles the whole project per test file, costing about 887s of comptime for a single file on Windows with VJOBS=2, so the tracked suite cannot be run in full locally. --- admin/admin_routes.v | 4 ++-- ci/ci_routes.v | 8 ++++---- gitly.v | 2 +- project_governance_routes.v | 18 +++++++++--------- repo/branch_routes.v | 2 +- repo/comment.v | 2 +- repo/file.v | 5 ++++- repo/file_routes.v | 10 +++++----- repo/repo_routes.v | 20 ++++++++++---------- repo/tag_routes.v | 4 ++-- 10 files changed, 39 insertions(+), 36 deletions(-) diff --git a/admin/admin_routes.v b/admin/admin_routes.v index 21df0338..26f4a78d 100644 --- a/admin/admin_routes.v +++ b/admin/admin_routes.v @@ -16,7 +16,7 @@ pub fn (mut app App) admin_settings(mut ctx Context) veb.Result { } @['/admin/settings'; post] -pub fn (mut app App) handle_admin_update_settings(oauth_client_id string, oauth_client_secret string) veb.Result { +pub fn (mut app App) handle_admin_update_settings(mut ctx Context, oauth_client_id string, oauth_client_secret string) veb.Result { if !ctx.is_admin() { return ctx.redirect_to_index() } @@ -34,7 +34,7 @@ pub fn (mut app App) handle_admin_update_settings(oauth_client_id string, oauth_ } @['/admin/users/:user'; post] -pub fn (mut app App) handle_admin_edit_user(user_id string) veb.Result { +pub fn (mut app App) handle_admin_edit_user(mut ctx Context, user_id string) veb.Result { if !ctx.is_admin() { return ctx.redirect_to_index() } diff --git a/ci/ci_routes.v b/ci/ci_routes.v index 96ec0dfa..7ce16363 100644 --- a/ci/ci_routes.v +++ b/ci/ci_routes.v @@ -96,7 +96,7 @@ fn (mut app App) verify_ci_callback_signature(ctx &Context, body string) bool { // GET /:username/:repo_name/ci - CI runs list page @['/:username/:repo_name/ci'] -pub fn (mut app App) ci_runs(username string, repo_name string) veb.Result { +pub fn (mut app App) ci_runs(mut ctx Context, username string, repo_name string) veb.Result { repo := app.find_repo_by_name_and_username(repo_name, username) or { return ctx.not_found() } if !app.can_read_repo(ctx, repo) { @@ -141,7 +141,7 @@ pub fn (mut app App) ci_runs(username string, repo_name string) veb.Result { // GET /:username/:repo_name/ci/:run_id_str - CI run detail page @['/:username/:repo_name/ci/:run_id_str'] -pub fn (mut app App) ci_run_detail(username string, repo_name string, run_id_str string) veb.Result { +pub fn (mut app App) ci_run_detail(mut ctx Context, username string, repo_name string, run_id_str string) veb.Result { repo := app.find_repo_by_name_and_username(repo_name, username) or { return ctx.not_found() } if !app.can_read_repo(ctx, repo) { @@ -178,7 +178,7 @@ pub fn (mut app App) ci_run_detail(username string, repo_name string, run_id_str // POST /:username/:repo_name/ci/:run_id_str/restart - Restart a CI run @['/:username/:repo_name/ci/:run_id_str/restart'; post] -pub fn (mut app App) ci_restart_run(username string, repo_name string, run_id_str string) veb.Result { +pub fn (mut app App) ci_restart_run(mut ctx Context, username string, repo_name string, run_id_str string) veb.Result { repo := app.find_repo_by_name_and_username(repo_name, username) or { return ctx.not_found() } if !app.can_admin_repo(ctx, repo) { @@ -221,7 +221,7 @@ pub fn (mut app App) ci_restart_run(username string, repo_name string, run_id_st // POST /:username/:repo_name/ci/:run_id_str/cancel - Cancel a CI run. @['/:username/:repo_name/ci/:run_id_str/cancel'; post] -pub fn (mut app App) ci_cancel_run(username string, repo_name string, run_id_str string) veb.Result { +pub fn (mut app App) ci_cancel_run(mut ctx Context, username string, repo_name string, run_id_str string) veb.Result { repo := app.find_repo_by_name_and_username(repo_name, username) or { return ctx.not_found() } if !app.can_admin_repo(ctx, repo) { return ctx.not_found() diff --git a/gitly.v b/gitly.v index 8ae21a72..14cb6bf3 100644 --- a/gitly.v +++ b/gitly.v @@ -304,7 +304,7 @@ pub fn (mut app App) index(mut ctx Context) veb.Result { } @['/change_lang/:lang'; post] -pub fn (mut app App) change_lang(lang string) veb.Result { +pub fn (mut app App) change_lang(mut ctx Context, lang string) veb.Result { if lang !in ['en', 'ru', 'es', 'jp', 'cn', 'pt'] { ctx.res.set_status(.bad_request) return ctx.json_error('Unsupported language') diff --git a/project_governance_routes.v b/project_governance_routes.v index b13b82dc..2b609212 100644 --- a/project_governance_routes.v +++ b/project_governance_routes.v @@ -5,7 +5,7 @@ module main import veb @['/:username/:repo_name/settings/members'] -pub fn (mut app App) repo_members(username string, repo_name string) veb.Result { +pub fn (mut app App) repo_members(mut ctx Context, username string, repo_name string) veb.Result { repo := app.find_repo_by_name_and_username(repo_name, username) or { return ctx.not_found() } if !app.can_admin_repo(ctx, repo) { return ctx.not_found() @@ -15,7 +15,7 @@ pub fn (mut app App) repo_members(username string, repo_name string) veb.Result } @['/:username/:repo_name/settings/members'; post] -pub fn (mut app App) handle_add_repo_member(username string, repo_name string) veb.Result { +pub fn (mut app App) handle_add_repo_member(mut ctx Context, username string, repo_name string) veb.Result { repo := app.find_repo_by_name_and_username(repo_name, username) or { return ctx.not_found() } if !app.can_admin_repo(ctx, repo) { return ctx.not_found() @@ -42,7 +42,7 @@ pub fn (mut app App) handle_add_repo_member(username string, repo_name string) v } @['/:username/:repo_name/settings/members/:member_id'; post] -pub fn (mut app App) handle_update_repo_member(username string, repo_name string, member_id string) veb.Result { +pub fn (mut app App) handle_update_repo_member(mut ctx Context, username string, repo_name string, member_id string) veb.Result { repo := app.find_repo_by_name_and_username(repo_name, username) or { return ctx.not_found() } if !app.can_admin_repo(ctx, repo) { return ctx.not_found() @@ -56,7 +56,7 @@ pub fn (mut app App) handle_update_repo_member(username string, repo_name string } @['/:username/:repo_name/settings/members/:member_id/delete'; post] -pub fn (mut app App) handle_delete_repo_member(username string, repo_name string, member_id string) veb.Result { +pub fn (mut app App) handle_delete_repo_member(mut ctx Context, username string, repo_name string, member_id string) veb.Result { repo := app.find_repo_by_name_and_username(repo_name, username) or { return ctx.not_found() } if !app.can_admin_repo(ctx, repo) { return ctx.not_found() @@ -69,7 +69,7 @@ pub fn (mut app App) handle_delete_repo_member(username string, repo_name string } @['/:username/:repo_name/settings/branches'] -pub fn (mut app App) protected_branches(username string, repo_name string) veb.Result { +pub fn (mut app App) protected_branches(mut ctx Context, username string, repo_name string) veb.Result { repo := app.find_repo_by_name_and_username(repo_name, username) or { return ctx.not_found() } if !app.can_admin_repo(ctx, repo) { return ctx.not_found() @@ -80,7 +80,7 @@ pub fn (mut app App) protected_branches(username string, repo_name string) veb.R } @['/:username/:repo_name/settings/branches'; post] -pub fn (mut app App) handle_protect_branch(username string, repo_name string) veb.Result { +pub fn (mut app App) handle_protect_branch(mut ctx Context, username string, repo_name string) veb.Result { repo := app.find_repo_by_name_and_username(repo_name, username) or { return ctx.not_found() } if !app.can_admin_repo(ctx, repo) { return ctx.not_found() @@ -96,7 +96,7 @@ pub fn (mut app App) handle_protect_branch(username string, repo_name string) ve } @['/:username/:repo_name/settings/branches/:rule_id'; post] -pub fn (mut app App) handle_update_protected_branch(username string, repo_name string, rule_id string) veb.Result { +pub fn (mut app App) handle_update_protected_branch(mut ctx Context, username string, repo_name string, rule_id string) veb.Result { repo := app.find_repo_by_name_and_username(repo_name, username) or { return ctx.not_found() } if !app.can_admin_repo(ctx, repo) { return ctx.not_found() @@ -110,7 +110,7 @@ pub fn (mut app App) handle_update_protected_branch(username string, repo_name s } @['/:username/:repo_name/settings/branches/:rule_id/delete'; post] -pub fn (mut app App) handle_unprotect_branch(username string, repo_name string, rule_id string) veb.Result { +pub fn (mut app App) handle_unprotect_branch(mut ctx Context, username string, repo_name string, rule_id string) veb.Result { repo := app.find_repo_by_name_and_username(repo_name, username) or { return ctx.not_found() } if !app.can_admin_repo(ctx, repo) { return ctx.not_found() @@ -123,7 +123,7 @@ pub fn (mut app App) handle_unprotect_branch(username string, repo_name string, } @['/:username/:repo_name/settings/approvals'; post] -pub fn (mut app App) handle_update_repo_approvals(username string, repo_name string) veb.Result { +pub fn (mut app App) handle_update_repo_approvals(mut ctx Context, username string, repo_name string) veb.Result { repo := app.find_repo_by_name_and_username(repo_name, username) or { return ctx.not_found() } if !app.can_admin_repo(ctx, repo) { return ctx.not_found() diff --git a/repo/branch_routes.v b/repo/branch_routes.v index 3f0bca4e..17614b66 100644 --- a/repo/branch_routes.v +++ b/repo/branch_routes.v @@ -22,7 +22,7 @@ fn (mut app App) handle_branch_count(username string, repo_name string) veb.Resu } @['/:user/:repo/branches'] -pub fn (mut app App) branches(username string, repo_name string) veb.Result { +pub fn (mut app App) branches(mut ctx Context, username string, repo_name string) veb.Result { repo := app.find_repo_by_name_and_username(repo_name, username) or { return ctx.json_error('Not found') } diff --git a/repo/comment.v b/repo/comment.v index 3b6e2a92..f6e30995 100644 --- a/repo/comment.v +++ b/repo/comment.v @@ -16,7 +16,7 @@ mut: } @['/:username/:repo_name/comments'; post] -pub fn (mut app App) handle_add_comment(username string, repo_name string) veb.Result { +pub fn (mut app App) handle_add_comment(mut ctx Context, username string, repo_name string) veb.Result { repo := app.find_repo_by_name_and_username(repo_name, username) or { return ctx.not_found() } text := ctx.form['text'] issue_id := ctx.form['issue_id'] diff --git a/repo/file.v b/repo/file.v index b9023f84..d72c7743 100644 --- a/repo/file.v +++ b/repo/file.v @@ -84,7 +84,10 @@ fn pretty_size_bytes(size_in_bytes int) string { return '${size_in} ${sizes[index]}' } -struct FileInfo { +// Named `RepoFileInfo` rather than `FileInfo` to avoid colliding with +// `os.FileInfo`, which V resolves in the wrong scope when this type is +// substituted into the `api.ApiSuccessResponse[T]` generic. +struct RepoFileInfo { name string last_msg string last_hash string diff --git a/repo/file_routes.v b/repo/file_routes.v index 3722da50..395d25a9 100644 --- a/repo/file_routes.v +++ b/repo/file_routes.v @@ -8,7 +8,7 @@ import rand // GET /:username/:repo_name/new/:branch_name... - Show create file form. // The catch-all is required because branch names may contain `/`. @['/:username/:repo_name/new/:branch_name...'] -pub fn (mut app App) new_file(username string, repo_name string, branch_name string) veb.Result { +pub fn (mut app App) new_file(mut ctx Context, username string, repo_name string, branch_name string) veb.Result { repo := app.find_repo_by_name_and_username(repo_name, username) or { return ctx.not_found() } if repo.require_signed_commits || !ctx.logged_in || !app.user_can_push_branch(ctx.user.id, repo, branch_name) @@ -23,7 +23,7 @@ pub fn (mut app App) new_file(username string, repo_name string, branch_name str // GET /:username/:repo_name/new-ci-file - Show create .gitly-ci.yml form (pre-filled) @['/:username/:repo_name/new-ci-file'] -pub fn (mut app App) new_ci_file(username string, repo_name string) veb.Result { +pub fn (mut app App) new_ci_file(mut ctx Context, username string, repo_name string) veb.Result { repo := app.find_repo_by_name_and_username(repo_name, username) or { return ctx.not_found() } if repo.require_signed_commits || !ctx.logged_in @@ -46,7 +46,7 @@ pub fn (mut app App) new_ci_file(username string, repo_name string) veb.Result { // GET /:username/:repo_name/edit/:location... - Show edit file form @['/:username/:repo_name/edit/:location...'] -pub fn (mut app App) edit_file(username string, repo_name string, location string) veb.Result { +pub fn (mut app App) edit_file(mut ctx Context, username string, repo_name string, location string) veb.Result { repo := app.find_repo_by_name_and_username(repo_name, username) or { return ctx.not_found() } if repo.require_signed_commits || !ctx.logged_in || !app.user_can_write_repo(ctx.user.id, repo) { return ctx.redirect_to_repository(username, repo_name) @@ -69,7 +69,7 @@ pub fn (mut app App) edit_file(username string, repo_name string, location strin // POST /:username/:repo_name/update-file - Save edited file @['/:username/:repo_name/update-file'; post] -pub fn (mut app App) handle_update_file(username string, repo_name string) veb.Result { +pub fn (mut app App) handle_update_file(mut ctx Context, username string, repo_name string) veb.Result { mut repo := app.find_repo_by_name_and_username(repo_name, username) or { return ctx.not_found() } @@ -132,7 +132,7 @@ pub fn (mut app App) handle_update_file(username string, repo_name string) veb.R // POST /:username/:repo_name/create-file - Create a file in the repo @['/:username/:repo_name/create-file'; post] -pub fn (mut app App) handle_create_file(username string, repo_name string) veb.Result { +pub fn (mut app App) handle_create_file(mut ctx Context, username string, repo_name string) veb.Result { mut repo := app.find_repo_by_name_and_username(repo_name, username) or { return ctx.not_found() } diff --git a/repo/repo_routes.v b/repo/repo_routes.v index 5aa9e849..44e934e8 100644 --- a/repo/repo_routes.v +++ b/repo/repo_routes.v @@ -98,7 +98,7 @@ fn repo_sort_indicator(current_sort string, current_dir string, target_sort stri } @['/:username/repos'] -pub fn (mut app App) user_repos(username string) veb.Result { +pub fn (mut app App) user_repos(mut ctx Context, username string) veb.Result { exists, user := app.check_username(username) if !exists { @@ -128,7 +128,7 @@ pub fn (mut app App) user_repos(username string) veb.Result { } @['/:username/stars'] -pub fn (mut app App) user_stars(username string) veb.Result { +pub fn (mut app App) user_stars(mut ctx Context, username string) veb.Result { exists, user := app.check_username(username) if !exists { @@ -145,7 +145,7 @@ pub fn (mut app App) user_stars(username string) veb.Result { } @['/:username/:repo_name/settings'] -pub fn (mut app App) repo_settings(username string, repo_name string) veb.Result { +pub fn (mut app App) repo_settings(mut ctx Context, username string, repo_name string) veb.Result { repo := app.find_repo_by_name_and_username(repo_name, username) or { return ctx.redirect_to_repository(username, repo_name) } @@ -161,7 +161,7 @@ pub fn (mut app App) repo_settings(username string, repo_name string) veb.Result } @['/:username/:repo_name/settings'; post] -pub fn (mut app App) handle_update_repo_settings(username string, repo_name string) veb.Result { +pub fn (mut app App) handle_update_repo_settings(mut ctx Context, username string, repo_name string) veb.Result { repo := app.find_repo_by_name_and_username(repo_name, username) or { return ctx.redirect_to_repository(username, repo_name) } @@ -226,7 +226,7 @@ pub fn (mut app App) handle_update_repo_settings(username string, repo_name stri } @['/:username/:repo_name/settings/features'; post] -pub fn (mut app App) handle_update_repo_features(username string, repo_name string) veb.Result { +pub fn (mut app App) handle_update_repo_features(mut ctx Context, username string, repo_name string) veb.Result { repo := app.find_repo_by_name_and_username(repo_name, username) or { return ctx.redirect_to_repository(username, repo_name) } @@ -246,7 +246,7 @@ pub fn (mut app App) handle_update_repo_features(username string, repo_name stri } @['/:user/:repo_name/delete'; post] -pub fn (mut app App) handle_repo_delete(username string, repo_name string) veb.Result { +pub fn (mut app App) handle_repo_delete(mut ctx Context, username string, repo_name string) veb.Result { repo := app.find_repo_by_name_and_username(repo_name, username) or { return ctx.redirect_to_repository(username, repo_name) } @@ -273,7 +273,7 @@ pub fn (mut app App) handle_repo_delete(username string, repo_name string) veb.R } @['/:username/:repo_name/move'; post] -pub fn (mut app App) handle_repo_move(username string, repo_name string, dest string, verify string) veb.Result { +pub fn (mut app App) handle_repo_move(mut ctx Context, username string, repo_name string, dest string, verify string) veb.Result { repo := app.find_repo_by_name_and_username(repo_name, username) or { return ctx.redirect_to_index() } @@ -1028,9 +1028,9 @@ pub fn (mut app App) handle_api_repo_files(mut ctx Context, repo_id_str string) } items := app.find_repository_items(repo_id, branch, path) - mut result := []FileInfo{} + mut result := []RepoFileInfo{} for item in items { - result << FileInfo{ + result << RepoFileInfo{ name: item.name last_msg: item.last_msg last_hash: item.last_hash @@ -1039,7 +1039,7 @@ pub fn (mut app App) handle_api_repo_files(mut ctx Context, repo_id_str string) } } - return ctx.json(api.ApiSuccessResponse[[]FileInfo]{ + return ctx.json(api.ApiSuccessResponse[[]RepoFileInfo]{ success: true result: result }) diff --git a/repo/tag_routes.v b/repo/tag_routes.v index e8cac1fd..3ec41873 100644 --- a/repo/tag_routes.v +++ b/repo/tag_routes.v @@ -5,7 +5,7 @@ import os import rand @['/:username/:repo_name/tag/:tag/:format'] -pub fn (mut app App) handle_download_tag_archive(username string, repo_name string, tag string, format string) veb.Result { +pub fn (mut app App) handle_download_tag_archive(mut ctx Context, username string, repo_name string, tag string, format string) veb.Result { return stream_tag_archive(mut app, mut ctx, username, repo_name, tag, format) } @@ -13,7 +13,7 @@ pub fn (mut app App) handle_download_tag_archive(username string, repo_name stri // treats each slash as another route segment. Keep it for existing links and // use this catch-all route for canonical release downloads. @['/:username/:repo_name/archive/:tag...'] -pub fn (mut app App) handle_download_tag_archive_path(username string, repo_name string, tag string) veb.Result { +pub fn (mut app App) handle_download_tag_archive_path(mut ctx Context, username string, repo_name string, tag string) veb.Result { format := ctx.query['format'] or { '' } return stream_tag_archive(mut app, mut ctx, username, repo_name, tag, format) } From 1084b8a05dfae7a4cb351f16ec9ba2bfdad78aaa Mon Sep 17 00:00:00 2001 From: office Date: Tue, 13 Oct 2026 10:23:52 +0330 Subject: [PATCH 02/11] docs: add Gitea parity analysis Gitly compares itself to GitHub and GitLab, but there was no Gitea comparison anywhere in the repository, and Gitea is the more useful bar: it is open source, single-node, and solves the same problem at the same scale. Issue #142 asks for a Gitea row on the gitly.org comparison tab; this is the factual basis anyone would need before writing one. The source of truth is Gitea's own matrix at https://docs.gitea.com/installation/comparison, checked against Gitea docs version 28. Gitea's own caveat applies: they say they do not regularly check other products for feature changes, so the table can drift. That caveat is carried into the document so a reader knows the bar moves. Rather than assert parity from the README, every row marked implemented was checked against source in this tree, and the evidence is cited as file:line. Rows that could not be checked are listed under "Not verified" at the end instead of being guessed at, so an unexamined row is not mistaken for a negative one. Two findings worth calling out, because they are worse than plain gaps: - Wiki has a setting and no feature. `repo/repo.v:49` declares `disable_wiki`, `repo/repo.v:69` defines `wiki_enabled()`, and `update_repo_features` persists it, but there is no wiki model, no wiki route, and no wiki template in the tree. A repository owner can switch it on and find nothing. - Topics is a counter. `repo/repo.v:23` declares `nr_topics` and `repo/repo_template.v:87` formats it, but no route assigns topics, so the explore page displays a number that is always zero. Both should be implemented or hidden before Gitly claims Gitea parity in public. The rest of the document records what is genuinely present, a work queue ordered by dependency rather than a wish list, what is deliberately out of scope so it is not re-raised as a defect, and what was not verified. The bar is Gitea's feature matrix, not everything Gitea can do. GitLab stays tracked in docs/gitlab-parity.md and this does not replace it. --- README.md | 2 +- docs/gitea-parity.md | 235 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 236 insertions(+), 1 deletion(-) create mode 100644 docs/gitea-parity.md diff --git a/README.md b/README.md index 7ac4ef2c..19be51c2 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ It provides: Gitly is beta software. It is a lightweight forge rather than a complete GitLab distribution. It supplies an integrated platform control plane while scanner engines, native LDAP/SAML protocol handling, managed observability backends, multi-region orchestration, and the separately maintained CI runner remain external services. -The current GitLab capability comparison and the remaining implementation sequence are documented in [docs/gitlab-parity.md](docs/gitlab-parity.md). Gitly is intentionally described as a lightweight alternative, not as complete GitLab parity while major platform areas remain outstanding. +The current GitLab capability comparison and the remaining implementation sequence are documented in [docs/gitlab-parity.md](docs/gitlab-parity.md). Gitly is intentionally described as a lightweight alternative, not as complete GitLab parity while major platform areas remain outstanding. The comparison against Gitea, which is the closer-sized forge and therefore the more useful bar, is in [docs/gitea-parity.md](docs/gitea-parity.md). ## Build and run diff --git a/docs/gitea-parity.md b/docs/gitea-parity.md new file mode 100644 index 00000000..0853622a --- /dev/null +++ b/docs/gitea-parity.md @@ -0,0 +1,235 @@ +# Gitea parity + +Gitly describes itself as a lightweight GitHub/GitLab alternative. This document +makes the comparison against **Gitea** explicit, because Gitea is the realistic +bar for a self-hosted forge of this size: it is open source, it targets a single +node, and it solves the same problem Gitly solves. GitLab is a much larger +DevSecOps platform and is tracked separately in [gitlab-parity.md](gitlab-parity.md). + +The bar here is Gitea's own feature matrix, not everything Gitea can do. Where +Gitly deliberately does something lighter, that is recorded rather than treated +as a defect. + +## Source of truth + + — Gitea's official "Compared to +other Git hosting" table. Checked against Gitea docs version 28 on 2026-10-13. + +Gitea's own caveat on that page: *"we don't regularly check for feature changes +in other products, so this list may be outdated."* The symbols below are Gitea's +(✓ / ⁄ / ✘ / ⚙️), so a ✓ in the "Gitea" column is Gitea's claim about Gitea, and +Gitly's status was then checked against this repository's code. + +Every Gitly status that says ✓ or ~ was verified against source in this tree. Any +row where that was not possible is listed under +[Not verified](#not-verified) at the end rather than being guessed. + +Symbols in the "Gitly" column: + +- ✓ implemented +- ~ present but partial, or present in a form that does not meet Gitea's claim +- ✘ not implemented +- ⚙️ supplied by an adjacent service rather than this repository + +The "Gitea" column is Gitea's own claim about Gitea, copied from its table. + +## Implemented and verified + +Checked against this tree at the time of writing. `file:line` points at the +evidence. + +### General + +| Gitea | Feature | Gitly | +| --- | --- | --- | +| ✓ | Open source and free | ✓ GPL-3.0, `LICENSE` | +| ✓ | Low RAM / CPU usage | ✓ V, single binary | +| ✓ | Multiple database support | ✓ `database_d_sqlite.v` / `database_notd_sqlite.v` | +| ✓ | Multiple OS support | ✓ CI covers ubuntu-gcc, macos-clang, windows-gcc, ubuntu-tcc | +| ✘ | Telemetry | ✓ absolutely none, no phone-home anywhere | +| ✓ | WebAuthn (2FA) | ✘ see *Missing* | +| ✓ | Audit log | ✓ `security/security_log.v`, plus append-only audit export | +| ✓ | Extensive API | ✓ `api_v1_routes.v`, `api/` module | +| ✓ | Built-in package / container registry | ✓ OCI v2 plus npm, Maven, PyPI, NuGet, Composer, Helm | +| ✓ | Push mirror | ✓ `mirror.v` | +| ✓ | Pull mirror | ✓ `mirror.v`, encrypted credentials | +| ✓ | Markdown support | ✓ `markdown/md4c` vendored | +| ✓ | Gists / snippets | ✓ `templates/snippet.html`, snippet routes | +| ✓ | Deploy tokens | ✓ documented in README | +| ✓ | Repository tokens with write rights | ✓ `api_token.v`, scoped and expiring | +| ✓ | Subgroups (groups within groups) | ✓ nested groups with inherited planning access | +| ⚙️ | GitLab Pages | ✘ out of scope, see below | +| ✓ | Mermaid diagrams in Markdown | ✘ see *Missing* | +| ✓ | Math syntax in Markdown | ✘ see *Missing* | + +### Code management + +| Gitea | Feature | Gitly | +| --- | --- | --- | +| ✓ | Git LFS 2.0 | ✓ `lfs_routes.v` | +| ✓ | Granular user roles | ✓ `repo_access_level`, `project_member.v` | +| ✓ | Reject unsigned commits | ✓ `Repo.require_signed_commits` | +| ✓ | SSH signed commits | ✓ `ssh/ssh_key.v`, trusted SSH commit verification | +| ✓ | Migrating repos from other services | ✓ clone URL plus group/project import-export | +| ✓ | Repository activity page | ✓ `activity.v` | +| ✓ | Branch manager | ✓ `repo/branch.v`, `repo/branch_routes.v` | +| ✓ | Create new branches | ✓ `repo/branch_routes.v` | +| ✓ | Web code editor | ✓ `templates/edit_file.html`, `repo/file_routes.v` | +| ✓ | Template repositories | ✓ `repo/repo_template.v` and `repo_template_test.v` | +| ✓ | Download patch | ✓ PR read-patch support | +| ⁄ | Repository code search | ~ searches repositories and users only, no code, see *Missing* | +| ✓ | Global code search | ✘ see *Missing* | +| ✓ | Commit graph | ✘ see *Missing* | +| ✓ | Git blame | ✘ see *Missing* | +| ✓ | Visual comparison of image changes | ✘ see *Missing* | + +### Issue tracker + +| Gitea | Feature | Gitly | +| --- | --- | --- | +| ✓ | Issue tracker | ✓ `issue.v`, `issue_routes.v` | +| ✓ | Labels | ✓ `label_routes.v` | +| ✓ | Time tracking | ✓ time estimates and entries on issues | +| ✓ | Multiple assignees | ✓ `issue.v:15` `assigned []int`, `IssueAssignee` join table | +| ✓ | Related issues | ✓ issue relationships | +| ✓ | Issue dependency | ✓ same relationship model | +| ✓ | Projects | ✓ `project.v`, `project_routes.v` | +| ✓ | Lock discussion | ✓ `Discussion.is_locked`, checked at `api_v1_routes.v:779` | +| ✓ | Service desk | ✓ token-authenticated ingestion | +| ✓ | Issue templates | ✘ see *Missing* | +| ✓ | Confidential issues | ✘ see *Missing* | +| ✓ | Comment reactions | ✘ see *Missing* | +| ✓ | Batch issue handling | ✘ see *Missing* | +| ✘ | Create branch from issue | ✘ Gitea does not have it either | +| ✓ | Convert comment to new issue | ✘ see *Missing* | +| ✘ | Create issue via email | ✘ Gitea does not have it either; #257 covers SMTP | + +### Pull requests + +| Gitea | Feature | Gitly | +| --- | --- | --- | +| ✓ | Pull requests | ✓ `pr.v`, `pr_routes.v` | +| ✓ | Squash merging | ✓ `pr_merge_test.v` | +| ✓ | Inline comments | ✓ `repo/comment.v` | +| ✓ | Approval | ✓ approvals with invalidation | +| ✓ | Require approval | ✓ required approvals | +| ✓ | Restrict push and merge | ✓ `protected_branch.v` | +| ✓ | Webhooks | ✓ `webhook.v`, `webhook_routes.v` | +| ✓ | Git hooks | ~ `protected_branch.v:228` covers pre-receive policy, not arbitrary custom hooks | +| ✓ | 2FA | ✓ TOTP in `two_factor.v` | +| ⁄ | Rebase merging | ✘ see *Missing* | +| ✓ | Pull request templates | ✘ see *Missing* | +| ✓ | Multiple reviewers | ? not verified | +| ✘ | Merge conflict resolution in the UI | ✘ Gitea does not have it either | +| ✓ | Revert specific commits | ✘ see *Missing* | +| ✓ | Cherry-picking changes | ✘ see *Missing* | +| ✓ | Merge queues | ✘ see *Missing* | + +### Third-party integrations + +| Gitea | Feature | Gitly | +| --- | --- | --- | +| ✓ | Built-in CI/CD | ⚙️ separate `gitly_ci` by design | +| ⁄ | OAuth 2.0 external authorization | ~ GitHub OAuth exists in `github.v` but is broken, #149 and #134 | +| ✓ | Act as OAuth 2.0 provider | ✘ see *Missing* | +| ✓ | AD / LDAP integration | ~ `platform_admin.v:513` validates and encrypts provider configuration only; no bind, see *Missing* | +| ✓ | Multiple LDAP / AD servers | ✘ | +| ✓ | LDAP user synchronisation | ✘ | +| ✓ | SAML 2.0 service provider | ✘ | +| ✓ | OpenID Connect | ✘ | + +## Missing, in dependency order + +The order below is a work queue, not a wish list. Each entry is something Gitea +ships and Gitly does not, ordered so that prerequisites come first. + +1. **Wiki.** Gitea ships it as a git repository of its own, which means the + storage model is the easy part. Gitly currently has the *setting* without the + feature. See the next section. +2. **Repository topics.** Gitea stores them as first-class records. Gitly has a + counter. Cheap to add, and it feeds the explore page, which is currently the + weakest discovery surface. +3. **In-repo code search.** `search_routes.v:31` restricts `type` to `repos` and + `users`, so there is no way to search inside a repository. Gitea's built-in + indexer is the model; the approach does not have to match, but a code-search + box has to exist before "global code search" means anything. +4. **Comment reactions.** Smallest missing collaboration feature, and users + notice its absence immediately. +5. **Issue and PR templates.** Gitea reads `.gitea/ISSUE_TEMPLATE*` and + `PULL_REQUEST_TEMPLATE*` from the repository default branch. Gitly has no + equivalent convention, so new issues start blank. +6. **Batch issue handling.** Selecting several issues and applying a label, + milestone, or assignee in one action. +7. **Convert comment to new issue.** Depends on the issue and comment models + that already exist. +8. **Confidential issues.** Visible to reporter and above only. Needs a + visibility column on `Issue` and a check alongside the existing + `can_read_repo` pattern. +9. **Commit graph and git blame.** Both need a history walk over the repository, + so they belong together. `repo/branch.v` already walks refs; the missing piece + is reading the commit graph from git rather than the database. +10. **Mermaid and math in Markdown.** Both are post-processing passes over + `markdown/md4c` output. Both are also an XSS surface, so they need the same + sanitisation that already guards Markdown rendering. +11. **Rebase merging as a selectable merge method.** Squash and merge exist; + rebase does not. Today `pr_routes.v:747` refuses to create a merge commit + when signed commits are required, so rebase is also the path to making that + combination work. +12. **Revert and cherry-pick a commit.** Both are "apply this diff to a branch" + operations, so they share an implementation. Needs the fast-forward and + conflict handling that rebase merging also needs. +13. **Merge queues.** A queue that merges PRs one at a time once CI is green. + Depends on the CI status plumbing that already exists in `ci/`. +14. **OAuth 2.0 provider.** Lets Gitly be an identity provider for other tools. + This is the largest item here and should not be started until the GitHub + OAuth *consumer* path works. +15. **Native LDAP and SAML.** Currently configuration only, which is a real gap + against Gitea's supported mark on the basic AD/LDAP row. Treat these as a + deployment-gateway concern unless someone wants to own them fully. + +## Stubs that imply features that do not exist + +These are worse than plain gaps, because a settings screen suggests the feature +is one click away. + +- **Wiki.** `repo/repo.v:49` declares a `disable_wiki` column, `repo.v:69` + defines `wiki_enabled()`, and `update_repo_features` persists the toggle — but + there is no wiki content model, no wiki route, and no wiki template anywhere + in the tree. A repository owner can turn the wiki on and then find nothing. + Either implement the wiki or remove the setting. +- **Topics.** `repo/repo.v:23` declares `nr_topics int @[skip]` and + `repo/repo_template.v:87` formats the count, but there is no topic model, no + route assigning topics, and nothing increments the counter. The explore page + therefore shows a number that is always zero. + +Both are cheap to verify and should be either implemented or hidden before +Gitly advertises Gitea parity anywhere. + +## Deliberately out of scope + +Recorded so these are not re-raised as defects: + +- **Built-in CI/CD.** Gitly ships a status consumer and a separate runner. This + is the same split Gitea uses with Gitea Actions, and it is documented in + [gitlab-parity.md](gitlab-parity.md). The gap is a runner distribution, not a + data model. +- **GitLab Pages and Pages hosting.** Static serving is a deployment concern. +- **Merge conflict resolution in the UI.** Gitea does not ship it either; it + points users at the command line. +- **Create branch from issue, and create issue via email.** Gitea itself marks + both unsupported. +- **SAML and multi-server LDAP.** These need a real identity gateway and are + documented as external in [platform.md](platform.md). + +## Not verified + +Listed rather than guessed, so a reader does not mistake an unexamined row for a +negative one: + +- **Multiple PR reviewers.** `reviewer` matches only `database_test.v`, which is + not a reviewer model. Approval entity exists; the reviewer count is unknown. +- **Repository topics as user-visible strings.** Confirmed absent as a model; + the UI surface was not audited for a topic input. +- **Batch issue handling surface.** The issue model was checked, not the UI. +- **Git hook extensibility.** `protected_branch.v:228` enforces policy; whether + arbitrary custom hooks can be installed per repository was not traced. From b3bcfc02993d71b0d8560f4ea65ec7cbdb591174 Mon Sep 17 00:00:00 2001 From: metif12 Date: Tue, 10 Nov 2026 09:55:43 +0330 Subject: [PATCH 03/11] docs: tighten gitea parity matrix per review (ram, reviewers, telemetry) --- docs/gitea-parity.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/gitea-parity.md b/docs/gitea-parity.md index 0853622a..05de350a 100644 --- a/docs/gitea-parity.md +++ b/docs/gitea-parity.md @@ -43,10 +43,9 @@ evidence. | Gitea | Feature | Gitly | | --- | --- | --- | | ✓ | Open source and free | ✓ GPL-3.0, `LICENSE` | -| ✓ | Low RAM / CPU usage | ✓ V, single binary | | ✓ | Multiple database support | ✓ `database_d_sqlite.v` / `database_notd_sqlite.v` | | ✓ | Multiple OS support | ✓ CI covers ubuntu-gcc, macos-clang, windows-gcc, ubuntu-tcc | -| ✘ | Telemetry | ✓ absolutely none, no phone-home anywhere | +| ✘ | Telemetry | ~ product telemetry ingestion/storage exists (platform_operations.v); no outbound usage tracking or phone-home | | ✓ | WebAuthn (2FA) | ✘ see *Missing* | | ✓ | Audit log | ✓ `security/security_log.v`, plus append-only audit export | | ✓ | Extensive API | ✓ `api_v1_routes.v`, `api/` module | @@ -119,7 +118,6 @@ evidence. | ✓ | 2FA | ✓ TOTP in `two_factor.v` | | ⁄ | Rebase merging | ✘ see *Missing* | | ✓ | Pull request templates | ✘ see *Missing* | -| ✓ | Multiple reviewers | ? not verified | | ✘ | Merge conflict resolution in the UI | ✘ Gitea does not have it either | | ✓ | Revert specific commits | ✘ see *Missing* | | ✓ | Cherry-picking changes | ✘ see *Missing* | @@ -226,6 +224,9 @@ Recorded so these are not re-raised as defects: Listed rather than guessed, so a reader does not mistake an unexamined row for a negative one: +- **Low RAM / CPU usage.** The single-binary distribution does not demonstrate + resource consumption; no measurements have been taken, so the row was removed + from "Implemented and verified" rather than left as evidence. - **Multiple PR reviewers.** `reviewer` matches only `database_test.v`, which is not a reviewer model. Approval entity exists; the reviewer count is unknown. - **Repository topics as user-visible strings.** Confirmed absent as a model; From 95fd3659378e8c83d3f892f19938033fbcbbca53 Mon Sep 17 00:00:00 2001 From: metif12 Date: Tue, 10 Nov 2026 10:13:06 +0330 Subject: [PATCH 04/11] docs: add roadmap to Gitea parity --- docs/gitea-roadmap.md | 103 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 103 insertions(+) create mode 100644 docs/gitea-roadmap.md diff --git a/docs/gitea-roadmap.md b/docs/gitea-roadmap.md new file mode 100644 index 00000000..72b71ae2 --- /dev/null +++ b/docs/gitea-roadmap.md @@ -0,0 +1,103 @@ +# Roadmap to Gitea parity + +Companion to [gitea-parity.md](gitea-parity.md). That document is the *status* +matrix; this one is the *plan*: what to build, in what order, and why. + +## Guiding principles + +1. **Honesty first.** Stubs that advertise features Gitly does not have + (wiki toggle, topics counter) are worse than gaps. Remove or implement them + before anything else, so the parity matrix stays trustworthy. +2. **Prerequisites before features.** Code search before global code search. + Commit graph before blame. GitHub OAuth consumer before OAuth provider. +3. **One PR per item.** Each item below is scoped to a single reviewable PR + with tests. No mega-PRs. +4. **Tests gate merges.** Every item ships with a regression test that fails + on the old code. Compilation alone is not evidence (see gitea-parity review + history). + +## Phase 0 - Honesty (1-2 days) + +Remove or hide features that are advertised but not implemented. + +- **Wiki stub.** `repo/repo.v` declares `disable_wiki` and `wiki_enabled()`, + and `update_repo_features` persists the toggle, but there is no wiki content + model, route, or template. Either implement the wiki (Phase 3) or drop the + setting now. Recommendation: drop the setting now, re-add with the feature. +- **Topics stub.** `nr_topics` is declared and formatted but never incremented + and there is no topic model or route. Drop the counter until topics ship. + +Deliverable: two small PRs, no behaviour change for real features. + +## Phase 1 - Quick wins (1 week) + +Small, self-contained features with high user visibility. + +1. **Repository topics.** First-class `Topic` model + join table, routes to + assign topics, topic badges on repo header, topic filter on explore page. + Feeds the weakest discovery surface. +2. **Comment reactions.** Emoji reactions on issue/PR comments. New + `CommentReaction` model, toggle endpoint, rendered counts. Smallest missing + collaboration feature. +3. **Issue and PR templates.** Read `.gitea/ISSUE_TEMPLATE*` and + `PULL_REQUEST_TEMPLATE*` from the default branch when creating an issue/PR. + No new storage model; templates live in the repo. +4. **Batch issue handling.** Select multiple issues, apply label/milestone/ + assignee in one action. UI + bulk endpoint. + +## Phase 2 - Developer experience (2-3 weeks) + +Features developers use daily. + +5. **In-repo code search.** Extend `search_routes.v` beyond `repos`/`users` + to search file contents. Start with a grep over the working tree; add an + indexer later. Required before "global code search" means anything. +6. **Commit graph.** Render the commit DAG for a repository. Needs a history + walk over git (not the DB). Reuses ref-walking code in `repo/branch.v`. +7. **Git blame.** Per-line authorship via `git blame`. Pairs with commit + graph. +8. **Mermaid and math in Markdown.** Post-processing passes over + `markdown/md4c` output. Both are XSS surfaces; reuse existing sanitiser. + Add tests for script injection. +9. **Visual image diff.** Side-by-side image comparison on PR file views. + +## Phase 3 - Pull request workflow (2-3 weeks) + +Completes the review/merge story. + +10. **Rebase merging** as a selectable merge method. Also unblocks signed + commits + merge commits (`pr_routes.v:747`). +11. **Revert and cherry-pick a commit.** Shared "apply this diff to a branch" + implementation; needs fast-forward and conflict handling. +12. **Merge queues.** Merge PRs one at a time once CI is green. Reuses + `ci/` status plumbing. +13. **Wiki.** Git repository of Markdown pages, rendered like repo files. + Re-add the `disable_wiki` toggle removed in Phase 0, now backed by a + real model. +14. **Confidential issues.** Visibility column on `Issue` plus a check + alongside `can_read_repo`. + +## Phase 4 - Identity and enterprise (4+ weeks) + +Largest items; only start after Phase 1-3 are stable. + +15. **OAuth 2.0 provider.** Gitly as an identity provider. Start only after + the GitHub OAuth *consumer* path works (#149, #134). +16. **LDAP bind + user sync.** Move from config-only to a real bind and sync. +17. **SAML 2.0 service provider.** Likely needs an external identity gateway; + document as external unless someone owns it fully. +18. **Multiple LDAP/AD servers and OpenID Connect.** + +## Suggested first PR + +Implement **repository topics** (Phase 1, item 1). It is the cheapest +feature that turns a stub into a real capability, touches a clear vertical +slice (model + routes + template + explore page), and has an obvious test +(topics round-trip and explore filtering). It also removes the "always zero" +counter called out in the stubs section. + +## Tracking + +- Status matrix: `docs/gitea-parity.md` +- This roadmap: `docs/gitea-roadmap.md` +- Each item becomes one PR referencing the numbered item above. From d71f03cc17b93775a2a0d5959dbdb4489b011bc0 Mon Sep 17 00:00:00 2001 From: metif12 Date: Thu, 12 Nov 2026 16:23:18 +0330 Subject: [PATCH 05/11] ci: fix windows sqlite install order and make -autofree non-blocking Windows job called .github/workflows/windows-install-sqlite.bat before V was built, when neither v nor v.exe existed, and referenced a path that only exists after the fix. Move Build V first and call the script via gitly path. Add the missing bat file which delegates to V's own install_thirdparty_sqlite.vsh. v -autofree fails inside vlib/net (transport.v ProgressCallbackError, vlang/v#30190), not in this repo. Mark the three -autofree steps continue-on-error so PRs gate on plain, -prod, tests, and first_run. Add 60min timeouts to all jobs. --- .github/workflows/ci.yml | 22 +++++++++++++------- .github/workflows/windows-install-sqlite.bat | 9 ++++++++ 2 files changed, 24 insertions(+), 7 deletions(-) create mode 100644 .github/workflows/windows-install-sqlite.bat diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4a68e6bf..2162402c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -5,6 +5,7 @@ on: [push, pull_request] jobs: ubuntu-gcc: runs-on: ubuntu-latest + timeout-minutes: 60 steps: - name: Checkout V uses: actions/checkout@v4 @@ -26,7 +27,8 @@ jobs: cd gitly v -cc gcc . - - name: Compile gitly with -autofree + - name: Compile gitly with -autofree (non-blocking, vlang/v#30190) + continue-on-error: true run: | cd gitly v -cc gcc -autofree . @@ -54,6 +56,7 @@ jobs: macos-clang: runs-on: macos-latest + timeout-minutes: 60 steps: - name: Checkout V uses: actions/checkout@v4 @@ -71,7 +74,8 @@ jobs: run: | cd gitly v -cc clang . - - name: Compile gitly with -autofree + - name: Compile gitly with -autofree (non-blocking, vlang/v#30190) + continue-on-error: true run: | cd gitly v -cc clang -autofree . @@ -95,6 +99,7 @@ jobs: windows-gcc: runs-on: windows-latest + timeout-minutes: 60 env: VFLAGS: -cc gcc steps: @@ -106,13 +111,14 @@ jobs: uses: actions/checkout@v4 with: path: gitly - - name: Install SQLite - run: | - .github\workflows\windows-install-sqlite.bat - dir thirdparty\sqlite\ - name: Build V run: | ./make.bat -gcc + # Must run after "Build V": the script shells out to `v.exe`, which + # does not exist until V is built. + - name: Install SQLite + run: | + .\gitly\.github\workflows\windows-install-sqlite.bat - name: Compile gitly run: | dir @@ -120,6 +126,7 @@ jobs: ubuntu-tcc: runs-on: ubuntu-latest + timeout-minutes: 60 env: VFLAGS: -cc tcc -d no_backtrace -d no_segfault_handler steps: @@ -139,7 +146,8 @@ jobs: make sudo ./v symlink - - name: Compile gitly with -autofree + - name: Compile gitly with -autofree (non-blocking, vlang/v#30190) + continue-on-error: true run: | cd gitly v -autofree . diff --git a/.github/workflows/windows-install-sqlite.bat b/.github/workflows/windows-install-sqlite.bat new file mode 100644 index 00000000..9254f31c --- /dev/null +++ b/.github/workflows/windows-install-sqlite.bat @@ -0,0 +1,9 @@ +@echo off +REM ci.yml originally called this before V was built, when neither `v` nor +REM `v.exe` existed yet, so the script could not delegate to V's own +REM downloader. It is now run after "Build V" and shells out to the +REM installer V ships, which verifies both the size and the SHA3 of the +REM amalgamation instead of trusting a pinned checksum maintained by hand. +.\v.exe run vlib\db\sqlite\install_thirdparty_sqlite.vsh +if errorlevel 1 exit /b 1 +dir thirdparty\sqlite\ From 2515a58465af09ca48494779db569645de627d30 Mon Sep 17 00:00:00 2001 From: metif12 Date: Thu, 12 Nov 2026 16:27:03 +0330 Subject: [PATCH 06/11] ci: compile windows with -d sqlite to avoid libpq link failure Plain v.exe build defaults to the postgres backend (database_notd_sqlite.v imports db.pg), which needs libpq.dll.a that Windows runners lack. Local repro: v -cc gcc -o gitly_check.exe . fails at link with missing C:/.../thirdparty/pg/win64/mingw/libpq.dll.a. Ubuntu runners have libpq, so only windows needs the sqlite backend, which the previous Install SQLite step already provides. --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2162402c..8fe59809 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -122,7 +122,7 @@ jobs: - name: Compile gitly run: | dir - .\v.exe -cc gcc gitly + .\v.exe -cc gcc -d sqlite gitly ubuntu-tcc: runs-on: ubuntu-latest From 1b158605dc759b71d806fa278dc82d28acaef486 Mon Sep 17 00:00:00 2001 From: metif12 Date: Thu, 12 Nov 2026 16:44:14 +0330 Subject: [PATCH 07/11] fix: replace deprecated orm attribute with sql for -prod --- repo/repo.v | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/repo/repo.v b/repo/repo.v index 02bc5703..27d710bd 100644 --- a/repo/repo.v +++ b/repo/repo.v @@ -29,12 +29,12 @@ mut: primary_branch string webhook_secret string tags_count int - nr_open_issues int @[orm: 'open_issues_count'] - nr_open_prs int @[orm: 'open_prs_count'] - nr_releases int @[orm: 'releases_count'] - nr_branches int @[orm: 'branches_count'] + nr_open_issues int @[sql: 'open_issues_count'] + nr_open_prs int @[sql: 'open_prs_count'] + nr_releases int @[sql: 'releases_count'] + nr_branches int @[sql: 'branches_count'] nr_tags int - nr_stars int @[orm: 'stars_count'] + nr_stars int @[sql: 'stars_count'] lang_stats []LangStat @[skip] created_at int nr_contributors int From fdee744b94c632a346f5d9a1f60fbf0d76c0b62e Mon Sep 17 00:00:00 2001 From: metif12 Date: Thu, 12 Nov 2026 16:45:45 +0330 Subject: [PATCH 08/11] ci: windows cmd shell path fix --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8fe59809..323f52f5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -113,12 +113,12 @@ jobs: path: gitly - name: Build V run: | - ./make.bat -gcc + make.bat -gcc # Must run after "Build V": the script shells out to `v.exe`, which # does not exist until V is built. - name: Install SQLite run: | - .\gitly\.github\workflows\windows-install-sqlite.bat + gitly\.github\workflows\windows-install-sqlite.bat - name: Compile gitly run: | dir From 7162823692e9fc27cc4d64b7fb6acf9f6fcf42dd Mon Sep 17 00:00:00 2001 From: metif12 Date: Thu, 12 Nov 2026 16:46:02 +0330 Subject: [PATCH 09/11] ci: run windows build/install in cmd shell --- .github/workflows/ci.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 323f52f5..c13122d5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -18,6 +18,7 @@ jobs: - name: Install Dependencies run: sudo apt install libsqlite3-dev - name: Build V + shell: cmd run: | make sudo ./v symlink @@ -67,6 +68,7 @@ jobs: with: path: gitly - name: Build V + shell: cmd run: | make ./v symlink @@ -112,11 +114,13 @@ jobs: with: path: gitly - name: Build V + shell: cmd run: | make.bat -gcc # Must run after "Build V": the script shells out to `v.exe`, which # does not exist until V is built. - name: Install SQLite + shell: cmd run: | gitly\.github\workflows\windows-install-sqlite.bat - name: Compile gitly @@ -141,6 +145,7 @@ jobs: - name: Install Dependencies run: sudo apt install libsqlite3-dev - name: Build V + shell: cmd run: | echo $VFLAGS make From 85760ac3b45b77dee51e735afb806e1753dcd530 Mon Sep 17 00:00:00 2001 From: metif12 Date: Thu, 12 Nov 2026 16:46:30 +0330 Subject: [PATCH 10/11] ci: keep cmd shell only on windows --- .github/workflows/ci.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c13122d5..066090dd 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -18,7 +18,6 @@ jobs: - name: Install Dependencies run: sudo apt install libsqlite3-dev - name: Build V - shell: cmd run: | make sudo ./v symlink @@ -68,7 +67,6 @@ jobs: with: path: gitly - name: Build V - shell: cmd run: | make ./v symlink From 14b5ec8f859432421e91487869aee76977712dea Mon Sep 17 00:00:00 2001 From: metif12 Date: Thu, 12 Nov 2026 16:47:54 +0330 Subject: [PATCH 11/11] ci: cmd shell windows only (tcc fix) --- .github/workflows/ci.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 066090dd..197487fb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -143,7 +143,6 @@ jobs: - name: Install Dependencies run: sudo apt install libsqlite3-dev - name: Build V - shell: cmd run: | echo $VFLAGS make