diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1a01d72b..197487fb 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: @@ -107,21 +112,23 @@ jobs: with: path: gitly - name: Build V + shell: cmd run: | - ./make.bat -gcc - - # Must come after "Build V": this used to run first, when neither `v` - # nor `v.exe` existed, so the referenced script could not work at all. + 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 - + shell: cmd + run: | + gitly\.github\workflows\windows-install-sqlite.bat - name: Compile gitly run: | dir - .\v.exe -cc gcc gitly + .\v.exe -cc gcc -d sqlite gitly ubuntu-tcc: runs-on: ubuntu-latest + timeout-minutes: 60 env: VFLAGS: -cc tcc -d no_backtrace -d no_segfault_handler steps: @@ -141,7 +148,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/fork.v b/fork.v index b63a43ef..86d2957a 100644 --- a/fork.v +++ b/fork.v @@ -254,6 +254,7 @@ fn (mut app App) create_fork(source Repo, owner_name string, owner_user_id int, disable_projects: source.disable_projects disable_milestones: source.disable_milestones disable_wiki: source.disable_wiki + topics: source.topics } app.add_repo(row) or { os.rmdir_all(target_path) or {} diff --git a/gitly.v b/gitly.v index 66ae334f..7aa3526d 100644 --- a/gitly.v +++ b/gitly.v @@ -740,6 +740,7 @@ fn (mut app App) migrate_tables() ! { app.add_missing_column('Repo', 'require_signed_commits', db_bool_column_type())! app.add_missing_column('Repo', 'service_desk_enabled', db_bool_column_type())! app.add_missing_column('Repo', 'service_desk_token_hash', "TEXT NOT NULL DEFAULT ''")! + app.add_missing_column('Repo', 'topics', "TEXT NOT NULL DEFAULT ''")! app.add_missing_column('Issue', 'status', 'INTEGER NOT NULL DEFAULT 0')! app.add_missing_column('Release', 'created_by', 'INTEGER NOT NULL DEFAULT 0')! app.add_missing_column('Release', 'is_manual', db_bool_column_type())! diff --git a/repo/repo.v b/repo/repo.v index 68282424..c65fbe28 100644 --- a/repo/repo.v +++ b/repo/repo.v @@ -16,33 +16,34 @@ struct Repo { user_id int user_name string description string + topics string is_public bool is_deleted bool users_contributed []string @[skip] users_authorized []string @[skip] - nr_topics int @[skip] + nr_topics int @[skip] views_count int - latest_update_hash string @[skip] + latest_update_hash string @[skip] latest_activity time.Time @[skip] mut: clone_url string @[skip] 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 labels []Label @[skip] status RepoStatus msg_cache map[string]string @[skip] - latest_commit_at int @[skip] - activity_buckets []int @[skip] + latest_commit_at int @[skip] + activity_buckets []int @[skip] disable_discussions bool disable_projects bool disable_milestones bool @@ -370,6 +371,12 @@ fn (mut app App) update_repo_features(repo_id int, disable_discussions bool, dis }! } +fn (mut app App) update_repo_topics(repo_id int, new_topics string) ! { + sql app.db { + update Repo set topics = new_topics where id == repo_id + }! +} + fn (mut app App) update_repo_general_settings(repo_id int, description string, is_public bool, primary_branch string) ! { if repo_id <= 0 || description.len > max_repo_description_len || !is_safe_ref(primary_branch) { return error('invalid repository settings') @@ -904,7 +911,7 @@ fn (mut app App) update_repo_after_ref_changes(repo_id int, updates []git.GitRef // 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 { + db: connect_db(conf) or { eprintln('bg_recompute_lang_stats: cannot open ${db_backend_name()} db: ${err}') return } @@ -965,10 +972,10 @@ fn (r &Repo) analyze_lang(app &App) ! { } lang_data := langs[lang] d_lang_stats << LangStat{ - repo_id: r.id - name: lang_data.name - pct: pct - color: lang_data.color + repo_id: r.id + name: lang_data.name + pct: pct + color: lang_data.color lines_count: amount } } @@ -1103,12 +1110,12 @@ fn (r &Repo) parse_ls(ls_line string, branch string) ?File { } return File{ - name: item_name - parent_path: parent_path - repo_id: r.id - branch: branch - is_dir: item_type == 'tree' - size: if item_type == 'blob' { item_size.int() } else { 0 } + name: item_name + parent_path: parent_path + repo_id: r.id + branch: branch + is_dir: item_type == 'tree' + size: if item_type == 'blob' { item_size.int() } else { 0 } is_size_calculated: item_type == 'blob' } } @@ -1145,12 +1152,12 @@ fn (r &Repo) parse_top_file_line(line string, branch string) ?File { parent_path := if parent_path_raw == '.' { '' } else { parent_path_raw } return File{ - name: item_name - parent_path: parent_path - repo_id: r.id - branch: branch - is_dir: false - size: meta_parts[3].int() + name: item_name + parent_path: parent_path + repo_id: r.id + branch: branch + is_dir: false + size: meta_parts[3].int() is_size_calculated: true } } @@ -1176,12 +1183,12 @@ fn (r &Repo) lookup_file_via_git(branch string, path string) ?File { parent_path_raw := os.dir(item_path) parent_path := if parent_path_raw == '.' { '' } else { parent_path_raw } return File{ - name: item_name - parent_path: parent_path - repo_id: r.id - branch: branch - is_dir: false - size: meta_parts[3].int() + name: item_name + parent_path: parent_path + repo_id: r.id + branch: branch + is_dir: false + size: meta_parts[3].int() is_size_calculated: true } } diff --git a/repo/repo_routes.v b/repo/repo_routes.v index 44e934e8..3fcba8e7 100644 --- a/repo/repo_routes.v +++ b/repo/repo_routes.v @@ -245,6 +245,19 @@ pub fn (mut app App) handle_update_repo_features(mut ctx Context, username strin return ctx.redirect('/${username}/${repo_name}/settings') } +@['/:username/:repo_name/settings/topics'; post] +pub fn (mut app App) handle_update_repo_topics(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) + } + if !app.can_admin_repo(ctx, repo) { + return ctx.redirect_to_repository(username, repo_name) + } + topics := ctx.form['topics'] or { '' } + app.update_repo_topics(repo.id, topics) or { app.info(err.str()) } + return ctx.redirect('/${username}/${repo_name}/settings') +} + @['/:user/:repo_name/delete'; post] 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 { @@ -456,15 +469,15 @@ pub fn (mut app App) handle_new_repo(mut ctx Context, name string, clone_url str } repo_path := os.join_path(owner_dir, name) mut new_repo := &Repo{ - name: name - description: description - git_dir: repo_path - user_id: ctx.user.id + name: name + description: description + git_dir: repo_path + user_id: ctx.user.id primary_branch: default_primary_branch - user_name: owner_name - clone_url: valid_clone_url - is_public: is_public - created_at: int(time.now().unix()) + user_name: owner_name + clone_url: valid_clone_url + is_public: is_public + created_at: int(time.now().unix()) } import_issues := ctx.form['import_issues'] == '1' import_prs := ctx.form['import_prs'] == '1' @@ -544,7 +557,7 @@ pub fn (mut app App) handle_new_repo(mut ctx Context, name string, clone_url str fn bg_fetch_files_info(repo_ Repo, branch string, path string, conf config.Config) { mut repo := repo_ mut app := &App{ - db: connect_db(conf) or { + db: connect_db(conf) or { eprintln('cannot open ${db_backend_name()} db connection for bg_fetch thread: ${err}') return } @@ -575,7 +588,7 @@ fn clone_repo(new_repo Repo, conf config.Config, import_issues bool, import_prs // Use a dedicated DB connection for the clone thread to avoid // sharing a connection across threads. mut app := &App{ - db: connect_db(conf) or { + db: connect_db(conf) or { eprintln('cannot open ${db_backend_name()} db connection for clone thread: ${err}') return } @@ -636,7 +649,7 @@ fn clone_repo(new_repo Repo, conf config.Config, import_issues bool, import_prs fn bg_import_github_repo_info(repo_id int, clone_url string, existing_description string, conf config.Config) { eprintln('[github-info] spawned thread for repo_id=${repo_id}') mut app := &App{ - db: connect_db(conf) or { + db: connect_db(conf) or { eprintln('[github-info] cannot open db connection: ${err}') return } @@ -661,7 +674,7 @@ fn bg_import_github_repo_info(repo_id int, clone_url string, existing_descriptio fn bg_import_github_issues(repo_id int, clone_url string, owner_user_id int, conf config.Config) { eprintln('[github-import] spawned thread for repo_id=${repo_id}') mut app := &App{ - db: connect_db(conf) or { + db: connect_db(conf) or { eprintln('[github-import] cannot open db connection for import thread: ${err}') return } @@ -981,7 +994,7 @@ pub fn (mut app App) handle_api_repo_star(mut ctx Context, repo_id_str string) v return ctx.json(api.ApiSuccessResponse[bool]{ success: true - result: is_repo_starred + result: is_repo_starred }) } @@ -1002,7 +1015,7 @@ pub fn (mut app App) handle_api_repo_watch(mut ctx Context, repo_id_str string) return ctx.json(api.ApiSuccessResponse[bool]{ success: true - result: is_watching + result: is_watching }) } @@ -1031,17 +1044,17 @@ pub fn (mut app App) handle_api_repo_files(mut ctx Context, repo_id_str string) mut result := []RepoFileInfo{} for item in items { result << RepoFileInfo{ - name: item.name - last_msg: item.last_msg + name: item.name + last_msg: item.last_msg last_hash: item.last_hash last_time: item.pretty_last_time() - size: item.pretty_tree_size() + size: item.pretty_tree_size() } } return ctx.json(api.ApiSuccessResponse[[]RepoFileInfo]{ success: true - result: result + result: result }) } diff --git a/templates/repo/settings.html b/templates/repo/settings.html index c88d6dd7..7b014d58 100644 --- a/templates/repo/settings.html +++ b/templates/repo/settings.html @@ -72,6 +72,15 @@

%features_section_title

+ +
+

Topics

+
+ + + +
+
diff --git a/templates/tree.html b/templates/tree.html index ff14d437..bd224418 100644 --- a/templates/tree.html +++ b/templates/tree.html @@ -21,6 +21,13 @@
@include 'layout/repo_menu.html' + @if repo.topics != '' +
+ @for topic in repo.topics.split(',') + @topic.trim_space() + @end +
+ @end @if is_top_directory
@if upstream_repo.id > 0