mirror of https://gitlab.com/gitlab-org/cli.git
Merge branch 'gmh-stacked-diffs-add-save-command' into 'gmh-stacked-diffs'
feat(stacked-diffs): Add save command See merge request https://gitlab.com/gitlab-org/cli/-/merge_requests/1462 Merged-by: Gary Holtz <gholtz@gitlab.com> Approved-by: Vitali Tatarintev <vtatarintev@gitlab.com> Reviewed-by: Gary Holtz <gholtz@gitlab.com> Reviewed-by: Jay McCure <jmccure@gitlab.com>
This commit is contained in:
commit
df8e7f4f41
|
@ -0,0 +1,113 @@
|
|||
package create
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/MakeNowJust/heredoc"
|
||||
"github.com/briandowns/spinner"
|
||||
"gitlab.com/gitlab-org/cli/internal/run"
|
||||
"gitlab.com/gitlab-org/cli/pkg/git"
|
||||
"gitlab.com/gitlab-org/cli/pkg/prompt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"gitlab.com/gitlab-org/cli/commands/cmdutils"
|
||||
)
|
||||
|
||||
var message string
|
||||
|
||||
func NewCmdSaveStack(f *cmdutils.Factory) *cobra.Command {
|
||||
stackSaveCmd := &cobra.Command{
|
||||
Use: "save",
|
||||
Short: `Save your progress within stacked diff`,
|
||||
Long: "\"save\" lets you save your current progress with a diff on the stack.\n",
|
||||
Example: heredoc.Doc(`
|
||||
glab stack save added_file
|
||||
glab stack save . -m "added a function"
|
||||
glab stack save -m "added a function"`),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
// check if there are even any changes before we start
|
||||
err := checkForChanges()
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not save: %v", err)
|
||||
}
|
||||
|
||||
// a title is required, so ask if one is not provided
|
||||
if message == "" {
|
||||
err := prompt.AskQuestionWithInput(&message, "title", "What would you like to name this change?", "", true)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error prompting for save title: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
s := spinner.New(spinner.CharSets[11], 100*time.Millisecond)
|
||||
|
||||
// git add files
|
||||
_, err = addFiles(args[0:])
|
||||
if err != nil {
|
||||
return fmt.Errorf("error adding files: %v", err)
|
||||
}
|
||||
|
||||
// get stack title
|
||||
title, err := git.GetCurrentStackTitle()
|
||||
if err != nil {
|
||||
return fmt.Errorf("error running git command: %v", err)
|
||||
}
|
||||
|
||||
if f.IO.IsOutputTTY() {
|
||||
color := f.IO.Color()
|
||||
|
||||
fmt.Fprintf(
|
||||
f.IO.StdOut,
|
||||
"%s %s: Saved with message: \"%s\".\n",
|
||||
color.ProgressIcon(),
|
||||
color.Blue(title),
|
||||
message,
|
||||
)
|
||||
}
|
||||
|
||||
s.Stop()
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
stackSaveCmd.Flags().StringVarP(&message, "message", "m", "", "name the change")
|
||||
|
||||
return stackSaveCmd
|
||||
}
|
||||
|
||||
func checkForChanges() error {
|
||||
gitCmd := git.GitCommand("status", "--porcelain")
|
||||
output, err := run.PrepareCmd(gitCmd).Output()
|
||||
if err != nil {
|
||||
return fmt.Errorf("error running git status: %v", err)
|
||||
}
|
||||
|
||||
if string(output) == "" {
|
||||
return fmt.Errorf("no changes to save")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func addFiles(args []string) (files []string, err error) {
|
||||
for _, file := range args {
|
||||
_, err = os.Stat(file)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
files = append(files, file)
|
||||
}
|
||||
|
||||
cmdargs := append([]string{"add"}, args...)
|
||||
gitCmd := git.GitCommand(cmdargs...)
|
||||
|
||||
_, err = run.PrepareCmd(gitCmd).Output()
|
||||
if err != nil {
|
||||
return []string{}, fmt.Errorf("error running git add: %v", err)
|
||||
}
|
||||
|
||||
return files, err
|
||||
}
|
|
@ -0,0 +1,197 @@
|
|||
package create
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
"gitlab.com/gitlab-org/cli/commands/cmdtest"
|
||||
"gitlab.com/gitlab-org/cli/internal/run"
|
||||
"gitlab.com/gitlab-org/cli/pkg/git"
|
||||
"gitlab.com/gitlab-org/cli/pkg/prompt"
|
||||
"gitlab.com/gitlab-org/cli/test"
|
||||
)
|
||||
|
||||
func runCommand(rt http.RoundTripper, isTTY bool, args string) (*test.CmdOut, error) {
|
||||
ios, _, stdout, stderr := cmdtest.InitIOStreams(isTTY, "")
|
||||
|
||||
factory := cmdtest.InitFactory(ios, rt)
|
||||
|
||||
_, _ = factory.HttpClient()
|
||||
|
||||
cmd := NewCmdSaveStack(factory)
|
||||
|
||||
return cmdtest.ExecuteCommand(cmd, args, stdout, stderr)
|
||||
}
|
||||
|
||||
func TestSaveNewStack(t *testing.T) {
|
||||
tests := []struct {
|
||||
desc string
|
||||
args []string
|
||||
files []string
|
||||
message string
|
||||
expected string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
desc: "adding regular files",
|
||||
args: []string{"testfile", "randomfile"},
|
||||
files: []string{"testfile", "randomfile"},
|
||||
message: "this is a commit message",
|
||||
expected: "• cool test feature: Saved with message: \"this is a commit message\".\n",
|
||||
},
|
||||
|
||||
{
|
||||
desc: "adding files with a dot argument",
|
||||
args: []string{"."},
|
||||
files: []string{"testfile", "randomfile"},
|
||||
message: "this is a commit message",
|
||||
expected: "• cool test feature: Saved with message: \"this is a commit message\".\n",
|
||||
},
|
||||
|
||||
{
|
||||
desc: "omitting a message",
|
||||
args: []string{"."},
|
||||
files: []string{"testfile"},
|
||||
expected: "• cool test feature: Saved with message: \"oh ok fine how about blah blah\".\n",
|
||||
},
|
||||
|
||||
{
|
||||
desc: "with no changed files",
|
||||
args: []string{"."},
|
||||
files: []string{},
|
||||
expected: "could not save: \"no changes to save\"",
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
if tc.message == "" {
|
||||
as, restoreAsk := prompt.InitAskStubber()
|
||||
defer restoreAsk()
|
||||
|
||||
as.Stub([]*prompt.QuestionStub{
|
||||
{
|
||||
Name: "title",
|
||||
Value: "oh ok fine how about blah blah",
|
||||
},
|
||||
})
|
||||
} else {
|
||||
tc.args = append(tc.args, "-m")
|
||||
tc.args = append(tc.args, "\""+tc.message+"\"")
|
||||
}
|
||||
|
||||
dir := git.InitGitRepoWithCommit(t)
|
||||
err := git.SetLocalConfig("glab.currentstack", "cool test feature")
|
||||
require.Nil(t, err)
|
||||
|
||||
createTemporaryFiles(t, dir, tc.files)
|
||||
|
||||
args := strings.Join(tc.args, " ")
|
||||
|
||||
output, err := runCommand(nil, true, args)
|
||||
|
||||
if tc.wantErr {
|
||||
require.Errorf(t, err, tc.expected)
|
||||
} else {
|
||||
require.Nil(t, err)
|
||||
require.Equal(t, tc.expected, output.String())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_addFiles(t *testing.T) {
|
||||
tests := []struct {
|
||||
desc string
|
||||
args []string
|
||||
expected []string
|
||||
}{
|
||||
{
|
||||
desc: "adding regular files",
|
||||
args: []string{"file1", "file2"},
|
||||
expected: []string{"file1", "file2"},
|
||||
},
|
||||
{
|
||||
desc: "adding files with a dot argument",
|
||||
args: []string{"."},
|
||||
expected: []string{"file1", "file2"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
dir := git.InitGitRepoWithCommit(t)
|
||||
err := git.SetLocalConfig("glab.currentstack", "cool test feature")
|
||||
require.Nil(t, err)
|
||||
|
||||
createTemporaryFiles(t, dir, tc.expected)
|
||||
|
||||
_, err = addFiles(tc.args)
|
||||
require.Nil(t, err)
|
||||
|
||||
gitCmd := git.GitCommand("status", "--short", "-u")
|
||||
output, err := run.PrepareCmd(gitCmd).Output()
|
||||
require.Nil(t, err)
|
||||
|
||||
normalizedFiles := []string{}
|
||||
for _, file := range tc.expected {
|
||||
file = "A " + file
|
||||
|
||||
normalizedFiles = append(normalizedFiles, file)
|
||||
}
|
||||
|
||||
formattedOutput := strings.Replace(string(output), "\n", "", -1)
|
||||
require.Equal(t, formattedOutput, strings.Join(normalizedFiles, ""))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_checkForChanges(t *testing.T) {
|
||||
tests := []struct {
|
||||
desc string
|
||||
args []string
|
||||
expected bool
|
||||
}{
|
||||
{
|
||||
desc: "check for changes with modified files",
|
||||
args: []string{"file1", "file2"},
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
desc: "check for changes without anything",
|
||||
args: []string{},
|
||||
expected: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
dir := git.InitGitRepoWithCommit(t)
|
||||
err := git.SetLocalConfig("glab.currentstack", "cool test feature")
|
||||
require.Nil(t, err)
|
||||
|
||||
createTemporaryFiles(t, dir, tc.args)
|
||||
|
||||
err = checkForChanges()
|
||||
if tc.expected {
|
||||
require.Nil(t, err)
|
||||
} else {
|
||||
require.Error(t, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func createTemporaryFiles(t *testing.T, dir string, files []string) {
|
||||
for _, file := range files {
|
||||
file = path.Join(dir, file)
|
||||
_, err := os.Create(file)
|
||||
|
||||
require.Nil(t, err)
|
||||
}
|
||||
}
|
|
@ -3,6 +3,7 @@ package stack
|
|||
import (
|
||||
"gitlab.com/gitlab-org/cli/commands/cmdutils"
|
||||
stackCreateCmd "gitlab.com/gitlab-org/cli/commands/stack/create"
|
||||
stackSaveCmd "gitlab.com/gitlab-org/cli/commands/stack/save"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
@ -18,5 +19,6 @@ func NewCmdStack(f *cmdutils.Factory) *cobra.Command {
|
|||
cmdutils.EnableRepoOverride(stackCmd, f)
|
||||
|
||||
stackCmd.AddCommand(stackCreateCmd.NewCmdCreateStack(f))
|
||||
stackCmd.AddCommand(stackSaveCmd.NewCmdSaveStack(f))
|
||||
return stackCmd
|
||||
}
|
||||
|
|
|
@ -34,3 +34,4 @@ stacks
|
|||
## Subcommands
|
||||
|
||||
- [`create`](create.md)
|
||||
- [`save`](save.md)
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
---
|
||||
stage: Create
|
||||
group: Code Review
|
||||
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments
|
||||
---
|
||||
|
||||
<!--
|
||||
This documentation is auto generated by a script.
|
||||
Please do not edit this file directly. Run `make gen-docs` instead.
|
||||
-->
|
||||
|
||||
# `glab stack save`
|
||||
|
||||
Save your progress within stacked diff
|
||||
|
||||
## Synopsis
|
||||
|
||||
"save" lets you save your current progress with a diff on the stack.
|
||||
|
||||
```plaintext
|
||||
glab stack save [flags]
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
```plaintext
|
||||
glab stack save added_file
|
||||
glab stack save . -m "added a function"
|
||||
glab stack save -m "added a function"
|
||||
```
|
||||
|
||||
## Options
|
||||
|
||||
```plaintext
|
||||
-m, --message string name the change
|
||||
```
|
||||
|
||||
## Options inherited from parent commands
|
||||
|
||||
```plaintext
|
||||
--help Show help for command
|
||||
-R, --repo OWNER/REPO Select another repository using the OWNER/REPO or `GROUP/NAMESPACE/REPO` format or full URL or git URL
|
||||
```
|
|
@ -294,6 +294,12 @@ func CheckoutBranch(branch string) error {
|
|||
return err
|
||||
}
|
||||
|
||||
func CheckoutNewBranch(branch string) error {
|
||||
configCmd := GitCommand("checkout", "-b", branch)
|
||||
err := run.PrepareCmd(configCmd).Run()
|
||||
return err
|
||||
}
|
||||
|
||||
func parseCloneArgs(extraArgs []string) (args []string, target string) {
|
||||
args, target = parseArgs(extraArgs)
|
||||
return
|
||||
|
|
|
@ -23,8 +23,8 @@ func InitGitRepo(t *testing.T) string {
|
|||
return tempDir
|
||||
}
|
||||
|
||||
func InitGitRepoWithCommit(t *testing.T) {
|
||||
InitGitRepo(t)
|
||||
func InitGitRepoWithCommit(t *testing.T) string {
|
||||
tempDir := InitGitRepo(t)
|
||||
|
||||
configureGitConfig(t)
|
||||
|
||||
|
@ -38,6 +38,8 @@ func InitGitRepoWithCommit(t *testing.T) {
|
|||
gitCommit := GitCommand("commit", "-m", "\"commit\"")
|
||||
_, err = run.PrepareCmd(gitCommit).Output()
|
||||
require.NoError(t, err)
|
||||
|
||||
return tempDir
|
||||
}
|
||||
|
||||
func configureGitConfig(t *testing.T) {
|
||||
|
|
Loading…
Reference in New Issue