diff options
Diffstat (limited to '')
-rw-r--r-- | acl.go | 69 | ||||
-rw-r--r-- | git_hooks_handle.go | 2 | ||||
-rw-r--r-- | git_misc.go | 43 | ||||
-rw-r--r-- | http_handle_group_index.go | 118 | ||||
-rw-r--r-- | http_handle_index.go | 2 | ||||
-rw-r--r-- | http_handle_repo_index.go | 9 | ||||
-rw-r--r-- | http_handle_repo_info.go | 42 | ||||
-rw-r--r-- | http_handle_repo_upload_pack.go | 44 | ||||
-rw-r--r-- | http_server.go | 42 | ||||
-rw-r--r-- | remote_url.go | 8 | ||||
-rw-r--r-- | ssh_handle_receive_pack.go | 8 | ||||
-rw-r--r-- | ssh_utils.go | 18 | ||||
-rw-r--r-- | static/style.css | 3 | ||||
-rw-r--r-- | templates/_group_path.tmpl | 8 | ||||
-rw-r--r-- | templates/_group_view.tmpl | 48 | ||||
-rw-r--r-- | templates/group.tmpl | 24 | ||||
-rw-r--r-- | templates/group_repos.tmpl | 40 | ||||
-rw-r--r-- | templates/index.tmpl | 37 | ||||
-rw-r--r-- | templates/repo_commit.tmpl | 2 | ||||
-rw-r--r-- | templates/repo_contrib_index.tmpl | 2 | ||||
-rw-r--r-- | templates/repo_contrib_one.tmpl | 2 | ||||
-rw-r--r-- | templates/repo_index.tmpl | 2 | ||||
-rw-r--r-- | templates/repo_log.tmpl | 2 | ||||
-rw-r--r-- | templates/repo_raw_dir.tmpl | 2 | ||||
-rw-r--r-- | templates/repo_tree_dir.tmpl | 2 | ||||
-rw-r--r-- | templates/repo_tree_file.tmpl | 4 | ||||
-rw-r--r-- | url.go | 7 |
27 files changed, 427 insertions, 163 deletions
@@ -5,36 +5,53 @@ package main import ( "context" + + "github.com/jackc/pgx/v5/pgtype" ) // get_path_perm_by_group_repo_key returns the filesystem path and direct // access permission for a given repo and a provided ssh public key. -func get_path_perm_by_group_repo_key(ctx context.Context, group_name, repo_name, ssh_pubkey string) (repo_id int, filesystem_path string, access bool, contrib_requirements string, user_type string, user_id int, err error) { - err = database.QueryRow(ctx, - `SELECT - r.id, - r.filesystem_path, - CASE - WHEN ugr.user_id IS NOT NULL THEN TRUE - ELSE FALSE - END AS has_role_in_group, - r.contrib_requirements, - COALESCE(u.type, ''), - COALESCE(u.id, 0) - FROM - groups g - JOIN - repos r ON r.group_id = g.id - LEFT JOIN - ssh_public_keys s ON s.key_string = $3 - LEFT JOIN - users u ON u.id = s.user_id - LEFT JOIN - user_group_roles ugr ON ugr.group_id = g.id AND ugr.user_id = u.id - WHERE - g.name = $1 - AND r.name = $2;`, - group_name, repo_name, ssh_pubkey, +func get_path_perm_by_group_repo_key(ctx context.Context, group_path []string, repo_name, ssh_pubkey string) (repo_id int, filesystem_path string, access bool, contrib_requirements string, user_type string, user_id int, err error) { + err = database.QueryRow(ctx, ` +WITH RECURSIVE group_path_cte AS ( + -- Start: match the first name in the path where parent_group IS NULL + SELECT + id, + parent_group, + name, + 1 AS depth + FROM groups + WHERE name = ($1::text[])[1] + AND parent_group IS NULL + + UNION ALL + + -- Recurse: join next segment of the path + SELECT + g.id, + g.parent_group, + g.name, + group_path_cte.depth + 1 + FROM groups g + JOIN group_path_cte ON g.parent_group = group_path_cte.id + WHERE g.name = ($1::text[])[group_path_cte.depth + 1] + AND group_path_cte.depth + 1 <= cardinality($1::text[]) +) +SELECT + r.id, + r.filesystem_path, + CASE WHEN ugr.user_id IS NOT NULL THEN TRUE ELSE FALSE END AS has_role_in_group, + r.contrib_requirements, + COALESCE(u.type, ''), + COALESCE(u.id, 0) +FROM group_path_cte g +JOIN repos r ON r.group_id = g.id +LEFT JOIN ssh_public_keys s ON s.key_string = $3 +LEFT JOIN users u ON u.id = s.user_id +LEFT JOIN user_group_roles ugr ON ugr.group_id = g.id AND ugr.user_id = u.id +WHERE g.depth = cardinality($1::text[]) + AND r.name = $2 +`, pgtype.FlatArray[string](group_path), repo_name, ssh_pubkey, ).Scan(&repo_id, &filesystem_path, &access, &contrib_requirements, &user_type, &user_id) return } diff --git a/git_hooks_handle.go b/git_hooks_handle.go index f2359a2..063eb50 100644 --- a/git_hooks_handle.go +++ b/git_hooks_handle.go @@ -159,7 +159,7 @@ func hooks_handle_connection(conn net.Conn) { wf_error(ssh_stderr, "Error creating merge request: %v", err) return 1 } - fmt.Fprintln(ssh_stderr, ansiec.Blue+"Created merge request at", generate_http_remote_url(pack_to_hook.group_name, pack_to_hook.repo_name)+"/contrib/"+strconv.FormatUint(uint64(new_mr_id), 10)+"/"+ansiec.Reset) + fmt.Fprintln(ssh_stderr, ansiec.Blue+"Created merge request at", generate_http_remote_url(pack_to_hook.group_path, pack_to_hook.repo_name)+"/contrib/"+strconv.FormatUint(uint64(new_mr_id), 10)+"/"+ansiec.Reset) } else { // Existing contrib branch var existing_merge_request_user_id int var is_ancestor bool diff --git a/git_misc.go b/git_misc.go index 4845009..e80c737 100644 --- a/git_misc.go +++ b/git_misc.go @@ -10,21 +10,54 @@ import ( "os" "strings" + "github.com/jackc/pgx/v5/pgtype" "github.com/go-git/go-git/v5" "github.com/go-git/go-git/v5/plumbing" "github.com/go-git/go-git/v5/plumbing/object" ) // open_git_repo opens a git repository by group and repo name. -func open_git_repo(ctx context.Context, group_name, repo_name string) (repo *git.Repository, description string, repo_id int, err error) { +func open_git_repo(ctx context.Context, group_path []string, repo_name string) (repo *git.Repository, description string, repo_id int, err error) { var fs_path string - err = database.QueryRow(ctx, - "SELECT r.filesystem_path, COALESCE(r.description, ''), r.id FROM repos r JOIN groups g ON r.group_id = g.id WHERE g.name = $1 AND r.name = $2;", - group_name, repo_name, - ).Scan(&fs_path, &description, &repo_id) + + err = database.QueryRow(ctx, ` +WITH RECURSIVE group_path_cte AS ( + -- Start: match the first name in the path where parent_group IS NULL + SELECT + id, + parent_group, + name, + 1 AS depth + FROM groups + WHERE name = ($1::text[])[1] + AND parent_group IS NULL + + UNION ALL + + -- Recurse: join next segment of the path + SELECT + g.id, + g.parent_group, + g.name, + group_path_cte.depth + 1 + FROM groups g + JOIN group_path_cte ON g.parent_group = group_path_cte.id + WHERE g.name = ($1::text[])[group_path_cte.depth + 1] + AND group_path_cte.depth + 1 <= cardinality($1::text[]) +) +SELECT + r.filesystem_path, + COALESCE(r.description, ''), + r.id +FROM group_path_cte g +JOIN repos r ON r.group_id = g.id +WHERE g.depth = cardinality($1::text[]) + AND r.name = $2 + `, pgtype.FlatArray[string](group_path), repo_name).Scan(&fs_path, &description, &repo_id) if err != nil { return } + repo, err = git.PlainOpen(fs_path) return } diff --git a/http_handle_group_index.go b/http_handle_group_index.go index 09a85f8..bbdff46 100644 --- a/http_handle_group_index.go +++ b/http_handle_group_index.go @@ -4,21 +4,127 @@ package main import ( + "fmt" "net/http" + + "github.com/jackc/pgx/v5" + "github.com/jackc/pgx/v5/pgtype" ) -func handle_group_repos(w http.ResponseWriter, r *http.Request, params map[string]any) { - var group_name string +func handle_group_index(w http.ResponseWriter, r *http.Request, params map[string]any) { + var group_path []string var repos []name_desc_t + var subgroups []name_desc_t var err error - group_name = params["group_name"].(string) - repos, err = query_name_desc_list(r.Context(), "SELECT r.name, COALESCE(r.description, '') FROM repos r JOIN groups g ON r.group_id = g.id WHERE g.name = $1;", group_name) + group_path = params["group_path"].([]string) + + // Repos + var rows pgx.Rows + rows, err = database.Query(r.Context(), ` + WITH RECURSIVE group_path_cte AS ( + SELECT + id, + parent_group, + name, + 1 AS depth + FROM groups + WHERE name = ($1::text[])[1] + AND parent_group IS NULL + + UNION ALL + + SELECT + g.id, + g.parent_group, + g.name, + group_path_cte.depth + 1 + FROM groups g + JOIN group_path_cte ON g.parent_group = group_path_cte.id + WHERE g.name = ($1::text[])[group_path_cte.depth + 1] + AND group_path_cte.depth + 1 <= cardinality($1::text[]) + ) + SELECT r.name, COALESCE(r.description, '') + FROM group_path_cte c + JOIN repos r ON r.group_id = c.id + WHERE c.depth = cardinality($1::text[]) + `, + pgtype.FlatArray[string](group_path), + ) if err != nil { - http.Error(w, "Error getting groups: "+err.Error(), http.StatusInternalServerError) + http.Error(w, "Error getting repos: "+err.Error(), http.StatusInternalServerError) return } + defer rows.Close() + + for rows.Next() { + var name, description string + if err = rows.Scan(&name, &description); err != nil { + http.Error(w, "Error getting repos: "+err.Error(), http.StatusInternalServerError) + return + } + repos = append(repos, name_desc_t{name, description}) + } + if err = rows.Err(); err != nil { + http.Error(w, "Error getting repos: "+err.Error(), http.StatusInternalServerError) + return + } + + // Subgroups + rows, err = database.Query(r.Context(), ` + WITH RECURSIVE group_path_cte AS ( + SELECT + id, + parent_group, + name, + 1 AS depth + FROM groups + WHERE name = ($1::text[])[1] + AND parent_group IS NULL + + UNION ALL + + SELECT + g.id, + g.parent_group, + g.name, + group_path_cte.depth + 1 + FROM groups g + JOIN group_path_cte ON g.parent_group = group_path_cte.id + WHERE g.name = ($1::text[])[group_path_cte.depth + 1] + AND group_path_cte.depth + 1 <= cardinality($1::text[]) + ) + SELECT g.name, COALESCE(g.description, '') + FROM group_path_cte c + JOIN groups g ON g.parent_group = c.id + WHERE c.depth = cardinality($1::text[]) + `, + pgtype.FlatArray[string](group_path), + ) + if err != nil { + http.Error(w, "Error getting subgroups: "+err.Error(), http.StatusInternalServerError) + return + } + defer rows.Close() + + for rows.Next() { + var name, description string + if err = rows.Scan(&name, &description); err != nil { + http.Error(w, "Error getting subgroups: "+err.Error(), http.StatusInternalServerError) + return + } + subgroups = append(subgroups, name_desc_t{name, description}) + } + if err = rows.Err(); err != nil { + http.Error(w, "Error getting subgroups: "+err.Error(), http.StatusInternalServerError) + return + } + params["repos"] = repos + params["subgroups"] = subgroups - render_template(w, "group_repos", params) + fmt.Println(group_path) + + render_template(w, "group", params) } + diff --git a/http_handle_index.go b/http_handle_index.go index f710315..4632526 100644 --- a/http_handle_index.go +++ b/http_handle_index.go @@ -11,7 +11,7 @@ func handle_index(w http.ResponseWriter, r *http.Request, params map[string]any) var err error var groups []name_desc_t - groups, err = query_name_desc_list(r.Context(), "SELECT name, COALESCE(description, '') FROM groups") + groups, err = query_name_desc_list(r.Context(), "SELECT name, COALESCE(description, '') FROM groups WHERE parent_group IS NULL") if err != nil { http.Error(w, "Error querying groups: "+err.Error(), http.StatusInternalServerError) return diff --git a/http_handle_repo_index.go b/http_handle_repo_index.go index 73f1007..66116c7 100644 --- a/http_handle_repo_index.go +++ b/http_handle_repo_index.go @@ -13,14 +13,15 @@ import ( func handle_repo_index(w http.ResponseWriter, r *http.Request, params map[string]any) { var repo *git.Repository - var repo_name, group_name string + var repo_name string + var group_path []string var ref_hash plumbing.Hash var err error var recent_commits []*object.Commit var commit_object *object.Commit var tree *object.Tree - repo, repo_name, group_name = params["repo"].(*git.Repository), params["repo_name"].(string), params["group_name"].(string) + repo, repo_name, group_path = params["repo"].(*git.Repository), params["repo_name"].(string), params["group_path"].([]string) if ref_hash, err = get_ref_hash_from_type_and_name(repo, params["ref_type"].(string), params["ref_name"].(string)); err != nil { http.Error(w, "Error getting ref hash: "+err.Error(), http.StatusInternalServerError) @@ -46,8 +47,8 @@ func handle_repo_index(w http.ResponseWriter, r *http.Request, params map[string params["readme_filename"], params["readme"] = render_readme_at_tree(tree) params["files"] = build_display_git_tree(tree) - params["http_clone_url"] = generate_http_remote_url(group_name, repo_name) - params["ssh_clone_url"] = generate_ssh_remote_url(group_name, repo_name) + params["http_clone_url"] = generate_http_remote_url(group_path, repo_name) + params["ssh_clone_url"] = generate_ssh_remote_url(group_path, repo_name) render_template(w, "repo_index", params) } diff --git a/http_handle_repo_info.go b/http_handle_repo_info.go index d8227c9..466e0bb 100644 --- a/http_handle_repo_info.go +++ b/http_handle_repo_info.go @@ -8,15 +8,47 @@ import ( "io" "net/http" "os/exec" + + "github.com/jackc/pgx/v5/pgtype" ) func handle_repo_info(w http.ResponseWriter, r *http.Request, params map[string]any) (err error) { - var group_name, repo_name, repo_path string + var group_path []string + var repo_name, repo_path string - group_name, repo_name = params["group_name"].(string), params["repo_name"].(string) - if err = database.QueryRow(r.Context(), - "SELECT r.filesystem_path FROM repos r JOIN groups g ON r.group_id = g.id WHERE g.name = $1 AND r.name = $2;", - group_name, repo_name, + if err := database.QueryRow(r.Context(), ` + WITH RECURSIVE group_path_cte AS ( + -- Start: match the first name in the path where parent_group IS NULL + SELECT + id, + parent_group, + name, + 1 AS depth + FROM groups + WHERE name = ($1::text[])[1] + AND parent_group IS NULL + + UNION ALL + + -- Recurse: jion next segment of the path + SELECT + g.id, + g.parent_group, + g.name, + group_path_cte.depth + 1 + FROM groups g + JOIN group_path_cte ON g.parent_group = group_path_cte.id + WHERE g.name = ($1::text[])[group_path_cte.depth + 1] + AND group_path_cte.depth + 1 <= cardinality($1::text[]) + ) + SELECT r.filesystem_path + FROM group_path_cte c + JOIN repos r ON r.group_id = c.id + WHERE c.depth = cardinality($1::text[]) + AND r.name = $2 + `, + pgtype.FlatArray[string](group_path), + repo_name, ).Scan(&repo_path); err != nil { return err } diff --git a/http_handle_repo_upload_pack.go b/http_handle_repo_upload_pack.go index b273da9..4d3ec09 100644 --- a/http_handle_repo_upload_pack.go +++ b/http_handle_repo_upload_pack.go @@ -8,24 +8,58 @@ import ( "net/http" "os" "os/exec" + + "github.com/jackc/pgx/v5/pgtype" ) func handle_upload_pack(w http.ResponseWriter, r *http.Request, params map[string]any) (err error) { - var group_name, repo_name string + var group_path []string + var repo_name string var repo_path string var stdout io.ReadCloser var stdin io.WriteCloser var cmd *exec.Cmd - group_name, repo_name = params["group_name"].(string), params["repo_name"].(string) + group_path, repo_name = params["group_path"].([]string), params["repo_name"].(string) - if err = database.QueryRow(r.Context(), - "SELECT r.filesystem_path FROM repos r JOIN groups g ON r.group_id = g.id WHERE g.name = $1 AND r.name = $2;", - group_name, repo_name, + if err := database.QueryRow(r.Context(), ` + WITH RECURSIVE group_path_cte AS ( + -- Start: match the first name in the path where parent_group IS NULL + SELECT + id, + parent_group, + name, + 1 AS depth + FROM groups + WHERE name = ($1::text[])[1] + AND parent_group IS NULL + + UNION ALL + + -- Recurse: jion next segment of the path + SELECT + g.id, + g.parent_group, + g.name, + group_path_cte.depth + 1 + FROM groups g + JOIN group_path_cte ON g.parent_group = group_path_cte.id + WHERE g.name = ($1::text[])[group_path_cte.depth + 1] + AND group_path_cte.depth + 1 <= cardinality($1::text[]) + ) + SELECT r.filesystem_path + FROM group_path_cte c + JOIN repos r ON r.group_id = c.id + WHERE c.depth = cardinality($1::text[]) + AND r.name = $2 + `, + pgtype.FlatArray[string](group_path), + repo_name, ).Scan(&repo_path); err != nil { return err } + w.Header().Set("Content-Type", "application/x-git-upload-pack-result") w.Header().Set("Connection", "Keep-Alive") w.Header().Set("Transfer-Encoding", "chunked") diff --git a/http_server.go b/http_server.go index 0acb9a7..7790d1f 100644 --- a/http_server.go +++ b/http_server.go @@ -93,40 +93,34 @@ func (router *http_router_t) ServeHTTP(w http.ResponseWriter, r *http.Request) { params["separator_index"] = separator_index - // TODO - if separator_index > 1 { - http.Error(w, "Subgroups haven't been implemented yet", http.StatusNotImplemented) - return - } - + var group_path []string var module_type string var module_name string - var group_name string + + if separator_index > 0 { + group_path = segments[:separator_index] + } else { + group_path = segments[:len(segments) - 1] + } + params["group_path"] = group_path switch { case non_empty_last_segments_len == 0: handle_index(w, r, params) case separator_index == -1: - http.Error(w, "Group indexing hasn't been implemented yet", http.StatusNotImplemented) - case non_empty_last_segments_len == separator_index+1: - http.Error(w, "Group root hasn't been implemented yet", http.StatusNotImplemented) - case non_empty_last_segments_len == separator_index+2: if redirect_with_slash(w, r) { return } - module_type = segments[separator_index+1] - params["group_name"] = segments[0] - switch module_type { - case "repos": - handle_group_repos(w, r, params) - default: - http.Error(w, fmt.Sprintf("Unknown module type: %s", module_type), http.StatusNotFound) - } + handle_group_index(w, r, params) + case non_empty_last_segments_len == separator_index+1: + http.Error(w, "Illegal path 1", http.StatusNotImplemented) + return + case non_empty_last_segments_len == separator_index+2: + http.Error(w, "Illegal path 2", http.StatusNotImplemented) + return default: module_type = segments[separator_index+1] module_name = segments[separator_index+2] - group_name = segments[0] - params["group_name"] = group_name switch module_type { case "repos": params["repo_name"] = module_name @@ -157,14 +151,16 @@ func (router *http_router_t) ServeHTTP(w http.ResponseWriter, r *http.Request) { // TODO: subgroups - if params["repo"], params["repo_description"], params["repo_id"], err = open_git_repo(r.Context(), group_name, module_name); err != nil { + if params["repo"], params["repo_description"], params["repo_id"], err = open_git_repo(r.Context(), group_path, module_name); err != nil { http.Error(w, "Error opening repo: "+err.Error(), http.StatusInternalServerError) return } + fmt.Println(non_empty_last_segments_len, separator_index, segments) + if non_empty_last_segments_len == separator_index+3 { if redirect_with_slash(w, r) { - return + return } handle_repo_index(w, r, params) return diff --git a/remote_url.go b/remote_url.go index e478324..506e35c 100644 --- a/remote_url.go +++ b/remote_url.go @@ -10,10 +10,10 @@ import ( // We don't use path.Join because it collapses multiple slashes into one. -func generate_ssh_remote_url(group_name, repo_name string) string { - return strings.TrimSuffix(config.SSH.Root, "/") + "/" + url.PathEscape(group_name) + "/:/repos/" + url.PathEscape(repo_name) +func generate_ssh_remote_url(group_path []string, repo_name string) string { + return strings.TrimSuffix(config.SSH.Root, "/") + "/" + path_escape_cat_segments(group_path) + "/:/repos/" + url.PathEscape(repo_name) } -func generate_http_remote_url(group_name, repo_name string) string { - return strings.TrimSuffix(config.HTTP.Root, "/") + "/" + url.PathEscape(group_name) + "/:/repos/" + url.PathEscape(repo_name) +func generate_http_remote_url(group_path []string, repo_name string) string { + return strings.TrimSuffix(config.HTTP.Root, "/") + "/" + path_escape_cat_segments(group_path) + "/:/repos/" + url.PathEscape(repo_name) } diff --git a/ssh_handle_receive_pack.go b/ssh_handle_receive_pack.go index 4816c61..45610bb 100644 --- a/ssh_handle_receive_pack.go +++ b/ssh_handle_receive_pack.go @@ -22,7 +22,7 @@ type pack_to_hook_t struct { repo_path string user_id int repo_id int - group_name string + group_path []string repo_name string } @@ -30,7 +30,7 @@ var pack_to_hook_by_cookie = cmap.Map[string, pack_to_hook_t]{} // ssh_handle_receive_pack handles attempts to push to repos. func ssh_handle_receive_pack(session glider_ssh.Session, pubkey string, repo_identifier string) (err error) { - group_name, repo_name, repo_id, repo_path, direct_access, contrib_requirements, user_type, user_id, err := get_repo_path_perms_from_ssh_path_pubkey(session.Context(), repo_identifier, pubkey) + group_path, repo_name, repo_id, repo_path, direct_access, contrib_requirements, user_type, user_id, err := get_repo_path_perms_from_ssh_path_pubkey(session.Context(), repo_identifier, pubkey) if err != nil { return err } @@ -86,8 +86,6 @@ func ssh_handle_receive_pack(session glider_ssh.Session, pubkey string, repo_ide fmt.Fprintln(session.Stderr(), "Error while generating cookie:", err) } - fmt.Println(group_name, repo_name) - pack_to_hook_by_cookie.Store(cookie, pack_to_hook_t{ session: session, pubkey: pubkey, @@ -95,7 +93,7 @@ func ssh_handle_receive_pack(session glider_ssh.Session, pubkey string, repo_ide repo_path: repo_path, user_id: user_id, repo_id: repo_id, - group_name: group_name, + group_path: group_path, repo_name: repo_name, repo: repo, }) diff --git a/ssh_utils.go b/ssh_utils.go index 0ec2065..d40facf 100644 --- a/ssh_utils.go +++ b/ssh_utils.go @@ -16,7 +16,7 @@ import ( var err_ssh_illegal_endpoint = errors.New("illegal endpoint during SSH access") -func get_repo_path_perms_from_ssh_path_pubkey(ctx context.Context, ssh_path string, ssh_pubkey string) (group_name string, repo_name string, repo_id int, repo_path string, direct_access bool, contrib_requirements string, user_type string, user_id int, err error) { +func get_repo_path_perms_from_ssh_path_pubkey(ctx context.Context, ssh_path string, ssh_pubkey string) (group_path []string, repo_name string, repo_id int, repo_path string, direct_access bool, contrib_requirements string, user_type string, user_id int, err error) { var segments []string var separator_index int var module_type, module_name string @@ -27,12 +27,12 @@ func get_repo_path_perms_from_ssh_path_pubkey(ctx context.Context, ssh_path stri var err error segments[i], err = url.PathUnescape(segment) if err != nil { - return "", "", 0, "", false, "", "", 0, err + return []string{}, "", 0, "", false, "", "", 0, err } } if segments[0] == ":" { - return "", "", 0, "", false, "", "", 0, err_ssh_illegal_endpoint + return []string{}, "", 0, "", false, "", "", 0, err_ssh_illegal_endpoint } separator_index = -1 @@ -48,21 +48,21 @@ func get_repo_path_perms_from_ssh_path_pubkey(ctx context.Context, ssh_path stri switch { case separator_index == -1: - return "", "", 0, "", false, "", "", 0, err_ssh_illegal_endpoint + return []string{}, "", 0, "", false, "", "", 0, err_ssh_illegal_endpoint case len(segments) <= separator_index+2: - return "", "", 0, "", false, "", "", 0, err_ssh_illegal_endpoint + return []string{}, "", 0, "", false, "", "", 0, err_ssh_illegal_endpoint } - group_name = segments[0] + group_path = segments[:separator_index] module_type = segments[separator_index+1] module_name = segments[separator_index+2] repo_name = module_name switch module_type { case "repos": - _1, _2, _3, _4, _5, _6, _7 := get_path_perm_by_group_repo_key(ctx, group_name, module_name, ssh_pubkey) - return group_name, repo_name, _1, _2, _3, _4, _5, _6, _7 + _1, _2, _3, _4, _5, _6, _7 := get_path_perm_by_group_repo_key(ctx, group_path, module_name, ssh_pubkey) + return group_path, repo_name, _1, _2, _3, _4, _5, _6, _7 default: - return "", "", 0, "", false, "", "", 0, err_ssh_illegal_endpoint + return []string{}, "", 0, "", false, "", "", 0, err_ssh_illegal_endpoint } } diff --git a/static/style.css b/static/style.css index 278ea22..5e35cd1 100644 --- a/static/style.css +++ b/static/style.css @@ -336,3 +336,6 @@ header#main-header > div#main-header-user { display: flex; align-items: center; } +table + table { + margin-top: 1rem; +} diff --git a/templates/_group_path.tmpl b/templates/_group_path.tmpl new file mode 100644 index 0000000..f8b5f47 --- /dev/null +++ b/templates/_group_path.tmpl @@ -0,0 +1,8 @@ +{{/* + SPDX-License-Identifier: AGPL-3.0-only + SPDX-FileContributor: Runxi Yu <https://runxiyu.org> +*/}} +{{- define "group_path_plain" -}} +{{ $p := . }} +{{ range $i, $s := . }}{{ $s }}{{ if ne $i (len $p) }}/{{ end }}{{ end }} +{{ end }} diff --git a/templates/_group_view.tmpl b/templates/_group_view.tmpl new file mode 100644 index 0000000..4783162 --- /dev/null +++ b/templates/_group_view.tmpl @@ -0,0 +1,48 @@ +{{/* + SPDX-License-Identifier: AGPL-3.0-only + SPDX-FileContributor: Runxi Yu <https://runxiyu.org> +*/}} +{{- define "group_view" -}} +{{ if .subgroups }} + <table class="wide"> + <thead> + <tr> + <th colspan="2" class="title-row">Subgroups</th> + </tr> + </thead> + <tbody> + {{- range .subgroups }} + <tr> + <td> + <a href="{{ .Name }}/">{{ .Name }}</a> + </td> + <td> + {{ .Description }} + </td> + </tr> + {{- end }} + </tbody> + </table> +{{ end }} +{{ if .repos }} +<table class="wide"> + <thead> + <tr> + <th colspan="2" class="title-row">Repos</th> + </tr> + </thead> + <tbody> + {{- range .repos }} + <tr> + <td> + <a href=":/repos/{{ .Name }}/">{{ .Name }}</a> + </td> + <td> + {{ .Description }} + </td> + </tr> + {{- end }} + </tbody> +</table> +{{ end }} +{{- end -}} diff --git a/templates/group.tmpl b/templates/group.tmpl new file mode 100644 index 0000000..f881458 --- /dev/null +++ b/templates/group.tmpl @@ -0,0 +1,24 @@ +{{/* + SPDX-License-Identifier: AGPL-3.0-only + SPDX-FileContributor: Runxi Yu <https://runxiyu.org> +*/}} +{{- define "group" -}} +{{ $group_path := .group_path }} +<!DOCTYPE html> +<html lang="en"> + <head> + {{ template "head_common" . }} + <title>{{ range $i, $s := .group_path }}{{ $s }}{{ if ne $i (len $group_path) }} / {{ end }}{{ end }} – {{ .global.forge_title }}</title> + </head> + <body class="group"> + {{ template "header" . }} + <div class="padding-wrapper"> + <p>{{ range $i, $s := .group_path }}{{ $s }}{{ if ne $i (len $group_path) }} / {{ end }}{{ end }} + {{ template "group_view" . }} + </div> + <footer> + {{ template "footer" . }} + </footer> + </body> +</html> +{{- end -}} diff --git a/templates/group_repos.tmpl b/templates/group_repos.tmpl deleted file mode 100644 index 3eae4f4..0000000 --- a/templates/group_repos.tmpl +++ /dev/null @@ -1,40 +0,0 @@ -{{/* - SPDX-License-Identifier: AGPL-3.0-only - SPDX-FileContributor: Runxi Yu <https://runxiyu.org> -*/}} -{{- define "group_repos" -}} -<!DOCTYPE html> -<html lang="en"> - <head> - {{ template "head_common" . }} - <title>Repos – {{ .group_name }} – {{ .global.forge_title }}</title> - </head> - <body class="group-repos"> - {{ template "header" . }} - <div class="padding-wrapper"> - <table class="wide"> - <thead> - <tr> - <th colspan="2" class="title-row">Repos in {{ .group_name }}</th> - </tr> - </thead> - <tbody> - {{- range .repos }} - <tr> - <td> - <a href="{{ .Name }}/">{{ .Name }}</a> - </td> - <td> - {{ .Description }} - </td> - </tr> - {{- end }} - </tbody> - </table> - </div> - <footer> - {{ template "footer" . }} - </footer> - </body> -</html> -{{- end -}} diff --git a/templates/index.tmpl b/templates/index.tmpl index c3b8bef..2e039a9 100644 --- a/templates/index.tmpl +++ b/templates/index.tmpl @@ -12,28 +12,25 @@ <body class="index"> {{ template "header" . }} <div class="padding-wrapper"> - <table class="wide"> - <thead> + <table class="wide"> + <thead> + <tr> + <th colspan="2" class="title-row">Groups</th> + </tr> + </thead> + <tbody> + {{- range .groups }} <tr> - <th colspan="2" class="title-row"> - Groups - </th> + <td> + <a href="{{ .Name }}/">{{ .Name }}</a> + </td> + <td> + {{ .Description }} + </td> </tr> - </thead> - <tbody> - {{- range .groups }} - <tr> - <td> - <a href="{{ .Name }}/:/repos/">{{ .Name }}</a> - </td> - <td> - {{ .Description }} - </td> - </tr> - {{- end }} - </tbody> - </table> - </div> + {{- end }} + </tbody> + </table> <div class="padding-wrapper"> <table class="wide"> <thead> diff --git a/templates/repo_commit.tmpl b/templates/repo_commit.tmpl index 1e34604..bdb8c24 100644 --- a/templates/repo_commit.tmpl +++ b/templates/repo_commit.tmpl @@ -7,7 +7,7 @@ <html lang="en"> <head> {{ template "head_common" . }} - <title>Commit {{ .commit_id }} – {{ .repo_name }} – {{ .group_name }} – {{ .global.forge_title }}</title> + <title>Commit {{ .commit_id }} – {{ .repo_name }} – {{ template "group_path_plain" .group_path }} – {{ .global.forge_title }}</title> </head> <body class="repo-commit"> {{ template "header" . }} diff --git a/templates/repo_contrib_index.tmpl b/templates/repo_contrib_index.tmpl index da779f8..512e352 100644 --- a/templates/repo_contrib_index.tmpl +++ b/templates/repo_contrib_index.tmpl @@ -7,7 +7,7 @@ <html lang="en"> <head> {{ template "head_common" . }} - <title>Merge requests – {{ .repo_name }} – {{ .group_name }} – {{ .global.forge_title }}</title> + <title>Merge requests – {{ .repo_name }} – {{ template "group_path_plain" .group_path }} – {{ .global.forge_title }}</title> </head> <body class="repo-contrib-index"> {{ template "header" . }} diff --git a/templates/repo_contrib_one.tmpl b/templates/repo_contrib_one.tmpl index 1083e8c..88bc2fc 100644 --- a/templates/repo_contrib_one.tmpl +++ b/templates/repo_contrib_one.tmpl @@ -7,7 +7,7 @@ <html lang="en"> <head> {{ template "head_common" . }} - <title>Merge requests – {{ .repo_name }} – {{ .group_name }} – {{ .global.forge_title }}</title> + <title>Merge requests – {{ .repo_name }} – {{ template "group_path_plain" .group_path }} – {{ .global.forge_title }}</title> </head> <body class="repo-contrib-one"> {{ template "header" . }} diff --git a/templates/repo_index.tmpl b/templates/repo_index.tmpl index 9dec745..3ce6d33 100644 --- a/templates/repo_index.tmpl +++ b/templates/repo_index.tmpl @@ -7,7 +7,7 @@ <html lang="en"> <head> {{ template "head_common" . }} - <title>{{ .repo_name }} – {{ .group_name }} – {{ .global.forge_title }}</title> + <title>{{ .repo_name }} – {{ template "group_path_plain" .group_path }} – {{ .global.forge_title }}</title> </head> <body class="repo-index"> {{ template "header" . }} diff --git a/templates/repo_log.tmpl b/templates/repo_log.tmpl index 8e33bea..dbe764e 100644 --- a/templates/repo_log.tmpl +++ b/templates/repo_log.tmpl @@ -7,7 +7,7 @@ <html lang="en"> <head> {{ template "head_common" . }} - <title>Log – {{ .repo_name }} – {{ .group_name }} – {{ .global.forge_title }}</title> + <title>Log – {{ .repo_name }} – {{ template "group_path_plain" .group_path }} – {{ .global.forge_title }}</title> </head> <body class="repo-log"> {{ template "header" . }} diff --git a/templates/repo_raw_dir.tmpl b/templates/repo_raw_dir.tmpl index 47ef69f..07f577a 100644 --- a/templates/repo_raw_dir.tmpl +++ b/templates/repo_raw_dir.tmpl @@ -7,7 +7,7 @@ <html lang="en"> <head> {{ template "head_common" . }} - <title>/{{ .path_spec }}{{ if ne .path_spec "" }}/{{ end }} – {{ .repo_name }} – {{ .group_name }} – {{ .global.forge_title }}</title> + <title>/{{ .path_spec }}{{ if ne .path_spec "" }}/{{ end }} – {{ .repo_name }} – {{ template "group_path_plain" .group_path }} – {{ .global.forge_title }}</title> </head> <body class="repo-raw-dir"> {{ template "header" . }} diff --git a/templates/repo_tree_dir.tmpl b/templates/repo_tree_dir.tmpl index e520225..ab9788b 100644 --- a/templates/repo_tree_dir.tmpl +++ b/templates/repo_tree_dir.tmpl @@ -7,7 +7,7 @@ <html lang="en"> <head> {{ template "head_common" . }} - <title>/{{ .path_spec }}{{ if ne .path_spec "" }}/{{ end }} – {{ .repo_name }} – {{ .group_name }} – {{ .global.forge_title }}</title> + <title>/{{ .path_spec }}{{ if ne .path_spec "" }}/{{ end }} – {{ .repo_name }} – {{ template "group_path_plain" .group_path }} – {{ .global.forge_title }}</title> </head> <body class="repo-tree-dir"> {{ template "header" . }} diff --git a/templates/repo_tree_file.tmpl b/templates/repo_tree_file.tmpl index d6890b8..333e1dc 100644 --- a/templates/repo_tree_file.tmpl +++ b/templates/repo_tree_file.tmpl @@ -8,13 +8,13 @@ <head> {{ template "head_common" . }} <link rel="stylesheet" href="/:/static/chroma.css" /> - <title>/{{ .path_spec }} – {{ .repo_name }} – {{ .group_name }} – {{ .global.forge_title }}</title> + <title>/{{ .path_spec }} – {{ .repo_name }} – {{ template "group_path_plain" .group_path }} – {{ .global.forge_title }}</title> </head> <body class="repo-tree-file"> {{ template "header" . }} <div class="padding"> <p> - /{{ .path_spec }} (<a href="/{{ .group_name }}/:/repos/{{ .repo_name }}/raw/{{ .path_spec }}{{ if .ref_type }}?{{ .ref_type }}={{ .ref_name }}{{ end }}">raw</a>) + /{{ .path_spec }} (<a href="/{{ template "group_path_plain" .group_path }}/:/repos/{{ .repo_name }}/raw/{{ .path_spec }}{{ if .ref_type }}?{{ .ref_type }}={{ .ref_name }}{{ end }}">raw</a>) </p> {{ .file_contents }} </div> @@ -98,3 +98,10 @@ func redirect_without_slash(w http.ResponseWriter, r *http.Request) bool { } return false } + +func path_escape_cat_segments(segments []string) string { + for i, segment := range segments { + segments[i] = url.PathEscape(segment) + } + return strings.Join(segments, "/") +} |