From 68cda1972ce15894105f297e843cf299bd87da6d Mon Sep 17 00:00:00 2001 From: Jabberwocky238 <7176656@qq.com> Date: Fri, 17 Jul 2026 05:55:31 -0400 Subject: [PATCH] 1 --- cmd/main.go | 20 +- common/config.go | 30 +- common/perm.go | 4 +- common/repolayout.go | 38 +- gitrpc/middleware.go | 38 +- gitrpc/server.go | 36 +- gitrpc/v1/gitrpc.pb.go | 570 +++++++++++++------------- gitrpc/v1/v1connect/gitrpc.connect.go | 388 +++++++++--------- hooks/event.go | 64 +-- hooks/hooks_test.go | 32 +- hooks/listener.go | 54 +-- hooks/template.go | 28 +- hooks/trigger.go | 48 +-- state/access.go | 42 +- state/db.go | 62 +-- state/http.go | 54 +-- state/models.go | 88 ++-- state/mut.go | 116 +++--- state/ssh.go | 82 ++-- 19 files changed, 897 insertions(+), 897 deletions(-) diff --git a/cmd/main.go b/cmd/main.go index 3bd00be..d5c2380 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -1,13 +1,13 @@ -// simplegit is the single entry point for the simplegit git-hosting server and -// its management TUI. The two used to be separate binaries (cmd/daemon and -// cmd/gitctl); they are now subcommands of one binary. -// -// simplegit daemon run the git hosting server (HTTP + SSH + manage) -// simplegit ctl run the gitctl TUI against the manage port -// simplegit (no args, or any other subcommand) print help -// -// Each subcommand owns its own flag set, so flags are parsed after the -// subcommand is selected: `simplegit daemon -skip-auth`, `simplegit ctl -manage ...`. + + + + + + + + + + package main import ( diff --git a/common/config.go b/common/config.go index 90389af..23853d6 100644 --- a/common/config.go +++ b/common/config.go @@ -9,9 +9,9 @@ import ( "sync/atomic" ) -// Config carries the daemon's startup params. The server needs the git root to -// open repos and the rest to answer the Status RPC (which also hands gitctl the -// db_uri so it can connect directly for inspection reads). + + + type Config struct { Root string RPCAddr string @@ -31,20 +31,20 @@ func (c *Config) RepoRoot() string { return filepath.Join(c.Root, "repos") } -// TemplateDir is the `git init --template` source: /template. The daemon -// materializes it at startup (hooks.EnsureTemplate) so every CreateRepo gets -// forwarding hooks. CreateRepo falls back to plain `git init --bare` if absent. + + + func (c *Config) TemplateDir() string { return filepath.Join(c.Root, "template") } -// HookSock is the unix socket the hook bus listens on: /hook.sock. Hook -// scripts dial it (via `simplegit hook --root=...`) to forward invocations. + + func (c *Config) HookSock() string { return filepath.Join(c.Root, "hook.sock") } -// Prepare checks that the config is valid and creates the repo root if needed. + func (c *Config) Prepare() error { if c.prepared.Load() { return nil @@ -91,20 +91,20 @@ func (c *Config) DBURI() string { return c.preparedDbUri } -// normalizeName strips one trailing ".git" so both "myrepo" and "myrepo.git" -// refer to the same repo. + + func normalizeName(name string) string { return strings.TrimSuffix(name, ".git") } -// relPath returns the repo path relative to root: "owner/name.git". + func relPath(owner, name string) string { return owner + "/" + normalizeName(name) + ".git" } -// RepoPath returns the absolute on-disk path for owner/name, but only if the -// repo is registered in the DB -- the DB is the authority, not the filesystem. -// In skip-auth mode the DB check is skipped (any path under root resolves). + + + func (s *Config) RepoPath(owner, name string) (string, error) { name = normalizeName(name) rel := relPath(owner, name) diff --git a/common/perm.go b/common/perm.go index c601c60..d918f68 100644 --- a/common/perm.go +++ b/common/perm.go @@ -8,8 +8,8 @@ const ( PermAdmin Perm = "admin" ) -// PermRank gives perm a hierarchy for implication checks: a grant of rank N -// satisfies a request of rank <= N. + + func (perm Perm) PermRank() int { switch perm { case PermAdmin: diff --git a/common/repolayout.go b/common/repolayout.go index 5e22186..d6ed7d7 100644 --- a/common/repolayout.go +++ b/common/repolayout.go @@ -46,11 +46,11 @@ func Resolve(root, name string) (string, error) { return full, nil } -// splitOwnerName splits a smart-HTTP/SSH repo path "owner/name.git" into its -// IState (owner, name) form: both without the ".git" suffix. owner may itself -// contain "/" for nested namespaces (e.g. "org/team/name.git" -> owner -// "org/team", name "name"); name is always the final segment. IState -// reconstructs the on-disk path / DB full-name as owner+"/"+name+".git". + + + + + func SplitRepo(repo string) (owner, name string, err error) { s := strings.TrimSuffix(repo, ".git") i := strings.LastIndex(s, "/") @@ -60,12 +60,12 @@ func SplitRepo(repo string) (owner, name string, err error) { return s[:i], s[i+1:], nil } -// parseDBURI maps a URI scheme to (driver, dsn), following simpleconsole's -// ParseDBURI convention so the same URIs work across both services. Unexported: -// Open is the only entry point callers need. -// -// sqlite://[?_pragma=...] -> driver "sqlite3", modernc DSN "file:?..." -// postgres:// -> driver "postgres", dsn passed through verbatim + + + + + + func ParseDBURI(root, uri string) (driver, dsn string, uriNew string, err error) { if uri == "" { return "", "", "", fmt.Errorf("database URI is empty") @@ -83,17 +83,17 @@ func ParseDBURI(root, uri string) (driver, dsn string, uriNew string, err error) uriNew := "sqlite3://" + filepath.Join(root, path) return "sqlite3", sqliteDSN(filepath.Join(root, path), query), uriNew, nil case "postgres", "postgresql": - // lib/pq parses the URL form natively; pass it through unchanged. + return "postgres", uri, uri, nil default: return "", "", "", fmt.Errorf("unsupported database URI scheme %q (want sqlite:// or postgres://)", scheme) } } -// sqliteDSN builds a modernc DSN: "file:?". Default pragmas (WAL, -// busy_timeout, foreign_keys) and _txlock=immediate are applied only when the -// caller supplied no pragmas of their own; an explicit pragma set is respected -// verbatim. + + + + func sqliteDSN(path, query string) string { v, _ := url.ParseQuery(query) if len(v["_pragma"]) == 0 { @@ -105,7 +105,7 @@ func sqliteDSN(path, query string) string { return "file:" + path + "?" + v.Encode() } -// sqlitePath extracts the file path from a modernc DSN ("file:?..."). + func sqlitePath(dsn string) string { s := strings.TrimPrefix(dsn, "file:") if i := strings.Index(s, "?"); i >= 0 { @@ -114,8 +114,8 @@ func sqlitePath(dsn string) string { return s } -// splitScheme splits "scheme:rest" at the first colon. ok is false when there -// is no colon. + + func splitScheme(uri string) (scheme, rest string, ok bool) { i := strings.Index(uri, ":") if i < 0 { diff --git a/gitrpc/middleware.go b/gitrpc/middleware.go index 22a9010..c67d6ad 100644 --- a/gitrpc/middleware.go +++ b/gitrpc/middleware.go @@ -68,11 +68,11 @@ type jwksSource struct { authURL string client *http.Client - // self-registration: when non-empty, every jwks refresh also announces this - // service to the console (RBAC center) via X-Service-* headers, so the - // console can discover and dial this gitrpc server. The console is the - // service-discovery hub; the only side channel it needs is the jwks fetch - // every consumer already makes. + + + + + selfName string selfKind string selfAddr string @@ -97,8 +97,8 @@ func (s *jwksSource) refresh() error { if err != nil { return fmt.Errorf("build jwks request: %w", err) } - // Announce ourselves so the console registers this service's address. The - // console answers jwks AND records the caller as a reachable service. + + if s.selfName != "" && s.selfAddr != "" { req.Header.Set("X-Service-Name", s.selfName) req.Header.Set("X-Service-Kind", s.selfKind) @@ -190,10 +190,10 @@ type MiddlewareClient struct { verifier *Verifier } -// NewMiddleware builds the console RBAC client. selfName/selfKind/selfAddr, when -// non-empty, are announced to the console on every jwks refresh so the console -// registers this server as a reachable service (service discovery). Pass the -// gitrpc HTTP address the console should dial back. + + + + func NewMiddleware(baseURL, selfName, selfKind, selfAddr string) *MiddlewareClient { verifier, err := NewJWKSVerifier(baseURL, selfName, selfKind, selfAddr) if err != nil { @@ -206,11 +206,11 @@ func NewMiddleware(baseURL, selfName, selfKind, selfAddr string) *MiddlewareClie } } -// ParseToken verifies a console-issued JWT and returns its claims. An empty -// token returns (nil, nil) so the caller can distinguish "no credential" -// (anonymous) from "credential present but invalid" (non-nil error): the HTTP -// and SSH access planes use this to decide between anonymous fallthrough and a -// 401. skip-auth callers never invoke this (middleware is nil). + + + + + func (c *MiddlewareClient) ParseToken(tokenStr string) (*Claims, error) { if tokenStr == "" { return nil, nil @@ -248,9 +248,9 @@ func (c *MiddlewareClient) Authz(ctx context.Context, userID int64, repo string, var ErrDenied = errors.New("access denied") -// ErrInvalidToken marks a credential that was present but could not be verified -// (expired, unsigned, malformed). It is distinct from anonymous (no credential -// at all) and from ErrDenied (verified identity lacking permission). + + + var ErrInvalidToken = errors.New("invalid or expired token") func (c *MiddlewareClient) Authn(ctx context.Context, fingerprint string) (userID int64, username string, err error) { diff --git a/gitrpc/server.go b/gitrpc/server.go index c8b046b..bc99d40 100644 --- a/gitrpc/server.go +++ b/gitrpc/server.go @@ -44,14 +44,14 @@ var ( _ v1connect.ManageServiceHandler = (*Server)(nil) ) -// ConnectHandler serves GitService (git kernel ops, per-repo authz). + func (s *Server) ConnectHandler() (string, http.Handler) { return v1connect.NewGitServiceHandler(s, connect.WithInterceptors(s.authIntercept())) } -// ManageConnectHandler serves ManageService (state.IStateMut: repo lifecycle + -// ACL) on a separate port (-manage). Unauthenticated -- the port is -// localhost-only and the caller (Updater/console) is trusted. + + + func (s *Server) ManageConnectHandler() (string, http.Handler) { return v1connect.NewManageServiceHandler(s) } @@ -402,11 +402,11 @@ func (s *Server) Merge(ctx context.Context, req *connect.Request[v1.MergeRequest return connect.NewResponse(mergeResultToV1(res)), nil } -// ---- ManageService handlers (system token; backed by state.IStateMut) ---- -// -// These delegate to the self-contained state DB (s.mut) for repo lifecycle and -// ACL grants. Unauthenticated (localhost-only -manage port); the state layer -// does the real work. + + + + + func (s *Server) CreateRepo(ctx context.Context, req *connect.Request[v1.CreateRepoRequest]) (*connect.Response[emptypb.Empty], error) { if err := s.mut.CreateRepo(req.Msg.Ns, req.Msg.Name); err != nil { @@ -490,12 +490,12 @@ func (s *Server) ACLDelete(ctx context.Context, req *connect.Request[v1.ACLDelet return connect.NewResponse(&emptypb.Empty{}), nil } -// ---- ManageService Status (diagnostics) ---- -// -// Returns the daemon's startup params, listen locations, start time / uptime, -// and the state DB URI. gitctl uses db_uri to connect to the DB directly for -// inspection reads (there is no list RPC on this service); the state package -// stays free of List*/management-read methods. + + + + + + func (s *Server) Status(ctx context.Context, req *connect.Request[emptypb.Empty]) (*connect.Response[v1.StatusResponse], error) { return connect.NewResponse(&v1.StatusResponse{ @@ -511,8 +511,8 @@ func (s *Server) Status(ctx context.Context, req *connect.Request[emptypb.Empty] }), nil } -// parsePublicKey parses authorized-keys text into an ssh.PublicKey (the first -// key if multiple are present). + + func parsePublicKey(text string) (ssh.PublicKey, error) { key, _, _, _, err := ssh.ParseAuthorizedKey([]byte(text)) if err != nil { @@ -521,7 +521,7 @@ func parsePublicKey(text string) (ssh.PublicKey, error) { return key, nil } -// toManageError maps state-layer errors to connect codes. + func toManageError(err error) error { switch { case errors.Is(err, state.ErrRepoNotFound), errors.Is(err, state.ErrNamespaceNotFound): diff --git a/gitrpc/v1/gitrpc.pb.go b/gitrpc/v1/gitrpc.pb.go index 7f61b6f..7a79e4c 100644 --- a/gitrpc/v1/gitrpc.pb.go +++ b/gitrpc/v1/gitrpc.pb.go @@ -1,29 +1,29 @@ -// Connect (crpc) contract for the gitrpc JSON-RPC server. -// -// This is a faithful translation of the hand-written JSON-RPC method set in -// handlers.go (registerRepoMethods + registerWriteMethods) plus the gitcmd -// result types those handlers return verbatim. Every rpc below maps 1:1 to a -// "repo." JSON-RPC method (noted in each comment); every message field -// keeps the snake_case name of the corresponding Go struct json tag so the -// wire shape matches what gitrpc serializes today. -// -// The server is path-driven and stateless: every method takes the repository -// NAME (owner/name.git) in `repo`; the server resolves it to an on-disk path -// and has already authorized the call before the handler runs. Auth/ACL and -// name->path resolution live in the caller (simpleconsole), not here. -// -// Generate Go + TS with protoc-gen-es (TS) / protoc-gen-go + protoc-gen-connect-go: -// protoc -I . --go_out=. --go_opt=paths=source_relative \ -// --connect_go_out=. --connect_go_opt=paths=source_relative \ -// --es_out=./gen --es_opt=target=ts \ -// --plugin=protoc-gen-es=./node_modules/.bin/protoc-gen-es \ -// gitrpc.proto -// Code generated by protoc-gen-go. DO NOT EDIT. -// versions: -// protoc-gen-go v1.36.11 -// protoc v7.35.0 -// source: gitrpc/gitrpc.proto + + + + + + + + + + + + + + + + + + + + + + + + + package v1 @@ -38,14 +38,14 @@ import ( ) const ( - // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) - // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) -// repoParams: the repository name (owner/name.git). JSON key is "repo" so it -// never collides with the in-repo file "path" param several methods also carry. + + type RepoRequest struct { state protoimpl.MessageState `protogen:"open.v1"` Repo string `protobuf:"bytes,1,opt,name=repo,proto3" json:"repo,omitempty"` @@ -78,7 +78,7 @@ func (x *RepoRequest) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use RepoRequest.ProtoReflect.Descriptor instead. + func (*RepoRequest) Descriptor() ([]byte, []int) { return file_gitrpc_gitrpc_proto_rawDescGZIP(), []int{0} } @@ -93,7 +93,7 @@ func (x *RepoRequest) GetRepo() string { type ListCommitsRequest struct { state protoimpl.MessageState `protogen:"open.v1"` Repo string `protobuf:"bytes,1,opt,name=repo,proto3" json:"repo,omitempty"` - Ref string `protobuf:"bytes,2,opt,name=ref,proto3" json:"ref,omitempty"` // "" -> HEAD (server-side default) + Ref string `protobuf:"bytes,2,opt,name=ref,proto3" json:"ref,omitempty"` Limit int32 `protobuf:"varint,3,opt,name=limit,proto3" json:"limit,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache @@ -124,7 +124,7 @@ func (x *ListCommitsRequest) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use ListCommitsRequest.ProtoReflect.Descriptor instead. + func (*ListCommitsRequest) Descriptor() ([]byte, []int) { return file_gitrpc_gitrpc_proto_rawDescGZIP(), []int{1} } @@ -185,7 +185,7 @@ func (x *ListCommitsByPathRequest) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use ListCommitsByPathRequest.ProtoReflect.Descriptor instead. + func (*ListCommitsByPathRequest) Descriptor() ([]byte, []int) { return file_gitrpc_gitrpc_proto_rawDescGZIP(), []int{2} } @@ -251,7 +251,7 @@ func (x *GetCommitRequest) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use GetCommitRequest.ProtoReflect.Descriptor instead. + func (*GetCommitRequest) Descriptor() ([]byte, []int) { return file_gitrpc_gitrpc_proto_rawDescGZIP(), []int{3} } @@ -303,7 +303,7 @@ func (x *GetCommitDiffRequest) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use GetCommitDiffRequest.ProtoReflect.Descriptor instead. + func (*GetCommitDiffRequest) Descriptor() ([]byte, []int) { return file_gitrpc_gitrpc_proto_rawDescGZIP(), []int{4} } @@ -326,7 +326,7 @@ type GetTreeRequest struct { state protoimpl.MessageState `protogen:"open.v1"` Repo string `protobuf:"bytes,1,opt,name=repo,proto3" json:"repo,omitempty"` Ref string `protobuf:"bytes,2,opt,name=ref,proto3" json:"ref,omitempty"` - Path string `protobuf:"bytes,3,opt,name=path,proto3" json:"path,omitempty"` // "" for repo root + Path string `protobuf:"bytes,3,opt,name=path,proto3" json:"path,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -356,7 +356,7 @@ func (x *GetTreeRequest) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use GetTreeRequest.ProtoReflect.Descriptor instead. + func (*GetTreeRequest) Descriptor() ([]byte, []int) { return file_gitrpc_gitrpc_proto_rawDescGZIP(), []int{5} } @@ -416,7 +416,7 @@ func (x *GetBlobRequest) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use GetBlobRequest.ProtoReflect.Descriptor instead. + func (*GetBlobRequest) Descriptor() ([]byte, []int) { return file_gitrpc_gitrpc_proto_rawDescGZIP(), []int{6} } @@ -445,8 +445,8 @@ func (x *GetBlobRequest) GetPath() string { type CompareRequest struct { state protoimpl.MessageState `protogen:"open.v1"` Repo string `protobuf:"bytes,1,opt,name=repo,proto3" json:"repo,omitempty"` - Base string `protobuf:"bytes,2,opt,name=base,proto3" json:"base,omitempty"` // "" -> HEAD - Head string `protobuf:"bytes,3,opt,name=head,proto3" json:"head,omitempty"` // "" -> HEAD + Base string `protobuf:"bytes,2,opt,name=base,proto3" json:"base,omitempty"` + Head string `protobuf:"bytes,3,opt,name=head,proto3" json:"head,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -476,7 +476,7 @@ func (x *CompareRequest) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use CompareRequest.ProtoReflect.Descriptor instead. + func (*CompareRequest) Descriptor() ([]byte, []int) { return file_gitrpc_gitrpc_proto_rawDescGZIP(), []int{7} } @@ -536,7 +536,7 @@ func (x *BlameRequest) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use BlameRequest.ProtoReflect.Descriptor instead. + func (*BlameRequest) Descriptor() ([]byte, []int) { return file_gitrpc_gitrpc_proto_rawDescGZIP(), []int{8} } @@ -565,7 +565,7 @@ func (x *BlameRequest) GetPath() string { type GetContributorsRequest struct { state protoimpl.MessageState `protogen:"open.v1"` Repo string `protobuf:"bytes,1,opt,name=repo,proto3" json:"repo,omitempty"` - Ref string `protobuf:"bytes,2,opt,name=ref,proto3" json:"ref,omitempty"` // "" -> default branch + Ref string `protobuf:"bytes,2,opt,name=ref,proto3" json:"ref,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -595,7 +595,7 @@ func (x *GetContributorsRequest) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use GetContributorsRequest.ProtoReflect.Descriptor instead. + func (*GetContributorsRequest) Descriptor() ([]byte, []int) { return file_gitrpc_gitrpc_proto_rawDescGZIP(), []int{9} } @@ -623,8 +623,8 @@ type CreateFileRequest struct { Message string `protobuf:"bytes,5,opt,name=message,proto3" json:"message,omitempty"` AuthorName string `protobuf:"bytes,6,opt,name=author_name,json=authorName,proto3" json:"author_name,omitempty"` AuthorEmail string `protobuf:"bytes,7,opt,name=author_email,json=authorEmail,proto3" json:"author_email,omitempty"` - ExpectedSha string `protobuf:"bytes,8,opt,name=expected_sha,json=expectedSha,proto3" json:"expected_sha,omitempty"` // atomic ref update against this branch tip - Strategy string `protobuf:"bytes,9,opt,name=strategy,proto3" json:"strategy,omitempty"` // "" / "plumbing" / "temprepo" + ExpectedSha string `protobuf:"bytes,8,opt,name=expected_sha,json=expectedSha,proto3" json:"expected_sha,omitempty"` + Strategy string `protobuf:"bytes,9,opt,name=strategy,proto3" json:"strategy,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -654,7 +654,7 @@ func (x *CreateFileRequest) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use CreateFileRequest.ProtoReflect.Descriptor instead. + func (*CreateFileRequest) Descriptor() ([]byte, []int) { return file_gitrpc_gitrpc_proto_rawDescGZIP(), []int{10} } @@ -761,7 +761,7 @@ func (x *DeleteFileRequest) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use DeleteFileRequest.ProtoReflect.Descriptor instead. + func (*DeleteFileRequest) Descriptor() ([]byte, []int) { return file_gitrpc_gitrpc_proto_rawDescGZIP(), []int{11} } @@ -861,7 +861,7 @@ func (x *MergeRequest) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use MergeRequest.ProtoReflect.Descriptor instead. + func (*MergeRequest) Descriptor() ([]byte, []int) { return file_gitrpc_gitrpc_proto_rawDescGZIP(), []int{12} } @@ -955,7 +955,7 @@ func (x *CreateRepoRequest) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use CreateRepoRequest.ProtoReflect.Descriptor instead. + func (*CreateRepoRequest) Descriptor() ([]byte, []int) { return file_gitrpc_gitrpc_proto_rawDescGZIP(), []int{13} } @@ -1007,7 +1007,7 @@ func (x *ExistRepoRequest) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use ExistRepoRequest.ProtoReflect.Descriptor instead. + func (*ExistRepoRequest) Descriptor() ([]byte, []int) { return file_gitrpc_gitrpc_proto_rawDescGZIP(), []int{14} } @@ -1059,7 +1059,7 @@ func (x *DeleteRepoRequest) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use DeleteRepoRequest.ProtoReflect.Descriptor instead. + func (*DeleteRepoRequest) Descriptor() ([]byte, []int) { return file_gitrpc_gitrpc_proto_rawDescGZIP(), []int{15} } @@ -1080,8 +1080,8 @@ func (x *DeleteRepoRequest) GetName() string { type MoveRepoRequest struct { state protoimpl.MessageState `protogen:"open.v1"` - Src string `protobuf:"bytes,1,opt,name=src,proto3" json:"src,omitempty"` // old ns/name - Dst string `protobuf:"bytes,2,opt,name=dst,proto3" json:"dst,omitempty"` // new ns/name + Src string `protobuf:"bytes,1,opt,name=src,proto3" json:"src,omitempty"` + Dst string `protobuf:"bytes,2,opt,name=dst,proto3" json:"dst,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -1111,7 +1111,7 @@ func (x *MoveRepoRequest) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use MoveRepoRequest.ProtoReflect.Descriptor instead. + func (*MoveRepoRequest) Descriptor() ([]byte, []int) { return file_gitrpc_gitrpc_proto_rawDescGZIP(), []int{16} } @@ -1132,8 +1132,8 @@ func (x *MoveRepoRequest) GetDst() string { type MoveNSRequest struct { state protoimpl.MessageState `protogen:"open.v1"` - Src string `protobuf:"bytes,1,opt,name=src,proto3" json:"src,omitempty"` // old namespace - Dst string `protobuf:"bytes,2,opt,name=dst,proto3" json:"dst,omitempty"` // new namespace + Src string `protobuf:"bytes,1,opt,name=src,proto3" json:"src,omitempty"` + Dst string `protobuf:"bytes,2,opt,name=dst,proto3" json:"dst,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -1163,7 +1163,7 @@ func (x *MoveNSRequest) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use MoveNSRequest.ProtoReflect.Descriptor instead. + func (*MoveNSRequest) Descriptor() ([]byte, []int) { return file_gitrpc_gitrpc_proto_rawDescGZIP(), []int{17} } @@ -1185,8 +1185,8 @@ func (x *MoveNSRequest) GetDst() string { type ACLUpsertSSHKeyOnNSRequest struct { state protoimpl.MessageState `protogen:"open.v1"` Ns string `protobuf:"bytes,1,opt,name=ns,proto3" json:"ns,omitempty"` - Key string `protobuf:"bytes,2,opt,name=key,proto3" json:"key,omitempty"` // authorized-keys text - Perm string `protobuf:"bytes,3,opt,name=perm,proto3" json:"perm,omitempty"` // read | write | admin + Key string `protobuf:"bytes,2,opt,name=key,proto3" json:"key,omitempty"` + Perm string `protobuf:"bytes,3,opt,name=perm,proto3" json:"perm,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -1216,7 +1216,7 @@ func (x *ACLUpsertSSHKeyOnNSRequest) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use ACLUpsertSSHKeyOnNSRequest.ProtoReflect.Descriptor instead. + func (*ACLUpsertSSHKeyOnNSRequest) Descriptor() ([]byte, []int) { return file_gitrpc_gitrpc_proto_rawDescGZIP(), []int{18} } @@ -1245,7 +1245,7 @@ func (x *ACLUpsertSSHKeyOnNSRequest) GetPerm() string { type ACLUpsertPATOnNSRequest struct { state protoimpl.MessageState `protogen:"open.v1"` Ns string `protobuf:"bytes,1,opt,name=ns,proto3" json:"ns,omitempty"` - Pat string `protobuf:"bytes,2,opt,name=pat,proto3" json:"pat,omitempty"` // plaintext PAT + Pat string `protobuf:"bytes,2,opt,name=pat,proto3" json:"pat,omitempty"` Perm string `protobuf:"bytes,3,opt,name=perm,proto3" json:"perm,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache @@ -1276,7 +1276,7 @@ func (x *ACLUpsertPATOnNSRequest) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use ACLUpsertPATOnNSRequest.ProtoReflect.Descriptor instead. + func (*ACLUpsertPATOnNSRequest) Descriptor() ([]byte, []int) { return file_gitrpc_gitrpc_proto_rawDescGZIP(), []int{19} } @@ -1304,7 +1304,7 @@ func (x *ACLUpsertPATOnNSRequest) GetPerm() string { type ACLUpsertSSHKeyOnRepoRequest struct { state protoimpl.MessageState `protogen:"open.v1"` - Reponame string `protobuf:"bytes,1,opt,name=reponame,proto3" json:"reponame,omitempty"` // ns/name + Reponame string `protobuf:"bytes,1,opt,name=reponame,proto3" json:"reponame,omitempty"` Key string `protobuf:"bytes,2,opt,name=key,proto3" json:"key,omitempty"` Perm string `protobuf:"bytes,3,opt,name=perm,proto3" json:"perm,omitempty"` unknownFields protoimpl.UnknownFields @@ -1336,7 +1336,7 @@ func (x *ACLUpsertSSHKeyOnRepoRequest) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use ACLUpsertSSHKeyOnRepoRequest.ProtoReflect.Descriptor instead. + func (*ACLUpsertSSHKeyOnRepoRequest) Descriptor() ([]byte, []int) { return file_gitrpc_gitrpc_proto_rawDescGZIP(), []int{20} } @@ -1396,7 +1396,7 @@ func (x *ACLUpsertPATOnRepoRequest) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use ACLUpsertPATOnRepoRequest.ProtoReflect.Descriptor instead. + func (*ACLUpsertPATOnRepoRequest) Descriptor() ([]byte, []int) { return file_gitrpc_gitrpc_proto_rawDescGZIP(), []int{21} } @@ -1454,7 +1454,7 @@ func (x *ACLDeleteRequest) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use ACLDeleteRequest.ProtoReflect.Descriptor instead. + func (*ACLDeleteRequest) Descriptor() ([]byte, []int) { return file_gitrpc_gitrpc_proto_rawDescGZIP(), []int{22} } @@ -1466,21 +1466,21 @@ func (x *ACLDeleteRequest) GetAclId() int64 { return 0 } -// StatusResponse is the daemon's config + runtime snapshot. db_uri is the state -// DB connection string (sqlite:// or postgres://) gitctl connects to -// directly for inspection reads; it carries credentials when postgres, so the -// manage port must stay localhost-only/trusted. + + + + type StatusResponse struct { state protoimpl.MessageState `protogen:"open.v1"` - Root string `protobuf:"bytes,1,opt,name=root,proto3" json:"root,omitempty"` // -root (git root) - RpcAddr string `protobuf:"bytes,2,opt,name=rpc_addr,json=rpcAddr,proto3" json:"rpc_addr,omitempty"` // -rpc listen - SshAddr string `protobuf:"bytes,3,opt,name=ssh_addr,json=sshAddr,proto3" json:"ssh_addr,omitempty"` // -ssh listen - ManageAddr string `protobuf:"bytes,4,opt,name=manage_addr,json=manageAddr,proto3" json:"manage_addr,omitempty"` // -manage listen - AuthUrl string `protobuf:"bytes,5,opt,name=auth_url,json=authUrl,proto3" json:"auth_url,omitempty"` // -auth RBAC center ("" if skip-auth) - DbUri string `protobuf:"bytes,6,opt,name=db_uri,json=dbUri,proto3" json:"db_uri,omitempty"` // -db state DB URI - SkipAuth bool `protobuf:"varint,7,opt,name=skip_auth,json=skipAuth,proto3" json:"skip_auth,omitempty"` // -skip-auth - StartedAt *timestamppb.Timestamp `protobuf:"bytes,8,opt,name=started_at,json=startedAt,proto3" json:"started_at,omitempty"` // daemon start time - UptimeSeconds int64 `protobuf:"varint,9,opt,name=uptime_seconds,json=uptimeSeconds,proto3" json:"uptime_seconds,omitempty"` // seconds since started_at + Root string `protobuf:"bytes,1,opt,name=root,proto3" json:"root,omitempty"` + RpcAddr string `protobuf:"bytes,2,opt,name=rpc_addr,json=rpcAddr,proto3" json:"rpc_addr,omitempty"` + SshAddr string `protobuf:"bytes,3,opt,name=ssh_addr,json=sshAddr,proto3" json:"ssh_addr,omitempty"` + ManageAddr string `protobuf:"bytes,4,opt,name=manage_addr,json=manageAddr,proto3" json:"manage_addr,omitempty"` + AuthUrl string `protobuf:"bytes,5,opt,name=auth_url,json=authUrl,proto3" json:"auth_url,omitempty"` + DbUri string `protobuf:"bytes,6,opt,name=db_uri,json=dbUri,proto3" json:"db_uri,omitempty"` + SkipAuth bool `protobuf:"varint,7,opt,name=skip_auth,json=skipAuth,proto3" json:"skip_auth,omitempty"` + StartedAt *timestamppb.Timestamp `protobuf:"bytes,8,opt,name=started_at,json=startedAt,proto3" json:"started_at,omitempty"` + UptimeSeconds int64 `protobuf:"varint,9,opt,name=uptime_seconds,json=uptimeSeconds,proto3" json:"uptime_seconds,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -1510,7 +1510,7 @@ func (x *StatusResponse) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use StatusResponse.ProtoReflect.Descriptor instead. + func (*StatusResponse) Descriptor() ([]byte, []int) { return file_gitrpc_gitrpc_proto_rawDescGZIP(), []int{23} } @@ -1610,7 +1610,7 @@ func (x *ACLIDResponse) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use ACLIDResponse.ProtoReflect.Descriptor instead. + func (*ACLIDResponse) Descriptor() ([]byte, []int) { return file_gitrpc_gitrpc_proto_rawDescGZIP(), []int{24} } @@ -1654,7 +1654,7 @@ func (x *ListBranchesResponse) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use ListBranchesResponse.ProtoReflect.Descriptor instead. + func (*ListBranchesResponse) Descriptor() ([]byte, []int) { return file_gitrpc_gitrpc_proto_rawDescGZIP(), []int{25} } @@ -1698,7 +1698,7 @@ func (x *ListTagsResponse) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use ListTagsResponse.ProtoReflect.Descriptor instead. + func (*ListTagsResponse) Descriptor() ([]byte, []int) { return file_gitrpc_gitrpc_proto_rawDescGZIP(), []int{26} } @@ -1742,7 +1742,7 @@ func (x *ListCommitsResponse) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use ListCommitsResponse.ProtoReflect.Descriptor instead. + func (*ListCommitsResponse) Descriptor() ([]byte, []int) { return file_gitrpc_gitrpc_proto_rawDescGZIP(), []int{27} } @@ -1786,7 +1786,7 @@ func (x *GetContributorsResponse) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use GetContributorsResponse.ProtoReflect.Descriptor instead. + func (*GetContributorsResponse) Descriptor() ([]byte, []int) { return file_gitrpc_gitrpc_proto_rawDescGZIP(), []int{28} } @@ -1798,17 +1798,17 @@ func (x *GetContributorsResponse) GetContributors() []*Contributor { return nil } -// gitcmd.Commit. Pinned snake_case json so renaming Go fields does not drift -// the wire contract. + + type Commit struct { state protoimpl.MessageState `protogen:"open.v1"` - Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` // full 40-char SHA - ShortSha string `protobuf:"bytes,2,opt,name=short_sha,json=shortSha,proto3" json:"short_sha,omitempty"` // 7-char abbreviated SHA - Message string `protobuf:"bytes,3,opt,name=message,proto3" json:"message,omitempty"` // full commit message - Author string `protobuf:"bytes,4,opt,name=author,proto3" json:"author,omitempty"` // author name - AuthorEmail string `protobuf:"bytes,5,opt,name=author_email,json=authorEmail,proto3" json:"author_email,omitempty"` // author email - When *timestamppb.Timestamp `protobuf:"bytes,6,opt,name=when,proto3" json:"when,omitempty"` // author timestamp - Parents []string `protobuf:"bytes,7,rep,name=parents,proto3" json:"parents,omitempty"` // parent SHAs + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + ShortSha string `protobuf:"bytes,2,opt,name=short_sha,json=shortSha,proto3" json:"short_sha,omitempty"` + Message string `protobuf:"bytes,3,opt,name=message,proto3" json:"message,omitempty"` + Author string `protobuf:"bytes,4,opt,name=author,proto3" json:"author,omitempty"` + AuthorEmail string `protobuf:"bytes,5,opt,name=author_email,json=authorEmail,proto3" json:"author_email,omitempty"` + When *timestamppb.Timestamp `protobuf:"bytes,6,opt,name=when,proto3" json:"when,omitempty"` + Parents []string `protobuf:"bytes,7,rep,name=parents,proto3" json:"parents,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -1838,7 +1838,7 @@ func (x *Commit) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use Commit.ProtoReflect.Descriptor instead. + func (*Commit) Descriptor() ([]byte, []int) { return file_gitrpc_gitrpc_proto_rawDescGZIP(), []int{29} } @@ -1892,8 +1892,8 @@ func (x *Commit) GetParents() []string { return nil } -// gitcmd.CommitDetail extends Commit with file change statistics. Commit fields -// are nested (proto has no struct embedding) rather than flattened. + + type CommitDetail struct { state protoimpl.MessageState `protogen:"open.v1"` Commit *Commit `protobuf:"bytes,1,opt,name=commit,proto3" json:"commit,omitempty"` @@ -1928,7 +1928,7 @@ func (x *CommitDetail) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use CommitDetail.ProtoReflect.Descriptor instead. + func (*CommitDetail) Descriptor() ([]byte, []int) { return file_gitrpc_gitrpc_proto_rawDescGZIP(), []int{30} } @@ -1989,7 +1989,7 @@ func (x *DiffStats) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use DiffStats.ProtoReflect.Descriptor instead. + func (*DiffStats) Descriptor() ([]byte, []int) { return file_gitrpc_gitrpc_proto_rawDescGZIP(), []int{31} } @@ -2027,7 +2027,7 @@ type FileDiff struct { Path string `protobuf:"bytes,1,opt,name=path,proto3" json:"path,omitempty"` Additions int32 `protobuf:"varint,2,opt,name=additions,proto3" json:"additions,omitempty"` Deletions int32 `protobuf:"varint,3,opt,name=deletions,proto3" json:"deletions,omitempty"` - Status string `protobuf:"bytes,4,opt,name=status,proto3" json:"status,omitempty"` // A (added), M (modified), D (deleted), R (renamed) + Status string `protobuf:"bytes,4,opt,name=status,proto3" json:"status,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -2057,7 +2057,7 @@ func (x *FileDiff) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use FileDiff.ProtoReflect.Descriptor instead. + func (*FileDiff) Descriptor() ([]byte, []int) { return file_gitrpc_gitrpc_proto_rawDescGZIP(), []int{32} } @@ -2128,7 +2128,7 @@ func (x *Branch) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use Branch.ProtoReflect.Descriptor instead. + func (*Branch) Descriptor() ([]byte, []int) { return file_gitrpc_gitrpc_proto_rawDescGZIP(), []int{33} } @@ -2220,7 +2220,7 @@ func (x *Tag) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use Tag.ProtoReflect.Descriptor instead. + func (*Tag) Descriptor() ([]byte, []int) { return file_gitrpc_gitrpc_proto_rawDescGZIP(), []int{34} } @@ -2278,9 +2278,9 @@ type TreeEntry struct { state protoimpl.MessageState `protogen:"open.v1"` Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` Path string `protobuf:"bytes,2,opt,name=path,proto3" json:"path,omitempty"` - Type string `protobuf:"bytes,3,opt,name=type,proto3" json:"type,omitempty"` // "blob" (file) or "tree" (directory) - Mode string `protobuf:"bytes,4,opt,name=mode,proto3" json:"mode,omitempty"` // git mode, e.g. "100644", "040000" - Size int64 `protobuf:"varint,5,opt,name=size,proto3" json:"size,omitempty"` // file size in bytes (0 for directories) + Type string `protobuf:"bytes,3,opt,name=type,proto3" json:"type,omitempty"` + Mode string `protobuf:"bytes,4,opt,name=mode,proto3" json:"mode,omitempty"` + Size int64 `protobuf:"varint,5,opt,name=size,proto3" json:"size,omitempty"` Sha string `protobuf:"bytes,6,opt,name=sha,proto3" json:"sha,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache @@ -2311,7 +2311,7 @@ func (x *TreeEntry) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use TreeEntry.ProtoReflect.Descriptor instead. + func (*TreeEntry) Descriptor() ([]byte, []int) { return file_gitrpc_gitrpc_proto_rawDescGZIP(), []int{35} } @@ -2392,7 +2392,7 @@ func (x *Tree) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use Tree.ProtoReflect.Descriptor instead. + func (*Tree) Descriptor() ([]byte, []int) { return file_gitrpc_gitrpc_proto_rawDescGZIP(), []int{36} } @@ -2422,8 +2422,8 @@ type Blob struct { state protoimpl.MessageState `protogen:"open.v1"` Ref string `protobuf:"bytes,1,opt,name=ref,proto3" json:"ref,omitempty"` Path string `protobuf:"bytes,2,opt,name=path,proto3" json:"path,omitempty"` - Content string `protobuf:"bytes,3,opt,name=content,proto3" json:"content,omitempty"` // raw file content (text or base64-encoded binary) - Encoding string `protobuf:"bytes,4,opt,name=encoding,proto3" json:"encoding,omitempty"` // "text" or "base64" + Content string `protobuf:"bytes,3,opt,name=content,proto3" json:"content,omitempty"` + Encoding string `protobuf:"bytes,4,opt,name=encoding,proto3" json:"encoding,omitempty"` Size int64 `protobuf:"varint,5,opt,name=size,proto3" json:"size,omitempty"` Sha string `protobuf:"bytes,6,opt,name=sha,proto3" json:"sha,omitempty"` IsBinary bool `protobuf:"varint,7,opt,name=is_binary,json=isBinary,proto3" json:"is_binary,omitempty"` @@ -2456,7 +2456,7 @@ func (x *Blob) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use Blob.ProtoReflect.Descriptor instead. + func (*Blob) Descriptor() ([]byte, []int) { return file_gitrpc_gitrpc_proto_rawDescGZIP(), []int{37} } @@ -2512,8 +2512,8 @@ func (x *Blob) GetIsBinary() bool { type DiffLine struct { state protoimpl.MessageState `protogen:"open.v1"` - Type string `protobuf:"bytes,1,opt,name=type,proto3" json:"type,omitempty"` // "context", "add", "delete" - Text string `protobuf:"bytes,2,opt,name=text,proto3" json:"text,omitempty"` // line content without the leading sigil + Type string `protobuf:"bytes,1,opt,name=type,proto3" json:"type,omitempty"` + Text string `protobuf:"bytes,2,opt,name=text,proto3" json:"text,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -2543,7 +2543,7 @@ func (x *DiffLine) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use DiffLine.ProtoReflect.Descriptor instead. + func (*DiffLine) Descriptor() ([]byte, []int) { return file_gitrpc_gitrpc_proto_rawDescGZIP(), []int{38} } @@ -2564,9 +2564,9 @@ func (x *DiffLine) GetText() string { type Hunk struct { state protoimpl.MessageState `protogen:"open.v1"` - OldStart int32 `protobuf:"varint,1,opt,name=old_start,json=oldStart,proto3" json:"old_start,omitempty"` // 0 if the old side is absent + OldStart int32 `protobuf:"varint,1,opt,name=old_start,json=oldStart,proto3" json:"old_start,omitempty"` OldCount int32 `protobuf:"varint,2,opt,name=old_count,json=oldCount,proto3" json:"old_count,omitempty"` - NewStart int32 `protobuf:"varint,3,opt,name=new_start,json=newStart,proto3" json:"new_start,omitempty"` // 0 if the new side is absent + NewStart int32 `protobuf:"varint,3,opt,name=new_start,json=newStart,proto3" json:"new_start,omitempty"` NewCount int32 `protobuf:"varint,4,opt,name=new_count,json=newCount,proto3" json:"new_count,omitempty"` Lines []*DiffLine `protobuf:"bytes,5,rep,name=lines,proto3" json:"lines,omitempty"` unknownFields protoimpl.UnknownFields @@ -2598,7 +2598,7 @@ func (x *Hunk) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use Hunk.ProtoReflect.Descriptor instead. + func (*Hunk) Descriptor() ([]byte, []int) { return file_gitrpc_gitrpc_proto_rawDescGZIP(), []int{39} } @@ -2640,9 +2640,9 @@ func (x *Hunk) GetLines() []*DiffLine { type FileChange struct { state protoimpl.MessageState `protogen:"open.v1"` - Path string `protobuf:"bytes,6,opt,name=path,proto3" json:"path,omitempty"` // resulting path (new path; old path if deleted) - OldPath string `protobuf:"bytes,1,opt,name=old_path,json=oldPath,proto3" json:"old_path,omitempty"` // set only on rename/copy - Status string `protobuf:"bytes,2,opt,name=status,proto3" json:"status,omitempty"` // A / M / D / R + Path string `protobuf:"bytes,6,opt,name=path,proto3" json:"path,omitempty"` + OldPath string `protobuf:"bytes,1,opt,name=old_path,json=oldPath,proto3" json:"old_path,omitempty"` + Status string `protobuf:"bytes,2,opt,name=status,proto3" json:"status,omitempty"` IsBinary bool `protobuf:"varint,3,opt,name=is_binary,json=isBinary,proto3" json:"is_binary,omitempty"` Additions int32 `protobuf:"varint,4,opt,name=additions,proto3" json:"additions,omitempty"` Deletions int32 `protobuf:"varint,5,opt,name=deletions,proto3" json:"deletions,omitempty"` @@ -2676,7 +2676,7 @@ func (x *FileChange) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use FileChange.ProtoReflect.Descriptor instead. + func (*FileChange) Descriptor() ([]byte, []int) { return file_gitrpc_gitrpc_proto_rawDescGZIP(), []int{40} } @@ -2762,7 +2762,7 @@ func (x *DiffResult) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use DiffResult.ProtoReflect.Descriptor instead. + func (*DiffResult) Descriptor() ([]byte, []int) { return file_gitrpc_gitrpc_proto_rawDescGZIP(), []int{41} } @@ -2778,10 +2778,10 @@ type CompareResult struct { state protoimpl.MessageState `protogen:"open.v1"` Base string `protobuf:"bytes,1,opt,name=base,proto3" json:"base,omitempty"` Head string `protobuf:"bytes,2,opt,name=head,proto3" json:"head,omitempty"` - Ahead int32 `protobuf:"varint,3,opt,name=ahead,proto3" json:"ahead,omitempty"` // commits reachable from head but not base - Behind int32 `protobuf:"varint,4,opt,name=behind,proto3" json:"behind,omitempty"` // commits reachable from base but not head - Commits []*Commit `protobuf:"bytes,5,rep,name=commits,proto3" json:"commits,omitempty"` // base..head, newest first - Diff *DiffResult `protobuf:"bytes,6,opt,name=diff,proto3" json:"diff,omitempty"` // combined file diff base..head + Ahead int32 `protobuf:"varint,3,opt,name=ahead,proto3" json:"ahead,omitempty"` + Behind int32 `protobuf:"varint,4,opt,name=behind,proto3" json:"behind,omitempty"` + Commits []*Commit `protobuf:"bytes,5,rep,name=commits,proto3" json:"commits,omitempty"` + Diff *DiffResult `protobuf:"bytes,6,opt,name=diff,proto3" json:"diff,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -2811,7 +2811,7 @@ func (x *CompareResult) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use CompareResult.ProtoReflect.Descriptor instead. + func (*CompareResult) Descriptor() ([]byte, []int) { return file_gitrpc_gitrpc_proto_rawDescGZIP(), []int{42} } @@ -2865,9 +2865,9 @@ type BlameLine struct { Author string `protobuf:"bytes,3,opt,name=author,proto3" json:"author,omitempty"` Email string `protobuf:"bytes,4,opt,name=email,proto3" json:"email,omitempty"` When *timestamppb.Timestamp `protobuf:"bytes,5,opt,name=when,proto3" json:"when,omitempty"` - LineNo int32 `protobuf:"varint,6,opt,name=line_no,json=lineNo,proto3" json:"line_no,omitempty"` // 1-based line number in the blamed file + LineNo int32 `protobuf:"varint,6,opt,name=line_no,json=lineNo,proto3" json:"line_no,omitempty"` Content string `protobuf:"bytes,7,opt,name=content,proto3" json:"content,omitempty"` - Summary string `protobuf:"bytes,8,opt,name=summary,proto3" json:"summary,omitempty"` // commit subject line + Summary string `protobuf:"bytes,8,opt,name=summary,proto3" json:"summary,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -2897,7 +2897,7 @@ func (x *BlameLine) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use BlameLine.ProtoReflect.Descriptor instead. + func (*BlameLine) Descriptor() ([]byte, []int) { return file_gitrpc_gitrpc_proto_rawDescGZIP(), []int{43} } @@ -2992,7 +2992,7 @@ func (x *BlameResult) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use BlameResult.ProtoReflect.Descriptor instead. + func (*BlameResult) Descriptor() ([]byte, []int) { return file_gitrpc_gitrpc_proto_rawDescGZIP(), []int{44} } @@ -3055,7 +3055,7 @@ func (x *Contributor) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use Contributor.ProtoReflect.Descriptor instead. + func (*Contributor) Descriptor() ([]byte, []int) { return file_gitrpc_gitrpc_proto_rawDescGZIP(), []int{45} } @@ -3107,7 +3107,7 @@ type RepoStats struct { TotalCommits int32 `protobuf:"varint,1,opt,name=total_commits,json=totalCommits,proto3" json:"total_commits,omitempty"` TotalBranches int32 `protobuf:"varint,2,opt,name=total_branches,json=totalBranches,proto3" json:"total_branches,omitempty"` TotalTags int32 `protobuf:"varint,3,opt,name=total_tags,json=totalTags,proto3" json:"total_tags,omitempty"` - TotalSize int64 `protobuf:"varint,4,opt,name=total_size,json=totalSize,proto3" json:"total_size,omitempty"` // git object size in bytes (count-objects) + TotalSize int64 `protobuf:"varint,4,opt,name=total_size,json=totalSize,proto3" json:"total_size,omitempty"` Contributors []*Contributor `protobuf:"bytes,5,rep,name=contributors,proto3" json:"contributors,omitempty"` FirstCommit *timestamppb.Timestamp `protobuf:"bytes,6,opt,name=first_commit,json=firstCommit,proto3" json:"first_commit,omitempty"` LastCommit *timestamppb.Timestamp `protobuf:"bytes,7,opt,name=last_commit,json=lastCommit,proto3" json:"last_commit,omitempty"` @@ -3140,7 +3140,7 @@ func (x *RepoStats) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use RepoStats.ProtoReflect.Descriptor instead. + func (*RepoStats) Descriptor() ([]byte, []int) { return file_gitrpc_gitrpc_proto_rawDescGZIP(), []int{46} } @@ -3196,13 +3196,13 @@ func (x *RepoStats) GetLastCommit() *timestamppb.Timestamp { type CountObjectsResult struct { state protoimpl.MessageState `protogen:"open.v1"` - Count int64 `protobuf:"varint,1,opt,name=count,proto3" json:"count,omitempty"` // loose objects - Size int64 `protobuf:"varint,2,opt,name=size,proto3" json:"size,omitempty"` // loose objects size, bytes - InPack int64 `protobuf:"varint,3,opt,name=in_pack,json=inPack,proto3" json:"in_pack,omitempty"` // objects inside packs - Packs int64 `protobuf:"varint,4,opt,name=packs,proto3" json:"packs,omitempty"` // number of packs - SizePack int64 `protobuf:"varint,5,opt,name=size_pack,json=sizePack,proto3" json:"size_pack,omitempty"` // packed objects size, bytes - Garbage int64 `protobuf:"varint,6,opt,name=garbage,proto3" json:"garbage,omitempty"` // garbage objects - SizeGarbage int64 `protobuf:"varint,7,opt,name=size_garbage,json=sizeGarbage,proto3" json:"size_garbage,omitempty"` // garbage size, bytes + Count int64 `protobuf:"varint,1,opt,name=count,proto3" json:"count,omitempty"` + Size int64 `protobuf:"varint,2,opt,name=size,proto3" json:"size,omitempty"` + InPack int64 `protobuf:"varint,3,opt,name=in_pack,json=inPack,proto3" json:"in_pack,omitempty"` + Packs int64 `protobuf:"varint,4,opt,name=packs,proto3" json:"packs,omitempty"` + SizePack int64 `protobuf:"varint,5,opt,name=size_pack,json=sizePack,proto3" json:"size_pack,omitempty"` + Garbage int64 `protobuf:"varint,6,opt,name=garbage,proto3" json:"garbage,omitempty"` + SizeGarbage int64 `protobuf:"varint,7,opt,name=size_garbage,json=sizeGarbage,proto3" json:"size_garbage,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -3232,7 +3232,7 @@ func (x *CountObjectsResult) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use CountObjectsResult.ProtoReflect.Descriptor instead. + func (*CountObjectsResult) Descriptor() ([]byte, []int) { return file_gitrpc_gitrpc_proto_rawDescGZIP(), []int{47} } @@ -3286,15 +3286,15 @@ func (x *CountObjectsResult) GetSizeGarbage() int64 { return 0 } -// MergeResult: exactly one of merge_commit (clean/FF) or conflicts (conflict) -// is meaningful. + + type MergeResult struct { state protoimpl.MessageState `protogen:"open.v1"` Base string `protobuf:"bytes,1,opt,name=base,proto3" json:"base,omitempty"` Head string `protobuf:"bytes,2,opt,name=head,proto3" json:"head,omitempty"` - MergeCommit *Commit `protobuf:"bytes,3,opt,name=merge_commit,json=mergeCommit,proto3" json:"merge_commit,omitempty"` // nil on conflict; on FF, head's tip + MergeCommit *Commit `protobuf:"bytes,3,opt,name=merge_commit,json=mergeCommit,proto3" json:"merge_commit,omitempty"` FastForward bool `protobuf:"varint,4,opt,name=fast_forward,json=fastForward,proto3" json:"fast_forward,omitempty"` - Conflicts []string `protobuf:"bytes,5,rep,name=conflicts,proto3" json:"conflicts,omitempty"` // conflicting paths (merge_commit nil) + Conflicts []string `protobuf:"bytes,5,rep,name=conflicts,proto3" json:"conflicts,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -3324,7 +3324,7 @@ func (x *MergeResult) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use MergeResult.ProtoReflect.Descriptor instead. + func (*MergeResult) Descriptor() ([]byte, []int) { return file_gitrpc_gitrpc_proto_rawDescGZIP(), []int{48} } @@ -3366,7 +3366,7 @@ func (x *MergeResult) GetConflicts() []string { type FileCommitResult struct { state protoimpl.MessageState `protogen:"open.v1"` - Commit *Commit `protobuf:"bytes,1,opt,name=commit,proto3" json:"commit,omitempty"` // the new commit that now tips the branch + Commit *Commit `protobuf:"bytes,1,opt,name=commit,proto3" json:"commit,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -3396,7 +3396,7 @@ func (x *FileCommitResult) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use FileCommitResult.ProtoReflect.Descriptor instead. + func (*FileCommitResult) Descriptor() ([]byte, []int) { return file_gitrpc_gitrpc_proto_rawDescGZIP(), []int{49} } @@ -3718,143 +3718,143 @@ func file_gitrpc_gitrpc_proto_rawDescGZIP() []byte { var file_gitrpc_gitrpc_proto_msgTypes = make([]protoimpl.MessageInfo, 50) var file_gitrpc_gitrpc_proto_goTypes = []any{ - (*RepoRequest)(nil), // 0: simplegit.v1.RepoRequest - (*ListCommitsRequest)(nil), // 1: simplegit.v1.ListCommitsRequest - (*ListCommitsByPathRequest)(nil), // 2: simplegit.v1.ListCommitsByPathRequest - (*GetCommitRequest)(nil), // 3: simplegit.v1.GetCommitRequest - (*GetCommitDiffRequest)(nil), // 4: simplegit.v1.GetCommitDiffRequest - (*GetTreeRequest)(nil), // 5: simplegit.v1.GetTreeRequest - (*GetBlobRequest)(nil), // 6: simplegit.v1.GetBlobRequest - (*CompareRequest)(nil), // 7: simplegit.v1.CompareRequest - (*BlameRequest)(nil), // 8: simplegit.v1.BlameRequest - (*GetContributorsRequest)(nil), // 9: simplegit.v1.GetContributorsRequest - (*CreateFileRequest)(nil), // 10: simplegit.v1.CreateFileRequest - (*DeleteFileRequest)(nil), // 11: simplegit.v1.DeleteFileRequest - (*MergeRequest)(nil), // 12: simplegit.v1.MergeRequest - (*CreateRepoRequest)(nil), // 13: simplegit.v1.CreateRepoRequest - (*ExistRepoRequest)(nil), // 14: simplegit.v1.ExistRepoRequest - (*DeleteRepoRequest)(nil), // 15: simplegit.v1.DeleteRepoRequest - (*MoveRepoRequest)(nil), // 16: simplegit.v1.MoveRepoRequest - (*MoveNSRequest)(nil), // 17: simplegit.v1.MoveNSRequest - (*ACLUpsertSSHKeyOnNSRequest)(nil), // 18: simplegit.v1.ACLUpsertSSHKeyOnNSRequest - (*ACLUpsertPATOnNSRequest)(nil), // 19: simplegit.v1.ACLUpsertPATOnNSRequest - (*ACLUpsertSSHKeyOnRepoRequest)(nil), // 20: simplegit.v1.ACLUpsertSSHKeyOnRepoRequest - (*ACLUpsertPATOnRepoRequest)(nil), // 21: simplegit.v1.ACLUpsertPATOnRepoRequest - (*ACLDeleteRequest)(nil), // 22: simplegit.v1.ACLDeleteRequest - (*StatusResponse)(nil), // 23: simplegit.v1.StatusResponse - (*ACLIDResponse)(nil), // 24: simplegit.v1.ACLIDResponse - (*ListBranchesResponse)(nil), // 25: simplegit.v1.ListBranchesResponse - (*ListTagsResponse)(nil), // 26: simplegit.v1.ListTagsResponse - (*ListCommitsResponse)(nil), // 27: simplegit.v1.ListCommitsResponse - (*GetContributorsResponse)(nil), // 28: simplegit.v1.GetContributorsResponse - (*Commit)(nil), // 29: simplegit.v1.Commit - (*CommitDetail)(nil), // 30: simplegit.v1.CommitDetail - (*DiffStats)(nil), // 31: simplegit.v1.DiffStats - (*FileDiff)(nil), // 32: simplegit.v1.FileDiff - (*Branch)(nil), // 33: simplegit.v1.Branch - (*Tag)(nil), // 34: simplegit.v1.Tag - (*TreeEntry)(nil), // 35: simplegit.v1.TreeEntry - (*Tree)(nil), // 36: simplegit.v1.Tree - (*Blob)(nil), // 37: simplegit.v1.Blob - (*DiffLine)(nil), // 38: simplegit.v1.DiffLine - (*Hunk)(nil), // 39: simplegit.v1.Hunk - (*FileChange)(nil), // 40: simplegit.v1.FileChange - (*DiffResult)(nil), // 41: simplegit.v1.DiffResult - (*CompareResult)(nil), // 42: simplegit.v1.CompareResult - (*BlameLine)(nil), // 43: simplegit.v1.BlameLine - (*BlameResult)(nil), // 44: simplegit.v1.BlameResult - (*Contributor)(nil), // 45: simplegit.v1.Contributor - (*RepoStats)(nil), // 46: simplegit.v1.RepoStats - (*CountObjectsResult)(nil), // 47: simplegit.v1.CountObjectsResult - (*MergeResult)(nil), // 48: simplegit.v1.MergeResult - (*FileCommitResult)(nil), // 49: simplegit.v1.FileCommitResult - (*timestamppb.Timestamp)(nil), // 50: google.protobuf.Timestamp - (*emptypb.Empty)(nil), // 51: google.protobuf.Empty + (*RepoRequest)(nil), + (*ListCommitsRequest)(nil), + (*ListCommitsByPathRequest)(nil), + (*GetCommitRequest)(nil), + (*GetCommitDiffRequest)(nil), + (*GetTreeRequest)(nil), + (*GetBlobRequest)(nil), + (*CompareRequest)(nil), + (*BlameRequest)(nil), + (*GetContributorsRequest)(nil), + (*CreateFileRequest)(nil), + (*DeleteFileRequest)(nil), + (*MergeRequest)(nil), + (*CreateRepoRequest)(nil), + (*ExistRepoRequest)(nil), + (*DeleteRepoRequest)(nil), + (*MoveRepoRequest)(nil), + (*MoveNSRequest)(nil), + (*ACLUpsertSSHKeyOnNSRequest)(nil), + (*ACLUpsertPATOnNSRequest)(nil), + (*ACLUpsertSSHKeyOnRepoRequest)(nil), + (*ACLUpsertPATOnRepoRequest)(nil), + (*ACLDeleteRequest)(nil), + (*StatusResponse)(nil), + (*ACLIDResponse)(nil), + (*ListBranchesResponse)(nil), + (*ListTagsResponse)(nil), + (*ListCommitsResponse)(nil), + (*GetContributorsResponse)(nil), + (*Commit)(nil), + (*CommitDetail)(nil), + (*DiffStats)(nil), + (*FileDiff)(nil), + (*Branch)(nil), + (*Tag)(nil), + (*TreeEntry)(nil), + (*Tree)(nil), + (*Blob)(nil), + (*DiffLine)(nil), + (*Hunk)(nil), + (*FileChange)(nil), + (*DiffResult)(nil), + (*CompareResult)(nil), + (*BlameLine)(nil), + (*BlameResult)(nil), + (*Contributor)(nil), + (*RepoStats)(nil), + (*CountObjectsResult)(nil), + (*MergeResult)(nil), + (*FileCommitResult)(nil), + (*timestamppb.Timestamp)(nil), + (*emptypb.Empty)(nil), } var file_gitrpc_gitrpc_proto_depIdxs = []int32{ - 50, // 0: simplegit.v1.StatusResponse.started_at:type_name -> google.protobuf.Timestamp - 33, // 1: simplegit.v1.ListBranchesResponse.branches:type_name -> simplegit.v1.Branch - 34, // 2: simplegit.v1.ListTagsResponse.tags:type_name -> simplegit.v1.Tag - 29, // 3: simplegit.v1.ListCommitsResponse.commits:type_name -> simplegit.v1.Commit - 45, // 4: simplegit.v1.GetContributorsResponse.contributors:type_name -> simplegit.v1.Contributor - 50, // 5: simplegit.v1.Commit.when:type_name -> google.protobuf.Timestamp - 29, // 6: simplegit.v1.CommitDetail.commit:type_name -> simplegit.v1.Commit - 31, // 7: simplegit.v1.CommitDetail.stats:type_name -> simplegit.v1.DiffStats - 32, // 8: simplegit.v1.CommitDetail.files:type_name -> simplegit.v1.FileDiff - 50, // 9: simplegit.v1.Branch.when:type_name -> google.protobuf.Timestamp - 50, // 10: simplegit.v1.Tag.tag_date:type_name -> google.protobuf.Timestamp - 35, // 11: simplegit.v1.Tree.entries:type_name -> simplegit.v1.TreeEntry - 38, // 12: simplegit.v1.Hunk.lines:type_name -> simplegit.v1.DiffLine - 39, // 13: simplegit.v1.FileChange.hunks:type_name -> simplegit.v1.Hunk - 40, // 14: simplegit.v1.DiffResult.files:type_name -> simplegit.v1.FileChange - 29, // 15: simplegit.v1.CompareResult.commits:type_name -> simplegit.v1.Commit - 41, // 16: simplegit.v1.CompareResult.diff:type_name -> simplegit.v1.DiffResult - 50, // 17: simplegit.v1.BlameLine.when:type_name -> google.protobuf.Timestamp - 43, // 18: simplegit.v1.BlameResult.lines:type_name -> simplegit.v1.BlameLine - 45, // 19: simplegit.v1.RepoStats.contributors:type_name -> simplegit.v1.Contributor - 50, // 20: simplegit.v1.RepoStats.first_commit:type_name -> google.protobuf.Timestamp - 50, // 21: simplegit.v1.RepoStats.last_commit:type_name -> google.protobuf.Timestamp - 29, // 22: simplegit.v1.MergeResult.merge_commit:type_name -> simplegit.v1.Commit - 29, // 23: simplegit.v1.FileCommitResult.commit:type_name -> simplegit.v1.Commit - 0, // 24: simplegit.v1.GitService.ListBranches:input_type -> simplegit.v1.RepoRequest - 0, // 25: simplegit.v1.GitService.ListTags:input_type -> simplegit.v1.RepoRequest - 1, // 26: simplegit.v1.GitService.ListCommits:input_type -> simplegit.v1.ListCommitsRequest - 2, // 27: simplegit.v1.GitService.ListCommitsByPath:input_type -> simplegit.v1.ListCommitsByPathRequest - 3, // 28: simplegit.v1.GitService.GetCommit:input_type -> simplegit.v1.GetCommitRequest - 4, // 29: simplegit.v1.GitService.GetCommitDiff:input_type -> simplegit.v1.GetCommitDiffRequest - 5, // 30: simplegit.v1.GitService.GetTree:input_type -> simplegit.v1.GetTreeRequest - 6, // 31: simplegit.v1.GitService.GetBlob:input_type -> simplegit.v1.GetBlobRequest - 7, // 32: simplegit.v1.GitService.Compare:input_type -> simplegit.v1.CompareRequest - 8, // 33: simplegit.v1.GitService.Blame:input_type -> simplegit.v1.BlameRequest - 9, // 34: simplegit.v1.GitService.GetContributors:input_type -> simplegit.v1.GetContributorsRequest - 0, // 35: simplegit.v1.GitService.GetStats:input_type -> simplegit.v1.RepoRequest - 0, // 36: simplegit.v1.GitService.CountObjects:input_type -> simplegit.v1.RepoRequest - 10, // 37: simplegit.v1.GitService.CreateFile:input_type -> simplegit.v1.CreateFileRequest - 11, // 38: simplegit.v1.GitService.DeleteFile:input_type -> simplegit.v1.DeleteFileRequest - 12, // 39: simplegit.v1.GitService.Merge:input_type -> simplegit.v1.MergeRequest - 13, // 40: simplegit.v1.ManageService.CreateRepo:input_type -> simplegit.v1.CreateRepoRequest - 14, // 41: simplegit.v1.ManageService.ExistRepo:input_type -> simplegit.v1.ExistRepoRequest - 15, // 42: simplegit.v1.ManageService.DeleteRepo:input_type -> simplegit.v1.DeleteRepoRequest - 16, // 43: simplegit.v1.ManageService.MoveRepo:input_type -> simplegit.v1.MoveRepoRequest - 17, // 44: simplegit.v1.ManageService.MoveNS:input_type -> simplegit.v1.MoveNSRequest - 18, // 45: simplegit.v1.ManageService.ACLUpsertSSHKeyOnNS:input_type -> simplegit.v1.ACLUpsertSSHKeyOnNSRequest - 19, // 46: simplegit.v1.ManageService.ACLUpsertPATOnNS:input_type -> simplegit.v1.ACLUpsertPATOnNSRequest - 20, // 47: simplegit.v1.ManageService.ACLUpsertSSHKeyOnRepo:input_type -> simplegit.v1.ACLUpsertSSHKeyOnRepoRequest - 21, // 48: simplegit.v1.ManageService.ACLUpsertPATOnRepo:input_type -> simplegit.v1.ACLUpsertPATOnRepoRequest - 22, // 49: simplegit.v1.ManageService.ACLDelete:input_type -> simplegit.v1.ACLDeleteRequest - 51, // 50: simplegit.v1.ManageService.Status:input_type -> google.protobuf.Empty - 25, // 51: simplegit.v1.GitService.ListBranches:output_type -> simplegit.v1.ListBranchesResponse - 26, // 52: simplegit.v1.GitService.ListTags:output_type -> simplegit.v1.ListTagsResponse - 27, // 53: simplegit.v1.GitService.ListCommits:output_type -> simplegit.v1.ListCommitsResponse - 27, // 54: simplegit.v1.GitService.ListCommitsByPath:output_type -> simplegit.v1.ListCommitsResponse - 30, // 55: simplegit.v1.GitService.GetCommit:output_type -> simplegit.v1.CommitDetail - 41, // 56: simplegit.v1.GitService.GetCommitDiff:output_type -> simplegit.v1.DiffResult - 36, // 57: simplegit.v1.GitService.GetTree:output_type -> simplegit.v1.Tree - 37, // 58: simplegit.v1.GitService.GetBlob:output_type -> simplegit.v1.Blob - 42, // 59: simplegit.v1.GitService.Compare:output_type -> simplegit.v1.CompareResult - 44, // 60: simplegit.v1.GitService.Blame:output_type -> simplegit.v1.BlameResult - 28, // 61: simplegit.v1.GitService.GetContributors:output_type -> simplegit.v1.GetContributorsResponse - 46, // 62: simplegit.v1.GitService.GetStats:output_type -> simplegit.v1.RepoStats - 47, // 63: simplegit.v1.GitService.CountObjects:output_type -> simplegit.v1.CountObjectsResult - 49, // 64: simplegit.v1.GitService.CreateFile:output_type -> simplegit.v1.FileCommitResult - 49, // 65: simplegit.v1.GitService.DeleteFile:output_type -> simplegit.v1.FileCommitResult - 48, // 66: simplegit.v1.GitService.Merge:output_type -> simplegit.v1.MergeResult - 51, // 67: simplegit.v1.ManageService.CreateRepo:output_type -> google.protobuf.Empty - 51, // 68: simplegit.v1.ManageService.ExistRepo:output_type -> google.protobuf.Empty - 51, // 69: simplegit.v1.ManageService.DeleteRepo:output_type -> google.protobuf.Empty - 51, // 70: simplegit.v1.ManageService.MoveRepo:output_type -> google.protobuf.Empty - 51, // 71: simplegit.v1.ManageService.MoveNS:output_type -> google.protobuf.Empty - 24, // 72: simplegit.v1.ManageService.ACLUpsertSSHKeyOnNS:output_type -> simplegit.v1.ACLIDResponse - 24, // 73: simplegit.v1.ManageService.ACLUpsertPATOnNS:output_type -> simplegit.v1.ACLIDResponse - 24, // 74: simplegit.v1.ManageService.ACLUpsertSSHKeyOnRepo:output_type -> simplegit.v1.ACLIDResponse - 24, // 75: simplegit.v1.ManageService.ACLUpsertPATOnRepo:output_type -> simplegit.v1.ACLIDResponse - 51, // 76: simplegit.v1.ManageService.ACLDelete:output_type -> google.protobuf.Empty - 23, // 77: simplegit.v1.ManageService.Status:output_type -> simplegit.v1.StatusResponse - 51, // [51:78] is the sub-list for method output_type - 24, // [24:51] is the sub-list for method input_type - 24, // [24:24] is the sub-list for extension type_name - 24, // [24:24] is the sub-list for extension extendee - 0, // [0:24] is the sub-list for field type_name + 50, + 33, + 34, + 29, + 45, + 50, + 29, + 31, + 32, + 50, + 50, + 35, + 38, + 39, + 40, + 29, + 41, + 50, + 43, + 45, + 50, + 50, + 29, + 29, + 0, + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 0, + 0, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19, + 20, + 21, + 22, + 51, + 25, + 26, + 27, + 27, + 30, + 41, + 36, + 37, + 42, + 44, + 28, + 46, + 47, + 49, + 49, + 48, + 51, + 51, + 51, + 51, + 51, + 24, + 24, + 24, + 24, + 51, + 23, + 51, + 24, + 24, + 24, + 0, } func init() { file_gitrpc_gitrpc_proto_init() } diff --git a/gitrpc/v1/v1connect/gitrpc.connect.go b/gitrpc/v1/v1connect/gitrpc.connect.go index 7b71cbf..72c500f 100644 --- a/gitrpc/v1/v1connect/gitrpc.connect.go +++ b/gitrpc/v1/v1connect/gitrpc.connect.go @@ -1,27 +1,27 @@ -// Connect (crpc) contract for the gitrpc JSON-RPC server. -// -// This is a faithful translation of the hand-written JSON-RPC method set in -// handlers.go (registerRepoMethods + registerWriteMethods) plus the gitcmd -// result types those handlers return verbatim. Every rpc below maps 1:1 to a -// "repo." JSON-RPC method (noted in each comment); every message field -// keeps the snake_case name of the corresponding Go struct json tag so the -// wire shape matches what gitrpc serializes today. -// -// The server is path-driven and stateless: every method takes the repository -// NAME (owner/name.git) in `repo`; the server resolves it to an on-disk path -// and has already authorized the call before the handler runs. Auth/ACL and -// name->path resolution live in the caller (simpleconsole), not here. -// -// Generate Go + TS with protoc-gen-es (TS) / protoc-gen-go + protoc-gen-connect-go: -// protoc -I . --go_out=. --go_opt=paths=source_relative \ -// --connect_go_out=. --connect_go_opt=paths=source_relative \ -// --es_out=./gen --es_opt=target=ts \ -// --plugin=protoc-gen-es=./node_modules/.bin/protoc-gen-es \ -// gitrpc.proto -// Code generated by protoc-gen-connect-go. DO NOT EDIT. -// -// Source: gitrpc/gitrpc.proto + + + + + + + + + + + + + + + + + + + + + + + package v1connect @@ -35,136 +35,136 @@ import ( strings "strings" ) -// This is a compile-time assertion to ensure that this generated file and the connect package are -// compatible. If you get a compiler error that this constant is not defined, this code was -// generated with a version of connect newer than the one compiled into your binary. You can fix the -// problem by either regenerating this code with an older version of connect or updating the connect -// version compiled into your binary. + + + + + const _ = connect.IsAtLeastVersion1_13_0 const ( - // GitServiceName is the fully-qualified name of the GitService service. + GitServiceName = "simplegit.v1.GitService" - // ManageServiceName is the fully-qualified name of the ManageService service. + ManageServiceName = "simplegit.v1.ManageService" ) -// These constants are the fully-qualified names of the RPCs defined in this package. They're -// exposed at runtime as Spec.Procedure and as the final two segments of the HTTP route. -// -// Note that these are different from the fully-qualified method names used by -// google.golang.org/protobuf/reflect/protoreflect. To convert from these constants to -// reflection-formatted method names, remove the leading slash and convert the remaining slash to a -// period. + + + + + + + const ( - // GitServiceListBranchesProcedure is the fully-qualified name of the GitService's ListBranches RPC. + GitServiceListBranchesProcedure = "/simplegit.v1.GitService/ListBranches" - // GitServiceListTagsProcedure is the fully-qualified name of the GitService's ListTags RPC. + GitServiceListTagsProcedure = "/simplegit.v1.GitService/ListTags" - // GitServiceListCommitsProcedure is the fully-qualified name of the GitService's ListCommits RPC. + GitServiceListCommitsProcedure = "/simplegit.v1.GitService/ListCommits" - // GitServiceListCommitsByPathProcedure is the fully-qualified name of the GitService's - // ListCommitsByPath RPC. + + GitServiceListCommitsByPathProcedure = "/simplegit.v1.GitService/ListCommitsByPath" - // GitServiceGetCommitProcedure is the fully-qualified name of the GitService's GetCommit RPC. + GitServiceGetCommitProcedure = "/simplegit.v1.GitService/GetCommit" - // GitServiceGetCommitDiffProcedure is the fully-qualified name of the GitService's GetCommitDiff - // RPC. + + GitServiceGetCommitDiffProcedure = "/simplegit.v1.GitService/GetCommitDiff" - // GitServiceGetTreeProcedure is the fully-qualified name of the GitService's GetTree RPC. + GitServiceGetTreeProcedure = "/simplegit.v1.GitService/GetTree" - // GitServiceGetBlobProcedure is the fully-qualified name of the GitService's GetBlob RPC. + GitServiceGetBlobProcedure = "/simplegit.v1.GitService/GetBlob" - // GitServiceCompareProcedure is the fully-qualified name of the GitService's Compare RPC. + GitServiceCompareProcedure = "/simplegit.v1.GitService/Compare" - // GitServiceBlameProcedure is the fully-qualified name of the GitService's Blame RPC. + GitServiceBlameProcedure = "/simplegit.v1.GitService/Blame" - // GitServiceGetContributorsProcedure is the fully-qualified name of the GitService's - // GetContributors RPC. + + GitServiceGetContributorsProcedure = "/simplegit.v1.GitService/GetContributors" - // GitServiceGetStatsProcedure is the fully-qualified name of the GitService's GetStats RPC. + GitServiceGetStatsProcedure = "/simplegit.v1.GitService/GetStats" - // GitServiceCountObjectsProcedure is the fully-qualified name of the GitService's CountObjects RPC. + GitServiceCountObjectsProcedure = "/simplegit.v1.GitService/CountObjects" - // GitServiceCreateFileProcedure is the fully-qualified name of the GitService's CreateFile RPC. + GitServiceCreateFileProcedure = "/simplegit.v1.GitService/CreateFile" - // GitServiceDeleteFileProcedure is the fully-qualified name of the GitService's DeleteFile RPC. + GitServiceDeleteFileProcedure = "/simplegit.v1.GitService/DeleteFile" - // GitServiceMergeProcedure is the fully-qualified name of the GitService's Merge RPC. + GitServiceMergeProcedure = "/simplegit.v1.GitService/Merge" - // ManageServiceCreateRepoProcedure is the fully-qualified name of the ManageService's CreateRepo - // RPC. + + ManageServiceCreateRepoProcedure = "/simplegit.v1.ManageService/CreateRepo" - // ManageServiceExistRepoProcedure is the fully-qualified name of the ManageService's ExistRepo RPC. + ManageServiceExistRepoProcedure = "/simplegit.v1.ManageService/ExistRepo" - // ManageServiceDeleteRepoProcedure is the fully-qualified name of the ManageService's DeleteRepo - // RPC. + + ManageServiceDeleteRepoProcedure = "/simplegit.v1.ManageService/DeleteRepo" - // ManageServiceMoveRepoProcedure is the fully-qualified name of the ManageService's MoveRepo RPC. + ManageServiceMoveRepoProcedure = "/simplegit.v1.ManageService/MoveRepo" - // ManageServiceMoveNSProcedure is the fully-qualified name of the ManageService's MoveNS RPC. + ManageServiceMoveNSProcedure = "/simplegit.v1.ManageService/MoveNS" - // ManageServiceACLUpsertSSHKeyOnNSProcedure is the fully-qualified name of the ManageService's - // ACLUpsertSSHKeyOnNS RPC. + + ManageServiceACLUpsertSSHKeyOnNSProcedure = "/simplegit.v1.ManageService/ACLUpsertSSHKeyOnNS" - // ManageServiceACLUpsertPATOnNSProcedure is the fully-qualified name of the ManageService's - // ACLUpsertPATOnNS RPC. + + ManageServiceACLUpsertPATOnNSProcedure = "/simplegit.v1.ManageService/ACLUpsertPATOnNS" - // ManageServiceACLUpsertSSHKeyOnRepoProcedure is the fully-qualified name of the ManageService's - // ACLUpsertSSHKeyOnRepo RPC. + + ManageServiceACLUpsertSSHKeyOnRepoProcedure = "/simplegit.v1.ManageService/ACLUpsertSSHKeyOnRepo" - // ManageServiceACLUpsertPATOnRepoProcedure is the fully-qualified name of the ManageService's - // ACLUpsertPATOnRepo RPC. + + ManageServiceACLUpsertPATOnRepoProcedure = "/simplegit.v1.ManageService/ACLUpsertPATOnRepo" - // ManageServiceACLDeleteProcedure is the fully-qualified name of the ManageService's ACLDelete RPC. + ManageServiceACLDeleteProcedure = "/simplegit.v1.ManageService/ACLDelete" - // ManageServiceStatusProcedure is the fully-qualified name of the ManageService's Status RPC. + ManageServiceStatusProcedure = "/simplegit.v1.ManageService/Status" ) -// GitServiceClient is a client for the simplegit.v1.GitService service. + type GitServiceClient interface { - // repo.listBranches + ListBranches(context.Context, *connect.Request[v1.RepoRequest]) (*connect.Response[v1.ListBranchesResponse], error) - // repo.listTags + ListTags(context.Context, *connect.Request[v1.RepoRequest]) (*connect.Response[v1.ListTagsResponse], error) - // repo.listCommits + ListCommits(context.Context, *connect.Request[v1.ListCommitsRequest]) (*connect.Response[v1.ListCommitsResponse], error) - // repo.listCommitsByPath + ListCommitsByPath(context.Context, *connect.Request[v1.ListCommitsByPathRequest]) (*connect.Response[v1.ListCommitsResponse], error) - // repo.getCommit + GetCommit(context.Context, *connect.Request[v1.GetCommitRequest]) (*connect.Response[v1.CommitDetail], error) - // repo.getCommitDiff + GetCommitDiff(context.Context, *connect.Request[v1.GetCommitDiffRequest]) (*connect.Response[v1.DiffResult], error) - // repo.getTree + GetTree(context.Context, *connect.Request[v1.GetTreeRequest]) (*connect.Response[v1.Tree], error) - // repo.getBlob + GetBlob(context.Context, *connect.Request[v1.GetBlobRequest]) (*connect.Response[v1.Blob], error) - // repo.compare + Compare(context.Context, *connect.Request[v1.CompareRequest]) (*connect.Response[v1.CompareResult], error) - // repo.blame + Blame(context.Context, *connect.Request[v1.BlameRequest]) (*connect.Response[v1.BlameResult], error) - // repo.getContributors + GetContributors(context.Context, *connect.Request[v1.GetContributorsRequest]) (*connect.Response[v1.GetContributorsResponse], error) - // repo.getStats + GetStats(context.Context, *connect.Request[v1.RepoRequest]) (*connect.Response[v1.RepoStats], error) - // repo.countObjects + CountObjects(context.Context, *connect.Request[v1.RepoRequest]) (*connect.Response[v1.CountObjectsResult], error) - // repo.createFile + CreateFile(context.Context, *connect.Request[v1.CreateFileRequest]) (*connect.Response[v1.FileCommitResult], error) - // repo.deleteFile + DeleteFile(context.Context, *connect.Request[v1.DeleteFileRequest]) (*connect.Response[v1.FileCommitResult], error) - // repo.merge + Merge(context.Context, *connect.Request[v1.MergeRequest]) (*connect.Response[v1.MergeResult], error) } -// NewGitServiceClient constructs a client for the simplegit.v1.GitService service. By default, it -// uses the Connect protocol with the binary Protobuf Codec, asks for gzipped responses, and sends -// uncompressed requests. To use the gRPC or gRPC-Web protocols, supply the connect.WithGRPC() or -// connect.WithGRPCWeb() options. -// -// The URL supplied here should be the base URL for the Connect or gRPC server (for example, -// http://api.acme.com or https://acme.com/grpc). + + + + + + + func NewGitServiceClient(httpClient connect.HTTPClient, baseURL string, opts ...connect.ClientOption) GitServiceClient { baseURL = strings.TrimRight(baseURL, "/") gitServiceMethods := v1.File_gitrpc_gitrpc_proto.Services().ByName("GitService").Methods() @@ -268,7 +268,7 @@ func NewGitServiceClient(httpClient connect.HTTPClient, baseURL string, opts ... } } -// gitServiceClient implements GitServiceClient. + type gitServiceClient struct { listBranches *connect.Client[v1.RepoRequest, v1.ListBranchesResponse] listTags *connect.Client[v1.RepoRequest, v1.ListTagsResponse] @@ -288,127 +288,127 @@ type gitServiceClient struct { merge *connect.Client[v1.MergeRequest, v1.MergeResult] } -// ListBranches calls simplegit.v1.GitService.ListBranches. + func (c *gitServiceClient) ListBranches(ctx context.Context, req *connect.Request[v1.RepoRequest]) (*connect.Response[v1.ListBranchesResponse], error) { return c.listBranches.CallUnary(ctx, req) } -// ListTags calls simplegit.v1.GitService.ListTags. + func (c *gitServiceClient) ListTags(ctx context.Context, req *connect.Request[v1.RepoRequest]) (*connect.Response[v1.ListTagsResponse], error) { return c.listTags.CallUnary(ctx, req) } -// ListCommits calls simplegit.v1.GitService.ListCommits. + func (c *gitServiceClient) ListCommits(ctx context.Context, req *connect.Request[v1.ListCommitsRequest]) (*connect.Response[v1.ListCommitsResponse], error) { return c.listCommits.CallUnary(ctx, req) } -// ListCommitsByPath calls simplegit.v1.GitService.ListCommitsByPath. + func (c *gitServiceClient) ListCommitsByPath(ctx context.Context, req *connect.Request[v1.ListCommitsByPathRequest]) (*connect.Response[v1.ListCommitsResponse], error) { return c.listCommitsByPath.CallUnary(ctx, req) } -// GetCommit calls simplegit.v1.GitService.GetCommit. + func (c *gitServiceClient) GetCommit(ctx context.Context, req *connect.Request[v1.GetCommitRequest]) (*connect.Response[v1.CommitDetail], error) { return c.getCommit.CallUnary(ctx, req) } -// GetCommitDiff calls simplegit.v1.GitService.GetCommitDiff. + func (c *gitServiceClient) GetCommitDiff(ctx context.Context, req *connect.Request[v1.GetCommitDiffRequest]) (*connect.Response[v1.DiffResult], error) { return c.getCommitDiff.CallUnary(ctx, req) } -// GetTree calls simplegit.v1.GitService.GetTree. + func (c *gitServiceClient) GetTree(ctx context.Context, req *connect.Request[v1.GetTreeRequest]) (*connect.Response[v1.Tree], error) { return c.getTree.CallUnary(ctx, req) } -// GetBlob calls simplegit.v1.GitService.GetBlob. + func (c *gitServiceClient) GetBlob(ctx context.Context, req *connect.Request[v1.GetBlobRequest]) (*connect.Response[v1.Blob], error) { return c.getBlob.CallUnary(ctx, req) } -// Compare calls simplegit.v1.GitService.Compare. + func (c *gitServiceClient) Compare(ctx context.Context, req *connect.Request[v1.CompareRequest]) (*connect.Response[v1.CompareResult], error) { return c.compare.CallUnary(ctx, req) } -// Blame calls simplegit.v1.GitService.Blame. + func (c *gitServiceClient) Blame(ctx context.Context, req *connect.Request[v1.BlameRequest]) (*connect.Response[v1.BlameResult], error) { return c.blame.CallUnary(ctx, req) } -// GetContributors calls simplegit.v1.GitService.GetContributors. + func (c *gitServiceClient) GetContributors(ctx context.Context, req *connect.Request[v1.GetContributorsRequest]) (*connect.Response[v1.GetContributorsResponse], error) { return c.getContributors.CallUnary(ctx, req) } -// GetStats calls simplegit.v1.GitService.GetStats. + func (c *gitServiceClient) GetStats(ctx context.Context, req *connect.Request[v1.RepoRequest]) (*connect.Response[v1.RepoStats], error) { return c.getStats.CallUnary(ctx, req) } -// CountObjects calls simplegit.v1.GitService.CountObjects. + func (c *gitServiceClient) CountObjects(ctx context.Context, req *connect.Request[v1.RepoRequest]) (*connect.Response[v1.CountObjectsResult], error) { return c.countObjects.CallUnary(ctx, req) } -// CreateFile calls simplegit.v1.GitService.CreateFile. + func (c *gitServiceClient) CreateFile(ctx context.Context, req *connect.Request[v1.CreateFileRequest]) (*connect.Response[v1.FileCommitResult], error) { return c.createFile.CallUnary(ctx, req) } -// DeleteFile calls simplegit.v1.GitService.DeleteFile. + func (c *gitServiceClient) DeleteFile(ctx context.Context, req *connect.Request[v1.DeleteFileRequest]) (*connect.Response[v1.FileCommitResult], error) { return c.deleteFile.CallUnary(ctx, req) } -// Merge calls simplegit.v1.GitService.Merge. + func (c *gitServiceClient) Merge(ctx context.Context, req *connect.Request[v1.MergeRequest]) (*connect.Response[v1.MergeResult], error) { return c.merge.CallUnary(ctx, req) } -// GitServiceHandler is an implementation of the simplegit.v1.GitService service. + type GitServiceHandler interface { - // repo.listBranches + ListBranches(context.Context, *connect.Request[v1.RepoRequest]) (*connect.Response[v1.ListBranchesResponse], error) - // repo.listTags + ListTags(context.Context, *connect.Request[v1.RepoRequest]) (*connect.Response[v1.ListTagsResponse], error) - // repo.listCommits + ListCommits(context.Context, *connect.Request[v1.ListCommitsRequest]) (*connect.Response[v1.ListCommitsResponse], error) - // repo.listCommitsByPath + ListCommitsByPath(context.Context, *connect.Request[v1.ListCommitsByPathRequest]) (*connect.Response[v1.ListCommitsResponse], error) - // repo.getCommit + GetCommit(context.Context, *connect.Request[v1.GetCommitRequest]) (*connect.Response[v1.CommitDetail], error) - // repo.getCommitDiff + GetCommitDiff(context.Context, *connect.Request[v1.GetCommitDiffRequest]) (*connect.Response[v1.DiffResult], error) - // repo.getTree + GetTree(context.Context, *connect.Request[v1.GetTreeRequest]) (*connect.Response[v1.Tree], error) - // repo.getBlob + GetBlob(context.Context, *connect.Request[v1.GetBlobRequest]) (*connect.Response[v1.Blob], error) - // repo.compare + Compare(context.Context, *connect.Request[v1.CompareRequest]) (*connect.Response[v1.CompareResult], error) - // repo.blame + Blame(context.Context, *connect.Request[v1.BlameRequest]) (*connect.Response[v1.BlameResult], error) - // repo.getContributors + GetContributors(context.Context, *connect.Request[v1.GetContributorsRequest]) (*connect.Response[v1.GetContributorsResponse], error) - // repo.getStats + GetStats(context.Context, *connect.Request[v1.RepoRequest]) (*connect.Response[v1.RepoStats], error) - // repo.countObjects + CountObjects(context.Context, *connect.Request[v1.RepoRequest]) (*connect.Response[v1.CountObjectsResult], error) - // repo.createFile + CreateFile(context.Context, *connect.Request[v1.CreateFileRequest]) (*connect.Response[v1.FileCommitResult], error) - // repo.deleteFile + DeleteFile(context.Context, *connect.Request[v1.DeleteFileRequest]) (*connect.Response[v1.FileCommitResult], error) - // repo.merge + Merge(context.Context, *connect.Request[v1.MergeRequest]) (*connect.Response[v1.MergeResult], error) } -// NewGitServiceHandler builds an HTTP handler from the service implementation. It returns the path -// on which to mount the handler and the handler itself. -// -// By default, handlers support the Connect, gRPC, and gRPC-Web protocols with the binary Protobuf -// and JSON codecs. They also support gzip compression. + + + + + func NewGitServiceHandler(svc GitServiceHandler, opts ...connect.HandlerOption) (string, http.Handler) { gitServiceMethods := v1.File_gitrpc_gitrpc_proto.Services().ByName("GitService").Methods() gitServiceListBranchesHandler := connect.NewUnaryHandler( @@ -547,7 +547,7 @@ func NewGitServiceHandler(svc GitServiceHandler, opts ...connect.HandlerOption) }) } -// UnimplementedGitServiceHandler returns CodeUnimplemented from all methods. + type UnimplementedGitServiceHandler struct{} func (UnimplementedGitServiceHandler) ListBranches(context.Context, *connect.Request[v1.RepoRequest]) (*connect.Response[v1.ListBranchesResponse], error) { @@ -614,43 +614,43 @@ func (UnimplementedGitServiceHandler) Merge(context.Context, *connect.Request[v1 return nil, connect.NewError(connect.CodeUnimplemented, errors.New("simplegit.v1.GitService.Merge is not implemented")) } -// ManageServiceClient is a client for the simplegit.v1.ManageService service. + type ManageServiceClient interface { - // Create a repo (git init --bare). Reuses the namespace's NamespaceID if it - // exists, else mints a new one. + + CreateRepo(context.Context, *connect.Request[v1.CreateRepoRequest]) (*connect.Response[emptypb.Empty], error) - // OK if the repo is registered; NotFound otherwise. + ExistRepo(context.Context, *connect.Request[v1.ExistRepoRequest]) (*connect.Response[emptypb.Empty], error) - // Remove a repo (DB row + repo-targeted ACL grants + on-disk dir). + DeleteRepo(context.Context, *connect.Request[v1.DeleteRepoRequest]) (*connect.Response[emptypb.Empty], error) - // Move/rename a repo (transfer and/or rename). + MoveRepo(context.Context, *connect.Request[v1.MoveRepoRequest]) (*connect.Response[emptypb.Empty], error) - // Rename a namespace (all its repos); NamespaceID is stable. + MoveNS(context.Context, *connect.Request[v1.MoveNSRequest]) (*connect.Response[emptypb.Empty], error) - // Grant an SSH key perm on a namespace; registers the key if new. Returns the ACL id. + ACLUpsertSSHKeyOnNS(context.Context, *connect.Request[v1.ACLUpsertSSHKeyOnNSRequest]) (*connect.Response[v1.ACLIDResponse], error) - // Grant a PAT perm on a namespace; creates the PAT if new. Returns the ACL id. + ACLUpsertPATOnNS(context.Context, *connect.Request[v1.ACLUpsertPATOnNSRequest]) (*connect.Response[v1.ACLIDResponse], error) - // Grant an SSH key perm on a repo; registers the key if new. Returns the ACL id. + ACLUpsertSSHKeyOnRepo(context.Context, *connect.Request[v1.ACLUpsertSSHKeyOnRepoRequest]) (*connect.Response[v1.ACLIDResponse], error) - // Grant a PAT perm on a repo; creates the PAT if new. Returns the ACL id. + ACLUpsertPATOnRepo(context.Context, *connect.Request[v1.ACLUpsertPATOnRepoRequest]) (*connect.Response[v1.ACLIDResponse], error) - // Remove a single ACL grant by id. + ACLDelete(context.Context, *connect.Request[v1.ACLDeleteRequest]) (*connect.Response[emptypb.Empty], error) - // Daemon status + config: startup params, listen locations, start time / - // uptime, and the state DB URI (gitctl uses db_uri to connect directly for - // inspection reads -- there is no list RPC on this service). Diagnostics only; - // does not touch the DB. + + + + Status(context.Context, *connect.Request[emptypb.Empty]) (*connect.Response[v1.StatusResponse], error) } -// NewManageServiceClient constructs a client for the simplegit.v1.ManageService service. By -// default, it uses the Connect protocol with the binary Protobuf Codec, asks for gzipped responses, -// and sends uncompressed requests. To use the gRPC or gRPC-Web protocols, supply the -// connect.WithGRPC() or connect.WithGRPCWeb() options. -// -// The URL supplied here should be the base URL for the Connect or gRPC server (for example, -// http://api.acme.com or https://acme.com/grpc). + + + + + + + func NewManageServiceClient(httpClient connect.HTTPClient, baseURL string, opts ...connect.ClientOption) ManageServiceClient { baseURL = strings.TrimRight(baseURL, "/") manageServiceMethods := v1.File_gitrpc_gitrpc_proto.Services().ByName("ManageService").Methods() @@ -724,7 +724,7 @@ func NewManageServiceClient(httpClient connect.HTTPClient, baseURL string, opts } } -// manageServiceClient implements ManageServiceClient. + type manageServiceClient struct { createRepo *connect.Client[v1.CreateRepoRequest, emptypb.Empty] existRepo *connect.Client[v1.ExistRepoRequest, emptypb.Empty] @@ -739,96 +739,96 @@ type manageServiceClient struct { status *connect.Client[emptypb.Empty, v1.StatusResponse] } -// CreateRepo calls simplegit.v1.ManageService.CreateRepo. + func (c *manageServiceClient) CreateRepo(ctx context.Context, req *connect.Request[v1.CreateRepoRequest]) (*connect.Response[emptypb.Empty], error) { return c.createRepo.CallUnary(ctx, req) } -// ExistRepo calls simplegit.v1.ManageService.ExistRepo. + func (c *manageServiceClient) ExistRepo(ctx context.Context, req *connect.Request[v1.ExistRepoRequest]) (*connect.Response[emptypb.Empty], error) { return c.existRepo.CallUnary(ctx, req) } -// DeleteRepo calls simplegit.v1.ManageService.DeleteRepo. + func (c *manageServiceClient) DeleteRepo(ctx context.Context, req *connect.Request[v1.DeleteRepoRequest]) (*connect.Response[emptypb.Empty], error) { return c.deleteRepo.CallUnary(ctx, req) } -// MoveRepo calls simplegit.v1.ManageService.MoveRepo. + func (c *manageServiceClient) MoveRepo(ctx context.Context, req *connect.Request[v1.MoveRepoRequest]) (*connect.Response[emptypb.Empty], error) { return c.moveRepo.CallUnary(ctx, req) } -// MoveNS calls simplegit.v1.ManageService.MoveNS. + func (c *manageServiceClient) MoveNS(ctx context.Context, req *connect.Request[v1.MoveNSRequest]) (*connect.Response[emptypb.Empty], error) { return c.moveNS.CallUnary(ctx, req) } -// ACLUpsertSSHKeyOnNS calls simplegit.v1.ManageService.ACLUpsertSSHKeyOnNS. + func (c *manageServiceClient) ACLUpsertSSHKeyOnNS(ctx context.Context, req *connect.Request[v1.ACLUpsertSSHKeyOnNSRequest]) (*connect.Response[v1.ACLIDResponse], error) { return c.aCLUpsertSSHKeyOnNS.CallUnary(ctx, req) } -// ACLUpsertPATOnNS calls simplegit.v1.ManageService.ACLUpsertPATOnNS. + func (c *manageServiceClient) ACLUpsertPATOnNS(ctx context.Context, req *connect.Request[v1.ACLUpsertPATOnNSRequest]) (*connect.Response[v1.ACLIDResponse], error) { return c.aCLUpsertPATOnNS.CallUnary(ctx, req) } -// ACLUpsertSSHKeyOnRepo calls simplegit.v1.ManageService.ACLUpsertSSHKeyOnRepo. + func (c *manageServiceClient) ACLUpsertSSHKeyOnRepo(ctx context.Context, req *connect.Request[v1.ACLUpsertSSHKeyOnRepoRequest]) (*connect.Response[v1.ACLIDResponse], error) { return c.aCLUpsertSSHKeyOnRepo.CallUnary(ctx, req) } -// ACLUpsertPATOnRepo calls simplegit.v1.ManageService.ACLUpsertPATOnRepo. + func (c *manageServiceClient) ACLUpsertPATOnRepo(ctx context.Context, req *connect.Request[v1.ACLUpsertPATOnRepoRequest]) (*connect.Response[v1.ACLIDResponse], error) { return c.aCLUpsertPATOnRepo.CallUnary(ctx, req) } -// ACLDelete calls simplegit.v1.ManageService.ACLDelete. + func (c *manageServiceClient) ACLDelete(ctx context.Context, req *connect.Request[v1.ACLDeleteRequest]) (*connect.Response[emptypb.Empty], error) { return c.aCLDelete.CallUnary(ctx, req) } -// Status calls simplegit.v1.ManageService.Status. + func (c *manageServiceClient) Status(ctx context.Context, req *connect.Request[emptypb.Empty]) (*connect.Response[v1.StatusResponse], error) { return c.status.CallUnary(ctx, req) } -// ManageServiceHandler is an implementation of the simplegit.v1.ManageService service. + type ManageServiceHandler interface { - // Create a repo (git init --bare). Reuses the namespace's NamespaceID if it - // exists, else mints a new one. + + CreateRepo(context.Context, *connect.Request[v1.CreateRepoRequest]) (*connect.Response[emptypb.Empty], error) - // OK if the repo is registered; NotFound otherwise. + ExistRepo(context.Context, *connect.Request[v1.ExistRepoRequest]) (*connect.Response[emptypb.Empty], error) - // Remove a repo (DB row + repo-targeted ACL grants + on-disk dir). + DeleteRepo(context.Context, *connect.Request[v1.DeleteRepoRequest]) (*connect.Response[emptypb.Empty], error) - // Move/rename a repo (transfer and/or rename). + MoveRepo(context.Context, *connect.Request[v1.MoveRepoRequest]) (*connect.Response[emptypb.Empty], error) - // Rename a namespace (all its repos); NamespaceID is stable. + MoveNS(context.Context, *connect.Request[v1.MoveNSRequest]) (*connect.Response[emptypb.Empty], error) - // Grant an SSH key perm on a namespace; registers the key if new. Returns the ACL id. + ACLUpsertSSHKeyOnNS(context.Context, *connect.Request[v1.ACLUpsertSSHKeyOnNSRequest]) (*connect.Response[v1.ACLIDResponse], error) - // Grant a PAT perm on a namespace; creates the PAT if new. Returns the ACL id. + ACLUpsertPATOnNS(context.Context, *connect.Request[v1.ACLUpsertPATOnNSRequest]) (*connect.Response[v1.ACLIDResponse], error) - // Grant an SSH key perm on a repo; registers the key if new. Returns the ACL id. + ACLUpsertSSHKeyOnRepo(context.Context, *connect.Request[v1.ACLUpsertSSHKeyOnRepoRequest]) (*connect.Response[v1.ACLIDResponse], error) - // Grant a PAT perm on a repo; creates the PAT if new. Returns the ACL id. + ACLUpsertPATOnRepo(context.Context, *connect.Request[v1.ACLUpsertPATOnRepoRequest]) (*connect.Response[v1.ACLIDResponse], error) - // Remove a single ACL grant by id. + ACLDelete(context.Context, *connect.Request[v1.ACLDeleteRequest]) (*connect.Response[emptypb.Empty], error) - // Daemon status + config: startup params, listen locations, start time / - // uptime, and the state DB URI (gitctl uses db_uri to connect directly for - // inspection reads -- there is no list RPC on this service). Diagnostics only; - // does not touch the DB. + + + + Status(context.Context, *connect.Request[emptypb.Empty]) (*connect.Response[v1.StatusResponse], error) } -// NewManageServiceHandler builds an HTTP handler from the service implementation. It returns the -// path on which to mount the handler and the handler itself. -// -// By default, handlers support the Connect, gRPC, and gRPC-Web protocols with the binary Protobuf -// and JSON codecs. They also support gzip compression. + + + + + func NewManageServiceHandler(svc ManageServiceHandler, opts ...connect.HandlerOption) (string, http.Handler) { manageServiceMethods := v1.File_gitrpc_gitrpc_proto.Services().ByName("ManageService").Methods() manageServiceCreateRepoHandler := connect.NewUnaryHandler( @@ -927,7 +927,7 @@ func NewManageServiceHandler(svc ManageServiceHandler, opts ...connect.HandlerOp }) } -// UnimplementedManageServiceHandler returns CodeUnimplemented from all methods. + type UnimplementedManageServiceHandler struct{} func (UnimplementedManageServiceHandler) CreateRepo(context.Context, *connect.Request[v1.CreateRepoRequest]) (*connect.Response[emptypb.Empty], error) { diff --git a/hooks/event.go b/hooks/event.go index 32dda2f..2a5e784 100644 --- a/hooks/event.go +++ b/hooks/event.go @@ -1,40 +1,40 @@ -// Package hooks wires git hook invocations back into the simplegit daemon. -// -// Git hooks are scripts git runs at fixed points in an operation (push, commit, -// ...). For a bare hosting repo only the server-side hooks fire during a push, -// but the same forwarding script is installed for every git-defined hook name so -// whichever one git runs is captured -- the bus listens to every hook type. -// -// Pipeline: -// -// git (during a push) -> hooks/ (rendered shell script) -> -// `simplegit hook --root=... [args]` (this package's client) -> -// unix socket /hook.sock -> daemon Listener -> buffered bus chan -> -// Updater (logs now; future: forwards to a subscribed URL or MQ). -// -// Hooks fail open: if the socket is absent or the daemon is unreachable the -// client exits 0, so a git operation (push) is never blocked by the event bus. -// Enforcement (pre-receive reject) is a future layer on the same response -// protocol; today the listener observes only. + + + + + + + + + + + + + + + + + + package hooks -// HookEvent is one hook invocation forwarded by a hook script. + type HookEvent struct { - Type string `json:"type"` // hook name, e.g. "pre-receive" - Repo string `json:"repo"` // "ns/name" resolved from the repo path, "" if unknown - RepoPath string `json:"repo_path"` // absolute bare repo path (the hook's cwd) - Args []string `json:"args"` // hook argv after the hook name - Stdin string `json:"stdin"` // raw stdin (ref-update lines for receive hooks) - Env map[string]string `json:"env"` // captured GIT_* env vars + Type string `json:"type"` + Repo string `json:"repo"` + RepoPath string `json:"repo_path"` + Args []string `json:"args"` + Stdin string `json:"stdin"` + Env map[string]string `json:"env"` } -// hookNames is every hook git looks for in $GIT_DIR/hooks. Only the server-side -// ones fire on a bare hosting repo during a push, but installing all of them -// means whichever git runs is forwarded -- and a hook name git does not use is -// simply ignored, so the full set is harmless. Keep this list in sync with -// githooks(5). + + + + + var hookNames = []string{ - // server-side (fire during a push to a bare repo) + "pre-receive", "update", "post-receive", @@ -42,7 +42,7 @@ var hookNames = []string{ "proc-receive", "push-to-checkout", "reference-transaction", - // client-side (fire in a working repo; dormant on a bare host) + "pre-applypatch", "post-applypatch", "pre-commit", diff --git a/hooks/hooks_test.go b/hooks/hooks_test.go index 188fa79..aceec8b 100644 --- a/hooks/hooks_test.go +++ b/hooks/hooks_test.go @@ -10,9 +10,9 @@ import ( "time" ) -// expectedHookBody is the exact forwarding body every rendered hook carries, -// with %s = the hook name. Kept here so drift in the generator output fails the -// test loudly. + + + const expectedHookBody = `#!/bin/sh # simplegit-managed git hook. Forwards this invocation to the simplegit daemon # hook bus via ` + "`simplegit hook`" + `. The daemon exports SIMPLEGIT_BIN (absolute @@ -23,9 +23,9 @@ test -n "$SIMPLEGIT_BIN" || exit 0 exec "$SIMPLEGIT_BIN" hook --root="${SIMPLEGIT_ROOT:-}" %s "$@" ` -// TestTemplateInSync asserts the checked-in template/hooks/ files exactly match -// hooks.hookNames: one real script per hook, no more, no less. If hookNames -// changes, rerun the template generator and commit. + + + func TestTemplateInSync(t *testing.T) { entries, err := fs.ReadDir(templateFS, "template/hooks") if err != nil { @@ -82,7 +82,7 @@ func TestEnsureTemplate(t *testing.T) { t.Errorf("hook %s: body mismatch\n got: %q\nwant: %q", name, string(body), want) } } - // Idempotent: a second run must not error. + if _, err := EnsureTemplate(root); err != nil { t.Fatalf("EnsureTemplate second run: %v", err) } @@ -97,8 +97,8 @@ func TestStartRoundTrip(t *testing.T) { t.Fatalf("Start: %v", err) } - // Give the listener a moment to bind (Bind is synchronous, but the accept - // loop runs in a goroutine; the socket file existing is the ready signal). + + deadline := time.Now().Add(2 * time.Second) for { if _, err := os.Stat(SockPath(root)); err == nil { @@ -133,8 +133,8 @@ func TestStartRoundTrip(t *testing.T) { } func TestForwardFailOpen(t *testing.T) { - // No listener on this root -> dial fails -> forward must return 0 (fail - // open) rather than blocking or rejecting the push. + + root := t.TempDir() exit := forward(HookEvent{Type: "pre-receive"}, SockPath(root)) if exit != 0 { @@ -144,15 +144,15 @@ func TestForwardFailOpen(t *testing.T) { func TestResolveRepo(t *testing.T) { root := t.TempDir() - // GIT_DIR is the primary signal git sets for the hook (relative "." under - // git http-backend / SSH receive-pack, which chdir into the repo); cwd is - // the fallback when GIT_DIR is unset. Both sides are symlink-resolved. + + + cases := []struct{ gitDir, cwd, want string }{ {filepath.Join(root, "repos", "acme", "widget.git"), "/somewhere", "acme/widget"}, {filepath.Join(root, "repos", "org", "team", "svc.git"), "/somewhere", "org/team/svc"}, - // GIT_DIR unset -> fall back to cwd. + {"", filepath.Join(root, "repos", "acme", "widget.git"), "acme/widget"}, - // neither under /repos. + {"", "/tmp/elsewhere.git", ""}, {filepath.Join(root, "repos"), "/somewhere", ""}, } diff --git a/hooks/listener.go b/hooks/listener.go index c9413fd..79c4296 100644 --- a/hooks/listener.go +++ b/hooks/listener.go @@ -12,36 +12,36 @@ import ( "path/filepath" ) -// SockPath returns the unix socket path for the hook bus: /hook.sock. + func SockPath(root string) string { return filepath.Join(root, "hook.sock") } -// Listener accepts hook events on a unix socket and fans them out to a buffered -// channel. It responds to each hook with exit 0 immediately -- observe-only, -// never block a push. If the channel is full an event is dropped (with a log) -// rather than blocking the hook. + + + + type Listener struct { sock string bus chan<- HookEvent ln net.Listener } -// NewListener creates a Listener that will serve on /hook.sock and publish -// received events to bus. + + func NewListener(root string, bus chan<- HookEvent) *Listener { return &Listener{sock: SockPath(root), bus: bus} } -// Start binds the hook listener on /hook.sock and returns a read-only -// channel of forwarded hook events. The accept loop runs in a goroutine until -// ctx is canceled. A bind error is returned synchronously so the caller can -// decide to proceed (hooks then fail open) or abort. -// -// The returned channel is buffered (256); when full, new events are dropped -// with a log rather than blocking a push. Nothing consumes it yet -- a full -// buffer is the expected steady state and pushes keep working (hooks always -// fail open). How the channel is consumed is left for later. + + + + + + + + + func Start(ctx context.Context, root string) (<-chan HookEvent, error) { bus := make(chan HookEvent, 256) l := NewListener(root, bus) @@ -56,9 +56,9 @@ func Start(ctx context.Context, root string) (<-chan HookEvent, error) { return bus, nil } -// Bind creates the unix socket, removing any stale socket first. Call once -// before Serve. Split from Serve so callers can fail fast on a bind error -// instead of learning about it from a background goroutine. + + + func (l *Listener) Bind() error { if err := os.RemoveAll(l.sock); err != nil && !os.IsNotExist(err) { return fmt.Errorf("hooks: remove stale socket: %w", err) @@ -72,9 +72,9 @@ func (l *Listener) Bind() error { return nil } -// Serve accepts connections until ctx is done or a fatal accept error occurs. -// Bind must have been called. Returns net.ErrClosed (wrapped) when shut down via -// ctx cancellation. + + + func (l *Listener) Serve(ctx context.Context) error { go func() { <-ctx.Done() @@ -92,9 +92,9 @@ func (l *Listener) Serve(ctx context.Context) error { } } -// handle reads one newline-delimited JSON event, responds exit 0, and dispatches -// the event to the bus asynchronously. Responding before dispatch keeps the hook -// (and thus the push) from waiting on the consumer. + + + func (l *Listener) handle(conn net.Conn) { defer conn.Close() reader := bufio.NewReader(conn) @@ -110,7 +110,7 @@ func (l *Listener) handle(conn net.Conn) { writeExit0(conn) return } - // Observe-only: always allow. Enforcement is a future layer. + writeExit0(conn) select { @@ -120,7 +120,7 @@ func (l *Listener) handle(conn net.Conn) { } } -// writeExit0 sends the fail-open response so the hook client exits 0. + func writeExit0(conn net.Conn) { _, _ = conn.Write([]byte(`{"exit":0}` + "\n")) } diff --git a/hooks/template.go b/hooks/template.go index d2d8843..3f1480b 100644 --- a/hooks/template.go +++ b/hooks/template.go @@ -8,26 +8,26 @@ import ( "path/filepath" ) -// templateFS is the checked-in git template directory. It ships real, copy-able -// hook scripts (one per git hook name) under template/hooks/. The daemon -// materializes it to /template at startup and CreateRepo passes -// --template=/template to `git init --bare`, so every new repo gets these -// hooks verbatim. This is the "template repo": a versioned, inspectable artifact -// rather than something generated from a Go string. -// + + + + + + + //go:embed template var templateFS embed.FS -// TemplateDir returns the on-disk git template directory for root: /template. -// CreateRepo passes --template= to `git init --bare`. + + func TemplateDir(root string) string { return filepath.Join(root, "template") } -// EnsureTemplate materializes the embedded template directory to /template -// and returns the template dir. Files under hooks/ are made executable (go:embed -// does not preserve the executable bit). Idempotent: rewrites every file each -// call, so an updated template is picked up on the next daemon start. + + + + func EnsureTemplate(root string) (string, error) { dst := TemplateDir(root) if err := os.MkdirAll(dst, 0o755); err != nil { @@ -48,7 +48,7 @@ func EnsureTemplate(root string) (string, error) { if err := os.WriteFile(out, data, 0o644); err != nil { return fmt.Errorf("hooks: write %s: %w", p, err) } - // Hook scripts must be executable; go:embed does not preserve mode bits. + if filepath.Dir(p) == "template/hooks" { if err := os.Chmod(out, 0o755); err != nil { return fmt.Errorf("hooks: chmod %s: %w", p, err) diff --git a/hooks/trigger.go b/hooks/trigger.go index 62011ff..b303f08 100644 --- a/hooks/trigger.go +++ b/hooks/trigger.go @@ -13,20 +13,20 @@ import ( "time" ) -// RunClient implements the `simplegit hook` subcommand invoked by rendered hook -// scripts. It forwards one hook invocation to the daemon's hook bus over -// /hook.sock and exits with the daemon's response code. It fails open: -// any IPC error (no socket, refused, timeout) exits 0 so the git operation is -// never blocked by the event bus. -// -// Usage: simplegit hook --root=PATH [hook-args...] -// -// --root is parsed manually (not via flag) so hook args that begin with '-' are -// passed through verbatim instead of being mistaken for flags. + + + + + + + + + + func RunClient(args []string) { root, rest := parseRoot(args) if root == "" || len(rest) < 1 { - // Malformed invocation (e.g. manual call): fail open, do not break git. + fmt.Fprintln(os.Stderr, "simplegit hook: usage: simplegit hook --root=PATH [args...]") os.Exit(0) } @@ -47,8 +47,8 @@ func RunClient(args []string) { os.Exit(forward(ev, SockPath(root))) } -// parseRoot extracts a --root=PATH (or --root PATH) flag from args and returns -// it plus the remaining positional args. + + func parseRoot(args []string) (root string, rest []string) { for i := 0; i < len(args); i++ { a := args[i] @@ -65,8 +65,8 @@ func parseRoot(args []string) (root string, rest []string) { return root, rest } -// forward sends ev to the hook socket and returns the daemon's requested exit -// code. Returns 0 on any error (fail open). + + func forward(ev HookEvent, sock string) int { body, err := json.Marshal(ev) if err != nil { @@ -75,8 +75,8 @@ func forward(ev HookEvent, sock string) int { } conn, err := net.DialTimeout("unix", sock, 5*time.Second) if err != nil { - // Daemon not running / socket absent: fail open and stay quiet (common in - // isolated mode or during a daemon restart). + + return 0 } defer conn.Close() @@ -101,11 +101,11 @@ func forward(ev HookEvent, sock string) int { return resp.Exit } -// resolveRepo turns the hook's repo directory into "ns/name". git sets GIT_DIR -// for the hook (relative "." under git http-backend / SSH receive-pack, which -// chdir into the repo); cwd is the fallback when GIT_DIR is unset. Both sides -// are symlink-resolved so a root passed as /tmp and a cwd that resolves to -// /private/tmp (macOS) still match. Returns "" if not under /repos. + + + + + func resolveRepo(root, gitDir, cwd string) string { dir := gitDir if dir == "" || !filepath.IsAbs(dir) { @@ -125,8 +125,8 @@ func resolveRepo(root, gitDir, cwd string) string { return strings.TrimSuffix(rel, ".git") } -// captureGitEnv collects GIT_* env vars (GIT_DIR, GIT_PUSH_CERT_*, etc.) so the -// event consumer sees the context git handed the hook. + + func captureGitEnv() map[string]string { env := map[string]string{} for _, kv := range os.Environ() { diff --git a/state/access.go b/state/access.go index 9b76707..802f53c 100644 --- a/state/access.go +++ b/state/access.go @@ -1,9 +1,9 @@ -// Shared access-check primitives and errors. -// -// state defines its own error sentinels (ErrDenied / ErrInvalidToken / -// ErrRepoNotFound) and stays free of any gitrpc dependency -- the cmd layer -// maps these to HTTP/SSH outcomes. hasAccess is the single ACL-rank check -// shared by the SSH and PAT access paths. + + + + + + package state @@ -15,27 +15,27 @@ import ( ) var ( - // ErrDenied means the caller is not permitted: an anonymous caller needing - // a grant, or an authenticated caller whose ACL grant is insufficient. + + ErrDenied = errors.New("access denied") - // ErrInvalidToken means a PAT was presented but could not be matched (unknown - // hash) or has expired. Distinct from anonymous (no PAT at all). + + ErrInvalidToken = errors.New("invalid or expired token") - // ErrRepoNotFound means no DB row for the (owner, name). Surfaced to callers - // of Open/RepoPath; access paths map it to ErrDenied so existence is never - // leaked. + + + ErrRepoNotFound = errors.New("repository not found") - // ErrNamespaceNotFound means no repo exists in the namespace yet, so its - // shared NamespaceID cannot be resolved. CreateRepo treats this as "mint a - // new NamespaceID"; ACLUpsertOnNS surfaces it (grant before any repo exists). + + + ErrNamespaceNotFound = errors.New("namespace does not exist") ) -// hasAccess reports whether the credential (credType, credID) holds an ACL grant -// satisfying perm on the repo (repoID) or its namespace (nsID). ACL targets are -// polymorphic -- TargetTypeRepo or TargetTypeNS -- so a grant on either applies, -// and the best-ranked matching grant wins. No grant means false (the public-read -// shortcut is handled by the caller before this is called). + + + + + func (s *LocalState) hasAccess(credType CredType, credID, repoID, nsID int64, perm common.Perm) (bool, error) { var acls []ACL err := s.engine.Where("cred_type = ? AND cred_id = ? AND ((target_type = ? AND target_id = ?) OR (target_type = ? AND target_id = ?))", diff --git a/state/db.go b/state/db.go index c2c8b31..9f70d8c 100644 --- a/state/db.go +++ b/state/db.go @@ -1,15 +1,15 @@ -// Database engine opener for simplegit's private DB. -// -// Two backends, selected by URI scheme: -// -// sqlite:// -> modernc.org/sqlite (pure Go, no CGO; single binary) -// postgres:// -> github.com/lib/pq -// -// Both are driven through xorm (see models.go's InitEngine). modernc registers -// its driver under the name "sqlite" by default, but xorm keys its sqlite -// dialect off "sqlite3" -- so we re-register modernc's driver as "sqlite3" in -// init(). This is the same trick simpleci's driver_sqlite_modernc.go uses, and -// it keeps CGO out of the build (unlike mattn/go-sqlite3). + + + + + + + + + + + + package state @@ -35,9 +35,9 @@ import ( "strings" "sync" - _ "github.com/lib/pq" // postgres driver, registered as "postgres" + _ "github.com/lib/pq" "golang.org/x/crypto/ssh" - "modernc.org/sqlite" // pure-Go sqlite driver; re-registered as "sqlite3" below + "modernc.org/sqlite" "xorm.io/xorm" ) @@ -45,7 +45,7 @@ const sqliteDriver = "sqlite3" type IState interface { Open(owner, name string) (*gitcmd.Repository, error) - // should check database, if not exist, return err directly + RepoPath(owner, name string) (string, error) KnownSSHKey(key ssh.PublicKey) (bool, error) AccessBySSHKey(ctx context.Context, key ssh.PublicKey, repons, reponame string, perm common.Perm) (ok bool, err error) @@ -69,7 +69,7 @@ type IStateMut interface { ACLDelete(id int64) error } -// Compile-time guarantees that LocalState satisfies both views. + var ( _ IState = (*LocalState)(nil) _ IStateMut = (*LocalState)(nil) @@ -85,8 +85,8 @@ type LocalState struct { } func init() { - // Register modernc's driver under "sqlite3" so xorm picks up its sqlite - // dialect while the actual driver stays CGO-free. + + sql.Register(sqliteDriver, &sqlite.Driver{}) } @@ -106,8 +106,8 @@ func Open(cfg *common.Config) (*LocalState, error) { }, nil } -// Signer returns the SSH host key, creating and persisting it on first use. -// Cached so the key is stable for the process lifetime. + + func (s *LocalState) Signers() ([]ssh.Signer, error) { s.signerMu.Lock() defer s.signerMu.Unlock() @@ -185,8 +185,8 @@ func fingerprint(key ssh.PublicKey) string { return "SHA256:" + hex.EncodeToString(sum[:]) } -// findRepo looks up a repo by (owner, name). name may include ".git". A missing -// row yields ErrRepoNotFound. + + func (s *LocalState) findRepo(owner, name string) (*Repo, error) { var r Repo has, err := s.engine.Where("namespace = ? AND name = ?", owner, strings.TrimSuffix(name, ".git")).Get(&r) @@ -203,9 +203,9 @@ func (s *LocalState) RepoPath(owner, name string) (string, error) { return s.cfg.RepoPath(owner, name) } -// Open returns a gitcmd.Repository for owner/name. The repo must be registered -// (RepoPath checks the DB) and present on disk. In skip-auth mode only the disk -// presence is required. + + + func (s *LocalState) Open(owner, name string) (*gitcmd.Repository, error) { path, err := s.RepoPath(owner, name) if err != nil { @@ -214,10 +214,10 @@ func (s *LocalState) Open(owner, name string) (*gitcmd.Repository, error) { return gitcmd.OpenRepository(context.Background(), path) } -// Engine returns the underlying xorm engine. It is the read egress for the -// ManageService inspection RPCs (ListRepos / GetRepoGrants): those queries live -// in the gitrpc server layer, so state itself stays free of List*/management- -// read methods (only access checks + Updater writes live here). nil in skip-auth -// mode -- but ManageService is disabled under skip-auth, so no caller is reached -// with a nil engine. + + + + + + func (s *LocalState) Engine() *xorm.Engine { return s.engine } diff --git a/state/http.go b/state/http.go index 90e3422..98b5f1f 100644 --- a/state/http.go +++ b/state/http.go @@ -1,13 +1,13 @@ -// HTTP-transport (PAT) access checks. -// -// A PAT is an opaque credential. Only its sha256 hash is stored; the plaintext -// is returned once at creation. AccessByPAT takes the PAT string directly (the -// cmd layer parses it out of the Authorization header) and checks it against the -// repo's (or its namespace's) ACL -- the state-layer half of the IState.AccessByPAT -// contract. -// -// PATs are created via ACLUpsertPATOnNS/OnRepo (IStateMut); there is no separate -// CreatePAT and intentionally no list/management read surface. + + + + + + + + + + package state @@ -22,18 +22,18 @@ import ( "simplegit/common" ) -const patPrefix = "sgp_" // plaintext token prefix +const patPrefix = "sgp_" + + + + + + + + + + -// AccessByPAT authorizes pat for perm on owner/name. -// -// - public read is open to everyone, including anonymous (pat ignored); -// - an empty pat is anonymous: allowed only by the public-read shortcut, -// otherwise ErrDenied (the caller decides 401 vs 403 from pat presence); -// - a present-but-unknown or expired pat yields ErrInvalidToken; -// - an authenticated but insufficient grant yields ErrDenied; -// - an unknown repo yields ErrDenied (existence never leaked). -// -// skip-auth allows all. func (s *LocalState) AccessByPAT(ctx context.Context, pat, owner, name string, perm common.Perm) (bool, error) { r, err := s.findRepo(owner, name) if err != nil { @@ -42,17 +42,17 @@ func (s *LocalState) AccessByPAT(ctx context.Context, pat, owner, name string, p } return false, err } - // public read is open to everyone, including anonymous + if perm == common.PermRead && !r.IsPrivate { return true, nil } if pat == "" { - // anonymous, but this needs a grant (private read, or any write) + return false, ErrDenied } p, err := s.lookupPAT(pat) if err != nil { - return false, err // ErrInvalidToken + return false, err } ok, err := s.hasAccess(CredTypePAT, p.ID, r.ID, r.NamespaceID, perm) if err != nil { @@ -64,8 +64,8 @@ func (s *LocalState) AccessByPAT(ctx context.Context, pat, owner, name string, p return true, nil } -// lookupPAT hashes the presented token and returns the matching PAT row, or -// ErrInvalidToken if unknown or expired. Best-effort last-used stamp. + + func (s *LocalState) lookupPAT(token string) (*PAT, error) { sum := sha256.Sum256([]byte(token)) var pat PAT @@ -79,7 +79,7 @@ func (s *LocalState) lookupPAT(token string) (*PAT, error) { if pat.ExpiresAt != nil && time.Now().After(*pat.ExpiresAt) { return nil, ErrInvalidToken } - // best-effort last-used stamp; a failure here must not block access + now := time.Now() if _, err := s.engine.ID(pat.ID).Cols("last_used_at").Update(&PAT{LastUsedAt: &now}); err == nil { pat.LastUsedAt = &now diff --git a/state/models.go b/state/models.go index 56eec18..a43924e 100644 --- a/state/models.go +++ b/state/models.go @@ -1,19 +1,19 @@ -// Private-DB models for simplegit. -// -// This is a credential-centric access model: there is NO users table. The two -// things that can authenticate a git operation are an SSH public key or a PAT, -// and each is granted access to repos directly via the ACL table. This matches -// the Store interface (cmd/listener.go): AccessBySSHKey / AccessByToken resolve -// a credential and check it against a repo, with no user id in between. -// -// Anonymous access (no credential) is governed by Repo.IsPrivate: a public repo -// allows anonymous read; write always requires a credential. That special case -// lives in the store layer, not in ACL -- ACL only records explicit grants. -// -// xorm derives the schema from the struct tags via Sync2 (see InitEngine), so -// there is no hand-written DDL. GonicMapper is required so SSHKey -> "ssh_key" -// (the default SnakeMapper would produce "s_s_h_key"); this matches -// simpleconsole, whose raw-SQL joins rely on the same names. + + + + + + + + + + + + + + + + package state import ( @@ -23,8 +23,8 @@ import ( "xorm.io/xorm/names" ) -// BaseModel is the common row shape: auto-increment PK plus xorm-managed -// created/updated/deleted timestamps (soft-delete via DeletedAt). + + type BaseModel struct { ID int64 `xorm:"pk autoincr" json:"id"` CreatedAt time.Time `xorm:"created" json:"created_at"` @@ -32,59 +32,59 @@ type BaseModel struct { DeletedAt *time.Time `xorm:"deleted" json:"deleted_at,omitempty"` } -// Repo is a registered repository. The on-disk bare repo lives under the git -// root; this row is the authority for its namespace/name and visibility. + + type Repo struct { BaseModel `xorm:"extends"` NamespaceID int64 `xorm:"notnull unique(cred)" json:"namespace_id"` Namespace string `xorm:"varchar(255) notnull index" json:"namespace"` NameID int64 `xorm:"notnull unique(cred)" json:"name_id"` Name string `xorm:"varchar(255) notnull" json:"name"` - IsPrivate bool `xorm:"default false" json:"is_private"` // public repos allow anonymous read + IsPrivate bool `xorm:"default false" json:"is_private"` } -// SSHKey is a registered public key, resolved by fingerprint in AccessBySSHKey. + type SSHKey struct { BaseModel `xorm:"extends"` PublicKey string `xorm:"text notnull" json:"-"` - Fingerprint string `xorm:"varchar(255) unique notnull" json:"fingerprint"` // "SHA256:..." - KeyType string `xorm:"varchar(50) notnull" json:"key_type"` // ssh-ed25519, ssh-rsa, ... + Fingerprint string `xorm:"varchar(255) unique notnull" json:"fingerprint"` + KeyType string `xorm:"varchar(50) notnull" json:"key_type"` Comment string `xorm:"varchar(255)" json:"comment,omitempty"` LastUsedAt *time.Time `xorm:"" json:"last_used_at,omitempty"` } -// PAT is an opaque HTTP credential. Only its sha256 hash is stored; the -// plaintext is returned once at creation. AccessByToken hashes the presented -// token and looks it up here by token_hash. + + + type PAT struct { BaseModel `xorm:"extends"` - TokenHash string `xorm:"varchar(64) unique notnull" json:"-"` // hex(sha256(plaintext)) - Prefix string `xorm:"varchar(20)" json:"prefix"` // first chars, for display only + TokenHash string `xorm:"varchar(64) unique notnull" json:"-"` + Prefix string `xorm:"varchar(20)" json:"prefix"` Name string `xorm:"varchar(255)" json:"name"` ExpiresAt *time.Time `xorm:"" json:"expires_at,omitempty"` LastUsedAt *time.Time `xorm:"" json:"last_used_at,omitempty"` } -// ACL grants a credential access to a repo. CredType selects which table CredID -// points at -- the foreign key is polymorphic by design, so it is NOT -// database-enforced: deleting a credential must clean up its ACL rows in the -// store layer. The unique(cred) group means one grant per credential per repo; -// raise it by updating perm, not by inserting a duplicate. + + + + + type ACL struct { BaseModel `xorm:"extends"` - CredType int `xorm:"notnull unique(cred) index" json:"cred_type"` // CredTypePAT or CredTypeSSH - CredID int64 `xorm:"notnull unique(cred)" json:"cred_id"` // pats.id or ssh_keys.id (per CredType) - TargetType int `xorm:"notnull unique(cred) index" json:"target_type"` // TargetType + CredType int `xorm:"notnull unique(cred) index" json:"cred_type"` + CredID int64 `xorm:"notnull unique(cred)" json:"cred_id"` + TargetType int `xorm:"notnull unique(cred) index" json:"target_type"` TargetID int64 `xorm:"notnull unique(cred)" json:"target_id"` - Perm string `xorm:"varchar(20) default 'read'" json:"perm"` // PermRead | PermWrite | PermAdmin + Perm string `xorm:"varchar(20) default 'read'" json:"perm"` } type CredType int -// Credential kinds for ACL.CredType. + const ( - CredTypePAT CredType = 0 // HTTP transport (PAT) - CredTypeSSH CredType = 1 // SSH transport (public key) + CredTypePAT CredType = 0 + CredTypeSSH CredType = 1 ) type TargetType int @@ -94,9 +94,9 @@ const ( TargetTypeNS TargetType = 1 ) -// InitEngine opens an xorm engine on driver/dsn, applies the GonicMapper (see -// package doc), and syncs all model tables. It mirrors simpleconsole's -// models.InitEngine so the two services share table/column naming. + + + func InitEngine(driverName, dataSourceName string) (*xorm.Engine, error) { engine, err := xorm.NewEngine(driverName, dataSourceName) if err != nil { diff --git a/state/mut.go b/state/mut.go index 2f84521..d64bc0a 100644 --- a/state/mut.go +++ b/state/mut.go @@ -1,13 +1,13 @@ -// Mutation surface (IStateMut): repo lifecycle + ACL grants. -// -// These are the Updater's write operations. NamespaceID/NameID are denormalized -// on Repo. CreateRepo reuses an existing namespace's NamespaceID if one is -// present, else mints a fresh one (max(namespace_id)+1); NameID is always fresh -// (max(name_id)+1). Both assume a single mutator (the Updater). All deletes are -// hard (Unscoped) so IDs are freed for reuse. -// -// ACL targets are polymorphic (TargetTypeRepo / TargetTypeNS): a grant on a -// namespace covers every repo sharing its NamespaceID. + + + + + + + + + + package state @@ -26,9 +26,9 @@ import ( "simplegit/common" ) -// parseRepoPath splits a repo path "ns/name[.git]" into (ns, name) on the last -// "/". ns may be nested ("org/team/name.git" -> ns "org/team", name "name"). -// Mirrors cmd.splitOwnerName. + + + func parseRepoPath(path string) (ns, name string, err error) { path = strings.TrimPrefix(path, "/") path = strings.TrimSuffix(path, ".git") @@ -38,9 +38,9 @@ func parseRepoPath(path string) (ns, name string, err error) { return "", "", fmt.Errorf("repo path %q has no namespace segment", path) } -// nsDir returns the absolute on-disk directory for a namespace (root/ns), -// verifying it stays under root. Unlike common.Resolve it does NOT require a -// ".git" suffix -- namespaces are directories, not repos. + + + func (s *LocalState) nsDir(ns string) (string, error) { ns = strings.TrimPrefix(ns, "/") if ns == "" || strings.Contains(ns, "..") { @@ -60,9 +60,9 @@ func (s *LocalState) nsDir(ns string) (string, error) { return abs, nil } -// resolveNamespaceID returns the (shared) NamespaceID for ns by reading any -// existing repo in that namespace. Errors if the namespace has no repos yet -- -// the Updater must bootstrap a namespace before CreateRepo / ACLUpsertOnNS. + + + func (s *LocalState) resolveNamespaceID(ns string) (int64, error) { var r Repo has, err := s.engine.Where("namespace = ?", ns).Get(&r) @@ -75,9 +75,9 @@ func (s *LocalState) resolveNamespaceID(ns string) (int64, error) { return r.NamespaceID, nil } -// nextNameID returns a fresh NameID (max(name_id)+1). Single-writer assumption -// (the Updater is the only mutator); swap for the Updater's own scheme if it -// manages NameID externally. + + + func (s *LocalState) nextNameID() (int64, error) { var row struct { M int64 `xorm:"m"` @@ -88,8 +88,8 @@ func (s *LocalState) nextNameID() (int64, error) { return row.M + 1, nil } -// nextNamespaceID returns a fresh NamespaceID (max(namespace_id)+1). Mirrors -// nextNameID; single-writer assumption (the Updater is the only mutator). + + func (s *LocalState) nextNamespaceID() (int64, error) { var row struct { M int64 `xorm:"m"` @@ -100,11 +100,11 @@ func (s *LocalState) nextNamespaceID() (int64, error) { return row.M + 1, nil } -// --- repo lifecycle --- -// CreateRepo registers a repo row and runs `git init --bare` on disk. The -// namespace must already exist (have at least one repo) so NamespaceID can be -// resolved; NameID is freshly assigned. skip-auth: disk-only (git init, no DB). + + + + func (s *LocalState) CreateRepo(ns, name string) error { path, err := s.cfg.RepoPath(ns, name) if err != nil { @@ -120,7 +120,7 @@ func (s *LocalState) CreateRepo(ns, name string) error { if !errors.Is(err, ErrNamespaceNotFound) { return err } - nsID, err = s.nextNamespaceID() // brand-new namespace + nsID, err = s.nextNamespaceID() if err != nil { return err } @@ -132,10 +132,10 @@ func (s *LocalState) CreateRepo(ns, name string) error { if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil { return fmt.Errorf("create repo: mkdir ns: %w", err) } - // Use the hook template (materialized by the daemon at startup) so every new - // repo gets forwarding hooks for all git hook types. Fall back to a plain - // init if the template is absent (e.g. a state test without the daemon); - // such a repo simply has no hooks and pushes still work. + + + + initArgs := []string{"init", "--bare"} if fi, err := os.Stat(s.cfg.TemplateDir()); err == nil && fi.IsDir() { initArgs = append(initArgs, "--template="+s.cfg.TemplateDir()) @@ -157,14 +157,14 @@ func (s *LocalState) CreateRepo(ns, name string) error { return nil } -// ExistRepo returns nil if a repo is registered, ErrRepoNotFound otherwise. + func (s *LocalState) ExistRepo(ns, name string) error { _, err := s.findRepo(ns, name) return err } -// DeleteRepo removes the repo row, its repo-targeted ACL grants, and the -// on-disk bare directory. Hard-deletes. skip-auth: disk-only. + + func (s *LocalState) DeleteRepo(ns, name string) error { path, _ := s.cfg.RepoPath(ns, name) r, err := s.findRepo(ns, name) @@ -183,9 +183,9 @@ func (s *LocalState) DeleteRepo(ns, name string) error { return nil } -// MoveRepo relocates a repo (transfer and/or rename): renames the on-disk dir -// and updates the row's Namespace/Name (and NamespaceID when the namespace -// changes). NameID is left stable across a rename. skip-auth: disk-only. + + + func (s *LocalState) MoveRepo(old, new string) error { oldNs, oldName, err := parseRepoPath(old) if err != nil { @@ -234,9 +234,9 @@ func (s *LocalState) MoveRepo(old, new string) error { return nil } -// MoveNS renames a namespace: renames the on-disk directory and updates the -// Namespace field of all its repos. NamespaceID is stable (unchanged), so -// ns-targeted ACL grants survive a rename. skip-auth: disk-only. + + + func (s *LocalState) MoveNS(old, new string) error { if old == new { return nil @@ -262,11 +262,11 @@ func (s *LocalState) MoveNS(old, new string) error { return nil } -// --- ACL grants --- -// upsertACL inserts or updates the (credType, credID, targetType, targetID) -// grant to perm and returns the ACL id. The unique(cred) group makes this one -// grant per credential per target. + + + + func (s *LocalState) upsertACL(credType CredType, credID int64, targetType TargetType, targetID int64, perm common.Perm) (int64, error) { var acl ACL has, err := s.engine.Where("cred_type = ? AND cred_id = ? AND target_type = ? AND target_id = ?", @@ -295,7 +295,7 @@ func (s *LocalState) upsertACL(credType CredType, credID int64, targetType Targe return acl.ID, nil } -// ACLDelete removes a single ACL grant by id. Hard-delete. Idempotent. + func (s *LocalState) ACLDelete(id int64) error { if _, err := s.engine.ID(id).Unscoped().Delete(&ACL{}); err != nil { return fmt.Errorf("delete acl: %w", err) @@ -303,7 +303,7 @@ func (s *LocalState) ACLDelete(id int64) error { return nil } -// ensureSSHKey returns the id of a registered SSH key, inserting it if new. + func (s *LocalState) ensureSSHKey(key ssh.PublicKey) (int64, error) { fp := fingerprint(key) var sk SSHKey @@ -325,9 +325,9 @@ func (s *LocalState) ensureSSHKey(key ssh.PublicKey) (int64, error) { return sk.ID, nil } -// ensurePAT returns the id of a PAT matching plaintext, inserting a new PAT row -// (hash + prefix) if it does not exist. PATs are created here -- there is no -// separate CreatePAT on IStateMut. + + + func (s *LocalState) ensurePAT(plaintext string) (int64, error) { sum := sha256.Sum256([]byte(plaintext)) hash := hex.EncodeToString(sum[:]) @@ -350,8 +350,8 @@ func (s *LocalState) ensurePAT(plaintext string) (int64, error) { return pat.ID, nil } -// ACLUpsertSSHKeyOnNS grants key perm on namespace ns (resolved to its shared -// NamespaceID). Registers the key if new. + + func (s *LocalState) ACLUpsertSSHKeyOnNS(ns string, key ssh.PublicKey, perm common.Perm) (int64, error) { nsID, err := s.resolveNamespaceID(ns) if err != nil { @@ -364,8 +364,8 @@ func (s *LocalState) ACLUpsertSSHKeyOnNS(ns string, key ssh.PublicKey, perm comm return s.upsertACL(CredTypeSSH, keyID, TargetTypeNS, nsID, perm) } -// ACLUpsertPATOnNS grants pat (plaintext) perm on namespace ns. Creates the PAT -// if new. + + func (s *LocalState) ACLUpsertPATOnNS(ns string, pat string, perm common.Perm) (int64, error) { nsID, err := s.resolveNamespaceID(ns) if err != nil { @@ -378,7 +378,7 @@ func (s *LocalState) ACLUpsertPATOnNS(ns string, pat string, perm common.Perm) ( return s.upsertACL(CredTypePAT, patID, TargetTypeNS, nsID, perm) } -// ACLUpsertSSHKeyOnRepo grants key perm on repo "ns/name". Registers the key if new. + func (s *LocalState) ACLUpsertSSHKeyOnRepo(reponame string, key ssh.PublicKey, perm common.Perm) (int64, error) { ns, name, err := parseRepoPath(reponame) if err != nil { @@ -395,8 +395,8 @@ func (s *LocalState) ACLUpsertSSHKeyOnRepo(reponame string, key ssh.PublicKey, p return s.upsertACL(CredTypeSSH, keyID, TargetTypeRepo, r.ID, perm) } -// ACLUpsertPATOnRepo grants pat (plaintext) perm on repo "ns/name". Creates the -// PAT if new. + + func (s *LocalState) ACLUpsertPATOnRepo(reponame string, pat string, perm common.Perm) (int64, error) { ns, name, err := parseRepoPath(reponame) if err != nil { diff --git a/state/ssh.go b/state/ssh.go index 9719205..4d04836 100644 --- a/state/ssh.go +++ b/state/ssh.go @@ -1,13 +1,13 @@ -// SSH-transport (public key) access checks. -// -// An SSH key is matched at request time by fingerprint. AccessBySSHKey resolves -// a presented key to an SSHKey row and checks it against the repo's (or its -// namespace's) ACL -- the state-layer half of the IState.AccessBySSHKey -// contract. SSH has no anonymous path: the key must be registered, else -// ErrDenied. -// -// SSH keys are created via ACLUpsertSSHKeyOnNS/OnRepo (IStateMut); there is no -// separate AddSSHKey and no list/management read surface. + + + + + + + + + + package state @@ -22,17 +22,17 @@ import ( "simplegit/common" ) -// AccessBySSHKey authorizes key for perm on owner/name. SSH has no anonymous -// path: the key must be registered, else ErrDenied. A registered key may read a -// public repo without a grant; anything else needs an ACL grant (on the repo or -// its namespace) whose rank satisfies perm. Returns (true, nil) on allow; -// (false, ErrDenied) on an unknown key, unknown repo, or insufficient grant. -// skip-auth allows all. + + + + + + func (s *LocalState) AccessBySSHKey(ctx context.Context, key ssh.PublicKey, owner, name string, perm common.Perm) (bool, error) { r, err := s.findRepo(owner, name) if err != nil { if errors.Is(err, ErrRepoNotFound) { - return false, ErrDenied // never leak existence + return false, ErrDenied } return false, err } @@ -43,15 +43,15 @@ func (s *LocalState) AccessBySSHKey(ctx context.Context, key ssh.PublicKey, owne return false, fmt.Errorf("lookup ssh key: %w", err) } if !has { - return false, ErrDenied // unknown key + return false, ErrDenied } - // best-effort last-used stamp; must not block access + now := time.Now() if _, err := s.engine.ID(sk.ID).Cols("last_used_at").Update(&SSHKey{LastUsedAt: &now}); err == nil { sk.LastUsedAt = &now } - // public read is open to any registered key + if perm == common.PermRead && !r.IsPrivate { return true, nil } @@ -65,21 +65,21 @@ func (s *LocalState) AccessBySSHKey(ctx context.Context, key ssh.PublicKey, owne return true, nil } -// KnownSSHKey reports whether key is an admissible SSH credential: it must be -// registered AND carry at least one ACL grant. A registered key with no grant -// (an orphan -- e.g. its grant was ACLDelete'd, or its repo was DeleteRepo'd) -// is rejected here, at the PublicKeyCallback, so the SSH client falls through -// to another offered key that does have a grant. -// -// This mirrors Gitea's model where every registered key belongs to a user -// (principal): a key without a principal can never authorize anything, so it -// is not admitted. simplegit's namespace plays the principal role -- keys -// granted on a namespace are all equivalent for that namespace's repos, so -// which one the client offers first no longer matters (the multi-key footgun). -// SSH access therefore requires a grant; public repos stay anonymously -// readable over HTTP. -// -// skip-auth admits everything (no engine). + + + + + + + + + + + + + + + func (s *LocalState) KnownSSHKey(key ssh.PublicKey) (bool, error) { var sk SSHKey has, err := s.engine.Where("fingerprint = ?", fingerprint(key)).Get(&sk) @@ -87,11 +87,11 @@ func (s *LocalState) KnownSSHKey(key ssh.PublicKey) (bool, error) { return false, fmt.Errorf("lookup ssh key: %w", err) } if !has { - return false, ErrDenied // unknown key + return false, ErrDenied } - // Admit only keys with at least one grant. A grant-less registered key can - // never pass AccessBySSHKey, and admitting it would block the client from - // trying a granted key. + + + hasGrant, err := s.keyHasGrant(sk.ID) if err != nil { return false, err @@ -102,8 +102,8 @@ func (s *LocalState) KnownSSHKey(key ssh.PublicKey) (bool, error) { return true, nil } -// keyHasGrant reports whether SSH key credID has any ACL grant (repo or -// namespace), regardless of perm. + + func (s *LocalState) keyHasGrant(credID int64) (bool, error) { count, err := s.engine.Where("cred_type = ? AND cred_id = ?", CredTypeSSH, credID).Count(&ACL{}) if err != nil {