From a5b4b12ff7ca53c771ef2a4d1bb443c062873169 Mon Sep 17 00:00:00 2001 From: shatrughan mishra Date: Wed, 4 Feb 2026 13:53:45 +0530 Subject: [PATCH 1/4] add repository initilisation --- internal/tui/keys.go | 18 ++++++++++++ internal/tui/model.go | 18 ++++++------ internal/tui/model_test.go | 59 ++++++++++++++++++++++++++++++++++++++ internal/tui/update.go | 29 +++++++++++++++++++ 4 files changed, 116 insertions(+), 8 deletions(-) diff --git a/internal/tui/keys.go b/internal/tui/keys.go index 2fac375..3ddfef6 100644 --- a/internal/tui/keys.go +++ b/internal/tui/keys.go @@ -48,6 +48,9 @@ type KeyMap struct { StashApply key.Binding StashPop key.Binding StashDrop key.Binding + + // Keybindings for StatusPanel + InitRepository key.Binding } // HelpSection is a struct to hold a title and keybindings for a help section. @@ -87,6 +90,10 @@ func (k KeyMap) FullHelp() []HelpSection { Title: "Stash", Bindings: []key.Binding{k.StashApply, k.StashPop, k.StashDrop}, }, + { + Title: "Status", + Bindings: []key.Binding{k.InitRepository}, + }, { Title: "Misc", Bindings: []key.Binding{k.SwitchTheme, k.ToggleHelp, k.Escape, k.Quit}, @@ -128,6 +135,12 @@ func (k KeyMap) StashPanelHelp() []key.Binding { return append(help, k.ShortHelp()...) } +// StatusPanelHelp returns a slice of key.Binding for the Status Panel help bar. +func (k KeyMap) StatusPanelHelp() []key.Binding { + help := []key.Binding{k.InitRepository} + return append(help, k.ShortHelp()...) +} + // DefaultKeyMap returns a set of default keybindings. func DefaultKeyMap() KeyMap { return KeyMap{ @@ -265,5 +278,10 @@ func DefaultKeyMap() KeyMap { key.WithKeys("d"), key.WithHelp("d", "Drop"), ), + + InitRepository: key.NewBinding( + key.WithKeys("i"), + key.WithHelp("i", "Initialize Repository"), + ), } } diff --git a/internal/tui/model.go b/internal/tui/model.go index 084df2f..f3aeb16 100644 --- a/internal/tui/model.go +++ b/internal/tui/model.go @@ -57,13 +57,13 @@ func initialModel() Model { cfg, _ := load_config() var selectedThemeName string - if t, ok := Themes[cfg.Theme]; ok{ + if t, ok := Themes[cfg.Theme]; ok { selectedThemeName = cfg.Theme _ = t // to avoid unused variable warning - } else{ - if _, err := load_custom_theme(cfg.Theme); err == nil{ - selectedThemeName = cfg.Theme - } else{ + } else { + if _, err := load_custom_theme(cfg.Theme); err == nil { + selectedThemeName = cfg.Theme + } else { //fallback selectedThemeName = themeNames[0] } @@ -118,9 +118,9 @@ func initialModel() Model { } } -func indexOf(arr []string, val string) int{ - for i, s := range arr{ - if s == val{ +func indexOf(arr []string, val string) int { + for i, s := range arr { + if s == val { return i } } @@ -158,6 +158,8 @@ func (m *Model) panelShortHelp() []key.Binding { return keys.CommitsPanelHelp() case StashPanel: return keys.StashPanelHelp() + case StatusPanel: + return keys.StatusPanelHelp() default: return keys.ShortHelp() } diff --git a/internal/tui/model_test.go b/internal/tui/model_test.go index b6cd63c..724fe68 100644 --- a/internal/tui/model_test.go +++ b/internal/tui/model_test.go @@ -408,3 +408,62 @@ func assertKeyBindingsEqual(t *testing.T, got, want []key.Binding) { t.Errorf("\n\tgot \t%v\n\twant \t%v", got, want) } } + +// TestInitRepositoryKeybinding tests that the InitRepository keybinding is properly configured +func TestInitRepositoryKeybinding(t *testing.T) { + keymap := DefaultKeyMap() + + if keymap.InitRepository.Help().Key == "" { + t.Error("InitRepository keybinding not configured") + } + + if len(keymap.InitRepository.Help().Key) == 0 { + t.Error("InitRepository keybinding has no key") + } +} + +// TestStatusPanelHelp tests that the StatusPanel help includes InitRepository keybinding +func TestStatusPanelHelp(t *testing.T) { + keymap := DefaultKeyMap() + help := keymap.StatusPanelHelp() + + if len(help) == 0 { + t.Error("StatusPanelHelp returned empty slice") + } + + // Verify that InitRepository keybinding is in the help by checking the help text + found := false + for _, binding := range help { + if binding.Help().Desc == keymap.InitRepository.Help().Desc { + found = true + break + } + } + if !found { + t.Error("InitRepository keybinding not found in StatusPanelHelp") + } +} + +// TestPanelShortHelpForStatus tests that panelShortHelp returns StatusPanel help when focused +func TestPanelShortHelpForStatus(t *testing.T) { + m := initialModel() + m.focusedPanel = StatusPanel + + help := m.panelShortHelp() + + if len(help) == 0 { + t.Error("panelShortHelp returned empty for StatusPanel") + } + + // Verify InitRepository keybinding is present by checking the help description + found := false + for _, binding := range help { + if binding.Help().Desc == keys.InitRepository.Help().Desc { + found = true + break + } + } + if !found { + t.Error("InitRepository keybinding not in StatusPanel short help") + } +} diff --git a/internal/tui/update.go b/internal/tui/update.go index 3dd138c..57c2244 100644 --- a/internal/tui/update.go +++ b/internal/tui/update.go @@ -586,6 +586,8 @@ func (m *Model) handlePanelKeys(msg tea.KeyMsg) tea.Cmd { return m.handleCommitsPanelKeys(msg) case StashPanel: return m.handleStashPanelKeys(msg) + case StatusPanel: + return m.handleStatusPanelKeys(msg) } return nil } @@ -958,6 +960,33 @@ func (m *Model) handleStashPanelKeys(msg tea.KeyMsg) tea.Cmd { return nil } +// handleStatusPanelKeys handles keybindings for the Status Panel. +func (m *Model) handleStatusPanelKeys(msg tea.KeyMsg) tea.Cmd { + switch { + case key.Matches(msg, keys.InitRepository): + m.mode = modeInput + m.promptTitle = "Initialize Repository" + m.textInput.Placeholder = "Enter path (or . for current directory)" + m.textInput.Reset() + m.textInput.Focus() + m.inputCallback = func(path string) tea.Cmd { + return func() tea.Msg { + if path == "" { + path = "." + } + _, err := m.git.InitRepository(path) + if err != nil { + return errMsg{err} + } + // Update repo info after initialization + m.repoName, m.branchName, _ = m.git.GetRepoInfo() + return commandExecutedMsg{fmt.Sprintf("git init %s", path)} + } + } + } + return nil +} + // handleFocusKeys changes the focused panel based on keyboard shortcuts. func (m *Model) handleFocusKeys(msg tea.KeyMsg) { switch { From 461a08b4ea95bd9e2c88f40a196c33cacbf8a7e0 Mon Sep 17 00:00:00 2001 From: shatrughan mishra Date: Thu, 5 Feb 2026 13:08:35 +0530 Subject: [PATCH 2/4] add flag --- cmd/gitx/main.go | 44 ++++++++++++++++++++------- internal/git/init_test.go | 62 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 95 insertions(+), 11 deletions(-) create mode 100644 internal/git/init_test.go diff --git a/cmd/gitx/main.go b/cmd/gitx/main.go index 4c8d359..ae5e114 100644 --- a/cmd/gitx/main.go +++ b/cmd/gitx/main.go @@ -3,13 +3,15 @@ package main import ( "errors" "fmt" + "log" + "os" + "os/exec" + tea "github.com/charmbracelet/bubbletea" + "github.com/gitxtui/gitx/internal/git" gitxlog "github.com/gitxtui/gitx/internal/log" "github.com/gitxtui/gitx/internal/tui" zone "github.com/lrstanley/bubblezone" - "log" - "os" - "os/exec" ) var version = "dev" @@ -22,8 +24,10 @@ func printHelp() { fmt.Println("Options:") fmt.Println(" -v, --version Show version information") fmt.Println(" -h, --help Show this help message") + fmt.Println(" -i, --init Initialize a new Git repository") fmt.Println() fmt.Println("Run 'gitx' inside a Git repository to start the TUI.") + fmt.Println("Or run 'gitx -i' to initialize a new Git repository in the current directory.") } func main() { @@ -37,11 +41,8 @@ func main() { } }() - if err := ensureGitRepo(); err != nil { - fmt.Fprintln(os.Stderr, err) // print to stderr - os.Exit(1) - } - + // Parse flags + shouldInit := false if len(os.Args) > 1 { switch os.Args[1] { case "--version", "-v": @@ -50,9 +51,17 @@ func main() { case "--help", "-h": printHelp() return + case "--init", "-i": + shouldInit = true } } + // Ensure git repo exists (initialize if flag is set) + if err := ensureGitRepo(shouldInit); err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } + zone.NewGlobal() defer zone.Close() @@ -66,10 +75,23 @@ func main() { fmt.Println("Bye from gitx! :)") } -func ensureGitRepo() error { +func ensureGitRepo(shouldInit bool) error { cmd := exec.Command("git", "rev-parse", "--is-inside-work-tree") - if err := cmd.Run(); err != nil { - return fmt.Errorf("error: not a git repository") + if err := cmd.Run(); err == nil { + return nil // Already inside a git repo + } + + if !shouldInit { + return fmt.Errorf("error: not a git repository\nrun gitx -i/--init to initialize a new git repository and open gitx") } + + // Initialize a new git repository + g := &git.GitCommands{} + _, err := g.InitRepository(".") + if err != nil { + return fmt.Errorf("failed to initialize git repository: %w", err) + } + + fmt.Println("Initialized new git repository in current directory") return nil } diff --git a/internal/git/init_test.go b/internal/git/init_test.go new file mode 100644 index 0000000..3ad33a3 --- /dev/null +++ b/internal/git/init_test.go @@ -0,0 +1,62 @@ +package git + +import ( + "os" + "os/exec" + "path/filepath" + "strings" + "testing" +) + +func TestInitRepository(t *testing.T) { + // Skip if git is not available + if _, err := exec.LookPath("git"); err != nil { + t.Skip("git not found in PATH; skipping integration test") + } + + tmp := t.TempDir() + g := &GitCommands{} + + msg, err := g.InitRepository(tmp) + if err != nil { + t.Fatalf("InitRepository failed: %v (msg=%q)", err, msg) + } + + // Check that .git directory was created + gitdir := filepath.Join(tmp, ".git") + if _, err := os.Stat(gitdir); err != nil { + t.Fatalf(".git directory missing: %v", err) + } + + // Check that the message contains the absolute path + abs, _ := filepath.Abs(tmp) + if !strings.Contains(msg, abs) { + t.Fatalf("unexpected message: %q does not contain %q", msg, abs) + } +} + +func TestInitRepositoryAlreadyExists(t *testing.T) { + // Skip if git is not available + if _, err := exec.LookPath("git"); err != nil { + t.Skip("git not found in PATH; skipping integration test") + } + + tmp := t.TempDir() + g := &GitCommands{} + + // Initialize repo first time + _, err := g.InitRepository(tmp) + if err != nil { + t.Fatalf("Initial InitRepository failed: %v", err) + } + + // Try to initialize again + msg, err := g.InitRepository(tmp) + if err != nil { + // It might error or succeed depending on git behavior + // Just verify we handle it gracefully + t.Logf("Second InitRepository returned error (expected): %v", err) + } else { + t.Logf("Second InitRepository succeeded with msg: %q", msg) + } +} From 0840608fa7de8b717ab884819acbb4e96587e1c8 Mon Sep 17 00:00:00 2001 From: shatrughan mishra Date: Thu, 5 Feb 2026 19:57:40 +0530 Subject: [PATCH 3/4] Revert "add repository initilisation" This reverts commit a5b4b12ff7ca53c771ef2a4d1bb443c062873169. --- internal/tui/keys.go | 18 ------------ internal/tui/model.go | 18 ++++++------ internal/tui/model_test.go | 59 -------------------------------------- internal/tui/update.go | 29 ------------------- 4 files changed, 8 insertions(+), 116 deletions(-) diff --git a/internal/tui/keys.go b/internal/tui/keys.go index 3ddfef6..2fac375 100644 --- a/internal/tui/keys.go +++ b/internal/tui/keys.go @@ -48,9 +48,6 @@ type KeyMap struct { StashApply key.Binding StashPop key.Binding StashDrop key.Binding - - // Keybindings for StatusPanel - InitRepository key.Binding } // HelpSection is a struct to hold a title and keybindings for a help section. @@ -90,10 +87,6 @@ func (k KeyMap) FullHelp() []HelpSection { Title: "Stash", Bindings: []key.Binding{k.StashApply, k.StashPop, k.StashDrop}, }, - { - Title: "Status", - Bindings: []key.Binding{k.InitRepository}, - }, { Title: "Misc", Bindings: []key.Binding{k.SwitchTheme, k.ToggleHelp, k.Escape, k.Quit}, @@ -135,12 +128,6 @@ func (k KeyMap) StashPanelHelp() []key.Binding { return append(help, k.ShortHelp()...) } -// StatusPanelHelp returns a slice of key.Binding for the Status Panel help bar. -func (k KeyMap) StatusPanelHelp() []key.Binding { - help := []key.Binding{k.InitRepository} - return append(help, k.ShortHelp()...) -} - // DefaultKeyMap returns a set of default keybindings. func DefaultKeyMap() KeyMap { return KeyMap{ @@ -278,10 +265,5 @@ func DefaultKeyMap() KeyMap { key.WithKeys("d"), key.WithHelp("d", "Drop"), ), - - InitRepository: key.NewBinding( - key.WithKeys("i"), - key.WithHelp("i", "Initialize Repository"), - ), } } diff --git a/internal/tui/model.go b/internal/tui/model.go index f3aeb16..084df2f 100644 --- a/internal/tui/model.go +++ b/internal/tui/model.go @@ -57,13 +57,13 @@ func initialModel() Model { cfg, _ := load_config() var selectedThemeName string - if t, ok := Themes[cfg.Theme]; ok { + if t, ok := Themes[cfg.Theme]; ok{ selectedThemeName = cfg.Theme _ = t // to avoid unused variable warning - } else { - if _, err := load_custom_theme(cfg.Theme); err == nil { - selectedThemeName = cfg.Theme - } else { + } else{ + if _, err := load_custom_theme(cfg.Theme); err == nil{ + selectedThemeName = cfg.Theme + } else{ //fallback selectedThemeName = themeNames[0] } @@ -118,9 +118,9 @@ func initialModel() Model { } } -func indexOf(arr []string, val string) int { - for i, s := range arr { - if s == val { +func indexOf(arr []string, val string) int{ + for i, s := range arr{ + if s == val{ return i } } @@ -158,8 +158,6 @@ func (m *Model) panelShortHelp() []key.Binding { return keys.CommitsPanelHelp() case StashPanel: return keys.StashPanelHelp() - case StatusPanel: - return keys.StatusPanelHelp() default: return keys.ShortHelp() } diff --git a/internal/tui/model_test.go b/internal/tui/model_test.go index 724fe68..b6cd63c 100644 --- a/internal/tui/model_test.go +++ b/internal/tui/model_test.go @@ -408,62 +408,3 @@ func assertKeyBindingsEqual(t *testing.T, got, want []key.Binding) { t.Errorf("\n\tgot \t%v\n\twant \t%v", got, want) } } - -// TestInitRepositoryKeybinding tests that the InitRepository keybinding is properly configured -func TestInitRepositoryKeybinding(t *testing.T) { - keymap := DefaultKeyMap() - - if keymap.InitRepository.Help().Key == "" { - t.Error("InitRepository keybinding not configured") - } - - if len(keymap.InitRepository.Help().Key) == 0 { - t.Error("InitRepository keybinding has no key") - } -} - -// TestStatusPanelHelp tests that the StatusPanel help includes InitRepository keybinding -func TestStatusPanelHelp(t *testing.T) { - keymap := DefaultKeyMap() - help := keymap.StatusPanelHelp() - - if len(help) == 0 { - t.Error("StatusPanelHelp returned empty slice") - } - - // Verify that InitRepository keybinding is in the help by checking the help text - found := false - for _, binding := range help { - if binding.Help().Desc == keymap.InitRepository.Help().Desc { - found = true - break - } - } - if !found { - t.Error("InitRepository keybinding not found in StatusPanelHelp") - } -} - -// TestPanelShortHelpForStatus tests that panelShortHelp returns StatusPanel help when focused -func TestPanelShortHelpForStatus(t *testing.T) { - m := initialModel() - m.focusedPanel = StatusPanel - - help := m.panelShortHelp() - - if len(help) == 0 { - t.Error("panelShortHelp returned empty for StatusPanel") - } - - // Verify InitRepository keybinding is present by checking the help description - found := false - for _, binding := range help { - if binding.Help().Desc == keys.InitRepository.Help().Desc { - found = true - break - } - } - if !found { - t.Error("InitRepository keybinding not in StatusPanel short help") - } -} diff --git a/internal/tui/update.go b/internal/tui/update.go index 57c2244..3dd138c 100644 --- a/internal/tui/update.go +++ b/internal/tui/update.go @@ -586,8 +586,6 @@ func (m *Model) handlePanelKeys(msg tea.KeyMsg) tea.Cmd { return m.handleCommitsPanelKeys(msg) case StashPanel: return m.handleStashPanelKeys(msg) - case StatusPanel: - return m.handleStatusPanelKeys(msg) } return nil } @@ -960,33 +958,6 @@ func (m *Model) handleStashPanelKeys(msg tea.KeyMsg) tea.Cmd { return nil } -// handleStatusPanelKeys handles keybindings for the Status Panel. -func (m *Model) handleStatusPanelKeys(msg tea.KeyMsg) tea.Cmd { - switch { - case key.Matches(msg, keys.InitRepository): - m.mode = modeInput - m.promptTitle = "Initialize Repository" - m.textInput.Placeholder = "Enter path (or . for current directory)" - m.textInput.Reset() - m.textInput.Focus() - m.inputCallback = func(path string) tea.Cmd { - return func() tea.Msg { - if path == "" { - path = "." - } - _, err := m.git.InitRepository(path) - if err != nil { - return errMsg{err} - } - // Update repo info after initialization - m.repoName, m.branchName, _ = m.git.GetRepoInfo() - return commandExecutedMsg{fmt.Sprintf("git init %s", path)} - } - } - } - return nil -} - // handleFocusKeys changes the focused panel based on keyboard shortcuts. func (m *Model) handleFocusKeys(msg tea.KeyMsg) { switch { From 7df318154ea54cb9febd033f17aa934c26b77905 Mon Sep 17 00:00:00 2001 From: Ayush Date: Fri, 6 Feb 2026 10:06:12 +0530 Subject: [PATCH 4/4] tests: remove redundant tests Signed-off-by: Ayush --- internal/git/init_test.go | 62 --------------------------------------- 1 file changed, 62 deletions(-) delete mode 100644 internal/git/init_test.go diff --git a/internal/git/init_test.go b/internal/git/init_test.go deleted file mode 100644 index 3ad33a3..0000000 --- a/internal/git/init_test.go +++ /dev/null @@ -1,62 +0,0 @@ -package git - -import ( - "os" - "os/exec" - "path/filepath" - "strings" - "testing" -) - -func TestInitRepository(t *testing.T) { - // Skip if git is not available - if _, err := exec.LookPath("git"); err != nil { - t.Skip("git not found in PATH; skipping integration test") - } - - tmp := t.TempDir() - g := &GitCommands{} - - msg, err := g.InitRepository(tmp) - if err != nil { - t.Fatalf("InitRepository failed: %v (msg=%q)", err, msg) - } - - // Check that .git directory was created - gitdir := filepath.Join(tmp, ".git") - if _, err := os.Stat(gitdir); err != nil { - t.Fatalf(".git directory missing: %v", err) - } - - // Check that the message contains the absolute path - abs, _ := filepath.Abs(tmp) - if !strings.Contains(msg, abs) { - t.Fatalf("unexpected message: %q does not contain %q", msg, abs) - } -} - -func TestInitRepositoryAlreadyExists(t *testing.T) { - // Skip if git is not available - if _, err := exec.LookPath("git"); err != nil { - t.Skip("git not found in PATH; skipping integration test") - } - - tmp := t.TempDir() - g := &GitCommands{} - - // Initialize repo first time - _, err := g.InitRepository(tmp) - if err != nil { - t.Fatalf("Initial InitRepository failed: %v", err) - } - - // Try to initialize again - msg, err := g.InitRepository(tmp) - if err != nil { - // It might error or succeed depending on git behavior - // Just verify we handle it gracefully - t.Logf("Second InitRepository returned error (expected): %v", err) - } else { - t.Logf("Second InitRepository succeeded with msg: %q", msg) - } -}