Skip to content

Commit ed57375

Browse files
author
Terminal Intelligence User
committed
Update files
1 parent 16b240a commit ed57375

2 files changed

Lines changed: 145 additions & 26 deletions

File tree

internal/git/client.go

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"time"
99

1010
"github.com/go-git/go-git/v5"
11+
"github.com/go-git/go-git/v5/plumbing/object"
1112
"github.com/go-git/go-git/v5/plumbing/transport/http"
1213
)
1314

@@ -705,6 +706,95 @@ func (c *Client) Stage() (*OperationResult, error) {
705706
}, nil
706707
}
707708

709+
// Commit creates a new commit with the staged changes.
710+
// It opens an existing repository in the working directory, gets the worktree,
711+
// and commits all staged changes with the provided commit message.
712+
//
713+
// Parameters:
714+
// - message: The commit message
715+
//
716+
// Returns:
717+
// - *OperationResult: Contains success status, commit hash, and any error
718+
// - error: Any error that occurred during the operation
719+
//
720+
// The OperationResult.Message field contains the commit hash on success.
721+
func (c *Client) Commit(message string) (*OperationResult, error) {
722+
// Open the existing repository
723+
repo, err := git.PlainOpen(c.workDir)
724+
if err != nil {
725+
return &OperationResult{
726+
Success: false,
727+
Message: "",
728+
Error: categorizeError(err),
729+
}, categorizeError(err)
730+
}
731+
732+
// Get the worktree
733+
worktree, err := repo.Worktree()
734+
if err != nil {
735+
return &OperationResult{
736+
Success: false,
737+
Message: "",
738+
Error: categorizeError(err),
739+
}, categorizeError(err)
740+
}
741+
742+
// Check if there are staged changes
743+
status, err := worktree.Status()
744+
if err != nil {
745+
return &OperationResult{
746+
Success: false,
747+
Message: "",
748+
Error: categorizeError(err),
749+
}, categorizeError(err)
750+
}
751+
752+
// Count staged files
753+
stagedCount := 0
754+
for _, fileStatus := range status {
755+
if fileStatus.Staging != git.Unmodified && fileStatus.Staging != git.Untracked {
756+
stagedCount++
757+
}
758+
}
759+
760+
if stagedCount == 0 {
761+
return &OperationResult{
762+
Success: false,
763+
Message: "",
764+
Error: fmt.Errorf("no changes staged for commit"),
765+
}, fmt.Errorf("no changes staged for commit")
766+
}
767+
768+
// Use default message if empty
769+
if message == "" {
770+
message = "Update files"
771+
}
772+
773+
// Create the commit
774+
hash, err := worktree.Commit(message, &git.CommitOptions{
775+
Author: &object.Signature{
776+
Name: "Terminal Intelligence User",
777+
Email: "user@terminal-intelligence.local",
778+
When: time.Now(),
779+
},
780+
})
781+
if err != nil {
782+
return &OperationResult{
783+
Success: false,
784+
Message: "",
785+
Error: categorizeError(err),
786+
}, categorizeError(err)
787+
}
788+
789+
// Success - return commit hash
790+
resultMessage := fmt.Sprintf("Committed %d file(s): %s", stagedCount, hash.String()[:7])
791+
return &OperationResult{
792+
Success: true,
793+
Message: resultMessage,
794+
Error: nil,
795+
}, nil
796+
}
797+
708798
// Status retrieves the current status of the repository.
709799
// It opens an existing repository in the working directory, gets the worktree status,
710800
// and formats it into three lists: modified files, staged files, and untracked files.

internal/ui/gitpane.go

Lines changed: 55 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ type GitPane struct {
3434
passInput textinput.Model // Input field for Git password or GitHub PAT (ghp_*)
3535

3636
// Button state
37-
// selectedButton: 0=Clone, 1=Pull, 2=Push, 3=Fetch, 4=Stage, 5=Status, 6=Restore
37+
// selectedButton: 0=Clone, 1=Pull, 2=Fetch, 3=Stage, 4=Commit, 5=Push, 6=Status, 7=Restore
3838
selectedButton int
3939

4040
// Status display
@@ -143,6 +143,11 @@ type GitFetchMsg struct {
143143
// GitStageMsg is sent when the user activates the Stage button.
144144
type GitStageMsg struct{}
145145

146+
// GitCommitMsg is sent when the user activates the Commit button.
147+
type GitCommitMsg struct {
148+
Message string
149+
}
150+
146151
// GitStatusMsg is sent when the user activates the Status button.
147152
type GitStatusMsg struct{}
148153

@@ -319,6 +324,19 @@ func (g *GitPane) executeStage() tea.Cmd {
319324
}
320325
}
321326

327+
// executeCommit triggers an async commit operation.
328+
// It sets the isProcessing flag and returns a command that will execute the commit
329+
// and send a GitCommitMsg when complete.
330+
func (g *GitPane) executeCommit(message string) tea.Cmd {
331+
g.isProcessing = true
332+
333+
return func() tea.Msg {
334+
return GitCommitMsg{
335+
Message: message,
336+
}
337+
}
338+
}
339+
322340
// executeStatus triggers an async status operation.
323341
// It sets the isProcessing flag and returns a command that will execute the status
324342
// and send a GitStatusMsg when complete.
@@ -462,6 +480,18 @@ func (g *GitPane) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
462480
}
463481
}
464482

