From 2d57a25bb6f7a839ef462cb5ef0af10b3625eab9 Mon Sep 17 00:00:00 2001 From: Abhinav Gupta Date: Sat, 30 Nov 2024 17:24:17 -0800 Subject: [PATCH] rebase continue: Add --no-edit flag to skip opening an editor Git's `rebase --continue` always opens an editor to edit the commit message after resoling the conflict. There are cases where this is not necessary. This adds a `--no-edit` flag to `gs rebase continue` to skip opening an editor. `rebase` does not have a `--no-edit` flag to skip the editor but we can get the same effect by setting the editor to `true` for that command. Resolves #503 --- .../unreleased/Added-20241130-172501.yaml | 6 + doc/includes/cli-reference.md | 12 +- doc/src/cli/config.md | 14 ++ internal/git/repo.go | 21 +++ internal/git/repo_test.go | 23 ++++ rebase_continue.go | 12 +- .../branch_restack_conflict_no_edit.txt | 120 ++++++++++++++++++ 7 files changed, 206 insertions(+), 2 deletions(-) create mode 100644 .changes/unreleased/Added-20241130-172501.yaml create mode 100644 testdata/script/branch_restack_conflict_no_edit.txt diff --git a/.changes/unreleased/Added-20241130-172501.yaml b/.changes/unreleased/Added-20241130-172501.yaml new file mode 100644 index 00000000..b8cb3dea --- /dev/null +++ b/.changes/unreleased/Added-20241130-172501.yaml @@ -0,0 +1,6 @@ +kind: Added +body: >- + rebase continue: + Add `--[no-]edit` flag to skip opening an editor when continuing a rebase, + or opt into it if the new `spice.rebaseContinue.edit` configuration is `true`. +time: 2024-11-30T17:25:01.584724-08:00 diff --git a/doc/includes/cli-reference.md b/doc/includes/cli-reference.md index 59533be2..83f82f61 100644 --- a/doc/includes/cli-reference.md +++ b/doc/includes/cli-reference.md @@ -819,7 +819,7 @@ Branches upstack are restacked as needed. ### gs rebase continue ``` -gs rebase (rb) continue (c) +gs rebase (rb) continue (c) [flags] ``` Continue an interrupted operation @@ -834,6 +834,16 @@ you can resolve the conflict and run 'gs rebase continue' The command can be used in place of 'git rebase --continue' even if a git-spice operation is not currently in progress. +Use the --no-edit flag to continue without opening an editor. +Make --no-edit the default by setting 'spice.rebaseContinue.edit' to false +and use --edit to override it. + +**Flags** + +* `--[no-]edit` ([:material-wrench:{ .middle title="spice.rebaseContinue.edit" }](/cli/config.md#spicerebasecontinueedit)): Whehter to open an editor to edit the commit message. + +**Configuration**: [spice.rebaseContinue.edit](/cli/config.md#spicerebasecontinueedit) + ### gs rebase abort ``` diff --git a/doc/src/cli/config.md b/doc/src/cli/config.md index 3fc7b555..9d3cfa0a 100644 --- a/doc/src/cli/config.md +++ b/doc/src/cli/config.md @@ -101,6 +101,20 @@ instead of showing just the current stack. - `true` - `false` (default) +### spice.rebaseContinue.edit + + + +Whether $$gs rebase continue$$ should open an editor to modify the commit message +when continuing after resolving a rebase conflict. + +If set to false, you can opt in to opening the editor with the `--edit` flag. + +**Accepted values:** + +- `true` (default) +- `false` + ### spice.submit.listTemplatesTimeout diff --git a/internal/git/repo.go b/internal/git/repo.go index 93e92861..b01c80ec 100644 --- a/internal/git/repo.go +++ b/internal/git/repo.go @@ -110,6 +110,7 @@ type Repository struct { log *log.Logger exec execer + cfg extraConfig } func newRepository(root, gitDir string, log *log.Logger, exec execer) *Repository { @@ -124,5 +125,25 @@ func newRepository(root, gitDir string, log *log.Logger, exec execer) *Repositor // gitCmd returns a gitCmd that will run // with the repository's root as the working directory. func (r *Repository) gitCmd(ctx context.Context, args ...string) *gitCmd { + args = append(r.cfg.Args(), args...) return newGitCmd(ctx, r.log, args...).Dir(r.root) } + +// WithEditor returns a copy of the repository +// that will use the given editor when running git commands. +func (r *Repository) WithEditor(editor string) *Repository { + newR := *r + newR.cfg.Editor = editor + return &newR +} + +type extraConfig struct { + Editor string // core.editor +} + +func (ec extraConfig) Args() (args []string) { + if ec.Editor != "" { + args = append(args, "-c", "core.editor="+ec.Editor) + } + return args +} diff --git a/internal/git/repo_test.go b/internal/git/repo_test.go index 5d99a700..e130fd0e 100644 --- a/internal/git/repo_test.go +++ b/internal/git/repo_test.go @@ -6,6 +6,7 @@ import ( "path/filepath" "testing" + "github.com/stretchr/testify/assert" "go.abhg.dev/gs/internal/logtest" ) @@ -22,3 +23,25 @@ func NewTestRepository(t testing.TB, dir string, execer execer) *Repository { return newRepository(dir, gitDir, logtest.New(t), execer) } + +func TestExtraConfig_Args(t *testing.T) { + tests := []struct { + name string + give extraConfig + want []string + }{ + {name: "empty"}, + { + name: "editor", + give: extraConfig{Editor: "vim"}, + want: []string{"-c", "core.editor=vim"}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := tt.give.Args() + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/rebase_continue.go b/rebase_continue.go index efed8b1b..ae113cb3 100644 --- a/rebase_continue.go +++ b/rebase_continue.go @@ -13,7 +13,9 @@ import ( "go.abhg.dev/gs/internal/ui" ) -type rebaseContinueCmd struct{} +type rebaseContinueCmd struct { + Edit bool `default:"true" negatable:"" config:"rebaseContinue.edit" help:"Whehter to open an editor to edit the commit message."` +} func (*rebaseContinueCmd) Help() string { return text.Dedent(` @@ -26,6 +28,10 @@ func (*rebaseContinueCmd) Help() string { The command can be used in place of 'git rebase --continue' even if a git-spice operation is not currently in progress. + + Use the --no-edit flag to continue without opening an editor. + Make --no-edit the default by setting 'spice.rebaseContinue.edit' to false + and use --edit to override it. `) } @@ -54,6 +60,10 @@ func (cmd *rebaseContinueCmd) Run( return errors.New("no rebase in progress") } + if !cmd.Edit { + repo = repo.WithEditor("true") + } + // Finish the ongoing rebase. if err := repo.RebaseContinue(ctx); err != nil { var rebaseErr *git.RebaseInterruptError diff --git a/testdata/script/branch_restack_conflict_no_edit.txt b/testdata/script/branch_restack_conflict_no_edit.txt new file mode 100644 index 00000000..5834b29a --- /dev/null +++ b/testdata/script/branch_restack_conflict_no_edit.txt @@ -0,0 +1,120 @@ +# 'rebase continue --no-edit' is able to continue from a conflict +# without opening an editor. + +as 'Test ' +at '2024-12-01T10:18:19Z' + +mkdir repo +cd repo +git init +git commit --allow-empty -m 'Initial commit' +gs repo init + +# add main -> feat1 +gs trunk +cp $WORK/extra/feat1.txt feat.txt +git add feat.txt +gs bc feat1 -m 'Add feature 1' + +# add main -> feat2 +gs trunk +cp $WORK/extra/feat2.txt feat.txt +git add feat.txt +gs bc feat2 -m 'Add feature 2' + +# add main -> feat3 +gs trunk +cp $WORK/extra/feat3.txt feat.txt +git add feat.txt +gs bc feat3 -m 'Add feature 3' + +# main: introduce a conflict +gs trunk +cp $WORK/extra/feat0.txt feat.txt +git add feat.txt +git commit -m 'Add feature 0' + +# Set editor to false to fail the test +# if the editor is opened by any command +env EDITOR=false + +# feat1: rebase continue --no-edit +gs bco feat1 +! gs branch restack +stderr 'There was a conflict' +cp $WORK/extra/feat1.txt feat.txt +git add feat.txt +gs rebase continue --no-edit + +# feat1: verify resolved +cmp feat.txt $WORK/extra/feat1.txt +git status --porcelain +! stdout '.' # no changes + +# Make --no-edit the default +git config spice.rebaseContinue.edit false + +# feat2: rebase continue --no-edit is default +gs bco feat2 +! gs branch restack +stderr 'There was a conflict' +cp $WORK/extra/feat2.txt feat.txt +git add feat.txt +gs rebase continue + +# feat2: verify resolved +cmp feat.txt $WORK/extra/feat2.txt +git status --porcelain +! stdout '.' # no changes + +# feat3: rebase continue, --edit opt-in +gs bco feat3 +! gs branch restack +stderr 'There was a conflict' + +env EDITOR=mockedit MOCKEDIT_GIVE=$WORK/input/feat3-msg.txt MOCKEDIT_RECORD=$WORK/feat3-conflict-msg.txt +cp $WORK/extra/feat3.txt feat.txt +git add feat.txt +gs rebase continue --edit +cmp $WORK/feat3-conflict-msg.txt $WORK/golden/feat3-conflict-msg.txt + +git graph +cmp stdout $WORK/golden/log.txt + +-- extra/feat0.txt -- +feature 0 + +-- extra/feat1.txt -- +feature 1 + +-- extra/feat2.txt -- +feature 2 + +-- extra/feat3.txt -- +feature 3 + +-- input/feat3-msg.txt -- +feat3: resolved + +-- golden/feat3-conflict-msg.txt -- +Add feature 3 + +# Conflicts: +# feat.txt + +# Please enter the commit message for your changes. Lines starting +# with '#' will be ignored, and an empty message aborts the commit. +# +# interactive rebase in progress; onto ccf2371 +# Last command done (1 command done): +# pick 712f10b Add feature 3 +# No commands remaining. +# You are currently rebasing branch 'feat3' on 'ccf2371'. +# +# Changes to be committed: +# modified: feat.txt +# +-- golden/log.txt -- +* 200dbc4 (HEAD -> feat3) feat3: resolved +* ccf2371 (main) Add feature 0 +* 585bc4c Initial commit