Why Forgejo?
- Fully featured code forge
(code development platform)
- Fully Open-Source
→ Data sovereignty
→ We can add custom features
Why not GitHub?
- Vendor lock-in:
no license → no access to own data
→ No Data sovereignty
- Microsoft/Atlassian push us in Cloud
Why not GitLab?
- Only Open-Core
→ Same problem
Go is kinda weird...
- Import things from Domains? "
import forgejo.org/cmd"
- Declare unit test with "
_test.go" suffix
- "
func SomeFunc()" is exported but "func someFunc()" is not
Anatomy of a Go Project
- A single module
(program or library)
- Many packages (each with muliple
.go files)
/go.mod
go.mod defines a module (automatically generated)
github.com/hashicorp/go-version is dependency, download from GitHub
- Fork, new maintainer, domain lost:
replace
/main.go
- Function
main in package main is entrypoint
- Use
cmd package in forgejo.org module
This is not a web URL!
/cmd/main.go
- Package name must be same as parent directory name
Forgejo's Layered Architecture
- Lower Layer self-contained
/models: database abstraction
/modules: could be third-party library
- Easier to reason about
- Other layers only use code below
- → Code in lower layers easier to reason about
Problem: Cyclic Imports
Logical Dependency
/services/automerge calls
/services/pull
(for mergeability check)
/services/pull calls
/services/automerge
(for noticing new PR reviews)
- Go doesn't allow cyclic imports
Problem: Cyclic Imports
Actual Imports
/services/automerge calls
/services/pull
(for mergeability check)
/services/pull calls
/services/automerge
(for noticing new PR reviews)
- Go doesn't allow cyclic imports
- Solution: A third package:
/services/notify
Problem: Cyclic Imports
Notify Topics
- Go doesn't allow cyclic imports
- Solution: A third package:
/services/notify
- Send/Receive message to/from
PullRequestReview topic
- Import for other direction
(here hidden)
- Easy to comprehend and extend system
Forgejo's Pub-Sub Pattern
Forgejo Actions
- CI/CD:
Run code at every push/PR/...
- Failure → developer needs to fix things
- How does she notice failure?
- Web UI (Pull) Already
present
- Mail (Push) My Job
- Webhook (Push) My Job
How to implement Push Notification
- Use pub-sub broker
/services/mailer and /services/webhook receive
ActionRunNowDone message
- Something sends
ActionRunNowDone message
/models/actions notices action
status changes
- Problem: layered architecture doesn't allow
importing
/services/notify
#7510: Refactoring
- Move code from
/models/actions to /services/actions
UpdateRunJob changes database ← notices status change
CleanRepoScheduleTasks calls UpdateRunJob
- Goal: change as little as possible
#7491:
ActionRunNowDone Topic
- Add
ActionRunNowDone topic
- Implement
sendActionRunNowDoneNotificationIfNeeded
UpdateRunJob calls sendActionRunNowDoneNotificationIfNeeded
- A lot of testing
#7509: Action Notification Mail
/services/mailer listens to ActionRunNowDone
- Figure out who to send mail to
- Template and send pretty email
- A lot of testing
#7508: Action Notification Webhook
/services/webhook listens to ActionRunNowDone
- Implement new Webhook type
- Update webhook settings
- A lot of testing
- Other PRs: #7697,
#8066, #8250, #8227 and #8242
- → cleanup, mail opt-in and resolve naming conflict
- 70 files touched
- 2423 lines inserted
- 630 lines deleted
Lessons
- Forgejo's Maintainers are amazing!
- Code research important
- Aim for smallest change
- Split feature into multiple PRs
- Testing, testing, testing
- → Easier to reason about → easier review
- Upstreamed → no fork needed
What we've Seen
- Forgejo is cool
- Go project structure
- Layered architecture
- Pub-sub pattern
- Experience in contributing