483+
case GitCommitMsg:
484+
// Handle commit operation - execute async via GitClient
485+
return g, func() tea.Msg {
486+
result, _ := g.gitClient.Commit(msg.Message)
487+
return GitOperationCompleteMsg{
488+
Operation: "commit",
489+
Success: result.Success,
490+
Message: result.Message,
491+
Error: result.Error,
492+
}
493+
}
494+
465495
case GitStatusMsg:
466496
// Handle status operation - execute async via GitClient
467497
return g, func() tea.Msg {
@@ -531,15 +561,17 @@ func (g *GitPane) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
531561
return g, g.executeClone(url, username, password)
532562
case 1: // Pull
533563
return g, g.executePull(username, password)
534-
case 2: // Push
535-
return g, g.executePush(username, password)
536-
case 3: // Fetch
564+
case 2: // Fetch
537565
return g, g.executeFetch(username, password)
538-
case 4: // Stage
566+
case 3: // Stage
539567
return g, g.executeStage()
540-
case 5: // Status
568+
case 4: // Commit
569+
return g, g.executeCommit("Update files")
570+
case 5: // Push
571+
return g, g.executePush(username, password)
572+
case 6: // Status
541573
return g, g.executeStatus()
542-
case 6: // Restore
574+
case 7: // Restore
543575
return g, g.executeRestore()
544576
}
545577
return g, nil
@@ -552,11 +584,11 @@ func (g *GitPane) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
552584
if msg.String() == "left" {
553585
g.selectedButton--
554586
if g.selectedButton < 0 {
555-
g.selectedButton = 6 // Wrap to last button (Restore)
587+
g.selectedButton = 7 // Wrap to last button (Restore)
556588
}
557589
} else { // "right"
558590
g.selectedButton++
559-
if g.selectedButton > 6 {
591+
if g.selectedButton > 7 {
560592
g.selectedButton = 0 // Wrap to first button (Clone)
561593
}
562594
}
@@ -616,7 +648,7 @@ func (g *GitPane) View() string {
616648
Border(lipgloss.RoundedBorder()).
617649
BorderForeground(lipgloss.Color("62")).
618650
Padding(1, 2).
619-
Width(80)
651+
Width(84)
620652

621653
buttonStyle := lipgloss.NewStyle().
622654
Foreground(lipgloss.Color("230")).
@@ -654,8 +686,8 @@ func (g *GitPane) View() string {
654686
content.WriteString(g.passInput.View())
655687
content.WriteString("\n\n")
656688

657-
// Buttons - distribute evenly across the panel width
658-
buttonNames := []string{"Clone", "Pull", "Push", "Fetch", "Stage", "Status", "Restore"}
689+
// Buttons - reordered and grouped: Clone Pull Fetch | Stage Commit Push | Status Restore
690+
buttonNames := []string{"Clone", "Pull", "Fetch", "Stage", "Commit", "Push", "Status", "Restore"}
659691
var buttons []string
660692
for i, name := range buttonNames {
661693
if g.focusedInput == 3 && g.selectedButton == i {
@@ -665,21 +697,18 @@ func (g *GitPane) View() string {
665697
}
666698
}
667699

668-
// Calculate total button width and spacing
669-
// Panel content width is 84 - 4 (border) - 4 (padding) = 76
670-
// Each button has padding(0, 1) which adds 2 chars per button, plus the text length
671-
// Button text lengths: Clone(5), Pull(4), Push(4), Fetch(5), Stage(5), Status(6), Restore(7)
672-
// Total text: 36 chars, with padding: 36 + (7 buttons * 2) = 50 chars
673-
// Available space for gaps: 76 - 50 = 26 chars
674-
// Number of gaps between 7 buttons: 6 gaps
675-
// Space per gap: 26 / 6 ≈ 4 chars (with some remainder)
700+
// Create button row with spacing and separators
701+
// Group 1: Clone Pull Fetch (remote operations)
702+
buttonRow := buttons[0] + " " + buttons[1] + " " + buttons[2]
703+
// Separator
704+
buttonRow += " | "
705+
// Group 2: Stage Commit Push (local to remote workflow)
706+
buttonRow += buttons[3] + " " + buttons[4] + " " + buttons[5]
707+
// Separator
708+
buttonRow += " | "
709+
// Group 3: Status Restore (info and undo)
710+
buttonRow += buttons[6] + " " + buttons[7]
676711

677-
// Create evenly spaced button row
678-
buttonRow := buttons[0]
679-
for i := 1; i < len(buttons); i++ {
680-
// Add spacing between buttons (4 spaces for even distribution)
681-
buttonRow += " " + buttons[i]
682-
}
683712
content.WriteString(buttonRow)
684713
content.WriteString("\n\n")
685714

0 commit comments

Comments
 (0)