# git-next ## Trunk-based developement manager. > A source-control branching model, where developers collaborate on code in a single branch > called ‘trunk’, resist any pressure to create other long-lived development branches by > employing documented techniques. They therefore avoid merge hell, do not break the build, > and live happily ever after. - [source](https://trunkbaseddevelopment.com) - Status: **BETA** - dog-fooding `git-next` is a combined server and command-line tool that enables trunk-based development workflows where each commit must pass CI before being included in the main branch. ## Features - Allows enforcing the requirement for each commit to pass the CI pipeline before being included in the main branch - Provides a server component that manages the trunk-based development process - Ensure a consistent, high-quality codebase by preventing untested changes from being added to main - Requires each commit uses conventional commit format. See [Behaviour](#behaviour) to learn how we do this. ## Prerequisits - Rust 1.76.0 or later - https://www.rust-lang.org - pgk-config - libssl-dev - libdbus-1-dev (ubuntu/debian) - dbus-devel (fedora) See `.cargo/config.toml` for how they are configured. ## Installation You can install `git-next` from : ```shell cargo install git-next ``` If you use [mise](https://mise.jdx.dev): ```shell mise use -g cargo:git-next ``` Or you can install `git-next` from source after cloning: ```shell cargo install --path crates/cli ``` ## Roadmap - [x] cli - [x] server - [x] notifications - notify user when intervention required (e.g. to rebase) - [ ] tui overview - [ ] webui overview ## Branch Names `git-next` uses three branches, `main`, `next` and `dev`, although they do not need to have those names. In the documentation we will use those names, but each repo must specify the names of the branches to use for each, even if they happen to have those same names. ## Configuration - The branches to use for `main`, `next` and `dev` must be specified in either the `.git-next.toml` in the repo itself, or in the server configuration file, `git-next-server.toml`. See below for details. - CI checks should be configured to run when the `next` branch is `pushed`. - The `dev` branch _must_ have the `main` branch as an ancestor. - The `next` branch _must_ have the `main` branch as an ancestor. ### Server The server is configured by the `git-next-server.toml` file. #### listen The server should listen for webhook notifications from each forge. ```toml [listen] http = { addr = "0.0.0.0", port = 8080 } url = "https://localhost:8080" ``` ##### http The server needs to be able to receive webhook notifications from your forge, (e.g. github.com). You can do this via any method that suits your environment, e.g. ngrok or a reverse proxy from a web server that itself can route traffic to the machine you are running the git-next server on. Specify the address and port the server should listen to for incoming webhooks. This is the address and port that your reverse proxy should route traffic to. - **addr** - the IP address the server should bind to - **port** - the IP port the server should bind to ##### url The HTTPS URL for forges to send webhooks to. Your forges need to know where they should route webhooks to. This should be an address this is accessible to the forge. So, for github.com, it would need to be a publicly accessible HTTPS URL. For a self-hosted forge, e.g. ForgeJo, on your own network, then it only needs to be accessible from the server your forge is running on. #### shout The server should be able to notify the user when manual intervention is required. ```toml [shout] desktop = true [shout.webhook] url = "https//localhost:9090" secret = "secret-password" [shout.email] from = "git-next@example.com" to = "developer@example.com" [shout.email.smtp] hostname = "smtp.example.com" username = "git-next@example.com" password = "MySecretEmailPassword42" ``` ##### desktop When specified as `true`, desktop notifications will be sent for some events. ##### webhook Will send a POST request for some events. - **url** - the URL to POST the notification to and the - **secret** - the sync key used to sign the webhook payload See [Notifications](#notifications) for more details. ##### email Will send an email for some events. - **from** - the email address to send the email from - **to** - the email address to send the email to With just `from` and `to` specified, `git-next` will attempt to send emails with `sendmail` if it is configured. Alternativly, you can use an SMTP relay. ###### smtp Will send emails using an SMTP relay. - **hostname** - the SMTP relay server - **username** - the account to authenticate as - **password** - the password to authenticate with #### storage ```toml [storage] path = "./data" ``` `git-next` will create a bare clone of each repo that you configure it to monitor. They will all be created in the directory specified here. This data does not need to be backed up, as any missing information will be cloned when the server starts up. - **path** - directory to store local copies of monitored repos #### forge Within the forge tree, specify each forge you want to monitor repos on. Give your forge an alias, e.g. `default`, `gh`, `github`. e.g. ```toml [forge.github] forge_type = "GitHub" hostname = "github.com" user = "username" token = "api-key" max_dev_commits = 25 ``` - **forge_type** - one of: `ForgeJo` or `GitHub` - **hostname** - the hostname for the forge. - **user** - the user to authenticate as - **token** - application token for the user. See [Forges](#forges) below for the permissions required for each forge. - **max_dev_commits** - [optional] the maximum number of commits allowed between `dev` and `main`. Defaults to 25. Generally, the `user` will need to be able to push to `main` and to _force-push_ to `next`. #### repos For each forge, you need to specify which repos on the forge you want to monitor. They do not need to be owned by the `user`, but they `user` must have the `push` and `force-push` permissions as mentioned above for each of the repositories. e.g. ```toml [forge.github.repos] my-repo = { repo = "owner/repo", branch = "main", gitdir = "/home/pcampbell/project/my-repo" } [forge.github.repos.other-repo] repo = "user/other" branch = "master" main = "master" next = "ci-testing" dev = "trunk" ``` Note that toml allows specifying the values on one line, or across multiple lines. Both are equivalent. What is not equivalent between `my-repo` and `other-repo`, is that one will require a configuration file within the repo itself. `other-repo` specifies the `main`, `next` and `dev` branches to be used, but `my-repo` doesn't. A sample `.git-next-toml` file that would need to exist in `my-repo`'s `owner/repo` repo, on the `main` branch: ```toml [branches] main = "main" next = "next" dev = "dev" ``` - **repo** - the owner and name of the repo to be monitored - **branch** - the branch to look for a `.git-next.toml` file if needed - **gitdir** - (optional) you can use a local copy of the repo - **main** - the branch to use as `main` - **next** - the branch to use as `next` - **dev** - the branch to use as `dev` ##### gitdir Additional notes on using `gitdir`: When you specify the `gitdir` value, the repo cloned in that directory will be used for perform the equivalent of `git fetch`, `git push` and `git push --force-with-lease`. These commands will not affect the contents of your working tree, nor will it change any local branches. Only the details about branches on the remote forge will be updated. Currently `git-next` can only use a `gitdir` if the forge and repo is the same one specified as the `origin` remote. Otherwise the behaviour is untested and undefined. ## Webhook Notifications When sending a Webhook Notification to a user they are sent using the Standard Webhooks format. That means all POST messages have the following headers: - `Webhook-Id` - `Webhook-Signature` - `Webhook-Timestamp` ### Events #### Dev Not Based on Main This message `type` indicates that the `dev` branch is not based on `main`. **Action Required**: Rebase the `dev` branch onto the `main` branch. Sample payload: ```json { "data": { "branches": { "dev": "dev", "main": "main" }, "forge_alias": "jo", "repo_alias": "kxio", "log": [ "* 9bfce91 (origin/dev) fix: add log graph to notifications", "| * c37bd2c (origin/next, origin/main) feat: add log graph to notifications", "|/", "* 8c19680 refactor: macros use a more common syntax" ] }, "timestamp": "1721760933", "type": "branch.dev.not-on-main" } ``` #### CI Check Failed This message `type` indicates that the commit on the tip of the `next` branch has failed the configured CI checks. **Action Required**: Either update the commit to correct the issue CI raised, or, if the issue is transient (e.g. a network issue), re-run/re-start the job in your CI. Sample payload: ```json { "data": { "commit": { "sha": "c37bd2caf6825f9770d725a681e5cfc09d7fd4f2", "message": "feat: add log graph to notifications (1 of 2)" }, "forge_alias": "jo", "repo_alias": "kxio", "log": [ "* 9bfce91 (origin/dev) feat: add log graph to notifications (2 of 2)", "* c37bd2c (origin/next) feat: add log graph to notifications (1 of 2)", "* 8c19680 (origin/main) refactor: macros use a more common syntax" ] }, "timestamp": "1721760933", "type": "cicheck.failed" } ``` #### Repo Config Load Failed This message `type` indicates that `git-next` wasn't able to load the configuration for the repo from the `git-next.toml` file in the repository. **Action Required**: Review the `reason` provided. Sample payload: ```json { "data": { "reason": "File not found: .git-next.toml", "forge_alias": "jo", "repo_alias": "kxio" }, "timestamp": "1721760933", "type": "config.load.failed" } ``` #### Webhook Registration Failed This message `type` indicates that `git-next` wasn't able to register it's webhook with the forge repository, so will not receive updates when the branches in the repo are updated. **Action Required**: Review the `reason` provided. Sample payload: ```json { "data": { "reason": "repo config not loaded", "forge_alias": "jo", "repo_alias": "kxio" }, "timestamp": "1721760933", "type": "webhook.registration.failed" } ``` ## Behaviour The branch names are configurable, but we will talk about `main`, `next` and `dev`. Development happens on the `dev` branch, where each commit is expected to be able to pass the CI checks. (Note: in the diagrams, mermaid isn't capable of showing `main` and `next` on the same commit, so we show `next` as empty) ```mermaid gitGraph commit commit branch next branch dev commit commit commit ``` When the `git-next` server sees that the `dev` branch is ahead of the `next` branch, it will push the `next` branch fast-forward one commit along the `dev` branch. ```mermaid gitGraph commit commit branch next commit branch dev commit commit ``` It will then wait for the CI checks to pass for the newly updated `next` branch. When the CI checks for the `next` branch pass, it will push the `main` branch fast-forward to the `next` branch. We return to the top and start again. ```mermaid gitGraph commit commit commit branch next branch dev commit commit ``` If the CI checks should fail for the `next` branch, the developer should **amend** that commit **in the history of their `dev` branch**. They should then force-push their rebased `dev` branch. ```mermaid gitGraph commit commit branch next commit checkout main branch dev commit commit commit ``` `git-next` will then detect that the `next` branch is no longer part of the `dev` branch ancestory, and will reset `next` back to `main`. We then return to the top, where `git-next` sees that `dev` is ahead of `next`. When the `dev` branch is on the same commit as the `main` branch, then there are no pending commits and `git-next` will wait until it receives a webhook indicating that there has been a push to one of the branches. At which point it will start at the top again. ### Important The `dev` branch _should_ have the `next` branch as an ancestor. However, when the commit on tip of the `next` branch has failed CI and is amended, this will not be the case. When this happens `git-next` will **force-push** the `next` branch back to the same commit as the `main` branch. This is the only time a force-push will happen in `git-next`. In short, the `next` branch **belongs** to `git-next`. Don't try to update it yourself. `git-next` will update the `next` it as it sees fit. ## Getting Started To use `git-next` for trunk-based development, follow these steps: ### Initialise the repo (optional) You need to specify which branches you are using. You can do this in the repo, or in the server configuration. To create a default config file for the repo, run this command in the root of your repo: ```shell git next init ``` This will create a `.git-next.toml` file. [Default](./crates/cli/default.toml) By default the expected branches are `main`, `next` and `dev`. Each of these three branches _must_ exist in your repo. ### Initialise the server The server uses the file `git-next-server.toml` for configuration. It expects to find this file the the current directory when executed. The create the default config file, run this command: ```shell git next server init ``` This will create a `git-next-server.toml` file. [Default](./crates/server/server-default.toml) Edit this file to your needs. See the [Configuration](#configuration) section above. ### Run the server In the directory with your `git-next-server.toml` file, run the command: ```shell git next server start ``` ### Forges The following forges are supported: - [ForgeJo](https://forgejo.org) (probably compatible with Gitea, but not tested) - [GitHub](https://github.com/) Note: ForgeJo is a hard fork of Gitea, but currently they are largely compatible. For now using a `forge_type` of `ForgeJo` with a Gitea instance will probably work okay. The only API calls we make are around registering and unregistering webhooks. So, as long as those APIs remain the same, they should be compatible. #### ForgeJo Configure the forge in `git-next-server.toml` like: ```toml [forge.jo] forge_type = "ForgeJo" hostname = "git.myforgejo.com" user = "bob" token = "..." [forge.jo.repos] hello = { repo = "user/hello", branch = "main", gitdir = "/opt/git/projects/user/hello.git" } # maps to https://git.example.net/user/hello on the branch 'main' world = { repo = "user/world", branch = "master", main = "master", next = "upcoming", "dev" = "develop" } # maps to the 'master' branch ``` The token is created on your ForgeJo instance at (for example) `https://git.myforgejo.com/user/settings/applications` and requires the `write:repository` permission. #### GitHub Configure the forge in `git-next-server.toml` like: ```toml [forge.gh] forge_type = "GitHub" hostname = "github.com" # required even for GitHub user = "bob" token = "..." [forge.gh.repos] hello = { repo = "user/hello", branch = "main", gitdir = "/opt/git/projects/user/hello.git" } # maps to https://github.com/user/hello on the branch 'main' world = { repo = "user/world", branch = "master", main = "master", next = "upcoming", "dev" = "develop" } # maps to the 'master' branch ``` The token is created [here](https://github.com/settings/tokens/new) and requires the `repo` and `admin:repo_hook` permissions. ## Docker `git-next` is available as a [Docker image](https://git.kemitix.net/kemitix/-/packages/container/git-next/). ```shell docker pull docker pull git.kemitix.net/kemitix/git-next:latest ``` ### Docker Compose Here is an example `docker-compose.yml`: ```yaml services: server: image: git.kemitix.net/kemitix/git-next:latest container_name: git-next-server restart: unless-stopped environment: RUST_LOG: "hyper=warn,info" ports: - 8080:8092 volumes: - ./:/app/ ``` Note: this assumes the `git-next-server.toml` has a `listen.http.port` of `8092` and that you are using a reverse proxy to route traffic arriving at `listen.url` to port `8080`. ### Docker Run This will run with the `server start` options: ```shell docker run -it -v .:/app/ git.kemitix.net/kemitix/git-next:latest ``` To perform `server init`: ```shell docker run -it -v .:/app/ git.kemitix.net/kemitix/git-next:latest server init ``` To perform repo `init`: ```shell docker run -it -v .:/app/ git.kemitix.net/kemitix/git-next:latest init ``` TUI support is not available in the docker container. See [kemitix/git-next#154](https://git.kemitix.net/kemitix/git-next/issues/154). ## Contributing Contributions to `git-next` are welcome! If you find a bug or have a feature request, please [create an issue](https://git.kemitix.net/kemitix/git-next/issues/new). If you'd like to contribute code, feel free to submit changes. Before you start committing, run the `just install-hooks` command to setup the Git Hooks. ([Get Just](https://just.systems/man/en/chapter_3.html)) ## Crate Dependency The following diagram shows the dependency between the crates that make up `git-next`: ```mermaid stateDiagram-v2 cli --> core cli --> forge_forgejo cli --> forge_github forge_forgejo --> core forge_github --> core ``` ## License `git-next` is released under the [MIT License](./LICENSE).