aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRunxi Yu <me@runxiyu.org>2025-03-06 15:17:57 +0800
committerRunxi Yu <me@runxiyu.org>2025-03-06 20:07:48 +0800
commit8ed0dbe4201a58b00d6f3743178f4cbe5328e2b0 (patch)
treea2f33fccac42b554b1176741d00c1d0cd2d0dceb
parentsql: Add purge and test scripts (diff)
downloadforge-8ed0dbe4201a58b00d6f3743178f4cbe5328e2b0.tar.gz
forge-8ed0dbe4201a58b00d6f3743178f4cbe5328e2b0.tar.zst
forge-8ed0dbe4201a58b00d6f3743178f4cbe5328e2b0.zip
*: Support subgroups via SQL recursion
Diffstat (limited to '')
-rw-r--r--acl.go69
-rw-r--r--git_hooks_handle.go2
-rw-r--r--git_misc.go43
-rw-r--r--http_handle_group_index.go118
-rw-r--r--http_handle_index.go2
-rw-r--r--http_handle_repo_index.go9
-rw-r--r--http_handle_repo_info.go42
-rw-r--r--http_handle_repo_upload_pack.go44
-rw-r--r--http_server.go42
-rw-r--r--remote_url.go8
-rw-r--r--ssh_handle_receive_pack.go8
-rw-r--r--ssh_utils.go18
-rw-r--r--static/style.css3
-rw-r--r--templates/_group_path.tmpl8
-rw-r--r--templates/_group_view.tmpl48
-rw-r--r--templates/group.tmpl24
-rw-r--r--templates/group_repos.tmpl40
-rw-r--r--templates/index.tmpl37
-rw-r--r--templates/repo_commit.tmpl2
-rw-r--r--templates/repo_contrib_index.tmpl2
-rw-r--r--templates/repo_contrib_one.tmpl2
-rw-r--r--templates/repo_index.tmpl2
-rw-r--r--templates/repo_log.tmpl2
-rw-r--r--templates/repo_raw_dir.tmpl2
-rw-r--r--templates/repo_tree_dir.tmpl2
-rw-r--r--templates/repo_tree_file.tmpl4
-rw-r--r--url.go7
27 files changed, 427 insertions, 163 deletions
diff --git a/acl.go b/acl.go
index a629da6..597760a 100644
--- a/acl.go
+++ b/acl.go
@@ -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 }} &ndash; {{ .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 &ndash; {{ .group_name }} &ndash; {{ .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 }} &ndash; {{ .repo_name }} &ndash; {{ .group_name }} &ndash; {{ .global.forge_title }}</title>
+ <title>Commit {{ .commit_id }} &ndash; {{ .repo_name }} &ndash; {{ template "group_path_plain" .group_path }} &ndash; {{ .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 &ndash; {{ .repo_name }} &ndash; {{ .group_name }} &ndash; {{ .global.forge_title }}</title>
+ <title>Merge requests &ndash; {{ .repo_name }} &ndash; {{ template "group_path_plain" .group_path }} &ndash; {{ .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 &ndash; {{ .repo_name }} &ndash; {{ .group_name }} &ndash; {{ .global.forge_title }}</title>
+ <title>Merge requests &ndash; {{ .repo_name }} &ndash; {{ template "group_path_plain" .group_path }} &ndash; {{ .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 }} &ndash; {{ .group_name }} &ndash; {{ .global.forge_title }}</title>
+ <title>{{ .repo_name }} &ndash; {{ template "group_path_plain" .group_path }} &ndash; {{ .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 &ndash; {{ .repo_name }} &ndash; {{ .group_name }} &ndash; {{ .global.forge_title }}</title>
+ <title>Log &ndash; {{ .repo_name }} &ndash; {{ template "group_path_plain" .group_path }} &ndash; {{ .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 }} &ndash; {{ .repo_name }} &ndash; {{ .group_name }} &ndash; {{ .global.forge_title }}</title>
+ <title>/{{ .path_spec }}{{ if ne .path_spec "" }}/{{ end }} &ndash; {{ .repo_name }} &ndash; {{ template "group_path_plain" .group_path }} &ndash; {{ .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 }} &ndash; {{ .repo_name }} &ndash; {{ .group_name }} &ndash; {{ .global.forge_title }}</title>
+ <title>/{{ .path_spec }}{{ if ne .path_spec "" }}/{{ end }} &ndash; {{ .repo_name }} &ndash; {{ template "group_path_plain" .group_path }} &ndash; {{ .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 }} &ndash; {{ .repo_name }} &ndash; {{ .group_name }} &ndash; {{ .global.forge_title }}</title>
+ <title>/{{ .path_spec }} &ndash; {{ .repo_name }} &ndash; {{ template "group_path_plain" .group_path }} &ndash; {{ .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>
diff --git a/url.go b/url.go
index d391de2..8be7047 100644
--- a/url.go
+++ b/url.go
@@ -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, "/")
+}