Skip to content

Commit 1a7bdcf

Browse files
authored
Feature/command history (#27)
* feat(logging): add file based logging setup and initialize logger in main Signed-off-by: Viswaijth K S <viswajithks.pz@gmail.com> * refactor: Centralize git command execution for logging Replaced all direct calls to 'exec.Command' throughout the 'internal/git' package with a single, central 'executeCommand' method. This ensures all git operations are now consistently logged to the application log file for easier debugging. Signed-off-by: Viswaijth K S <viswajithks.pz@gmail.com> * refactor: modify log.go to return command string and change respective calls in the internal/git package * feat: Implement command history and logging Adds a file-based logger for debugging and displays a real-time, session-based command history in the secondary panel. * fix(tui): ensure certain commands log into the tui correctly * add detail error logging to the history panel --------- Signed-off-by: Viswaijth K S <viswajithks.pz@gmail.com>
1 parent 6c04133 commit 1a7bdcf

File tree

24 files changed

+376
-274
lines changed

24 files changed

+376
-274
lines changed

cmd/gitx/main.go

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,13 @@ package main
33
import (
44
"errors"
55
"fmt"
6-
"log"
7-
"os"
8-
"os/exec"
9-
106
tea "github.com/charmbracelet/bubbletea"
7+
gitxlog "github.com/gitxtui/gitx/internal/log"
118
"github.com/gitxtui/gitx/internal/tui"
129
zone "github.com/lrstanley/bubblezone"
10+
"log"
11+
"os"
12+
"os/exec"
1313
)
1414

1515
var version = "dev"
@@ -27,6 +27,16 @@ func printHelp() {
2727
}
2828

2929
func main() {
30+
logFile, err := gitxlog.SetupLogger()
31+
if err != nil {
32+
fmt.Fprintf(os.Stderr, "Failed to set up logger: %v\n", err)
33+
}
34+
defer func() {
35+
if err := logFile.Close(); err != nil {
36+
fmt.Fprintf(os.Stderr, "Failed to close log file: %v\n", err)
37+
}
38+
}()
39+
3040
if err := ensureGitRepo(); err != nil {
3141
fmt.Fprintln(os.Stderr, err) // print to stderr
3242
os.Exit(1)

internal/git/branch.go

Lines changed: 27 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,7 @@ func (g *GitCommands) GetBranches() ([]*Branch, error) {
1818
format := "%(committerdate:relative)\t%(refname:short)\t%(HEAD)"
1919
args := []string{"for-each-ref", "--sort=-committerdate", "refs/heads/", fmt.Sprintf("--format=%s", format)}
2020

21-
cmd := ExecCommand("git", args...)
22-
output, err := cmd.CombinedOutput()
21+
output, _, err := g.executeCommand(args...)
2322
if err != nil {
2423
return nil, err
2524
}
@@ -98,71 +97,70 @@ type BranchOptions struct {
9897
}
9998

10099
// ManageBranch creates or deletes branches.
101-
func (g *GitCommands) ManageBranch(options BranchOptions) (string, error) {
100+
func (g *GitCommands) ManageBranch(options BranchOptions) (string, string, error) {
102101
args := []string{"branch"}
103102

104103
if options.Delete {
104+
args = append(args, "-d", options.Name)
105105
if options.Name == "" {
106-
return "", fmt.Errorf("branch name is required for deletion")
106+
return "", "", fmt.Errorf("branch name is required for deletion")
107107
}
108-
args = append(args, "-d", options.Name)
109108
} else if options.Create {
109+
args = append(args, options.Name)
110110
if options.Name == "" {
111-
return "", fmt.Errorf("branch name is required for creation")
111+
return "", "", fmt.Errorf("branch name is required for creation")
112112
}
113-
args = append(args, options.Name)
114113
}
115114

116-
cmd := ExecCommand("git", args...)
117-
output, err := cmd.CombinedOutput()
115+
output, cmdStr, err := g.executeCommand(args...)
118116
if err != nil {
119-
return string(output), fmt.Errorf("branch operation failed: %v", err)
117+
return string(output), cmdStr, err
120118
}
121119

122-
return string(output), nil
120+
return string(output), cmdStr, nil
123121
}
124122

125123
// Checkout switches branches or restores working tree files.
126-
func (g *GitCommands) Checkout(branchName string) (string, error) {
124+
func (g *GitCommands) Checkout(branchName string) (string, string, error) {
127125
if branchName == "" {
128-
return "", fmt.Errorf("branch name is required")
126+
return "", "", fmt.Errorf("branch name is required")
129127
}
128+
args := []string{"checkout", branchName}
130129

131-
cmd := ExecCommand("git", "checkout", branchName)
132-
output, err := cmd.CombinedOutput()
130+
output, cmdStr, err := g.executeCommand(args...)
133131
if err != nil {
134-
return string(output), fmt.Errorf("failed to checkout branch: %v", err)
132+
return string(output), cmdStr, err
135133
}
136134

137-
return string(output), nil
135+
return string(output), cmdStr, nil
138136
}
139137

140138
// Switch switches to a specified branch.
141-
func (g *GitCommands) Switch(branchName string) (string, error) {
139+
func (g *GitCommands) Switch(branchName string) (string, string, error) {
142140
if branchName == "" {
143-
return "", fmt.Errorf("branch name is required")
141+
return "", "", fmt.Errorf("branch name is required")
144142
}
143+
args := []string{"switch", branchName}
145144

146-
cmd := ExecCommand("git", "switch", branchName)
147-
output, err := cmd.CombinedOutput()
145+
output, cmdStr, err := g.executeCommand(args...)
148146
if err != nil {
149-
return string(output), fmt.Errorf("failed to switch branch: %v", err)
147+
return string(output), "", err
150148
}
151149

152-
return string(output), nil
150+
return string(output), cmdStr, nil
153151
}
154152

155153
// RenameBranch renames a branch.
156-
func (g *GitCommands) RenameBranch(oldName, newName string) (string, error) {
154+
func (g *GitCommands) RenameBranch(oldName, newName string) (string, string, error) {
157155
if oldName == "" || newName == "" {
158-
return "", fmt.Errorf("both old and new branch names are required")
156+
return "", "", fmt.Errorf("both old and new branch names are required")
159157
}
158+
args := []string{"branch", "-m", oldName, newName}
160159

161-
cmd := ExecCommand("git", "branch", "-m", oldName, newName)
162-
output, err := cmd.CombinedOutput()
160+
output, cmdStr, err := g.executeCommand(args...)
163161
if err != nil {
164-
return string(output), fmt.Errorf("failed to rename branch: %v", err)
162+
return string(output), cmdStr, err
165163
}
166164

167-
return string(output), nil
165+
return string(output), cmdStr, nil
168166
}

internal/git/clone.go

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package git
22

33
import (
44
"fmt"
5-
"os/exec"
65
)
76

87
// CloneRepository clones a repository from a given URL into a specified directory.
@@ -11,16 +10,14 @@ func (g *GitCommands) CloneRepository(repoURL, directory string) (string, error)
1110
return "", fmt.Errorf("repository URL is required")
1211
}
1312

14-
var cmd *exec.Cmd
13+
args := []string{"clone", repoURL}
1514
if directory != "" {
16-
cmd = exec.Command("git", "clone", repoURL, directory)
17-
} else {
18-
cmd = exec.Command("git", "clone", repoURL)
15+
args = append(args, directory)
1916
}
2017

21-
output, err := cmd.CombinedOutput()
18+
output, _, err := g.executeCommand(args...)
2219
if err != nil {
23-
return string(output), fmt.Errorf("failed to clone repository: %v", err)
20+
return string(output), err
2421
}
2522

2623
return fmt.Sprintf("Successfully cloned repository: %s", repoURL), nil

internal/git/commit.go

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package git
22

33
import (
44
"fmt"
5-
"os/exec"
65
)
76

87
// CommitOptions specifies the options for the git commit command.
@@ -12,9 +11,9 @@ type CommitOptions struct {
1211
}
1312

1413
// Commit records changes to the repository.
15-
func (g *GitCommands) Commit(options CommitOptions) (string, error) {
14+
func (g *GitCommands) Commit(options CommitOptions) (string, string, error) {
1615
if options.Message == "" && !options.Amend {
17-
return "", fmt.Errorf("commit message is required unless amending")
16+
return "", "", fmt.Errorf("commit message is required unless amending")
1817
}
1918

2019
args := []string{"commit"}
@@ -27,25 +26,24 @@ func (g *GitCommands) Commit(options CommitOptions) (string, error) {
2726
args = append(args, "-m", options.Message)
2827
}
2928

30-
cmd := exec.Command("git", args...)
31-
output, err := cmd.CombinedOutput()
29+
output, cmdStr, err := g.executeCommand(args...)
3230
if err != nil {
33-
return string(output), fmt.Errorf("failed to commit changes: %v", err)
31+
return string(output), cmdStr, err
3432
}
3533

36-
return string(output), nil
34+
return string(output), cmdStr, nil
3735
}
3836

3937
// ShowCommit shows the details of a specific commit.
4038
func (g *GitCommands) ShowCommit(commitHash string) (string, error) {
4139
if commitHash == "" {
4240
commitHash = "HEAD"
4341
}
42+
args := []string{"show", "--color=always", commitHash}
4443

45-
cmd := exec.Command("git", "show", "--color=always", commitHash)
46-
output, err := cmd.CombinedOutput()
44+
output, _, err := g.executeCommand(args...)
4745
if err != nil {
48-
return string(output), fmt.Errorf("failed to show commit: %v", err)
46+
return string(output), err
4947
}
5048

5149
return string(output), nil

internal/git/diff.go

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,5 @@
11
package git
22

3-
import (
4-
"fmt"
5-
"os/exec"
6-
)
7-
83
// DiffOptions specifies the options for the git diff command.
94
type DiffOptions struct {
105
Commit1 string
@@ -39,10 +34,9 @@ func (g *GitCommands) ShowDiff(options DiffOptions) (string, error) {
3934
args = append(args, options.Commit2)
4035
}
4136

42-
cmd := exec.Command("git", args...)
43-
output, err := cmd.CombinedOutput()
37+
output, _, err := g.executeCommand(args...)
4438
if err != nil {
45-
return string(output), fmt.Errorf("failed to get diff: %v", err)
39+
return string(output), err
4640
}
4741

4842
return string(output), nil

internal/git/files.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,13 @@ package git
22

33
import (
44
"fmt"
5-
"os/exec"
65
)
76

87
// ListFiles shows information about files in the index and the working tree.
98
func (g *GitCommands) ListFiles() (string, error) {
10-
cmd := exec.Command("git", "ls-files")
11-
output, err := cmd.CombinedOutput()
9+
args := []string{"ls-files"}
10+
11+
output, _, err := g.executeCommand(args...)
1212
if err != nil {
1313
return string(output), fmt.Errorf("failed to list files: %v", err)
1414
}
@@ -22,10 +22,10 @@ func (g *GitCommands) BlameFile(filePath string) (string, error) {
2222
return "", fmt.Errorf("file path is required")
2323
}
2424

25-
cmd := exec.Command("git", "blame", filePath)
26-
output, err := cmd.CombinedOutput()
25+
args := []string{"blame", filePath}
26+
output, _, err := g.executeCommand(args...)
2727
if err != nil {
28-
return string(output), fmt.Errorf("failed to blame file: %v", err)
28+
return string(output), err
2929
}
3030

3131
return string(output), nil

internal/git/git.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
package git
22

33
import (
4+
"errors"
5+
"fmt"
6+
"log"
47
"os/exec"
8+
"strings"
59
)
610

711
// ExecCommand is a variable that holds the exec.Command function
@@ -15,3 +19,36 @@ type GitCommands struct{}
1519
func NewGitCommands() *GitCommands {
1620
return &GitCommands{}
1721
}
22+
23+
// executeCommand centralizes the execution of all git commands and serves
24+
// as a single point for logging. It takes a list of flags passed to the git
25+
// command as arguments and returns 1. standard output, 2. the command string
26+
// and 3. standard error
27+
func (g *GitCommands) executeCommand(args ...string) (string, string, error) {
28+
cmdStr := "git " + strings.Join(args, " ")
29+
log.Printf("Executing command: %s", cmdStr)
30+
31+
cmd := ExecCommand("git", args...)
32+
output, err := cmd.CombinedOutput()
33+
34+
if err != nil {
35+
log.Printf("Error: %v, Output: %s", err, string(output))
36+
37+
var exitErr *exec.ExitError
38+
exitCode := 0
39+
40+
if errors.As(err, &exitErr) {
41+
exitCode = exitErr.ExitCode()
42+
}
43+
44+
gitMsg := strings.TrimSpace(string(output))
45+
gitMsg = strings.TrimPrefix(gitMsg, "fatal: ")
46+
gitMsg = strings.TrimPrefix(gitMsg, "error: ")
47+
48+
detailedError := fmt.Errorf("[ERROR - %d] %s", exitCode, gitMsg)
49+
50+
return "", cmdStr, detailedError
51+
}
52+
53+
return string(output), cmdStr, nil
54+
}

0 commit comments

Comments
 (0)