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 Method inspects 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) and SynchronizableSource (for LDAP sync) extend the protocol for specific needs without bloating the core Method.

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 Config so xorm can serialize/deserialize it and the source can back-reference its parent Source row.
  • 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 detection
    • SSHKeyProvider — can this source provide SSH keys?
    • RegisterableSource — needs lifecycle callbacks on create/update (RegisterSource(), UnregisterSource())
    • PasswordAuthenticator — supports password-based login
    • SynchronizableSource — supports background user sync
  • Design quality: The core interface is tiny; optional capabilities are discovered via type assertion on concrete Cfg values. 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 implementations
    • mailer.Notifier — sends email notifications
    • webhook.Notifier — enqueues webhook payloads
    • indexer.Notifier — triggers search index updates via queue
    • feed.Notifier — writes activity-feed entries
    • uinotification.Notifier — generates in-app UI notifications
    • automerge.Notifier — schedules auto-merge checks after PR updates
    • mirror.Notifier — reacts to push events for push-mirrors
    • actions.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 NullNotifier to embed so you only implement what you need. New events only require adding a method here and a no-op to NullNotifier. Practically effective, but coupling all observers to all events is costly (adding WorkflowJobStatusUpdate requires 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 RegisterStorageType and 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 (embedding io.ReadCloser + io.Seeker + Stat()) is a clean associated type. The ServeDirectURL method (for generating time-limited signed URLs to bypass Gitea as a proxy) is a thoughtful addition for cloud storage scenarios. ErrURLNotSupported is 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. Downloader reads from a source forge (GitHub, GitLab, Bitbucket, Gitea); Uploader writes to a destination (currently always Gitea itself). The DownloaderFactory matches 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. The Rollback() method on Uploader enables transactional semantics on migration failure. The SupportGetRepoComments() predicate on Downloader is 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.Indexer methods:
    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 Indexer covers lifecycle (init, ping, close); the domain-specific layer adds domain operations. A parallel code/internal.Indexer exists for code-search (same base, different domain methods). A dummyIndexer implementing 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, base Indexer. Notifier is 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.Indexer embeds internal.Indexer). NullNotifier is the base no-op embed for all concrete notifiers. Object embeds io.ReadCloser and io.Seeker.
  • Implicit satisfaction: All interfaces are satisfied implicitly (no explicit var _ I = (*Impl)(nil) declarations except the NullNotifier compile-time check). Interfaces are defined close to where they are consumed (consumer-side in the case of auth.Method; provider-side in the case of ObjectStorage).
  • Optional capability interfaces: Heavy use in the auth subsystem — SkipVerifiable, HasTLSer, UseTLSer, SSHKeyProvider, RegisterableSource are all discovered via type assertion on a Config. This is the standard Go optional-interface pattern applied systematically.
  • stdlib interfaces used: io.ReadCloser, io.Seeker, io.Reader, io.Writer (via ObjectStorage); os.FileInfo (via Object.Stat()); context.Context is a parameter on virtually every interface method (not embedded, always passed explicitly).

Key abstractions#

  1. notify.Notifier — The central event bus. Every significant state change in the application (issue created, PR merged, push received) fans out through registered Notifier implementations. 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 because NullNotifier handles the boilerplate and new events only require two touch points.

  2. 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 widening Method itself.

  3. 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.

  4. 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.

  5. indexer/issues/internal.Indexer — The two-level indexer hierarchy. The base lifecycle interface composes cleanly into domain-specific interfaces. The dummyIndexer pattern (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 implementing Method for HTTP flows and Config for admin-UI configuration. The optional-interface approach means new capabilities (like SAML SSO) can add RegisterableSource without changing the core Config interface.

  • Storage backends (storage.ObjectStorage): New backends (e.g., Azure Blob, Google Cloud Storage) register themselves via RegisterStorageType. 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. The SupportedSearchModes() capability list means the UI adapts to backend features automatically.

  • Repository migration (migration.Downloader / DownloaderFactory): New source forges register a DownloaderFactory in 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) implement Notifier embedding NullNotifier and register at startup. The 40-method surface is the cost of entry.