Gitea — Interfaces#
Interface catalog#
auth.Method#
- Package:
code.gitea.io/gitea/services/auth - File:
services/auth/interface.go - Methods:
Verify(http *http.Request, w http.ResponseWriter, store DataStore, sess SessionStore) (*user_model.User, error) Name() string - Purpose: Defines one plug-in authentication step in the HTTP auth pipeline. Each
Methodinspects an HTTP request for credentials and either returns a user or nil (no match). The pipeline tries each method in order: session → basic → token → OAuth2 → SSPI. - Implementations:
SessionAuth,BasicAuth,OAuth2Auth,ReverseProxy,SSPI— one struct per auth mechanism registered at startup. - Design quality: Excellent ISP adherence. Two methods, one tight contract. The clean nil/user/error tristate is idiomatic. Companion interfaces
PasswordAuthenticator(for username+password flows) andSynchronizableSource(for LDAP sync) extend the protocol for specific needs without bloating the coreMethod.
auth/source.Config#
- Package:
code.gitea.io/gitea/models/auth - File:
models/auth/source.go - Methods:
// embeds xorm convert.Conversion (ToJSON / FromContent) SetAuthSource(*Source) - Purpose: Marker + serialization contract for auth source configuration structs stored in the database as JSON blobs. Each auth type (LDAP, SMTP, OAuth2, PAM, SSPI) implements
Configso xorm can serialize/deserialize it and the source can back-reference its parentSourcerow. - Implementations:
ldap.Source,smtp.Source,oauth2.Source,pam.Source,sspi.Source - Companion optional interfaces (detected via type assertion):
SkipVerifiable— does this source skip TLS cert verification?HasTLSer/UseTLSer— TLS capability detectionSSHKeyProvider— can this source provide SSH keys?RegisterableSource— needs lifecycle callbacks on create/update (RegisterSource(),UnregisterSource())PasswordAuthenticator— supports password-based loginSynchronizableSource— supports background user sync
- Design quality: The core interface is tiny; optional capabilities are discovered via type assertion on concrete
Cfgvalues. This avoids fat interfaces but requires callers to know about all the optional interfaces. It’s an established Go pattern (“optional interface”) used well, though the proliferation of seven optional interfaces is somewhat complex.
notify.Notifier#
- Package:
code.gitea.io/gitea/services/notify - File:
services/notify/notifier.go - Methods: 40+ event callbacks, organized by domain:
Run() // Repository lifecycle AdoptRepository, CreateRepository, MigrateRepository, DeleteRepository, ForkRepository, RenameRepository, TransferRepository, RepoPendingTransfer // Issue/PR events NewIssue, IssueChangeStatus, DeleteIssue, IssueChangeMilestone, IssueChangeAssignee, PullRequestReviewRequest, IssueChangeContent, IssueClearLabels, IssueChangeTitle, IssueChangeRef, IssueChangeLabels // Pull request events NewPullRequest, MergePullRequest, AutoMergePullRequest, PullRequestSynchronized, PullRequestReview, PullRequestCodeComment, PullRequestChangeTargetBranch, PullRequestPushCommits, PullReviewDismiss // Comment events CreateIssueComment, UpdateComment, DeleteComment // Wiki, releases, git refs NewWikiPage, EditWikiPage, DeleteWikiPage, NewRelease, UpdateRelease, DeleteRelease, PushCommits, CreateRef, DeleteRef, SyncPushCommits, SyncCreateRef, SyncDeleteRef // Packages, branches, statuses, Actions PackageCreate, PackageDelete, ChangeDefaultBranch, CreateCommitStatus, WorkflowRunStatusUpdate, WorkflowJobStatusUpdate - Purpose: Observer / event-bus contract. When a service-layer operation completes, it calls the matching
notify.*function, which broadcasts the event to all registered notifiers in a fan-out loop. - Implementations (registered at startup via
RegisterNotifier):NullNotifier— base no-op struct; all other notifiers embed or reference it to get default no-op implementationsmailer.Notifier— sends email notificationswebhook.Notifier— enqueues webhook payloadsindexer.Notifier— triggers search index updates via queuefeed.Notifier— writes activity-feed entriesuinotification.Notifier— generates in-app UI notificationsautomerge.Notifier— schedules auto-merge checks after PR updatesmirror.Notifier— reacts to push events for push-mirrorsactions.Notifier— triggers CI workflow dispatch
- Design quality: This is the largest interface in the codebase and a clear ISP violation by textbook standards — 40+ methods covering every domain event in one type. The trade-off is deliberate: it gives implementors a single point of registration and a base
NullNotifierto embed so you only implement what you need. New events only require adding a method here and a no-op toNullNotifier. Practically effective, but coupling all observers to all events is costly (addingWorkflowJobStatusUpdaterequires every notifier to be touched). A narrower event-type approach (e.g.,interface{ Handles() []EventType }with a typed event union) would be more scalable.
storage.ObjectStorage#
- Package:
code.gitea.io/gitea/modules/storage - File:
modules/storage/storage.go - Methods:
Open(path string) (Object, error) Save(path string, r io.Reader, size int64) (int64, error) Stat(path string) (os.FileInfo, error) Delete(path string) error ServeDirectURL(path, name, method string, opt *ServeDirectOptions) (*url.URL, error) IterateObjects(basePath string, iterator func(fullPath string, obj Object) error) error - Purpose: Pluggable blob storage contract. All user uploads (attachments, LFS objects, avatars, package binaries, Actions artifacts) are stored through this interface. Backends are registered by type name with
RegisterStorageTypeand created on demand. - Implementations:
LocalStorage(local filesystem),S3Storage(AWS S3),MinioStorage(MinIO),AzureBlobStorage— one package per backend. - Design quality: Well-scoped. Six methods covering the full file-object lifecycle including signed URLs for direct client access.
Object(embeddingio.ReadCloser+io.Seeker+Stat()) is a clean associated type. TheServeDirectURLmethod (for generating time-limited signed URLs to bypass Gitea as a proxy) is a thoughtful addition for cloud storage scenarios.ErrURLNotSupportedis returned by backends that don’t support direct serving, letting callers fall back to proxied streaming.
migration.Downloader / migration.Uploader#
- Package:
code.gitea.io/gitea/modules/migration - Files:
modules/migration/downloader.go,modules/migration/uploader.go - Downloader methods:
GetRepoInfo(ctx) (*Repository, error) GetTopics(ctx) ([]string, error) GetMilestones(ctx) ([]*Milestone, error) GetReleases(ctx) ([]*Release, error) GetLabels(ctx) ([]*Label, error) GetIssues(ctx, page, perPage int) ([]*Issue, bool, error) GetComments(ctx, commentable Commentable) ([]*Comment, bool, error) GetAllComments(ctx, page, perPage int) ([]*Comment, bool, error) SupportGetRepoComments() bool GetPullRequests(ctx, page, perPage int) ([]*PullRequest, bool, error) GetReviews(ctx, reviewable Reviewable) ([]*Review, error) FormatCloneURL(opts MigrateOptions, remoteAddr string) (string, error) - Uploader methods:
MaxBatchInsertSize(tp string) int CreateRepo, CreateTopics, CreateMilestones, CreateReleases, SyncTags, SyncBranches CreateLabels, CreateIssues, CreateComments, CreatePullRequests, CreateReviews Rollback() error Finish(ctx) error Close() - DownloaderFactory:
New(ctx, opts MigrateOptions) (Downloader, error) GitServiceType() structs.GitServiceType - Purpose: Bidirectional migration protocol.
Downloaderreads from a source forge (GitHub, GitLab, Bitbucket, Gitea);Uploaderwrites to a destination (currently always Gitea itself). TheDownloaderFactorymatches and instantiates the right downloader for a given service type. - Implementations:
GithubDownloader,GitlabDownloader,BitbucketDownloader,GiteaDownloader;GiteaLocalUploader(the only uploader); factories registered in a global slice. - Design quality: Clean source/sink separation. Pagination baked into
GetIssues/GetPullRequests(page+perPage) is practical for rate-limited APIs. TheRollback()method onUploaderenables transactional semantics on migration failure. TheSupportGetRepoComments()predicate onDownloaderis a capability flag to switch between bulk and per-issue comment fetching — an optional-interface pattern via a bool method rather than a separate interface type.
indexer/issues/internal.Indexer (+ base indexer/internal.Indexer)#
- Package:
code.gitea.io/gitea/modules/indexer/issues/internal - File:
modules/indexer/issues/internal/indexer.go - Base
internal.Indexermethods:Init(ctx context.Context) (bool, error) Ping(ctx context.Context) error Close() - Issues
Indexer(embeds base):Index(ctx context.Context, issue ...*IndexerData) error Delete(ctx context.Context, ids ...int64) error Search(ctx context.Context, options *SearchOptions) (*SearchResult, error) SupportedSearchModes() []indexer.SearchMode - Purpose: Two-layer interface hierarchy for search indexing. The base
Indexercovers lifecycle (init, ping, close); the domain-specific layer adds domain operations. A parallelcode/internal.Indexerexists for code-search (same base, different domain methods). AdummyIndexerimplementing both layers returns errors, serving as the “not yet ready” placeholder before async initialization completes. - Implementations:
BleveIndexer,ElasticsearchIndexer,MeilisearchIndexer— one per backend for each domain (issues and code). - Design quality: The two-level composition is clean — the base covers infrastructure concerns common to all indexers; domain interfaces extend it.
SupportedSearchModes()returns a capability list (fuzzy, exact, etc.) so callers can offer the UI options only available for the active backend.
Interface patterns#
- Size distribution: Bimodal. Most interfaces are small (2–6 methods):
Method,ObjectStorage,Config, baseIndexer.Notifieris a significant outlier at 40+ methods — it functions as a global event hub, not a behavioral abstraction. - Embedding: The indexer hierarchy uses embedding cleanly (
issues.Indexerembedsinternal.Indexer).NullNotifieris the base no-op embed for all concrete notifiers.Objectembedsio.ReadCloserandio.Seeker. - Implicit satisfaction: All interfaces are satisfied implicitly (no explicit
var _ I = (*Impl)(nil)declarations except theNullNotifiercompile-time check). Interfaces are defined close to where they are consumed (consumer-side in the case ofauth.Method; provider-side in the case ofObjectStorage). - Optional capability interfaces: Heavy use in the auth subsystem —
SkipVerifiable,HasTLSer,UseTLSer,SSHKeyProvider,RegisterableSourceare all discovered via type assertion on aConfig. This is the standard Go optional-interface pattern applied systematically. - stdlib interfaces used:
io.ReadCloser,io.Seeker,io.Reader,io.Writer(viaObjectStorage);os.FileInfo(viaObject.Stat());context.Contextis a parameter on virtually every interface method (not embedded, always passed explicitly).
Key abstractions#
notify.Notifier— The central event bus. Every significant state change in the application (issue created, PR merged, push received) fans out through registeredNotifierimplementations. The observer registry (RegisterNotifier) is how cross-cutting concerns (email, webhooks, indexing, activity feed, CI) decouple from service logic. Despite violating ISP by breadth, it works becauseNullNotifierhandles the boilerplate and new events only require two touch points.auth.Method— The pluggable HTTP authentication pipeline. The two-method interface cleanly separates “can I authenticate this request?” from the mechanism. The companion optional interfaces (PasswordAuthenticator,SynchronizableSource) extend the contract for non-HTTP flows without wideningMethoditself.storage.ObjectStorage— The pluggable blob backend. Used by eight named global instances (attachments, LFS, avatars, packages, actions, etc.), each initialized from config. The interface is stable enough that swapping from local to S3/MinIO requires only config changes, not code changes.migration.Downloader/Uploader— The forge-migration protocol. A textbook source/sink pair. The factory pattern (DownloaderFactory) decouples service type detection from migration execution, making it easy to add new forges without touching the migration runner.indexer/issues/internal.Indexer— The two-level indexer hierarchy. The base lifecycle interface composes cleanly into domain-specific interfaces. ThedummyIndexerpattern (returns errors until async init completes) is a reusable technique for managing late-initializing backends safely.
Interface-driven extensibility#
Gitea uses interfaces at four distinct extension points:
Auth methods (
auth.Method+auth/source.Config): Adding a new authentication mechanism requires implementingMethodfor HTTP flows andConfigfor admin-UI configuration. The optional-interface approach means new capabilities (like SAML SSO) can addRegisterableSourcewithout changing the coreConfiginterface.Storage backends (
storage.ObjectStorage): New backends (e.g., Azure Blob, Google Cloud Storage) register themselves viaRegisterStorageType. All storage consumers use the global named instances; no consumer imports a concrete backend.Search indexers (
indexer/*/internal.Indexer): New search engines implement the two-level indexer interface. TheSupportedSearchModes()capability list means the UI adapts to backend features automatically.Repository migration (
migration.Downloader/DownloaderFactory): New source forges register aDownloaderFactoryin a global slice. The migration runner discovers them at runtime and creates the right downloader for the URL.Event observers (
notify.Notifier): New cross-cutting concerns (e.g., a hypothetical Slack notifier) implementNotifierembeddingNullNotifierand register at startup. The 40-method surface is the cost of entry.