Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changes/unreleased/Changed-20250829-213725.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
kind: Changed
body: >-
gitlab:
Submit Merge Requests with `remove_source_branch=true`.
This will delete the source branch when the MR is merged.
Opt out of this behavior with the `spice.forge.gitlab.removeSourceBranch` option.
time: 2025-08-29T21:37:25.235962-07:00
2 changes: 1 addition & 1 deletion doc/includes/cli-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ gs (git-spice) is a command line tool for stacking Git branches.
* `-C`, `--dir=DIR`: Change to DIR before doing anything
* `--[no-]prompt`: Whether to prompt for missing information

**Configuration**: [spice.forge.github.apiUrl](/cli/config.md#spiceforgegithubapiurl), [spice.forge.github.url](/cli/config.md#spiceforgegithuburl), [spice.forge.gitlab.apiURL](/cli/config.md#spiceforgegitlabapiurl), [spice.forge.gitlab.oauth.clientID](/cli/config.md#spiceforgegitlaboauthclientid), [spice.forge.gitlab.url](/cli/config.md#spiceforgegitlaburl)
**Configuration**: [spice.forge.github.apiUrl](/cli/config.md#spiceforgegithubapiurl), [spice.forge.github.url](/cli/config.md#spiceforgegithuburl), [spice.forge.gitlab.apiURL](/cli/config.md#spiceforgegitlabapiurl), [spice.forge.gitlab.oauth.clientID](/cli/config.md#spiceforgegitlaboauthclientid), [spice.forge.gitlab.removeSourceBranch](/cli/config.md#spiceforgegitlabremovesourcebranch), [spice.forge.gitlab.url](/cli/config.md#spiceforgegitlaburl)

## Shell

Expand Down
11 changes: 11 additions & 0 deletions doc/src/cli/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,17 @@ For Self-Hosted GitLab instances, you must set this value to a custom Client ID.

See also [GitLab Self-Hosted](../setup/auth.md#gitlab-self-hosted).

### spice.forge.gitlab.removeSourceBranch

<!-- gs:unreleased -->

Whether to remove the source branch when a Merge Request is merged.

**Accepted values:**

- `true` (default)
- `false`

### spice.log.all

Whether $$gs log short$$ and $$gs log long$$ should show all stacks by default,
Expand Down
7 changes: 3 additions & 4 deletions internal/fixturetest/fixturetest.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,10 @@ type TestingT interface {

// Config configures the behavior of the fixture system.
type Config struct {
// Update is a pointer to a value speciftying
// whether we're in update mode or read mode.
// Update reports whether we're in update more or read mode.
//
// This must not be nil.
Update *bool // required
Update func() bool // required
}

// Fixture is a value that is sourced from a function in update mode,
Expand All @@ -71,7 +70,7 @@ func (f Fixture[T]) Get(t TestingT) T {
t.Helper()

fpath := filepath.Join("testdata", t.Name(), f.name)
if *f.cfg.Update {
if f.cfg.Update() {
v := f.gen()

require.NoError(t, os.MkdirAll(filepath.Dir(fpath), 0o755))
Expand Down
10 changes: 6 additions & 4 deletions internal/fixturetest/fixturetest_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,21 @@ import (
func TestFixture(t *testing.T) {
t.Chdir(t.TempDir())

cfg := fixturetest.Config{Update: new(bool)}
var giveUpdate bool

cfg := fixturetest.Config{Update: func() bool { return giveUpdate }}
fixture := fixturetest.New(cfg, "number", rand.Int)

// Initial generation.
*cfg.Update = true
giveUpdate = true
v1 := fixture.Get(t)

// Read from disk.
*cfg.Update = false
giveUpdate = false
assert.Equal(t, v1, fixture.Get(t))

// Update again.
*cfg.Update = true
giveUpdate = true
// At least one attempt out of N should succeed.
assert.EventuallyWithT(t, func(t *assert.CollectT) {
v2 := fixture.Get(collectTAdapter{t, "Update"})
Expand Down
11 changes: 5 additions & 6 deletions internal/forge/github/flag_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,12 @@ package github

import "flag"

var UpdateFixtures *bool
func UpdateFixtures() bool {
return flag.Lookup("update").Value.(flag.Getter).Get().(bool)
}

func init() {
if updateFlag := flag.Lookup("update"); updateFlag != nil {
value := updateFlag.Value.(flag.Getter).Get().(bool)
UpdateFixtures = &value
} else {
UpdateFixtures = flag.Bool("update", false, "update test fixtures")
if flag.Lookup("update") == nil {
flag.Bool("update", false, "update test fixtures")
}
}
15 changes: 6 additions & 9 deletions internal/forge/github/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,7 @@ import (
// This file tests basic, end-to-end interactions with the GitHub API
// using recorded fixtures.

var (
_update = github.UpdateFixtures
_fixtures = fixturetest.Config{Update: _update}
)
var _fixtures = fixturetest.Config{Update: github.UpdateFixtures}

// To avoid looking this up for every test that needs the repo ID,
// we'll just hardcode it here.
Expand All @@ -52,7 +49,7 @@ func newRecorder(t *testing.T, name string) *recorder.Recorder {
})

return httptest.NewTransportRecorder(t, name, httptest.TransportRecorderOptions{
Update: _update,
Update: github.UpdateFixtures,
WrapRealTransport: func(t testing.TB, transport http.RoundTripper) http.RoundTripper {
githubToken := os.Getenv("GITHUB_TOKEN")
require.NotEmpty(t, githubToken,
Expand Down Expand Up @@ -247,7 +244,7 @@ func TestIntegration_Repository_SubmitEditChange(t *testing.T) {
gitRepo *git.Repository // only when _update is true
gitWork *git.Worktree
)
if *_update {
if github.UpdateFixtures() {
t.Setenv("GIT_AUTHOR_EMAIL", "bot@example.com")
t.Setenv("GIT_AUTHOR_NAME", "gs-test[bot]")
t.Setenv("GIT_COMMITTER_EMAIL", "bot@example.com")
Expand Down Expand Up @@ -331,7 +328,7 @@ func TestIntegration_Repository_SubmitEditChange(t *testing.T) {

newBase := newBaseFixture.Get(t)
t.Logf("Pushing new base: %s", newBase)
if *_update {
if github.UpdateFixtures() {
require.NoError(t,
gitWork.Push(t.Context(), git.PushOptions{
Remote: "origin",
Expand Down Expand Up @@ -409,7 +406,7 @@ func TestIntegration_Repository_SubmitEditChange_labels(t *testing.T) {
gitRepo *git.Repository // only when _update is true
gitWork *git.Worktree
)
if *_update {
if github.UpdateFixtures() {
t.Setenv("GIT_AUTHOR_EMAIL", "bot@example.com")
t.Setenv("GIT_AUTHOR_NAME", "gs-test[bot]")
t.Setenv("GIT_COMMITTER_EMAIL", "bot@example.com")
Expand Down Expand Up @@ -566,7 +563,7 @@ func TestIntegration_Repository_SubmitChange_baseBranchDoesNotExist(t *testing.T
gitRepo *git.Repository // only when _update is true
gitWork *git.Worktree
)
if *_update {
if github.UpdateFixtures() {
t.Setenv("GIT_AUTHOR_EMAIL", "bot@example.com")
t.Setenv("GIT_AUTHOR_NAME", "gs-test[bot]")
t.Setenv("GIT_COMMITTER_EMAIL", "bot@example.com")
Expand Down
2 changes: 1 addition & 1 deletion internal/forge/gitlab/comment_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ func TestListChangeComments(t *testing.T) {
"owner", "repo",
silogtest.New(t),
client,
&repoID,
&repositoryOptions{RepositoryID: &repoID},
)
require.NoError(t, err)

Expand Down
11 changes: 5 additions & 6 deletions internal/forge/gitlab/flag_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,12 @@ package gitlab

import "flag"

var UpdateFixtures *bool
func UpdateFixtures() bool {
return flag.Lookup("update").Value.(flag.Getter).Get().(bool)
}

func init() {
if updateFlag := flag.Lookup("update"); updateFlag != nil {
value := updateFlag.Value.(flag.Getter).Get().(bool)
UpdateFixtures = &value
} else {
UpdateFixtures = flag.Bool("update", false, "update test fixtures")
if flag.Lookup("update") == nil {
flag.Bool("update", false, "update test fixtures")
}
}
8 changes: 7 additions & 1 deletion internal/forge/gitlab/forge.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ type Options struct {
// ClientID is the OAuth client ID for GitLab OAuth device flow.
// This should be used if the GitLab instance is Self Managed.
ClientID string `name:"gitlab-oauth-client-id" hidden:"" env:"GITLAB_OAUTH_CLIENT_ID" config:"forge.gitlab.oauth.clientID" help:"GitLab OAuth client ID"`

// RemoveSourceBranch specifies whether a branch should be deleted
// after its Merge Request is merged.
RemoveSourceBranch bool `name:"gitlab-remove-source-branch" hidden:"" config:"forge.gitlab.removeSourceBranch" default:"true" help:"Remove source branch after merging a merge request"`
}

// Forge builds a GitLab Forge.
Expand Down Expand Up @@ -101,7 +105,9 @@ func (f *Forge) OpenRepository(ctx context.Context, token forge.AuthenticationTo
return nil, fmt.Errorf("create GitLab client: %w", err)
}

return newRepository(ctx, f, rid.owner, rid.name, f.logger(), glc, nil)
return newRepository(ctx, f, rid.owner, rid.name, f.logger(), glc, &repositoryOptions{
RemoveSourceBranchOnMerge: f.Options.RemoveSourceBranch,
})
}

// RepositoryID is a unique identifier for a GitLab repository.
Expand Down
Loading
Loading