open #1 temp
@@ -1,15 +0,0 @@ -CREATE TABLE IF NOT EXISTS repo_mirrors ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - repo_id UUID NOT NULL UNIQUE REFERENCES repositories(id) ON DELETE CASCADE, - remote_url TEXT NOT NULL, - enabled BOOLEAN NOT NULL DEFAULT TRUE, - last_synced_at TIMESTAMPTZ, - last_error TEXT, - created_by UUID REFERENCES users(id) ON DELETE SET NULL, - created_at TIMESTAMPTZ NOT NULL DEFAULT now(), - updated_at TIMESTAMPTZ NOT NULL DEFAULT now(), - CONSTRAINT repo_mirrors_remote_url_nonempty CHECK (char_length(trim(remote_url)) > 0) -); -CREATE INDEX IF NOT EXISTS repo_mirrors_enabled_idx - ON repo_mirrors(enabled) WHERE enabled = TRUE;
@@ -1,18 +0,0 @@ -CREATE TABLE IF NOT EXISTS audit_log ( - id BIGSERIAL PRIMARY KEY, - user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, - actor_id UUID REFERENCES users(id) ON DELETE SET NULL, - action TEXT NOT NULL, - ip TEXT, - user_agent TEXT, - metadata JSONB NOT NULL DEFAULT '{}'::jsonb, - created_at TIMESTAMPTZ NOT NULL DEFAULT now() -); - -CREATE INDEX IF NOT EXISTS audit_log_user_id_created_at_idx - ON audit_log(user_id, created_at DESC); -CREATE INDEX IF NOT EXISTS audit_log_created_at_idx - ON audit_log(created_at DESC); -CREATE INDEX IF NOT EXISTS audit_log_action_idx - ON audit_log(action);
@@ -1,10 +0,0 @@ - -ALTER TABLE users - ADD COLUMN IF NOT EXISTS theme TEXT NOT NULL DEFAULT 'system'; - -ALTER TABLE users - DROP CONSTRAINT IF EXISTS users_theme_check; - -ALTER TABLE users - ADD CONSTRAINT users_theme_check CHECK (theme IN ('light', 'dark', 'system'));
@@ -1,14 +0,0 @@ -CREATE TABLE IF NOT EXISTS deploy_keys ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - repo_id UUID NOT NULL REFERENCES repositories(id) ON DELETE CASCADE, - name TEXT NOT NULL DEFAULT 'deploy', - public_key TEXT NOT NULL, - fingerprint TEXT NOT NULL UNIQUE, - read_only BOOLEAN NOT NULL DEFAULT TRUE, - created_by UUID REFERENCES users(id) ON DELETE SET NULL, - created_at TIMESTAMPTZ NOT NULL DEFAULT now() -); - -CREATE INDEX IF NOT EXISTS deploy_keys_repo_id_idx ON deploy_keys(repo_id); -CREATE INDEX IF NOT EXISTS deploy_keys_fingerprint_idx ON deploy_keys(fingerprint);
@@ -1,27 +0,0 @@ - -CREATE TABLE webhooks ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - repo_id UUID NOT NULL REFERENCES repositories(id) ON DELETE CASCADE, - url TEXT NOT NULL, - secret TEXT NOT NULL DEFAULT '', - events TEXT[] NOT NULL DEFAULT '{}', - active BOOLEAN NOT NULL DEFAULT TRUE, - created_at TIMESTAMPTZ NOT NULL DEFAULT now(), - CONSTRAINT webhooks_url_http CHECK (url ~* '^https?://') -); -CREATE INDEX webhooks_repo_id_idx ON webhooks(repo_id); - -CREATE TABLE webhook_deliveries ( - id BIGSERIAL PRIMARY KEY, - webhook_id UUID NOT NULL REFERENCES webhooks(id) ON DELETE CASCADE, - event TEXT NOT NULL, - action TEXT NOT NULL DEFAULT '', - success BOOLEAN NOT NULL DEFAULT FALSE, - status_code INT, - error TEXT, - duration_ms INT, - created_at TIMESTAMPTZ NOT NULL DEFAULT now() -); -CREATE INDEX webhook_deliveries_webhook_id_idx ON webhook_deliveries(webhook_id); -CREATE INDEX webhook_deliveries_created_at_idx ON webhook_deliveries(created_at DESC);
@@ -1,20 +0,0 @@ - -CREATE TABLE IF NOT EXISTS notifications ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, - kind TEXT NOT NULL, - title TEXT NOT NULL, - body TEXT NOT NULL DEFAULT '', - href TEXT NOT NULL DEFAULT '', - repo_id UUID REFERENCES repositories(id) ON DELETE SET NULL, - read_at TIMESTAMPTZ, - created_at TIMESTAMPTZ NOT NULL DEFAULT now() -); - -CREATE INDEX IF NOT EXISTS notifications_user_id_created_at_idx - ON notifications (user_id, created_at DESC); - -CREATE INDEX IF NOT EXISTS notifications_user_id_unread_idx - ON notifications (user_id) - WHERE read_at IS NULL;
@@ -1,55 +0,0 @@ - -DROP TABLE IF EXISTS issue_labels; - -CREATE TABLE labels ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - repo_id UUID NOT NULL REFERENCES repositories(id) ON DELETE CASCADE, - name TEXT NOT NULL, - color TEXT NOT NULL DEFAULT '0969da', - description TEXT NOT NULL DEFAULT '', - created_at TIMESTAMPTZ NOT NULL DEFAULT now(), - UNIQUE (repo_id, name), - CONSTRAINT labels_name_len CHECK (char_length(name) BETWEEN 1 AND 50), - CONSTRAINT labels_color_format CHECK (color ~ '^[0-9a-fA-F]{6}$') -); -CREATE INDEX labels_repo_id_idx ON labels(repo_id); - -CREATE TABLE milestones ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - repo_id UUID NOT NULL REFERENCES repositories(id) ON DELETE CASCADE, - title TEXT NOT NULL, - description TEXT NOT NULL DEFAULT '', - due_on DATE, - state TEXT NOT NULL DEFAULT 'open' CHECK (state IN ('open', 'closed')), - created_at TIMESTAMPTZ NOT NULL DEFAULT now(), - updated_at TIMESTAMPTZ NOT NULL DEFAULT now(), - closed_at TIMESTAMPTZ, - UNIQUE (repo_id, title), - CONSTRAINT milestones_title_len CHECK (char_length(title) BETWEEN 1 AND 120) -); -CREATE INDEX milestones_repo_id_idx ON milestones(repo_id); -CREATE INDEX milestones_repo_state_idx ON milestones(repo_id, state); - -ALTER TABLE issues - ADD COLUMN IF NOT EXISTS milestone_id UUID REFERENCES milestones(id) ON DELETE SET NULL; -CREATE INDEX IF NOT EXISTS issues_milestone_id_idx ON issues(milestone_id); - -ALTER TABLE pull_requests - ADD COLUMN IF NOT EXISTS milestone_id UUID REFERENCES milestones(id) ON DELETE SET NULL; -CREATE INDEX IF NOT EXISTS pull_requests_milestone_id_idx ON pull_requests(milestone_id); - -CREATE TABLE issue_labels ( - issue_id UUID NOT NULL REFERENCES issues(id) ON DELETE CASCADE,
Large diffs are not rendered by default. Showing the first 50 of 61 lines. Show full diff
@@ -1,9 +0,0 @@ -CREATE TABLE IF NOT EXISTS pull_reviews ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - pull_id UUID NOT NULL REFERENCES pull_requests(id) ON DELETE CASCADE, - reviewer_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, - state TEXT NOT NULL CHECK (state IN ('approved','changes_requested','commented')), - body TEXT NOT NULL DEFAULT '', - created_at TIMESTAMPTZ NOT NULL DEFAULT now() -); -CREATE INDEX IF NOT EXISTS pull_reviews_pull_id_idx ON pull_reviews(pull_id);
@@ -1,98 +0,0 @@ -//! Repo-scoped SSH deploy keys (read-only or read-write). - -use anyhow::Result; -use chrono::{DateTime, Utc}; -use serde::Serialize; -use sqlx::{FromRow, PgPool}; -use uuid::Uuid; - -#[derive(Debug, Clone, FromRow, Serialize)] -pub struct DeployKey { - pub id: Uuid, - pub repo_id: Uuid, - pub name: String, - pub public_key: String, - pub fingerprint: String, - pub read_only: bool, - pub created_by: Option<Uuid>, - pub created_at: DateTime<Utc>, -} - -impl DeployKey { - pub fn permission_label(&self) -> &'static str { - if self.read_only { - "Read-only" - } else { - "Read-write" - } - } -} - -pub async fn add_deploy_key( - pool: &PgPool, - repo_id: Uuid, - name: &str, - public_key: &str, - fingerprint: &str, - read_only: bool, - created_by: Option<Uuid>, -) -> Result<DeployKey> { - let clash: Option<(Uuid,)> = sqlx::query_as( - "SELECT id FROM ssh_keys WHERE rtrim(fingerprint, '=') = rtrim($1, '=') LIMIT 1", - ) - .bind(fingerprint) - .fetch_optional(pool)
Large diffs are not rendered by default. Showing the first 50 of 105 lines. Show full diff
@@ -1,9 +1,6 @@ -pub mod deploy_keys; pub mod models; pub mod queries; -pub use deploy_keys::DeployKey; - use anyhow::{Context, Result}; use sqlx::postgres::PgPoolOptions; use sqlx::PgPool;
@@ -17,21 +17,10 @@ pub struct User { pub is_suspended: bool, pub show_email: bool, pub vigilant_mode: bool, - /// `light`, `dark`, or `system` (follows prefers-color-scheme). - pub theme: String, pub created_at: DateTime<Utc>, pub updated_at: DateTime<Utc>, } -impl User { - pub fn theme_pref(&self) -> &str { - match self.theme.as_str() { - "light" | "dark" => self.theme.as_str(), - _ => "system", - } - } -} - #[derive(Debug, Clone, FromRow)] pub struct UserMfa { pub user_id: Uuid, @@ -112,19 +101,6 @@ pub struct BranchRule { pub created_at: DateTime<Utc>, } -#[derive(Debug, Clone, FromRow, Serialize)] -pub struct RepoMirror { - pub id: Uuid, - pub repo_id: Uuid, - pub remote_url: String, - pub enabled: bool, - pub last_synced_at: Option<DateTime<Utc>>, - pub last_error: Option<String>, - pub created_by: Option<Uuid>, - pub created_at: DateTime<Utc>, - pub updated_at: DateTime<Utc>, -} - #[derive(Debug, Clone, FromRow)] pub struct CommentReaction { pub comment_id: Uuid, @@ -182,19 +158,6 @@ pub struct ActivityEvent { pub created_at: DateTime<Utc>, }
Large diffs are not rendered by default. Showing the first 50 of 195 lines. Show full diff
@@ -1,7 +1,7 @@ use super::models::*; use anyhow::Result; use chrono::{DateTime, NaiveDate, Utc}; -use sqlx::{FromRow, PgPool}; +use sqlx::PgPool; use uuid::Uuid; pub async fn upsert_user_from_oidc( @@ -1326,29 +1326,6 @@ pub async fn user_owns_email(pool: &PgPool, user_id: Uuid, email: &str) -> Resul Ok(row.is_some()) } -/// Resolve a kitgit username from a commit author email (primary or secondary). -pub async fn username_by_email(pool: &PgPool, email: &str) -> Result<Option<String>> { - let email = email.trim().to_ascii_lowercase(); - if email.is_empty() { - return Ok(None); - } - let row: Option<(String,)> = sqlx::query_as( - r#" - SELECT u.username FROM users u - WHERE lower(u.email) = $1 - UNION - SELECT u.username FROM users u - JOIN user_emails e ON e.user_id = u.id - WHERE lower(e.email) = $1 - LIMIT 1 - "#, - ) - .bind(&email) - .fetch_optional(pool) - .await?; - Ok(row.map(|(u,)| u)) -} - pub async fn list_all_gpg_keys(pool: &PgPool) -> Result<Vec<GpgKey>> { Ok(sqlx::query_as::<_, GpgKey>("SELECT * FROM gpg_keys ORDER BY created_at DESC") .fetch_all(pool) @@ -1522,50 +1499,22 @@ pub async fn create_issue( } pub async fn list_issues(pool: &PgPool, repo_id: Uuid, state: Option<&str>) -> Result<Vec<Issue>> { - list_issues_filtered(pool, repo_id, state, None, None).await -} -
Large diffs are not rendered by default. Showing the first 50 of 1124 lines. Show full diff
@@ -1,134 +0,0 @@ -//! Blame and path history helpers (git2). - -use anyhow::{anyhow, Result}; -use git2::{BlameOptions, Commit, Oid, Repository as G2Repo, Sort}; -use std::collections::HashMap; -use std::path::Path; - -use super::repo::{read_blob, resolve_ref, CommitInfo}; - -/// One displayed line in a blame view. -#[derive(Debug, Clone)] -pub struct BlameLine { - pub line_no: usize, - pub content: String, - pub commit_id: String, - pub short_id: String, - pub author: String, - pub time: i64, - pub summary: String, - /// First line of a contiguous blame hunk (same final commit). - pub hunk_start: bool, -} - -/// Commits that introduced a change to `path` (blob OID differs from parent), newest first. -pub fn list_commits_for_path( - repo: &G2Repo, - reference: &str, - path: &str, - limit: usize, -) -> Result<Vec<CommitInfo>> { - let oid = resolve_ref(repo, reference)?; - let mut revwalk = repo.revwalk()?; - revwalk.push(oid)?; - revwalk.set_sorting(Sort::TIME)?; - let mut out = Vec::new(); - for id in revwalk { - let id = id?; - let c = repo.find_commit(id)?; - if !path_changed_in_commit(&c, path)? { - continue; - } - let author = c.author(); - out.push(CommitInfo { - id: id.to_string(),
Large diffs are not rendered by default. Showing the first 50 of 140 lines. Show full diff
@@ -166,18 +166,6 @@ async fn post_push_hooks( .bind(repo.id) .execute(&state.pool) .await; - crate::webhooks::spawn_dispatch( - state.pool.clone(), - crate::webhooks::EVENT_PUSH, - "push".into(), - repo.clone(), - owner.to_string(), - Some(user.clone()), - serde_json::json!({ - "ref": format!("refs/heads/{}", repo.default_branch), - "default_branch": repo.default_branch, - }), - ); Ok(()) }
@@ -1,4 +1,3 @@ -pub mod blame; pub mod http; pub mod languages; pub mod lfs; @@ -6,6 +5,5 @@ pub mod repo; pub mod ssh; pub mod verify; -pub use blame::{blame_file, list_commits_for_path}; pub use repo::*; pub use verify::{extract_commit_signature, verify_commit_signature};
@@ -79,83 +79,6 @@ pub fn rename_branch(repo: &G2Repo, old: &str, new: &str) -> Result<()> { Ok(()) } -/// Whether `url` is an allowed remote for pull-mirroring (no local/file paths). -pub fn is_safe_mirror_url(url: &str) -> bool { - let url = url.trim(); - if url.is_empty() || url.contains('\0') || url.contains('\n') || url.contains('\r') { - return false; - } - let lower = url.to_ascii_lowercase(); - if lower.starts_with("file:") - || lower.starts_with('/') - || lower.starts_with("\\\\") - || (url.len() >= 2 && url.as_bytes()[1] == b':') - { - return false; - } - lower.starts_with("https://") - || lower.starts_with("http://") - || lower.starts_with("git://") - || lower.starts_with("ssh://") - || (url.contains('@') && url.contains(':') && !lower.contains("://")) -} - -/// Fetch refs from `remote_url` into the existing bare repo (pull mirror). -pub fn mirror_fetch(repos_root: &Path, owner: &str, name: &str, remote_url: &str) -> Result<()> { - if !is_safe_mirror_url(remote_url) { - return Err(anyhow!("unsupported or unsafe mirror URL")); - } - let path = bare_path(repos_root, owner, name); - if !path.exists() { - return Err(anyhow!("local bare repository missing")); - } - let path_str = path - .to_str() - .ok_or_else(|| anyhow!("repository path is not valid UTF-8"))?; - let url = remote_url.trim(); - - // Dedicated remote so we do not clobber a user's `origin`. - let _ = std::process::Command::new("git") - .args(["-C", path_str, "remote", "remove", "kitgit-mirror"]) - .output(); - let add = std::process::Command::new("git") - .args(["-C", path_str, "remote", "add", "kitgit-mirror", url]) - .output()
Large diffs are not rendered by default. Showing the first 50 of 102 lines. Show full diff
@@ -101,46 +101,28 @@ impl Server for SshServer { state: self.state.clone(), user_id: None, username: None, - deploy_key: None, stdin: None, receive_meta: None, } } } -/// Authenticated via a repo-scoped deploy key (not a user key). -struct DeployKeyAuth { - repo_id: Uuid, - read_only: bool, - created_by: Option<Uuid>, - name: String, -} - struct SshHandler { state: AppState, user_id: Option<Uuid>, username: Option<String>, - deploy_key: Option<DeployKeyAuth>, stdin: Option<Arc<Mutex<tokio::process::ChildStdin>>>, receive_meta: Option<(String, String)>, } impl SshHandler { - fn is_authenticated(&self) -> bool { - self.user_id.is_some() || self.deploy_key.is_some() - } - /// Print `static/text.txt` (with `{user}` substituted) and close — no shell access. async fn greet_and_quit( &self, channel: ChannelId, session: &mut Session, ) -> Result<(), anyhow::Error> { - let username = self - .username - .as_deref() - .or_else(|| self.deploy_key.as_ref().map(|d| d.name.as_str())) - .unwrap_or("git"); + let username = self.username.as_deref().unwrap_or("git"); let path = self.state.config.static_dir.join("text.txt");
Large diffs are not rendered by default. Showing the first 50 of 217 lines. Show full diff
@@ -8,7 +8,6 @@ mod mfa; mod og; mod state; mod web; -mod webhooks; use crate::auth::AuthState; use crate::config::Config;
@@ -4,74 +4,16 @@ pub mod templates; use crate::git; use crate::state::AppState; -use axum::body::{Body, to_bytes}; use axum::extract::{DefaultBodyLimit, Request}; -use axum::http::{header, HeaderValue, StatusCode}; +use axum::http::{header, StatusCode}; use axum::middleware::{from_fn, Next}; use axum::response::{IntoResponse, Response}; use axum::routing::{get, post}; use axum::Router; -use std::time::{Duration, Instant}; use tower_http::limit::RequestBodyLimitLayer; use tower_http::services::ServeDir; -const RENDER_MS_MARKER: &[u8] = b"<!--kg-render-ms-->"; - -fn format_render_duration(elapsed: Duration) -> String { - let ms = elapsed.as_secs_f64() * 1000.0; - if ms < 10.0 { - format!("{ms:.1}ms") - } else { - format!("{:.0}ms", ms.round()) - } -} - -fn inject_render_marker(html: &[u8], label: &str) -> Option<Vec<u8>> { - let pos = html - .windows(RENDER_MS_MARKER.len()) - .position(|window| window == RENDER_MS_MARKER)?; - let mut out = Vec::with_capacity(html.len() - RENDER_MS_MARKER.len() + label.len()); - out.extend_from_slice(&html[..pos]); - out.extend_from_slice(label.as_bytes()); - out.extend_from_slice(&html[pos + RENDER_MS_MARKER.len()..]); - Some(out) -} - -/// Measure HTML page handler time and fill the layout footer timing marker. -async fn inject_render_timing(req: Request, next: Next) -> Response { - let start = Instant::now(); - let res = next.run(req).await; - let elapsed = start.elapsed(); - - let is_html = res - .headers()
Large diffs are not rendered by default. Showing the first 50 of 315 lines. Show full diff
@@ -20,7 +20,7 @@ use serde::Deserialize; use std::path::PathBuf; use uuid::Uuid; -// ── helpers ────────────────────────────────────────────────────────────────── +// ── helpers ────────────────────────────────────────────────────────────────── pub type AppResult<T> = Result<T, AppError>; @@ -113,95 +113,6 @@ fn redirect_with_cookies(to: &str, cookies: Vec<HeaderValue>) -> Response { res } -/// Best-effort client IP / User-Agent from reverse-proxy headers. -pub fn request_client_meta(headers: &HeaderMap) -> (Option<String>, Option<String>) { - let ua = headers - .get(axum::http::header::USER_AGENT) - .and_then(|v| v.to_str().ok()) - .map(|s| s.chars().take(512).collect::<String>()); - let ip = headers - .get("x-forwarded-for") - .and_then(|v| v.to_str().ok()) - .and_then(|s| s.split(',').next()) - .map(|s| s.trim().to_string()) - .filter(|s| !s.is_empty()) - .or_else(|| { - headers - .get("x-real-ip") - .and_then(|v| v.to_str().ok()) - .map(|s| s.trim().to_string()) - .filter(|s| !s.is_empty()) - }); - (ip, ua) -} - -/// Write an audit_log row; failures are logged and never fail the request. -pub async fn record_audit( - state: &AppState, - headers: &HeaderMap, - user_id: Uuid, - actor_id: Option<Uuid>, - action: &str, - metadata: serde_json::Value, -) { - let (ip, ua) = request_client_meta(headers); - if let Err(e) = queries::record_audit_log(
Large diffs are not rendered by default. Showing the first 50 of 2112 lines. Show full diff
@@ -1,15 +1,14 @@ //! Social, account, branch, raw, and admin helpers. use super::routes::{ - audit_entries_view, avatar_url_for, clone_urls, load_repo_context, record_audit, - redirect_see_other, require_login, AppError, AppResult, + avatar_url_for, load_repo_context, redirect_see_other, require_login, AppError, AppResult, }; use crate::auth::{self, clear_session_cookie, hash_token, token_from_headers}; use crate::db::queries; use crate::git; use crate::state::AppState; use crate::web::templates::*; -use axum::extract::{Form, Path, Query, State}; +use axum::extract::{Form, Path, State}; use axum::http::header::{CONTENT_DISPOSITION, CONTENT_TYPE, SET_COOKIE}; use axum::http::{HeaderMap, HeaderValue, StatusCode}; use axum::response::{IntoResponse, Response}; @@ -234,14 +233,13 @@ pub async fn branch_rule_delete( #[derive(Deserialize)] pub struct RenameBranchForm { - pub branch: String, pub new_name: String, } pub async fn branch_rename( State(state): State<AppState>, headers: HeaderMap, - Path((owner, repo)): Path<(String, String)>, + Path((owner, repo, branch)): Path<(String, String, String)>, Form(form): Form<RenameBranchForm>, ) -> AppResult<Response> { let (repository, _, viewer, access) = @@ -250,38 +248,22 @@ pub async fn branch_rename( if !access.can_write() { return Err(AppError::forbidden()); } - let branch = form.branch.trim(); - if branch.is_empty() { - return Err(AppError::bad("branch required")); - } if branch == repository.default_branch { return Err(AppError::bad("cannot rename default branch here")); } let new_name = form.new_name.trim();
Large diffs are not rendered by default. Showing the first 50 of 818 lines. Show full diff
@@ -1,9 +1,8 @@ use crate::og::SocialMeta; use crate::db::models::{ - Access, BranchRule, CommitDay, GpgKey, Issue, Label, Milestone, Notification, PullRequest, - Release, RepoMirror, Repository, SshKey, User, UserEmail, + Access, BranchRule, CommitDay, GpgKey, Issue, PullRequest, Release, Repository, SshKey, User, + UserEmail, }; -use crate::db::DeployKey; use askama::Template; use askama_web::WebTemplate; use chrono::{DateTime, Utc}; @@ -41,34 +40,6 @@ pub struct CommentView { pub reactions: Vec<ReactionView>, } -pub struct PullReviewView { - pub id: Uuid, - pub reviewer: User, - pub avatar_url: String, - pub state: String, - pub body: String, - pub body_html: String, - pub created_at: DateTime<Utc>, -} - -impl PullReviewView { - pub fn state_label(&self) -> &'static str { - match self.state.as_str() { - "approved" => "approved", - "changes_requested" => "changes requested", - _ => "commented", - } - } - - pub fn state_class(&self) -> &'static str { - match self.state.as_str() { - "approved" => "kg-badge--approved", - "changes_requested" => "kg-badge--changes", - _ => "kg-badge--commented", - } - } -} - pub struct DiffFileView { pub path: String,
Large diffs are not rendered by default. Showing the first 50 of 410 lines. Show full diff
@@ -1,189 +0,0 @@ -//! Repository webhook delivery (JSON POST + optional HMAC-SHA256). - -use crate::db::models::{Repository, User, Webhook}; -use crate::db::queries; -use anyhow::Result; -use hmac::{Hmac, Mac}; -use serde_json::{json, Value}; -use sha2::Sha256; -use sqlx::PgPool; -use std::time::Instant; -use uuid::Uuid; - -type HmacSha256 = Hmac<Sha256>; - -pub const EVENT_PUSH: &str = "push"; -pub const EVENT_ISSUES: &str = "issues"; -pub const EVENT_PULL_REQUEST: &str = "pull_request"; -pub const EVENT_RELEASE: &str = "release"; - -pub const ALL_EVENTS: &[&str] = &[EVENT_PUSH, EVENT_ISSUES, EVENT_PULL_REQUEST, EVENT_RELEASE]; - -pub fn normalize_events(raw: &[String]) -> Vec<String> { - let mut out = Vec::new(); - for e in raw { - let e = e.trim(); - if ALL_EVENTS.contains(&e) && !out.iter().any(|x| x == e) { - out.push(e.to_string()); - } - } - out -} - -fn sign_body(secret: &str, body: &[u8]) -> Option<String> { - if secret.is_empty() { - return None; - } - let mut mac = HmacSha256::new_from_slice(secret.as_bytes()).ok()?; - mac.update(body); - Some(format!( - "sha256={}", - hex::encode(mac.finalize().into_bytes()) - )) -} -
Large diffs are not rendered by default. Showing the first 50 of 196 lines. Show full diff
@@ -1,8 +1,7 @@ /* kitgit — brand theme ink on paper. no chrome. */ -:root, -[data-theme="light"] { +:root { --kg-bg: #ffffff; --kg-bg-raised: #f7f7f7; --kg-fg: #0a0a0a; @@ -35,7 +34,6 @@ color-scheme: light; } -[data-theme="dark"], html.kg-dark { --kg-bg: #0a0a0a; --kg-bg-raised: #141414; @@ -45,7 +43,6 @@ html.kg-dark { --kg-line: #262626; --kg-line-strong: #f5f5f5; --kg-link: #f5f5f5; - --kg-focus: #f5f5f5; --kg-danger: #ff4d5a; --kg-danger-bg: #2a1214; --kg-invert-bg: #f5f5f5; @@ -54,26 +51,6 @@ html.kg-dark { color-scheme: dark; } -@media (prefers-color-scheme: dark) { - [data-theme="system"] { - --kg-bg: #0a0a0a; - --kg-bg-raised: #141414; - --kg-fg: #f5f5f5; - --kg-muted: #8a8a8a; - --kg-faint: #5c5c5c; - --kg-line: #262626; - --kg-line-strong: #f5f5f5; - --kg-link: #f5f5f5; - --kg-focus: #f5f5f5; - --kg-danger: #ff4d5a; - --kg-danger-bg: #2a1214; - --kg-invert-bg: #f5f5f5; - --kg-invert-fg: #0a0a0a; - --kg-invert-muted: #525252;
Large diffs are not rendered by default. Showing the first 50 of 759 lines. Show full diff
@@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10.268 21a2 2 0 0 0 3.464 0"/><path d="M3.262 15.326A1 1 0 0 0 4 17h16a1 1 0 0 0 .74-1.673C19.41 13.956 18 12.499 18 8A6 6 0 0 0 6 8c0 4.499-1.411 5.956-2.738 7.326"/></svg>
@@ -20,28 +20,6 @@ {% endif %} <div class="kg-settings-section" style="margin-top:1rem;border-top:0;padding-top:0"> - <span class="kg-kicker">appearance</span> - <p class="kg-muted">Ink on paper by day, charcoal by night — or follow your system.</p> - <form class="kg-form" method="post" action="/settings/account/theme"> - <label class="row"> - <input type="radio" name="theme" value="light"{% if user.theme == "light" %} checked{% endif %} /> - light - </label> - <label class="row"> - <input type="radio" name="theme" value="dark"{% if user.theme == "dark" %} checked{% endif %} /> - dark - </label> - <label class="row"> - <input type="radio" name="theme" value="system"{% if user.theme != "light" && user.theme != "dark" %} checked{% endif %} /> - system - </label> - <div class="kg-actions"> - <button class="kg-btn" type="submit">save theme</button> - </div> - </form> - </div> - - <div class="kg-settings-section"> <span class="kg-kicker">username</span> <form class="kg-form" method="post" action="/settings/account/username"> <label> @@ -97,16 +75,11 @@ {% if e.is_primary %}<span class="kg-badge">primary</span>{% endif %} {% if e.verified %}<span class="kg-badge">verified</span>{% endif %} </span> - <span class="kg-actions"> - {% if !e.is_primary %} - <form method="post" action="/settings/account/emails/{{ e.id }}/primary"> - <button class="kg-btn kg-btn--ghost" type="submit">make primary</button> - </form> - <form method="post" action="/settings/account/emails/{{ e.id }}/delete"> - <button class="kg-btn kg-btn--danger-ghost" type="submit">remove</button> - </form> - {% endif %} - </span> + {% if !e.is_primary %} + <form method="post" action="/settings/account/emails/{{ e.id }}/delete"> + <button class="kg-btn kg-btn--danger-ghost" type="submit">remove</button>
Large diffs are not rendered by default. Showing the first 50 of 101 lines. Show full diff
@@ -104,7 +104,6 @@ </span> </div> <div class="kg-admin-actions"> - <a class="kg-btn kg-btn--ghost" href="/admin/users/{{ u.username }}/audit">audit</a> <form method="post" action="/admin/users/suspend" style="display:inline"> <input type="hidden" name="user_id" value="{{ u.id }}" /> {% if u.is_suspended %}
@@ -1,37 +0,0 @@ -{% extends "layout.html" %} -{% block title %}audit — {{ user.username }} — kitgit{% endblock %} -{% block content %} -<section class="kg-section"> - <span class="kg-kicker">admin</span> - <h1 class="kg-title">Audit log · {{ user.username }}</h1> - <p class="kg-muted"> - <a href="/admin">← back to admin</a> - · <a href="/{{ user.username }}">profile</a> - </p> - - {% if audit_entries.is_empty() %} - <p class="kg-muted" style="margin-top:1.5rem">No audit events for this user.</p> - {% else %} - <ul class="kg-list" style="margin-top:1.5rem"> - {% for e in audit_entries %} - <li> - <span> - <strong>{{ e.action_label }}</strong> - <code class="kg-meta">{{ e.action }}</code> - <span class="kg-meta"> · {{ e.created_at }}</span> - {% if !e.ip.is_empty() %} - <span class="kg-meta"> · {{ e.ip }}</span> - {% endif %} - {% if !e.user_agent.is_empty() %} - <span class="kg-meta"> · {{ e.user_agent }}</span> - {% endif %} - {% if !e.metadata.is_empty() %} - <span class="kg-meta"> · <code>{{ e.metadata }}</code></span> - {% endif %} - </span> - </li> - {% endfor %} - </ul> - {% endif %} -</section> -{% endblock %}
@@ -62,7 +62,7 @@ <p class="kg-meta" style="margin:0.25rem 0 0">{{ item.repo.description }}</p> {% endif %} </div> - <span class="kg-meta">{{ item.repo.updated_at.format("%Y-%m-%d") }}</span> + <span class="kg-meta">{{ item.repo.updated_at }}</span> </li> {% endfor %} </ul> @@ -113,7 +113,7 @@ {{ item.owner }}/{{ item.repo_name }}#{{ item.number }} {{ item.title }} </a> </div> - <span class="kg-meta">{{ item.updated_at.format("%Y-%m-%d") }}</span> + <span class="kg-meta">{{ item.updated_at }}</span> </li> {% endfor %} </ul> @@ -138,7 +138,7 @@ {{ item.owner }}/{{ item.repo_name }}#{{ item.number }} {{ item.title }} </a> </div> - <span class="kg-meta">{{ item.updated_at.format("%Y-%m-%d") }}</span> + <span class="kg-meta">{{ item.updated_at }}</span> </li> {% endfor %} </ul>
@@ -20,28 +20,6 @@ body <textarea name="body" rows="8"></textarea> </label> - {% if !labels.is_empty() %} - <fieldset class="kg-fieldset"> - <legend>labels</legend> - {% for l in labels %} - <label class="row"> - <input type="checkbox" name="label_id" value="{{ l.id }}" /> - <span class="kg-label-chip" style="background:{{ l.bg_color() }};color:{{ l.text_color() }}">{{ l.name }}</span> - </label> - {% endfor %} - </fieldset> - {% endif %} - {% if !milestones.is_empty() %} - <label> - milestone - <select name="milestone_id"> - <option value="">none</option> - {% for m in milestones %} - <option value="{{ m.id }}">{{ m.title }}</option> - {% endfor %} - </select> - </label> - {% endif %} <div class="kg-actions"> <button class="kg-btn" type="submit">create</button> <a class="kg-btn kg-btn--ghost" href="/{{ owner.username }}/{{ repo.name }}/issues">cancel</a>
@@ -12,101 +12,46 @@ {% let archive_ref = repo.default_branch %} {% include "partials/repo_tabs.html" %} - <div class="kg-issue-layout"> - <div class="kg-issue-main"> - <article class="kg-comment"> - <header> - <span> - <img class="kg-avatar kg-avatar--sm" src="{{ author_avatar }}" alt="" /> - <a href="/{{ author.username }}">{{ author.username }}</a> - </span> - <span>{{ issue.created_at }}</span> - </header> - <div class="kg-readme">{{ body_html|safe }}</div> - </article> + <article class="kg-comment"> + <header> + <span> + <img class="kg-avatar kg-avatar--sm" src="{{ author_avatar }}" alt="" /> + <a href="/{{ author.username }}">{{ author.username }}</a> + </span> + <span>{{ issue.created_at }}</span> + </header> + <div class="kg-readme">{{ body_html|safe }}</div> + </article> - {% for c in comments %} - <article class="kg-comment"> - <header> - <span> - <img class="kg-avatar kg-avatar--sm" src="{{ c.avatar_url }}" alt="" /> - <a href="/{{ c.author.username }}">{{ c.author.username }}</a> - </span> - <span>{{ c.created_at }}</span> - </header> - <div class="kg-readme">{{ c.body_html|safe }}</div> - {% include "partials/reactions.html" %} - </article> - {% endfor %} + {% for c in comments %} + <article class="kg-comment"> + <header> + <span> + <img class="kg-avatar kg-avatar--sm" src="{{ c.avatar_url }}" alt="" /> + <a href="/{{ c.author.username }}">{{ c.author.username }}</a>
Large diffs are not rendered by default. Showing the first 50 of 142 lines. Show full diff
@@ -18,27 +18,19 @@ {% if viewer.is_some() %} <a class="kg-btn" href="/{{ owner.username }}/{{ repo.name }}/issues/new">new issue</a> {% endif %} - <a class="kg-btn kg-btn--ghost" href="/{{ owner.username }}/{{ repo.name }}/labels">labels</a> - <a class="kg-btn kg-btn--ghost" href="/{{ owner.username }}/{{ repo.name }}/milestones">milestones</a> </div> {% if issues.is_empty() %} <p class="kg-muted">No {{ state_filter }} issues.</p> {% else %} <ul class="kg-list"> - {% for item in issues %} + {% for issue in issues %} <li> <span> - <span class="kg-badge {% if item.issue.state == "open" %}kg-badge--open{% else %}kg-badge--closed{% endif %}">{{ item.issue.state }}</span> - <a href="/{{ owner.username }}/{{ repo.name }}/issues/{{ item.issue.number }}">#{{ item.issue.number }} {{ item.issue.title }}</a> - {% for l in item.labels %} - <span class="kg-label-chip" style="background:{{ l.bg_color() }};color:{{ l.text_color() }}">{{ l.name }}</span> - {% endfor %} - {% if let Some(m) = item.milestone %} - <span class="kg-meta">milestone: {{ m.title }}</span> - {% endif %} + <span class="kg-badge {% if issue.state == "open" %}kg-badge--open{% else %}kg-badge--closed{% endif %}">{{ issue.state }}</span> + <a href="/{{ owner.username }}/{{ repo.name }}/issues/{{ issue.number }}">#{{ issue.number }} {{ issue.title }}</a> </span> - <span class="kg-meta">{{ item.issue.updated_at.format("%Y-%m-%d") }}</span> + <span class="kg-meta">{{ issue.updated_at }}</span> </li> {% endfor %} </ul>
@@ -17,7 +17,7 @@ {% endif %} <span class="kg-kicker" style="margin-top:1.5rem">SSH keys</span> - <p class="kg-muted">Authentication keys let you push/pull over SSH. Signing keys verify signed commits on kitgit (Verified badge).</p> + <p class="kg-muted">Authentication keys let you push/pull over SSH. Signing keys verify signed commits.</p> {% if !keys.is_empty() %} <ul class="kg-list"> {% for key in keys %} @@ -70,14 +70,6 @@ </div> </form> - <div class="kg-signing-hint"> - <strong>Sign commits with SSH</strong> - <p class="kg-muted" style="margin:0.35rem 0 0">Add the public key above with usage <em>Signing</em> or <em>Authentication & Signing</em>, then configure git locally. Author email must match a verified email on your account.</p> - <pre>git config --global gpg.format ssh -git config --global user.signingkey ~/.ssh/id_ed25519.pub -git config --global commit.gpgsign true</pre> - </div> - <span class="kg-kicker" style="margin-top:2.5rem">GPG keys</span> <p class="kg-muted">Used for commit verification. Vigilant mode flags unsigned commits on your profile.</p> {% if !gpg_keys.is_empty() %} @@ -112,12 +104,5 @@ git config --global commit.gpgsign true</pre> <button class="kg-btn" type="submit">add</button> </div> </form> - - <div class="kg-signing-hint"> - <strong>Sign commits with GPG</strong> - <p class="kg-muted" style="margin:0.35rem 0 0">Paste your public key above, then point git at the key id. Author email must match a verified email on your account.</p> - <pre>git config --global user.signingkey YOUR_KEY_ID -git config --global commit.gpgsign true</pre> - </div> </section> {% endblock %}
@@ -1,15 +0,0 @@ -{% extends "layout.html" %} -{% block title %}labels · {{ owner.username }}/{{ repo.name }} — kitgit{% endblock %} -{% block content %} -<section class="kg-section"> - <h1 class="kg-title"><a href="/{{ owner.username }}/{{ repo.name }}">{{ owner.username }}/{{ repo.name }}</a> · labels</h1> - {% let tab = "settings" %} - {% let archive_ref = repo.default_branch %} - {% include "partials/repo_tabs.html" %} - {% if let Some(err) = error %}<div class="kg-flash">{{ err }}</div>{% endif %} - <div class="kg-actions"><a class="kg-btn kg-btn--ghost" href="/{{ owner.username }}/{{ repo.name }}/milestones">milestones</a><a class="kg-btn kg-btn--ghost" href="/{{ owner.username }}/{{ repo.name }}/settings">settings</a></div> - {% if labels.is_empty() %}<p class="kg-muted">No labels yet.</p>{% else %} - <ul class="kg-list">{% for l in labels %}<li><span><span class="kg-label-chip" style="background:{{ l.bg_color() }};color:{{ l.text_color() }}">{{ l.name }}</span> <span class="kg-meta">{{ l.description }}</span></span>{% if access.can_admin() %}<form method="post" action="/{{ owner.username }}/{{ repo.name }}/labels/{{ l.id }}/delete"><button class="kg-btn kg-btn--danger-ghost" type="submit">delete</button></form>{% endif %}</li>{% if access.can_admin() %}<li><form class="kg-form" method="post" action="/{{ owner.username }}/{{ repo.name }}/labels/{{ l.id }}/update"><label>name <input name="name" value="{{ l.name }}" required /></label><label>color <input name="color" value="{{ l.color }}" required /></label><label>description <input name="description" value="{{ l.description }}" /></label><button class="kg-btn kg-btn--ghost" type="submit">update</button></form></li>{% endif %}{% endfor %}</ul>{% endif %} - {% if access.can_admin() %}<form class="kg-form" method="post" action="/{{ owner.username }}/{{ repo.name }}/labels"><span class="kg-kicker">new label</span><label>name <input name="name" required /></label><label>color <input name="color" value="0969da" required /></label><label>description <input name="description" /></label><button class="kg-btn" type="submit">create label</button></form>{% endif %} -</section> -{% endblock %}
@@ -1,5 +1,5 @@ <!DOCTYPE html> -<html lang="en" data-theme="{% if let Some(u) = viewer %}{{ u.theme_pref() }}{% else %}system{% endif %}"> +<html lang="en"> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> @@ -23,11 +23,6 @@ <a href="/explore"><span class="kg-icon" style="--icon:url('/static/icons/code.svg')"></span> explore</a> {% if let Some(u) = viewer %} <a href="/new"><span class="kg-icon" style="--icon:url('/static/icons/plus.svg')"></span> new</a> - <a id="kg-nav-notifications" href="/notifications"> - <span class="kg-icon" style="--icon:url('/static/icons/bell.svg')"></span> - notifications - <span id="kg-notif-count" class="kg-notif-count" hidden></span> - </a> {% if u.is_site_admin %}<a href="/admin"><span class="kg-icon" style="--icon:url('/static/icons/settings.svg')"></span> admin</a>{% endif %} <a href="/settings/profile"><span class="kg-icon" style="--icon:url('/static/icons/user.svg')"></span> settings</a> <a class="kg-userchip" href="/{{ u.username }}"> @@ -44,10 +39,7 @@ <main> {% block content %}{% endblock %} </main> - <footer class="kg-footer"> - <span>kitgit</span> - <span class="kg-footer__timing">rendered in <!--kg-render-ms--></span> - </footer> + <footer class="kg-footer">kitgit</footer> </div> <script> (async () => { @@ -62,19 +54,6 @@ } } catch (_) {} })(); - (async () => { - const badge = document.getElementById('kg-notif-count'); - if (!badge) return; - try { - const r = await fetch('/notifications/unread.json', { credentials: 'same-origin' }); - if (!r.ok) return; - const j = await r.json(); - const n = Number(j && j.count); - if (!n) return; - badge.textContent = n > 99 ? '99+' : String(n); - badge.hidden = false;
Large diffs are not rendered by default. Showing the first 50 of 55 lines. Show full diff
@@ -1,19 +0,0 @@ -{% extends "layout.html" %} -{% block title %}milestones · {{ owner.username }}/{{ repo.name }} — kitgit{% endblock %} -{% block content %} -<section class="kg-section"> - <h1 class="kg-title"><a href="/{{ owner.username }}/{{ repo.name }}">{{ owner.username }}/{{ repo.name }}</a> · milestones</h1> - {% let tab = "settings" %} - {% let archive_ref = repo.default_branch %} - {% include "partials/repo_tabs.html" %} - {% if let Some(err) = error %}<div class="kg-flash">{{ err }}</div>{% endif %} - <div class="kg-actions"> - <a class="kg-btn kg-btn--ghost" href="/{{ owner.username }}/{{ repo.name }}/milestones?state=open"{% if state_filter == "open" %} aria-current="page"{% endif %}>open</a> - <a class="kg-btn kg-btn--ghost" href="/{{ owner.username }}/{{ repo.name }}/milestones?state=closed"{% if state_filter == "closed" %} aria-current="page"{% endif %}>closed</a> - <a class="kg-btn kg-btn--ghost" href="/{{ owner.username }}/{{ repo.name }}/labels">labels</a> - </div> - {% if milestones.is_empty() %}<p class="kg-muted">No {{ state_filter }} milestones.</p>{% else %} - <ul class="kg-list">{% for m in milestones %}<li><span><span class="kg-badge {% if m.state == "open" %}kg-badge--open{% else %}kg-badge--closed{% endif %}">{{ m.state }}</span> <strong>{{ m.title }}</strong>{% if let Some(due) = m.due_on %} <span class="kg-meta">due {{ due }}</span>{% endif %}</span>{% if access.can_admin() %}<span class="kg-actions">{% if m.state == "open" %}<form method="post" action="/{{ owner.username }}/{{ repo.name }}/milestones/{{ m.id }}/close"><button class="kg-btn kg-btn--ghost" type="submit">close</button></form>{% else %}<form method="post" action="/{{ owner.username }}/{{ repo.name }}/milestones/{{ m.id }}/reopen"><button class="kg-btn kg-btn--ghost" type="submit">reopen</button></form>{% endif %}<form method="post" action="/{{ owner.username }}/{{ repo.name }}/milestones/{{ m.id }}/delete"><button class="kg-btn kg-btn--danger-ghost" type="submit">delete</button></form></span>{% endif %}</li>{% endfor %}</ul>{% endif %} - {% if access.can_admin() %}<form class="kg-form" method="post" action="/{{ owner.username }}/{{ repo.name }}/milestones"><span class="kg-kicker">new milestone</span><label>title <input name="title" required /></label><label>due on <input type="date" name="due_on" /></label><label>description <textarea name="description" rows="3"></textarea></label><button class="kg-btn" type="submit">create milestone</button></form>{% endif %} -</section> -{% endblock %}
@@ -1,47 +0,0 @@ -{% extends "layout.html" %} - -{% block title %}notifications — kitgit{% endblock %} - -{% block content %} -<section class="kg-section"> - <div class="kg-actions" style="justify-content: space-between; align-items: baseline;"> - <h1 class="kg-title">notifications</h1> - {% if unread_count > 0 %} - <form method="post" action="/notifications/read-all"> - <button class="kg-btn kg-btn--ghost" type="submit">mark all read</button> - </form> - {% endif %} - </div> - - {% if notifications.is_empty() %} - <p class="kg-muted">No notifications yet.</p> - {% else %} - <ul class="kg-list kg-notif-list"> - {% for n in notifications %} - <li class="{% if n.read_at.is_none() %}kg-notif--unread{% endif %}"> - <span class="kg-notif-main"> - {% if n.href.is_empty() %} - <strong>{{ n.title }}</strong> - {% else %} - <a href="{{ n.href }}"><strong>{{ n.title }}</strong></a> - {% endif %} - {% if !n.body.is_empty() %} - <span class="kg-muted">{{ n.body }}</span> - {% endif %} - <span class="kg-meta">{{ n.created_at }} · {{ n.kind }}</span> - </span> - <span class="kg-notif-actions"> - {% if n.read_at.is_none() %} - <form method="post" action="/notifications/{{ n.id }}/read"> - <button class="kg-btn kg-btn--ghost" type="submit">mark read</button> - </form> - {% else %} - <span class="kg-faint">read</span> - {% endif %} - </span> - </li> - {% endfor %} - </ul>
Large diffs are not rendered by default. Showing the first 50 of 53 lines. Show full diff
@@ -1,10 +1,33 @@ -{# Expects: owner, repo, branch, latest_commit: Option<CommitView>, access, upload_path, commit_count #} -<div class="kg-toolbar"> - <a class="kg-btn kg-btn--ghost kg-toolbar__branch" href="/{{ owner.username }}/{{ repo.name }}/branches"> - <span class="kg-icon" style="--icon:url('/static/icons/git-branch.svg')"></span> - {{ branch }} - </a> - <div class="kg-toolbar__actions"> +{# Expects: owner, repo, branch, latest_commit: Option<CommitView>, access (optional for upload), upload_path (optional) #} +<div class="kg-branchbar"> + <div class="kg-branchbar__left"> + <a class="kg-branchbar__branch" href="/{{ owner.username }}/{{ repo.name }}/branches"> + <span class="kg-icon" style="--icon:url('/static/icons/git-branch.svg')"></span> + {{ branch }} + </a> + {% if let Some(c) = latest_commit %} + <div class="kg-branchbar__commit"> + <a class="kg-branchbar__sha" href="/{{ owner.username }}/{{ repo.name }}/commits" title="View commits"> + <span class="kg-icon" style="--icon:url('/static/icons/git-commit.svg')"></span> + {{ c.short_id }} + </a> + <a class="kg-branchbar__msg" href="/{{ owner.username }}/{{ repo.name }}/commit/{{ c.id }}" title="View commit">{{ c.message }}</a> + {% if c.verified %} + <details class="kg-verify"> + <summary class="kg-badge kg-badge--verified">Verified</summary> + <div class="kg-verify__panel" role="dialog" aria-label="Commit signature"> + <p class="kg-verify__fp"> + <strong>{{ c.verify_fingerprint_label }}:</strong> + <code>{{ c.verify_fingerprint }}</code> + </p> + <p class="kg-meta">Verified on {{ c.verified_at }}</p> + </div> + </details> + {% endif %} + </div> + {% endif %} + </div> + <div class="kg-branchbar__actions"> {% if upload_path != "" %} <a class="kg-btn kg-btn--ghost" href="/{{ owner.username }}/{{ repo.name }}/archive.zip?ref={{ branch }}&path={{ upload_path }}" title="Download this folder as ZIP"> <span class="kg-icon" style="--icon:url('/static/icons/download.svg')"></span> @@ -45,27 +68,3 @@ {% endif %} </div> </div> -
Large diffs are not rendered by default. Showing the first 50 of 73 lines. Show full diff
@@ -31,7 +31,6 @@ </a> {% endif %} </div> - {% if tab == "code" %} <details class="kg-dropdown"> <summary class="kg-btn kg-btn--ghost"> <span class="kg-icon" style="--icon:url('/static/icons/download.svg')"></span> @@ -59,5 +58,4 @@ </div> </div> </details> - {% endif %} </nav>
@@ -1,24 +1,12 @@ -{# Expects CommitView as `c`. Optional: `show_unverified` (bool) — show Unverified for unsigned too. #} -{% if c.verified %} +{% if let Some(v) = verification %} <details class="kg-verify"> <summary class="kg-badge kg-badge--verified">Verified</summary> <div class="kg-verify__panel" role="dialog" aria-label="Commit signature"> - <p class="kg-verify__row"><strong>Type:</strong> {{ c.verify_kind_label() }}</p> <p class="kg-verify__fp"> - <strong>{{ c.verify_fingerprint_label }}:</strong> - <code>{{ c.verify_fingerprint }}</code> + <strong>{{ v.fingerprint_label() }}:</strong> + <code>{{ v.fingerprint }}</code> </p> - <p class="kg-meta">Verified on {{ c.verified_at }}</p> + <p class="kg-meta">Verified on {{ v.verified_at }}</p> </div> </details> -{% else if c.signed %} -<details class="kg-verify"> - <summary class="kg-badge kg-badge--unverified">Unverified</summary> - <div class="kg-verify__panel" role="dialog" aria-label="Commit signature"> - <p class="kg-verify__row">Signature present but could not be verified.</p> - <p class="kg-meta">Add a matching signing SSH or GPG key under <a href="/settings/keys">Settings → SSH / GPG</a>. The commit author email must match a verified email on that account.</p> - </div> -</details> -{% else if show_unverified %} -<span class="kg-badge kg-badge--unverified" title="Commit is not signed">Unverified</span> {% endif %}
@@ -1,20 +1,45 @@ {% extends "layout.html" %} + {% block title %}new pull · {{ owner.username }}/{{ repo.name }} — kitgit{% endblock %} + {% block content %} <section class="kg-section"> <h1 class="kg-title">New pull request</h1> {% let tab = "pulls" %} {% let archive_ref = repo.default_branch %} {% include "partials/repo_tabs.html" %} - {% if let Some(err) = error %}<div class="kg-flash">{{ err }}</div>{% endif %} + {% if let Some(err) = error %} + <div class="kg-flash">{{ err }}</div> + {% endif %} <form class="kg-form" method="post" action="/{{ owner.username }}/{{ repo.name }}/pulls"> - <label>title <input type="text" name="title" required autofocus /></label> - <label>source branch<select name="source_branch" required>{% for b in branches %}<option value="{{ b }}">{{ b }}</option>{% endfor %}</select></label> - <label>target branch<select name="target_branch" required>{% for b in branches %}<option value="{{ b }}"{% if b.as_str() == repo.default_branch.as_str() %} selected{% endif %}>{{ b }}</option>{% endfor %}</select></label> - <label>body <textarea name="body" rows="8"></textarea></label> - {% if !labels.is_empty() %}<fieldset class="kg-fieldset"><legend>labels</legend>{% for l in labels %}<label class="row"><input type="checkbox" name="label_id" value="{{ l.id }}" /> <span class="kg-label-chip" style="background:{{ l.bg_color() }};color:{{ l.text_color() }}">{{ l.name }}</span></label>{% endfor %}</fieldset>{% endif %} - {% if !milestones.is_empty() %}<label>milestone<select name="milestone_id"><option value="">none</option>{% for m in milestones %}<option value="{{ m.id }}">{{ m.title }}</option>{% endfor %}</select></label>{% endif %} - <div class="kg-actions"><button class="kg-btn" type="submit">create</button><a class="kg-btn kg-btn--ghost" href="/{{ owner.username }}/{{ repo.name }}/pulls">cancel</a></div> + <label> + title + <input type="text" name="title" required autofocus /> + </label> + <label> + source branch + <select name="source_branch" required> + {% for b in branches %} + <option value="{{ b }}">{{ b }}</option> + {% endfor %} + </select> + </label> + <label> + target branch + <select name="target_branch" required> + {% for b in branches %} + <option value="{{ b }}"{% if b.as_str() == repo.default_branch.as_str() %} selected{% endif %}>{{ b }}</option> + {% endfor %} + </select> + </label> + <label> + body + <textarea name="body" rows="8"></textarea>
Large diffs are not rendered by default. Showing the first 50 of 58 lines. Show full diff
@@ -9,19 +9,6 @@ #{{ pull.number }} {{ pull.title }} </h1> <p class="kg-meta">{{ pull.source_branch }} → {{ pull.target_branch }}</p> - <aside class="kg-issue-sidebar" style="margin:1rem 0"> - <div class="kg-sidebar-block"> - <h2 class="kg-kicker">labels</h2> - {% if labels.is_empty() %}<p class="kg-muted">None yet.</p>{% else %}<div class="kg-label-row">{% for l in labels %}<span class="kg-label-chip" style="background:{{ l.bg_color() }};color:{{ l.text_color() }}">{{ l.name }}</span>{% endfor %}</div>{% endif %} - {% if can_triage %}<form class="kg-form" method="post" action="/{{ owner.username }}/{{ repo.name }}/pulls/{{ pull.number }}/labels">{% for opt in label_options %}<label class="row"><input type="checkbox" name="label_id" value="{{ opt.label.id }}"{% if opt.selected %} checked{% endif %} /> <span class="kg-label-chip" style="background:{{ opt.label.bg_color() }};color:{{ opt.label.text_color() }}">{{ opt.label.name }}</span></label>{% endfor %}{% if !label_options.is_empty() %}<button class="kg-btn kg-btn--ghost" type="submit">save labels</button>{% endif %}</form>{% endif %} - </div> - <div class="kg-sidebar-block"> - <h2 class="kg-kicker">milestone</h2> - {% if let Some(m) = milestone %}<p>{{ m.title }}</p>{% else %}<p class="kg-muted">No milestone</p>{% endif %} - {% if can_triage %}<form class="kg-form" method="post" action="/{{ owner.username }}/{{ repo.name }}/pulls/{{ pull.number }}/milestone"><label><select name="milestone_id"><option value="">none</option>{% for opt in milestone_options %}<option value="{{ opt.milestone.id }}"{% if opt.selected %} selected{% endif %}>{{ opt.milestone.title }}</option>{% endfor %}</select></label><button class="kg-btn kg-btn--ghost" type="submit">save milestone</button></form>{% endif %} - </div> - </aside> - {% let pr_tab = tab %} {% let tab = "pulls" %} {% let archive_ref = repo.default_branch %} @@ -38,7 +25,7 @@ commits <span class="kg-pr-tabs__count">{{ commits.len() }}</span> </a> - <a href="/{{ owner.username }}/{{ repo.name }}/pulls/{{ pull.number }}?tab=files{% if show_full %}&full=1{% endif %}"{% if pr_tab == "files" %} aria-current="page"{% endif %}> + <a href="/{{ owner.username }}/{{ repo.name }}/pulls/{{ pull.number }}?tab=files"{% if pr_tab == "files" %} aria-current="page"{% endif %}> <span class="kg-icon" style="--icon:url('/static/icons/file.svg')"></span> files <span class="kg-pr-tabs__count">{{ diff_files.len() }}</span> @@ -71,51 +58,6 @@ </article> {% endfor %} - {% for r in reviews %} - <article class="kg-comment kg-review"> - <header> - <span> - <img class="kg-avatar kg-avatar--sm" src="{{ r.avatar_url }}" alt="" /> - <a href="/{{ r.reviewer.username }}">{{ r.reviewer.username }}</a> - <span class="kg-badge {{ r.state_class() }}">{{ r.state_label() }}</span> - </span> - <span>{{ r.created_at }}</span> - </header> - {% if !r.body.is_empty() %} - <div class="kg-readme">{{ r.body_html|safe }}</div> - {% endif %}
Large diffs are not rendered by default. Showing the first 50 of 119 lines. Show full diff
@@ -1,20 +1,41 @@ {% extends "layout.html" %} + {% block title %}pulls · {{ owner.username }}/{{ repo.name }} — kitgit{% endblock %} + {% block content %} <section class="kg-section"> - <h1 class="kg-title"><a href="/{{ owner.username }}/{{ repo.name }}">{{ owner.username }}/{{ repo.name }}</a> · pulls</h1> + <h1 class="kg-title"> + <a href="/{{ owner.username }}/{{ repo.name }}">{{ owner.username }}/{{ repo.name }}</a> + · pulls + </h1> {% let tab = "pulls" %} {% let archive_ref = repo.default_branch %} {% include "partials/repo_tabs.html" %} + <div class="kg-actions"> <a class="kg-btn kg-btn--ghost" href="/{{ owner.username }}/{{ repo.name }}/pulls?state=open"{% if state_filter == "open" %} aria-current="page"{% endif %}>open</a> <a class="kg-btn kg-btn--ghost" href="/{{ owner.username }}/{{ repo.name }}/pulls?state=closed"{% if state_filter == "closed" %} aria-current="page"{% endif %}>closed</a> <a class="kg-btn kg-btn--ghost" href="/{{ owner.username }}/{{ repo.name }}/pulls?state=merged"{% if state_filter == "merged" %} aria-current="page"{% endif %}>merged</a> - {% if viewer.is_some() %}<a class="kg-btn" href="/{{ owner.username }}/{{ repo.name }}/pulls/new">new pull</a>{% endif %} - <a class="kg-btn kg-btn--ghost" href="/{{ owner.username }}/{{ repo.name }}/labels">labels</a> - <a class="kg-btn kg-btn--ghost" href="/{{ owner.username }}/{{ repo.name }}/milestones">milestones</a> + {% if viewer.is_some() %} + <a class="kg-btn" href="/{{ owner.username }}/{{ repo.name }}/pulls/new">new pull</a> + {% endif %} </div> - {% if pulls.is_empty() %}<p class="kg-muted">No {{ state_filter }} pulls.</p>{% else %} - <ul class="kg-list">{% for item in pulls %}<li><span><span class="kg-badge {% if item.pull.state == "open" %}kg-badge--open{% else %}kg-badge--closed{% endif %}">{{ item.pull.state }}</span> <a href="/{{ owner.username }}/{{ repo.name }}/pulls/{{ item.pull.number }}">#{{ item.pull.number }} {{ item.pull.title }}</a> <span class="kg-meta">{{ item.pull.source_branch }} → {{ item.pull.target_branch }}</span>{% for l in item.labels %} <span class="kg-label-chip" style="background:{{ l.bg_color() }};color:{{ l.text_color() }}">{{ l.name }}</span>{% endfor %}{% if let Some(m) = item.milestone %} <span class="kg-meta">milestone: {{ m.title }}</span>{% endif %}</span><span class="kg-meta">{{ item.pull.updated_at.format("%Y-%m-%d") }}</span></li>{% endfor %}</ul>{% endif %} + + {% if pulls.is_empty() %} + <p class="kg-muted">No {{ state_filter }} pulls.</p> + {% else %} + <ul class="kg-list"> + {% for pull in pulls %} + <li> + <span> + <span class="kg-badge {% if pull.state == "open" %}kg-badge--open{% else %}kg-badge--closed{% endif %}">{{ pull.state }}</span> + <a href="/{{ owner.username }}/{{ repo.name }}/pulls/{{ pull.number }}">#{{ pull.number }} {{ pull.title }}</a> + <span class="kg-meta">{{ pull.source_branch }} → {{ pull.target_branch }}</span> + </span> + <span class="kg-meta">{{ pull.updated_at }}</span> + </li> + {% endfor %} + </ul> + {% endif %}
Large diffs are not rendered by default. Showing the first 50 of 52 lines. Show full diff
@@ -1,66 +0,0 @@ -{% extends "layout.html" %} - -{% block title %}blame · {{ path }} · {{ owner.username }}/{{ repo.name }} — kitgit{% endblock %} - -{% block content %} -<section class="kg-section"> - <h1 class="kg-title"> - <a href="/{{ owner.username }}/{{ repo.name }}">{{ owner.username }}/{{ repo.name }}</a> - · blame - </h1> - {% let tab = "code" %} - {% let archive_ref = branch %} - {% include "partials/repo_tabs.html" %} - - <p class="kg-meta kg-breadcrumbs"> - <a href="/{{ owner.username }}/{{ repo.name }}/tree/{{ branch }}">{{ branch }}</a> - {% for (name, full) in breadcrumbs %} - / - {% if loop.last %} - {{ name }} - {% else %} - <a href="/{{ owner.username }}/{{ repo.name }}/tree/{{ branch }}/{{ full }}">{{ name }}</a> - {% endif %} - {% endfor %} - </p> - - <div class="kg-blobbar"> - <span class="kg-meta">{{ path }}</span> - <div class="kg-blobbar__actions"> - <a class="kg-btn kg-btn--ghost" href="/{{ owner.username }}/{{ repo.name }}/blob/{{ branch }}/{{ path }}">View file</a> - <a class="kg-btn kg-btn--ghost" href="/{{ owner.username }}/{{ repo.name }}/history/{{ branch }}/{{ path }}">History</a> - <a class="kg-btn kg-btn--ghost" href="/{{ owner.username }}/{{ repo.name }}/raw/{{ branch }}/{{ path }}"> - <span class="kg-icon" style="--icon:url('/static/icons/download.svg')"></span> - Download raw - </a> - </div> - </div> - - {% if binary %} - <p class="kg-muted">Binary file — blame is not available.</p> - {% else if lines.is_empty() %} - <p class="kg-muted">Empty file.</p> - {% else %} - <div class="kg-blame" role="table" aria-label="Blame for {{ path }}">
Large diffs are not rendered by default. Showing the first 50 of 72 lines. Show full diff
@@ -33,8 +33,6 @@ <div class="kg-blobbar"> <span class="kg-meta">{{ path }}</span> <div class="kg-blobbar__actions"> - <a class="kg-btn kg-btn--ghost" href="/{{ owner.username }}/{{ repo.name }}/blame/{{ branch }}/{{ path }}">Blame</a> - <a class="kg-btn kg-btn--ghost" href="/{{ owner.username }}/{{ repo.name }}/history/{{ branch }}/{{ path }}">History</a> <a class="kg-btn kg-btn--ghost" href="/{{ owner.username }}/{{ repo.name }}/raw/{{ branch }}/{{ path }}"> <span class="kg-icon" style="--icon:url('/static/icons/download.svg')"></span> Download raw
@@ -61,15 +61,13 @@ <td> {% if access.can_write() && !b.is_default %} <div class="kg-branch-actions"> - <form method="post" action="/{{ owner.username }}/{{ repo.name }}/branches/rename" style="display:inline-flex;gap:0.35rem"> - <input type="hidden" name="branch" value="{{ b.name }}" /> + <form method="post" action="/{{ owner.username }}/{{ repo.name }}/branches/{{ b.name }}/rename" style="display:inline-flex;gap:0.35rem"> <input type="text" name="new_name" placeholder="rename" required style="width:7rem;font:inherit;border:1px solid var(--kg-line);padding:0.25em 0.4em" /> <button class="kg-btn kg-btn--ghost" type="submit" title="Rename"> <span class="kg-icon" style="--icon:url('/static/icons/pencil.svg')"></span> </button> </form> - <form method="post" action="/{{ owner.username }}/{{ repo.name }}/branches/delete" style="display:inline" onsubmit="return confirm('Delete branch {{ b.name }}?')"> - <input type="hidden" name="branch" value="{{ b.name }}" /> + <form method="post" action="/{{ owner.username }}/{{ repo.name }}/branches/{{ b.name }}/delete" style="display:inline" onsubmit="return confirm('Delete branch {{ b.name }}?')"> <button class="kg-btn kg-btn--danger-ghost" type="submit" title="Delete"> <span class="kg-icon" style="--icon:url('/static/icons/trash.svg')"></span> </button> @@ -141,15 +139,13 @@ <td> {% if access.can_write() %} <div class="kg-branch-actions"> - <form method="post" action="/{{ owner.username }}/{{ repo.name }}/tags/rename" style="display:inline-flex;gap:0.35rem"> - <input type="hidden" name="tag" value="{{ t.name }}" /> + <form method="post" action="/{{ owner.username }}/{{ repo.name }}/tags/{{ t.name }}/rename" style="display:inline-flex;gap:0.35rem"> <input type="text" name="new_name" placeholder="rename" required style="width:7rem;font:inherit;border:1px solid var(--kg-line);padding:0.25em 0.4em" /> <button class="kg-btn kg-btn--ghost" type="submit" title="Rename"> <span class="kg-icon" style="--icon:url('/static/icons/pencil.svg')"></span> </button> </form> - <form method="post" action="/{{ owner.username }}/{{ repo.name }}/tags/delete" style="display:inline" onsubmit="return confirm('Delete tag {{ t.name }}?')"> - <input type="hidden" name="tag" value="{{ t.name }}" /> + <form method="post" action="/{{ owner.username }}/{{ repo.name }}/tags/{{ t.name }}/delete" style="display:inline" onsubmit="return confirm('Delete tag {{ t.name }}?')"> <button class="kg-btn kg-btn--danger-ghost" type="submit" title="Delete"> <span class="kg-icon" style="--icon:url('/static/icons/trash.svg')"></span> </button>
@@ -15,20 +15,22 @@ <p><code>{{ commit.id }}</code></p> <p class="kg-title"> {{ message_html|safe }} - {% let c = commit %} - {% let show_unverified = true %} - {% include "partials/verified_badge.html" %} + {% if commit.verified %} + <details class="kg-verify"> + <summary class="kg-badge kg-badge--verified">Verified</summary> + <div class="kg-verify__panel" role="dialog" aria-label="Commit signature"> + <p class="kg-verify__fp"> + <strong>{{ commit.verify_fingerprint_label }}:</strong> + <code>{{ commit.verify_fingerprint }}</code> + </p> + <p class="kg-meta">Verified on {{ commit.verified_at }}</p> + </div> + </details> + {% endif %} </p> - <p class="kg-meta">{{ commit.author }} <{{ commit.email }}> · {{ commit.time_display() }}</p> + <p class="kg-meta">{{ commit.author }} <{{ commit.email }}> · {{ commit.time }}</p> - <p><a href="/{{ owner.username }}/{{ repo.name }}/diff/{{ commit.id }}{% if show_full %}?full=1{% endif %}">view full diff</a></p> + <p><a href="/{{ owner.username }}/{{ repo.name }}/diff/{{ commit.id }}">view full diff</a></p> {{ diff_html|safe }} - {% if truncated && !show_full %} - <p class="kg-diff__truncated"> - Large diffs are not rendered by default. - Showing the first 50 of {{ total_lines }} lines. - <a class="kg-btn kg-btn--ghost" href="/{{ owner.username }}/{{ repo.name }}/commit/{{ commit.id }}?full=1">Show full diff</a> - </p> - {% endif %} </section> {% endblock %}
@@ -31,11 +31,21 @@ <code>{{ c.short_id }}</code> </a> <a href="/{{ owner.username }}/{{ repo.name }}/commit/{{ c.id }}">{{ c.message }}</a> - {% let show_unverified = false %} - {% include "partials/verified_badge.html" %} + {% if c.verified %} + <details class="kg-verify"> + <summary class="kg-badge kg-badge--verified">Verified</summary> + <div class="kg-verify__panel" role="dialog" aria-label="Commit signature"> + <p class="kg-verify__fp"> + <strong>{{ c.verify_fingerprint_label }}:</strong> + <code>{{ c.verify_fingerprint }}</code> + </p> + <p class="kg-meta">Verified on {{ c.verified_at }}</p> + </div> + </details> + {% endif %} <span class="kg-muted"> — {{ c.author }}</span> </span> - <span class="kg-meta">{{ c.time_display() }}</span> + <span class="kg-meta">{{ c.time }}</span> </li> {% endfor %} </ul>
@@ -20,19 +20,9 @@ · {{ commit.author }} </p> <p class="kg-title"> - <a href="/{{ owner.username }}/{{ repo.name }}/commit/{{ commit.id }}{% if show_full %}?full=1{% endif %}">{{ commit.message }}</a> - {% let c = commit %} - {% let show_unverified = true %} - {% include "partials/verified_badge.html" %} + <a href="/{{ owner.username }}/{{ repo.name }}/commit/{{ commit.id }}">{{ commit.message }}</a> </p> {{ diff_html|safe }} - {% if truncated && !show_full %} - <p class="kg-diff__truncated"> - Large diffs are not rendered by default. - Showing the first 50 of {{ total_lines }} lines. - <a class="kg-btn kg-btn--ghost" href="/{{ owner.username }}/{{ repo.name }}/diff/{{ commit.id }}?full=1">Show full diff</a> - </p> - {% endif %} </section> {% endblock %}
@@ -1,67 +0,0 @@ -{% extends "layout.html" %} - -{% block title %}history · {{ path }} · {{ owner.username }}/{{ repo.name }} — kitgit{% endblock %} - -{% block content %} -<section class="kg-section"> - <h1 class="kg-title"> - <a href="/{{ owner.username }}/{{ repo.name }}">{{ owner.username }}/{{ repo.name }}</a> - · history - </h1> - {% let tab = "code" %} - {% let archive_ref = branch %} - {% include "partials/repo_tabs.html" %} - - <p class="kg-meta kg-breadcrumbs"> - <a href="/{{ owner.username }}/{{ repo.name }}/tree/{{ branch }}">{{ branch }}</a> - {% for (name, full) in breadcrumbs %} - / - {% if loop.last %} - {{ name }} - {% else %} - <a href="/{{ owner.username }}/{{ repo.name }}/tree/{{ branch }}/{{ full }}">{{ name }}</a> - {% endif %} - {% endfor %} - </p> - - <div class="kg-blobbar"> - <span class="kg-meta">{{ path }}</span> - <div class="kg-blobbar__actions"> - <a class="kg-btn kg-btn--ghost" href="/{{ owner.username }}/{{ repo.name }}/blob/{{ branch }}/{{ path }}">View file</a> - <a class="kg-btn kg-btn--ghost" href="/{{ owner.username }}/{{ repo.name }}/blame/{{ branch }}/{{ path }}">Blame</a> - </div> - </div> - - {% if commits.is_empty() %} - <p class="kg-muted">No commits touch this path.</p> - {% else %} - <ul class="kg-list"> - {% for c in commits %} - <li> - <span> - <a href="/{{ owner.username }}/{{ repo.name }}/commit/{{ c.id }}" title="View commit"> - <span class="kg-icon" style="--icon:url('/static/icons/git-commit.svg')"></span> - <code>{{ c.short_id }}</code>
Large diffs are not rendered by default. Showing the first 50 of 73 lines. Show full diff
@@ -104,8 +104,7 @@ {% if e.is_dir %} <span class="kg-icon" style="--icon:url('/static/icons/folder.svg')"></span> <a href="/{{ owner.username }}/{{ repo.name }}/tree/{{ current_branch }}/{{ e.path }}">{{ e.name }}</a> - <span class="kg-tree__commit kg-meta" title="{{ e.commit_message }}">{{ e.commit_message }}</span> - <span class="kg-tree__time kg-meta">{{ e.commit_time }}</span> + <span class="kg-meta">{{ e.mode }}</span> <a class="kg-tree__dl kg-btn kg-btn--ghost" href="/{{ owner.username }}/{{ repo.name }}/archive.zip?ref={{ current_branch }}&path={{ e.path }}" title="Download {{ e.name }} as ZIP"> <span class="kg-icon" style="--icon:url('/static/icons/download.svg')"></span> <span class="kg-sr-only">Download</span> @@ -113,8 +112,7 @@ {% else %} <span class="kg-icon" style="--icon:url('/static/icons/file.svg')"></span> <a href="/{{ owner.username }}/{{ repo.name }}/blob/{{ current_branch }}/{{ e.path }}">{{ e.name }}</a> - <span class="kg-tree__commit kg-meta" title="{{ e.commit_message }}">{{ e.commit_message }}</span> - <span class="kg-tree__time kg-meta">{{ e.commit_time }}</span> + <span class="kg-meta">{{ e.mode }}</span> {% endif %} </li> {% endfor %}
@@ -146,73 +146,6 @@ </div> <div class="kg-settings-section"> - <span class="kg-kicker">mirror</span> - <p class="kg-muted">Pull from another remote into this repository (fetch heads and tags).</p> - {% if let Some(m) = mirror %} - <p class="kg-meta" style="margin:0.5rem 0"> - {% if m.enabled %} - <span class="kg-badge">enabled</span> - {% else %} - <span class="kg-badge">disabled</span> - {% endif %} - {% if let Some(synced) = m.last_synced_at %} - · last synced {{ synced }} - {% else %} - · never synced - {% endif %} - </p> - {% if let Some(err) = m.last_error %} - <div class="kg-flash">{{ err }}</div> - {% endif %} - <form class="kg-form" method="post" action="/{{ owner.username }}/{{ repo.name }}/settings/mirror" style="margin-top:1rem"> - <label> - remote URL - <input type="text" name="remote_url" required value="{{ m.remote_url }}" placeholder="https://github.com/org/repo.git" /> - </label> - <label class="row"> - <input type="checkbox" name="enabled"{% if m.enabled %} checked{% endif %} /> - enabled - </label> - <div class="kg-actions"> - <button class="kg-btn" type="submit">save mirror</button> - </div> - </form> - <div class="kg-actions" style="margin-top:0.75rem;gap:0.5rem;display:flex;flex-wrap:wrap"> - <form method="post" action="/{{ owner.username }}/{{ repo.name }}/settings/mirror/sync"> - <button class="kg-btn kg-btn--ghost" type="submit">sync now</button> - </form> - <form method="post" action="/{{ owner.username }}/{{ repo.name }}/settings/mirror/delete"> - <button class="kg-btn kg-btn--danger-ghost" type="submit">remove mirror</button> - </form> - </div> - {% else %} - <p class="kg-muted">No mirror configured.</p> - <form class="kg-form" method="post" action="/{{ owner.username }}/{{ repo.name }}/settings/mirror" style="margin-top:1rem">
Large diffs are not rendered by default. Showing the first 50 of 203 lines. Show full diff
@@ -27,8 +27,7 @@ {% if e.is_dir %} <span class="kg-icon" style="--icon:url('/static/icons/folder.svg')"></span> <a href="/{{ owner.username }}/{{ repo.name }}/tree/{{ branch }}/{{ e.path }}">{{ e.name }}</a> - <span class="kg-tree__commit kg-meta" title="{{ e.commit_message }}">{{ e.commit_message }}</span> - <span class="kg-tree__time kg-meta">{{ e.commit_time }}</span> + <span class="kg-meta">{{ e.mode }}</span> <a class="kg-tree__dl kg-btn kg-btn--ghost" href="/{{ owner.username }}/{{ repo.name }}/archive.zip?ref={{ branch }}&path={{ e.path }}" title="Download {{ e.name }} as ZIP"> <span class="kg-icon" style="--icon:url('/static/icons/download.svg')"></span> <span class="kg-sr-only">Download</span> @@ -36,8 +35,7 @@ {% else %} <span class="kg-icon" style="--icon:url('/static/icons/file.svg')"></span> <a href="/{{ owner.username }}/{{ repo.name }}/blob/{{ branch }}/{{ e.path }}">{{ e.name }}</a> - <span class="kg-tree__commit kg-meta" title="{{ e.commit_message }}">{{ e.commit_message }}</span> - <span class="kg-tree__time kg-meta">{{ e.commit_time }}</span> + <span class="kg-meta">{{ e.mode }}</span> {% endif %} </li> {% endfor %}
@@ -5,13 +5,7 @@ {% block content %} <section class="kg-section kg-auth"> {% if !signups_enabled %} - <div class="kg-flash kg-flash--banner"> - {% if invite.is_empty() %} - {{ signup_disabled_message }} - {% else %} - Invite-only signup — your invite code unlocks registration. - {% endif %} - </div> + <div class="kg-flash kg-flash--banner">{{ signup_disabled_message }}</div> {% endif %} <p class="kg-landing__brand"> <span class="kg-mark__glyph" aria-hidden="true">狐</span> @@ -21,11 +15,12 @@ {% if let Some(err) = error %} <div class="kg-flash">{{ err }}</div> {% endif %} + {% if signups_enabled || !invite.is_empty() %} <form class="kg-form" method="post" action="/auth/signup" autocomplete="on"> {% if !signups_enabled %} <label> invite code - <input type="text" name="invite" required value="{{ invite }}" autocomplete="off" spellcheck="false" placeholder="Enter invite code" /> + <input type="text" name="invite" required value="{{ invite }}" autocomplete="off" spellcheck="false" /> </label> {% endif %} <label> @@ -48,6 +43,17 @@ <button class="kg-btn" type="submit">create account</button> </div> </form> + {% else %} + <form class="kg-form" method="get" action="/auth/signup" style="margin-top:1rem"> + <label> + invite code + <input type="text" name="invite" required autocomplete="off" spellcheck="false" placeholder="Enter an invite code" /> + </label> + <div class="kg-actions"> + <button class="kg-btn" type="submit">continue</button> + </div> + </form> + {% endif %} <p class="kg-meta" style="margin-top:1.25rem"> Already have an account? <a href="/auth/login">Log in</a>
Large diffs are not rendered by default. Showing the first 50 of 51 lines. Show full diff