From ce9060058f8cc6250ab8668b7994aa5983e18eb9 Mon Sep 17 00:00:00 2001 From: Firdous2307 Date: Sat, 26 Oct 2024 14:33:15 +0000 Subject: [PATCH 1/4] feat: add ability to push and fetch stacked diffs metadata --- pkg/git/stack_struct.go | 12 ++++++ pkg/git/stack_struct_test.go | 79 ++++++++++++++++++++++++++++++++++++ pkg/git/stacked.go | 37 +++++++++++++++++ pkg/git/stacked_test.go | 49 ++++++++++++++++++++++ 4 files changed, 177 insertions(+) diff --git a/pkg/git/stack_struct.go b/pkg/git/stack_struct.go index 5cf3f6579..13cecc3fe 100644 --- a/pkg/git/stack_struct.go +++ b/pkg/git/stack_struct.go @@ -9,6 +9,7 @@ import ( "os" "path/filepath" "strings" + "os/exec" ) type StackRef struct { @@ -31,6 +32,7 @@ type StackRef struct { type Stack struct { Title string Refs map[string]StackRef + MetadataHash string `json:"metadata_hash,omitempty"` } func (s Stack) Empty() bool { return len(s.Refs) == 0 } @@ -300,3 +302,13 @@ func (r StackRef) Subject() string { return ls[0][:69] + "..." } + +func PushStackMetadata(remote string) error { + cmd := exec.Command("git", "push", remote, "+refs/stacked/*:refs/stacked/*") + return cmd.Run() +} + +func FetchStackMetadata(remote string) error { + cmd := exec.Command("git", "fetch", remote, "+refs/stacked/*:refs/stacked/*") + return cmd.Run() +} diff --git a/pkg/git/stack_struct_test.go b/pkg/git/stack_struct_test.go index c6d6062f6..55b9c526c 100644 --- a/pkg/git/stack_struct_test.go +++ b/pkg/git/stack_struct_test.go @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/require" "gitlab.com/gitlab-org/cli/internal/config" "gitlab.com/gitlab-org/cli/internal/run" + "os/exec" ) func Test_StackRemoveRef(t *testing.T) { @@ -620,3 +621,81 @@ func createBranches(t *testing.T, refs map[string]StackRef) { require.Nil(t, err) } } + +func TestPushStackMetadata(t *testing.T) { + tests := []struct { + name string + remote string + wantErr bool + }{ + { + name: "push to origin", + remote: "origin", + wantErr: false, + }, + { + name: "push to non-existent remote", + remote: "non-existent", + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + InitGitRepoWithCommit(t) + + if tt.remote == "origin" { + cmd := exec.Command("git", "remote", "add", "origin", "https://gitlab.com/test/repo.git") + err := cmd.Run() + require.NoError(t, err) + } + + err := PushStackMetadata(tt.remote) + + if tt.wantErr { + require.Error(t, err) + } else { + require.NoError(t, err) + } + }) + } +} + +func TestFetchStackMetadata(t *testing.T) { + tests := []struct { + name string + remote string + wantErr bool + }{ + { + name: "fetch from origin", + remote: "origin", + wantErr: false, + }, + { + name: "fetch from non-existent remote", + remote: "non-existent", + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + InitGitRepoWithCommit(t) + + if tt.remote == "origin" { + cmd := exec.Command("git", "remote", "add", "origin", "https://gitlab.com/test/repo.git") + err := cmd.Run() + require.NoError(t, err) + } + + err := FetchStackMetadata(tt.remote) + + if tt.wantErr { + require.Error(t, err) + } else { + require.NoError(t, err) + } + }) + } +} diff --git a/pkg/git/stacked.go b/pkg/git/stacked.go index cde17dbba..118262006 100644 --- a/pkg/git/stacked.go +++ b/pkg/git/stacked.go @@ -1,10 +1,13 @@ package git import ( + "bytes" "encoding/json" "fmt" "os" + "os/exec" "path/filepath" + "strings" "gitlab.com/gitlab-org/cli/internal/run" ) @@ -158,3 +161,37 @@ func GetStacks() (stacks []Stack, err error) { } return } + +func CreateStack(name string, base string, head string) error { + stack := &Stack{ + Title: name, + Refs: map[string]StackRef{ + base: {SHA: base}, + head: {SHA: head}, + }, + } + + // Serialize stack to JSON + jsonData, err := json.Marshal(stack) + if err != nil { + return err + } + + // Create Git object + cmd := exec.Command("git", "hash-object", "-w", "--stdin") + cmd.Stdin = bytes.NewReader(jsonData) + output, err := cmd.Output() + if err != nil { + return err + } + + stack.MetadataHash = strings.TrimSpace(string(output)) + + // Write ref + gitDir, err := ToplevelDir() + if err != nil { + return err + } + refPath := filepath.Join(gitDir, "refs", "stacked", name) + return os.WriteFile(refPath, []byte(stack.MetadataHash), 0644) +} diff --git a/pkg/git/stacked_test.go b/pkg/git/stacked_test.go index 72fe3686f..74ada72c1 100644 --- a/pkg/git/stacked_test.go +++ b/pkg/git/stacked_test.go @@ -246,3 +246,52 @@ func createRefFiles(refs map[string]StackRef, title string) error { return nil } + +func TestCreateStack(t *testing.T) { + tests := []struct { + name string + title string + base string + head string + wantErr bool + }{ + { + name: "create valid stack", + title: "test-stack", + base: "main", + head: "feature", + wantErr: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + dir := InitGitRepoWithCommit(t) + + err := CreateStack(tt.title, tt.base, tt.head) + + if tt.wantErr { + require.Error(t, err) + } else { + require.NoError(t, err) + + // Check if the stack metadata file was created + metadataPath := filepath.Join(dir, ".git", "refs", "stacked", tt.title) + require.FileExists(t, metadataPath) + + // Read the metadata file and verify its contents + content, err := os.ReadFile(metadataPath) + require.NoError(t, err) + + var stack Stack + err = json.Unmarshal(content, &stack) + require.NoError(t, err) + + require.Equal(t, tt.title, stack.Title) + require.Equal(t, tt.base, stack.Refs[tt.base].SHA) + require.Equal(t, tt.head, stack.Refs[tt.head].SHA) + require.NotEmpty(t, stack.MetadataHash) + } + }) + } +} -- GitLab From 591a3ffaf72a171473e18fc1c50570d7ad9a296e Mon Sep 17 00:00:00 2001 From: Firdous2307 Date: Sat, 26 Oct 2024 14:45:48 +0000 Subject: [PATCH 2/4] build: install gofumpt and goimports formatting tools --- pkg/git/stack_struct.go | 6 +++--- pkg/git/stack_struct_test.go | 2 +- pkg/git/stacked.go | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/pkg/git/stack_struct.go b/pkg/git/stack_struct.go index 13cecc3fe..7e7dbb5d2 100644 --- a/pkg/git/stack_struct.go +++ b/pkg/git/stack_struct.go @@ -7,9 +7,9 @@ import ( "io/fs" "iter" "os" + "os/exec" "path/filepath" "strings" - "os/exec" ) type StackRef struct { @@ -30,8 +30,8 @@ type StackRef struct { // All stacks must be created with GatherStackRefs // which validates the stack for consistency. type Stack struct { - Title string - Refs map[string]StackRef + Title string + Refs map[string]StackRef MetadataHash string `json:"metadata_hash,omitempty"` } diff --git a/pkg/git/stack_struct_test.go b/pkg/git/stack_struct_test.go index 55b9c526c..6961609c2 100644 --- a/pkg/git/stack_struct_test.go +++ b/pkg/git/stack_struct_test.go @@ -1,6 +1,7 @@ package git import ( + "os/exec" "path" "slices" "testing" @@ -9,7 +10,6 @@ import ( "github.com/stretchr/testify/require" "gitlab.com/gitlab-org/cli/internal/config" "gitlab.com/gitlab-org/cli/internal/run" - "os/exec" ) func Test_StackRemoveRef(t *testing.T) { diff --git a/pkg/git/stacked.go b/pkg/git/stacked.go index 118262006..778ea93cd 100644 --- a/pkg/git/stacked.go +++ b/pkg/git/stacked.go @@ -193,5 +193,5 @@ func CreateStack(name string, base string, head string) error { return err } refPath := filepath.Join(gitDir, "refs", "stacked", name) - return os.WriteFile(refPath, []byte(stack.MetadataHash), 0644) + return os.WriteFile(refPath, []byte(stack.MetadataHash), 0o644) } -- GitLab From 7d16c080d5dfb65919e43811947ec89e187611f4 Mon Sep 17 00:00:00 2001 From: Firdous2307 Date: Sat, 26 Oct 2024 15:37:35 +0000 Subject: [PATCH 3/4] chore: Refactor tests --- pkg/git/stack_struct_test.go | 30 ++++++++++++++++++------------ pkg/git/stacked_test.go | 22 ++++++++++++++++++---- 2 files changed, 36 insertions(+), 16 deletions(-) diff --git a/pkg/git/stack_struct_test.go b/pkg/git/stack_struct_test.go index 6961609c2..12a484ef7 100644 --- a/pkg/git/stack_struct_test.go +++ b/pkg/git/stack_struct_test.go @@ -642,14 +642,17 @@ func TestPushStackMetadata(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - InitGitRepoWithCommit(t) + // Set up a mock git environment - if tt.remote == "origin" { - cmd := exec.Command("git", "remote", "add", "origin", "https://gitlab.com/test/repo.git") - err := cmd.Run() - require.NoError(t, err) - } + oldExecCommand := GitCommand + defer func() { GitCommand = oldExecCommand }() + GitCommand = func(args ...string) *exec.Cmd { + if tt.wantErr { + return exec.Command("false") + } + return exec.Command("true") + } err := PushStackMetadata(tt.remote) if tt.wantErr { @@ -681,12 +684,15 @@ func TestFetchStackMetadata(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - InitGitRepoWithCommit(t) - - if tt.remote == "origin" { - cmd := exec.Command("git", "remote", "add", "origin", "https://gitlab.com/test/repo.git") - err := cmd.Run() - require.NoError(t, err) + // Set up a mock git environment + oldExecCommand := GitCommand + defer func() { GitCommand = oldExecCommand }() + + GitCommand = func(args ...string) *exec.Cmd { + if tt.wantErr { + return exec.Command("false") + } + return exec.Command("true") } err := FetchStackMetadata(tt.remote) diff --git a/pkg/git/stacked_test.go b/pkg/git/stacked_test.go index 74ada72c1..44dfee8f8 100644 --- a/pkg/git/stacked_test.go +++ b/pkg/git/stacked_test.go @@ -266,9 +266,23 @@ func TestCreateStack(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - dir := InitGitRepoWithCommit(t) + // Create a temporary directory + dir, err := os.MkdirTemp("", "TestCreateStack") + require.NoError(t, err) + defer os.RemoveAll(dir) + + // Set up the git environment in the temporary directory + err = os.MkdirAll(filepath.Join(dir, ".git", "refs"), 0o755) + require.NoError(t, err) + + // Change to the temporary directory + oldWd, err := os.Getwd() + require.NoError(t, err) + err = os.Chdir(dir) + require.NoError(t, err) + defer os.Chdir(oldWd) - err := CreateStack(tt.title, tt.base, tt.head) + err = CreateStack(tt.title, tt.base, tt.head) if tt.wantErr { require.Error(t, err) @@ -288,8 +302,8 @@ func TestCreateStack(t *testing.T) { require.NoError(t, err) require.Equal(t, tt.title, stack.Title) - require.Equal(t, tt.base, stack.Refs[tt.base].SHA) - require.Equal(t, tt.head, stack.Refs[tt.head].SHA) + require.Contains(t, stack.Refs, tt.base) + require.Contains(t, stack.Refs, tt.head) require.NotEmpty(t, stack.MetadataHash) } }) -- GitLab From 2cc7bfedab2fd132831519ad77f279dc72e4cfab Mon Sep 17 00:00:00 2001 From: Firdous2307 Date: Sat, 26 Oct 2024 16:00:40 +0000 Subject: [PATCH 4/4] chore: added new changes --- pkg/git/stack_struct.go | 3 ++ pkg/git/stacked.go | 22 +++++++++------ pkg/git/stacked_test.go | 61 +++++++++++++++++++++++++++++------------ 3 files changed, 60 insertions(+), 26 deletions(-) diff --git a/pkg/git/stack_struct.go b/pkg/git/stack_struct.go index 7e7dbb5d2..af11b353d 100644 --- a/pkg/git/stack_struct.go +++ b/pkg/git/stack_struct.go @@ -33,6 +33,9 @@ type Stack struct { Title string Refs map[string]StackRef MetadataHash string `json:"metadata_hash,omitempty"` + Name string + Base string + Head string } func (s Stack) Empty() bool { return len(s.Refs) == 0 } diff --git a/pkg/git/stacked.go b/pkg/git/stacked.go index 778ea93cd..b2067789c 100644 --- a/pkg/git/stacked.go +++ b/pkg/git/stacked.go @@ -170,28 +170,34 @@ func CreateStack(name string, base string, head string) error { head: {SHA: head}, }, } - // Serialize stack to JSON jsonData, err := json.Marshal(stack) if err != nil { - return err + return fmt.Errorf("failed to marshal stack: %w", err) } // Create Git object cmd := exec.Command("git", "hash-object", "-w", "--stdin") cmd.Stdin = bytes.NewReader(jsonData) - output, err := cmd.Output() + output, err := cmd.CombinedOutput() if err != nil { - return err + return fmt.Errorf("failed to create git object: %w, output: %s", err, string(output)) } stack.MetadataHash = strings.TrimSpace(string(output)) + // Ensure the refs/stacked directory exists + refDir := filepath.Join(".git", "refs", "stacked") + if err := os.MkdirAll(refDir, 0o755); err != nil { + return fmt.Errorf("failed to create refs/stacked directory: %w", err) + } + // Write ref - gitDir, err := ToplevelDir() + refPath := filepath.Join(refDir, name) + err = os.WriteFile(refPath, []byte(stack.MetadataHash), 0o644) if err != nil { - return err + return fmt.Errorf("failed to write ref file: %w", err) } - refPath := filepath.Join(gitDir, "refs", "stacked", name) - return os.WriteFile(refPath, []byte(stack.MetadataHash), 0o644) + + return nil } diff --git a/pkg/git/stacked_test.go b/pkg/git/stacked_test.go index 44dfee8f8..c1f1209e3 100644 --- a/pkg/git/stacked_test.go +++ b/pkg/git/stacked_test.go @@ -3,6 +3,7 @@ package git import ( "encoding/json" "os" + "os/exec" "path/filepath" "strings" "testing" @@ -250,16 +251,16 @@ func createRefFiles(refs map[string]StackRef, title string) error { func TestCreateStack(t *testing.T) { tests := []struct { name string - title string - base string - head string + stack Stack wantErr bool }{ { - name: "create valid stack", - title: "test-stack", - base: "main", - head: "feature", + name: "create valid stack", + stack: Stack{ + Name: "test-stack", + Base: "main", + Head: "feature", + }, wantErr: false, }, } @@ -271,26 +272,50 @@ func TestCreateStack(t *testing.T) { require.NoError(t, err) defer os.RemoveAll(dir) - // Set up the git environment in the temporary directory - err = os.MkdirAll(filepath.Join(dir, ".git", "refs"), 0o755) - require.NoError(t, err) - // Change to the temporary directory oldWd, err := os.Getwd() require.NoError(t, err) err = os.Chdir(dir) require.NoError(t, err) - defer os.Chdir(oldWd) + defer func() { + err := os.Chdir(oldWd) + require.NoError(t, err) + }() + + // Initialize Git repository + cmd := exec.Command("git", "init") + output, err := cmd.CombinedOutput() + require.NoError(t, err, "Git init failed: %s", string(output)) - err = CreateStack(tt.title, tt.base, tt.head) + // Create an initial commit + cmd = exec.Command("git", "commit", "--allow-empty", "-m", "Initial commit") + output, err = cmd.CombinedOutput() + require.NoError(t, err, "Initial commit failed: %s", string(output)) + // Set up Git user configuration + cmd = exec.Command("git", "config", "user.name", "Test User") + output, err = cmd.CombinedOutput() + require.NoError(t, err, "Setting Git user.name failed: %s", string(output)) + + cmd = exec.Command("git", "config", "user.email", "test@example.com") + output, err = cmd.CombinedOutput() + require.NoError(t, err, "Setting Git user.email failed: %s", string(output)) + + // Log Git status before creating stack + cmd = exec.Command("git", "status") + output, err = cmd.CombinedOutput() + require.NoError(t, err, "Git status failed: %s", string(output)) + + err = CreateStack(tt.stack.Name, tt.stack.Base, tt.stack.Head) if tt.wantErr { require.Error(t, err) } else { - require.NoError(t, err) + if err != nil { + t.Fatalf("CreateStack failed: %v", err) + } // Check if the stack metadata file was created - metadataPath := filepath.Join(dir, ".git", "refs", "stacked", tt.title) + metadataPath := filepath.Join(dir, ".git", "refs", "stacked", tt.stack.Name) require.FileExists(t, metadataPath) // Read the metadata file and verify its contents @@ -301,9 +326,9 @@ func TestCreateStack(t *testing.T) { err = json.Unmarshal(content, &stack) require.NoError(t, err) - require.Equal(t, tt.title, stack.Title) - require.Contains(t, stack.Refs, tt.base) - require.Contains(t, stack.Refs, tt.head) + require.Equal(t, tt.stack.Name, stack.Name) + require.Equal(t, tt.stack.Base, stack.Base) + require.Equal(t, tt.stack.Head, stack.Head) require.NotEmpty(t, stack.MetadataHash) } }) -- GitLab