Skip to main content

Parallel branches

Several teams or Generators can work on separate branches at the same time. When you query one of those branches, Infrahub returns the default branch's data as of that branch's branched_from, plus the changes made on that branch. Merging one branch into the default branch changes nothing on the others, and no status indicates that the default branch has changed, so Infrahub reports conflicts and duplicate values only when it compares a branch with the default branch again: when you refresh the branch's diff, run its Proposed Change checks, rebase it or merge it.

On each branch: allocate unique values from resource pools, rebase after another branch merges, resolve the conflicts a refused rebase lists, then merge.

What changes when another branch merges​

Nothing changes on your branch. Query it and Infrahub still returns the default branch's data as of your branched_from, plus your own changes, until you rebase.

Infrahub blocks some actions on a branch while a merge is running, after a merge has failed, after the branch has merged, or when the branch needs a rebase. It marks each of these with a branch status. OPEN does not mean a branch is up to date with the default branch, and Infrahub does not require a rebase before a merge, except after an upgrade sets NEED_UPGRADE_REBASE. Branch statuses lists each status with what it stops you doing and what you have to do next.

To see your branch's branched_from, open the branch details page in the web interface, or query it through GraphQL:

List branches with their branched_from time
query BranchState {
Branch {
name
status
branched_from
is_default
}
}

To list what changed on the default branch since then, compare the default branch with itself between your branch's branched_from and now, as described in Compare changes between two timestamps. This comparison runs through the GraphQL API. To apply those changes to your branch, rebase it.

A merge does make the stored diff of every other open branch out of date, and Infrahub queues a recalculation for each one. With a few branches open you will not notice. With dozens of long-lived branches, those recalculations compete with generators, artifacts and checks for task workers, and you can turn the automatic update off. See Diff updates after a merge.

Allocate values from a pool​

A value allocated from a resource pool is reserved for every branch at the moment you allocate it, so a request from another branch to the same pool gets a different one. Infrahub also locks each pool while it allocates from it, so simultaneous requests from different branches or Generators get different values.

A value you type into a unique attribute is not reserved. Two branches can each hold the same typed value. Once one of them merges, the other is refused at its next rebase or merge, and its Proposed Change reports the duplicate under Schema Integrity.

How you request a value depends on where you work:

  • Web interface: when you create the object, click the pool button next to the field, labeled "select a pool", and choose the pool. On an IP address, the button is next to Address.
  • GraphQL API: pass from_pool on the attribute or relationship. For an IP address pool or an IP prefix pool, you can also call the pool's mutation directly, InfrahubIPAddressPoolGetResource or InfrahubIPPrefixPoolGetResource.

Through the GraphQL API, you can pass an identifier when you allocate from an IP address pool or an IP prefix pool, and Infrahub returns the same value for a later request with the same identifier. The web interface sends no identifier, and Infrahub applies its default. Build one identifier per object, from the object it is allocated to, such as a device and its interface. On another branch that cannot yet read the object the value was allocated to, Infrahub allocates a different value for the same identifier, with no error. See Idempotent allocation.

When a branch is deleted. The values a branch took from a pool go back to the pool only if no branch can still read the objects they were allocated to. A branch deleted without merging usually frees its values, because the objects only ever existed on it. A branch that merged first does not, because the objects are now on the default branch. See Branch-agnostic data.

Rebase after each merge​

Rebase each open branch soon after another branch merges. Each rebase then compares fewer new changes from the default branch, and Infrahub reports any conflict on the branch where it has to be fixed, before review.

For both ways to run a rebase, and what to do when one is refused, see Rebase a branch. Rebases and merges each take the global graph lock, so they run one at a time.

Automate the rebase (optional)​

You can build a small service that rebases the open branches each time a branch merges. Infrahub's event actions add objects to groups or remove them, and run Generators, and none of them rebases a branch, so this approach combines a webhook with the GraphQL API. You build and run the service yourself.

  1. Create a webhook for the infrahub.branch.merged event that points at your service, and set its branch scope to All Branches. With the default scope, Default Branch, the webhook does not fire on a merge. See Configuring branch types.
  2. When your service receives the webhook, list the branches with the Branch query shown in What changes when another branch merges. Skip the default branch (is_default) and any branch with the status MERGED or MERGING.
  3. For each remaining branch, send BranchRebase to /graphql and let it wait for the rebase, which is the default, so the response contains the outcome.
  4. Handle a refusal according to the code in the error's extensions:
    • MERGE_IN_PROGRESS: another merge is running. Retry after a delay.
    • MERGE_RECOVERY_REQUIRED: a merge failed. Stop and alert an administrator, who runs infrahub recover merge.
    • Any other code: read the message. A conflict refusal lists each conflict and what it needs, so send it to the owner of the branch. The message Cannot rebase a branch while a merge is in progress. means a merge started during the request: retry it.
Do not check a branch with BranchValidate before rebasing

BranchValidate reads the branch's stored diff without refreshing it, and it reports conflicts already resolved in favor of the branch, which do not block a rebase. Run the rebase itself instead: a refused rebase changes no data on the branch.

What blocks a rebase​

See What blocks a rebase for each cause and its fix.

Merge the branch​

Merge the branch through a Proposed Change. At merge time, Infrahub refreshes the diff and checks conflicts and constraints again against the current default branch, so a Proposed Change whose checks passed can still fail to merge when another branch merged first. If that happens, rebase the branch, fix what the refused rebase lists, and merge again.

For what the merge checks, how writes are blocked while it runs, and merging a branch directly, see Merge a branch. For what a failed re-check does to the Proposed Change, see Proposed Changes.

Limits​

  • Infrahub does not indicate when the default branch has changed since you created or last rebased your branch. To see what changed on the default branch, compare it over time as described in What changes when another branch merges.
  • A rebase has no dry run. A refused rebase changes no data on the branch, so running the rebase is the check.
  • Infrahub does not reserve an object for one branch. You can change the same object on two branches, and Infrahub reports the conflict on the second branch after the first one merges.