Skip to content
18 changes: 14 additions & 4 deletions cmd/gitx/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@ package main
import (
"errors"
"fmt"
"log"
"os"
"os/exec"

tea "github.com/charmbracelet/bubbletea"
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"
Expand All @@ -27,6 +27,16 @@ func printHelp() {
}

func main() {
logFile, err := gitxlog.SetupLogger()
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to set up logger: %v\n", err)
}
defer func() {
if err := logFile.Close(); err != nil {
fmt.Fprintf(os.Stderr, "Failed to close log file: %v\n", err)
}
}()

if err := ensureGitRepo(); err != nil {
fmt.Fprintln(os.Stderr, err) // print to stderr
os.Exit(1)
Expand Down
56 changes: 27 additions & 29 deletions internal/git/branch.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,7 @@ func (g *GitCommands) GetBranches() ([]*Branch, error) {
format := "%(committerdate:relative)\t%(refname:short)\t%(HEAD)"
args := []string{"for-each-ref", "--sort=-committerdate", "refs/heads/", fmt.Sprintf("--format=%s", format)}

cmd := ExecCommand("git", args...)
output, err := cmd.CombinedOutput()
output, _, err := g.executeCommand(args...)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -98,71 +97,70 @@ type BranchOptions struct {
}

// ManageBranch creates or deletes branches.
func (g *GitCommands) ManageBranch(options BranchOptions) (string, error) {
func (g *GitCommands) ManageBranch(options BranchOptions) (string, string, error) {
args := []string{"branch"}

if options.Delete {
args = append(args, "-d", options.Name)
if options.Name == "" {
return "", fmt.Errorf("branch name is required for deletion")
return "", "", fmt.Errorf("branch name is required for deletion")
}
args = append(args, "-d", options.Name)
} else if options.Create {
args = append(args, options.Name)
if options.Name == "" {
return "", fmt.Errorf("branch name is required for creation")
return "", "", fmt.Errorf("branch name is required for creation")
}
args = append(args, options.Name)
}

cmd := ExecCommand("git", args...)
output, err := cmd.CombinedOutput()
output, cmdStr, err := g.executeCommand(args...)
if err != nil {
return string(output), fmt.Errorf("branch operation failed: %v", err)
return string(output), cmdStr, err
}

return string(output), nil
return string(output), cmdStr, nil
}

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

cmd := ExecCommand("git", "checkout", branchName)
output, err := cmd.CombinedOutput()
output, cmdStr, err := g.executeCommand(args...)
if err != nil {
return string(output), fmt.Errorf("failed to checkout branch: %v", err)
return string(output), cmdStr, err
}

return string(output), nil
return string(output), cmdStr, nil
}

// Switch switches to a specified branch.
func (g *GitCommands) Switch(branchName string) (string, error) {
func (g *GitCommands) Switch(branchName string) (string, string, error) {
if branchName == "" {
return "", fmt.Errorf("branch name is required")
return "", "", fmt.Errorf("branch name is required")
}
args := []string{"switch", branchName}

cmd := ExecCommand("git", "switch", branchName)
output, err := cmd.CombinedOutput()
output, cmdStr, err := g.executeCommand(args...)
if err != nil {
return string(output), fmt.Errorf("failed to switch branch: %v", err)
return string(output), "", err
}

return string(output), nil
return string(output), cmdStr, nil
}

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

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

return string(output), nil
return string(output), cmdStr, nil
}
11 changes: 4 additions & 7 deletions internal/git/clone.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package git

import (
"fmt"
"os/exec"
)

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

var cmd *exec.Cmd
args := []string{"clone", repoURL}
if directory != "" {
cmd = exec.Command("git", "clone", repoURL, directory)
} else {
cmd = exec.Command("git", "clone", repoURL)
args = append(args, directory)
}

output, err := cmd.CombinedOutput()
output, _, err := g.executeCommand(args...)
if err != nil {
return string(output), fmt.Errorf("failed to clone repository: %v", err)
return string(output), err
}

return fmt.Sprintf("Successfully cloned repository: %s", repoURL), nil
Expand Down
18 changes: 8 additions & 10 deletions internal/git/commit.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package git

import (
"fmt"
"os/exec"
)

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

// Commit records changes to the repository.
func (g *GitCommands) Commit(options CommitOptions) (string, error) {
func (g *GitCommands) Commit(options CommitOptions) (string, string, error) {
if options.Message == "" && !options.Amend {
return "", fmt.Errorf("commit message is required unless amending")
return "", "", fmt.Errorf("commit message is required unless amending")
}

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

cmd := exec.Command("git", args...)
output, err := cmd.CombinedOutput()
output, cmdStr, err := g.executeCommand(args...)
if err != nil {
return string(output), fmt.Errorf("failed to commit changes: %v", err)
return string(output), cmdStr, err
}

return string(output), nil
return string(output), cmdStr, nil
}

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

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

return string(output), nil
Expand Down
10 changes: 2 additions & 8 deletions internal/git/diff.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,5 @@
package git

import (
"fmt"
"os/exec"
)

// DiffOptions specifies the options for the git diff command.
type DiffOptions struct {
Commit1 string
Expand Down Expand Up @@ -39,10 +34,9 @@ func (g *GitCommands) ShowDiff(options DiffOptions) (string, error) {
args = append(args, options.Commit2)
}

cmd := exec.Command("git", args...)
output, err := cmd.CombinedOutput()
output, _, err := g.executeCommand(args...)
if err != nil {
return string(output), fmt.Errorf("failed to get diff: %v", err)
return string(output), err
}

return string(output), nil
Expand Down
12 changes: 6 additions & 6 deletions internal/git/files.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@ package git

import (
"fmt"
"os/exec"
)

// ListFiles shows information about files in the index and the working tree.
func (g *GitCommands) ListFiles() (string, error) {
cmd := exec.Command("git", "ls-files")
output, err := cmd.CombinedOutput()
args := []string{"ls-files"}

output, _, err := g.executeCommand(args...)
if err != nil {
return string(output), fmt.Errorf("failed to list files: %v", err)
}
Expand All @@ -22,10 +22,10 @@ func (g *GitCommands) BlameFile(filePath string) (string, error) {
return "", fmt.Errorf("file path is required")
}

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

return string(output), nil
Expand Down
37 changes: 37 additions & 0 deletions internal/git/git.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
package git

import (
"errors"
"fmt"
"log"
"os/exec"
"strings"
)

// ExecCommand is a variable that holds the exec.Command function
Expand All @@ -15,3 +19,36 @@ type GitCommands struct{}
func NewGitCommands() *GitCommands {
return &GitCommands{}
}

// executeCommand centralizes the execution of all git commands and serves
// as a single point for logging. It takes a list of flags passed to the git
// command as arguments and returns 1. standard output, 2. the command string
// and 3. standard error
func (g *GitCommands) executeCommand(args ...string) (string, string, error) {
cmdStr := "git " + strings.Join(args, " ")
log.Printf("Executing command: %s", cmdStr)

cmd := ExecCommand("git", args...)
output, err := cmd.CombinedOutput()

if err != nil {
log.Printf("Error: %v, Output: %s", err, string(output))

var exitErr *exec.ExitError
exitCode := 0

if errors.As(err, &exitErr) {
exitCode = exitErr.ExitCode()
}

gitMsg := strings.TrimSpace(string(output))
gitMsg = strings.TrimPrefix(gitMsg, "fatal: ")
gitMsg = strings.TrimPrefix(gitMsg, "error: ")

detailedError := fmt.Errorf("[ERROR - %d] %s", exitCode, gitMsg)

return "", cmdStr, detailedError
}

return string(output), cmdStr, nil
}
Loading