Skip to content

Merging stacked change requests

v0.30.0

Experimental feature

Stack merging is experimental. You must enable the merge experiment before using the merge commands.

git config spice.experiment.merge true

git-spice can merge submitted Change Requests (CRs) from the command line. For stacked CRs, git-spice merges them bottom-up: merging CRs at the bottom of the stack, restacking and resubmitting their upstack as needed until all CRs have been merged.

Merging: 2 of 5 changes

■■■■■■■■■■■■■■■■■■■■◆◆◆◆◆◆◆◆◆◆××××××××××□□□□□□□□□□
merged: 2   waiting: 1   pending: 1   failed: 1

Press Ctrl-C to cancel merge operation.

Why does git-spice need to restack while merging?

Many repositories merge CRs with squash-merges. These replace commits in the merged branch with new commits on trunk. Git does not consider the new commit equivalent to the originals, so branches upstack from the merged branch point to a stale history. These need to be retargeted to trunk, restacked, and updated before they can merge.

See also Squash-merges restack the upstack to read more about the underlying limitation.

Merge commands

Use gs branch merge to merge a branch stacked on top of trunk.

main feat1 feat2 feat3
$ gs branch checkout feat1
$ gs branch merge
# merges feat1 into main

To merge a branch and all its downstack branches down to trunk use gs downstack merge.

main feat1 feat2 feat3
$ gs branch checkout feat2
$ gs downstack merge
# merges feat1, feat2 into main

To merge a branch, all its downstack branches, and all upstack branches use gs stack merge.

main feat1 feat2 feat3
$ gs branch checkout feat2
$ gs stack merge
# merges feat1, feat2, feat3, feat4 into main

When is a CR ready to merge?

Before git-spice requests a merge, it waits for the CR to be ready.

By default, the definition of "ready to merge" for a CR depends on the forge and repository settings. This can include CI checks, required approvals, or other requirements.

git-spice will poll the forge until the CR is reported as mergeable, waiting up to 30 minutes for the CR to become ready. The wait time can be changed with the spice.merge.ready.timeout configuration option.

If a CR is not ready to merge after the configured timeout, that CR is assumed to be blocked and it, and its upstack branches, will not be merged.

Custom merge readiness

If a repository's configured definition of "ready to merge" is not sufficient, git-spice offers a spice.merge.ready.command configuration option to customize it.

If set, git-spice will run the configured command to poll for a CR's readiness. The configuration value can be set to a shell command or a script/executable.

Its exit status determines whether the CR is ready to merge:

  • exit status 0 means the CR is ready
  • exit status 1 means git-spice should try again later
  • exit status 2 means the CR is blocked and waiting won't help; the CR and its upstack branches will not be merged
Example: Wait for GitHub mergeability and review For example, this command waits until GitHub reports a PR mergeable, with at least one approval and no blocking reviews.
Configuration
git config \
  spice.merge.ready.command \
  "$HOME/bin/merge-ready.sh"
$HOME/bin/merge-ready.sh
#!/usr/bin/env bash
set -euo pipefail

readonly PR="${GIT_SPICE_GITHUB_PR_NUMBER}"

MERGEABLE="$(
  gh pr view "$PR" \
    --json mergeable \
    --jq .mergeable
)"

case "$MERGEABLE" in
  MERGEABLE)
    ;;
  UNKNOWN)
    # GitHub may return UNKNOWN while it computes whether the PR can merge.
    # Treat that as temporary and let git-spice poll again.
    exit 1
    ;;
  CONFLICTING)
    exit 2
    ;;
esac

REVIEW="$(
  gh pr view "$PR" \
    --json reviewDecision \
    --jq .reviewDecision
)"

case "$REVIEW" in
  APPROVED)
    exit 0
    ;;
  "" | REVIEW_REQUIRED)
    # GitHub may return an empty reviewDecision
    # when no review requirement applies or no review decision is available.
    # For this policy, both empty and REVIEW_REQUIRED mean keep waiting.
    exit 1
    ;;
  CHANGES_REQUESTED)
    exit 2
    ;;
esac

exit 1

See Command environment for the variables passed to spice.merge.ready.command.

Merging multiple stacks

All three merge commands accept --branch. Repeat --branch to include multiple branches in the selection. This has different effects depending on the command.

gs branch merge merges only the specified branches. These must collectively form a path to trunk.

main feat1 feat2 feat3 feat4
$ gs branch merge --branch feat1 --branch feat2
# merges feat1, feat2 into main

gs downstack merge selects the downstack paths of the specified branches. These are combined and merged into trunk.

main feat1 feat2 feat3 feat4
$ gs downstack merge --branch feat2 --branch feat3
# merges feat1, feat2, feat3 into main

gs stack merge selects the upstack and downstack paths of the specified branches, combining the results and merging them into trunk.

main feat1 feat2 feat3 feat4 feat5
$ gs stack merge --branch feat2 --branch feat4
# merges all branches into main

Custom merge processes

git-spice uses the Forge's merge APIs to merge CRs. This is not always desirable, as some repositories have custom merge processes. git-spice supports custom merge workflows with the spice.merge.command configuration option.

For example, a GitHub repository may require a contributor to comment /merge on a PR to request a merge. To support this, configure git-spice like this:

git config \
  spice.merge.command \
  'gh pr comment "$GIT_SPICE_GITHUB_PR_NUMBER" --body /merge'

If the script is more complex, the configuration can be pointed to a script or executable instead.

git config \
  spice.merge.command \
  "$HOME/bin/request-merge.sh"
$HOME/bin/request-merge.sh
#!/usr/bin/env bash
set -euo pipefail

NUM="${GIT_SPICE_GITHUB_PR_NUMBER}"
LABEL=ready-to-merge
COMMENT="@myfancybot merge"

gh pr edit "$NUM" --add-label "$LABEL"
gh pr comment "$NUM" --body "$COMMENT"

git-spice waits for the CR to be ready to merge before running the configured command, and waits for the CR to finish merging before moving upstack. If the merge workflow is slow, the spice.merge.timeout configuration option can be used to increase the wait time.

If the command exits with a non-zero exit code, the merge is considered to have failed and the upstack branches are not merged.

See Command environment for the variables passed to spice.merge.command.

Command environment

spice.merge.ready.command and spice.merge.command receive the following environment variables when executed:

  • GIT_SPICE_FORGE_ID
  • GIT_SPICE_BRANCH
  • GIT_SPICE_BASE_BRANCH
  • GIT_SPICE_TRUNK_BRANCH
  • GIT_SPICE_CHANGE_URL
  • GIT_SPICE_HEAD_SHA

Depending on the forge, the following additional variables may be set:

Environment variable Forge
GIT_SPICE_GITHUB_PR_NUMBER GitHub
GIT_SPICE_GITLAB_MR_IID GitLab
GIT_SPICE_BITBUCKET_PR_ID Bitbucket
GIT_SPICE_FORGEJO_PR_NUMBER Forgejo
GIT_SPICE_GITEA_PR_NUMBER Gitea