Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 27 additions & 12 deletions install.sh
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
#!/usr/bin/env bash
# This file is auto-generated by scripts/build.sh
# DO NOT EDIT - modify scripts/*.src.sh instead and run 'make buildscripts'

# ── Embedded hook files ── that's a base64 of scripts/git-undo-hook.bash ────
EMBEDDED_BASH_HOOK='IyBWYXJpYWJsZSB0byBzdG9yZSB0aGUgZ2l0IGNvbW1hbmQgdGVtcG9yYXJpbHkKR0lUX0NPTU1BTkRfVE9fTE9HPSIiCgojIEZ1bmN0aW9uIHRvIHN0b3JlIHRoZSBnaXQgY29tbWFuZCB0ZW1wb3JhcmlseQpzdG9yZV9naXRfY29tbWFuZCgpIHsKICBsb2NhbCByYXdfY21kPSIkMSIKICBsb2NhbCBoZWFkPSR7cmF3X2NtZCUlICp9CiAgbG9jYWwgcmVzdD0ke3Jhd19jbWQjIiRoZWFkIn0KCiAgIyBDaGVjayBpZiB0aGUgY29tbWFuZCBpcyBhbiBhbGlhcyBhbmQgZXhwYW5kIGl0CiAgaWYgYWxpYXMgIiRoZWFkIiAmPi9kZXYvbnVsbDsgdGhlbgogICAgbG9jYWwgZGVmPSQoYWxpYXMgIiRoZWFkIikKICAgICMgRXh0cmFjdCB0aGUgZXhwYW5zaW9uIGZyb20gYWxpYXMgb3V0cHV0IChmb3JtYXQ6IGFsaWFzIG5hbWU9J2V4cGFuc2lvbicpCiAgICBsb2NhbCBleHBhbnNpb249JHtkZWYjKlwnfQogICAgZXhwYW5zaW9uPSR7ZXhwYW5zaW9uJVwnfQogICAgcmF3X2NtZD0iJHtleHBhbnNpb259JHtyZXN0fSIKICBmaQoKICAjIE9ubHkgc3RvcmUgaWYgaXQncyBhIGdpdCBjb21tYW5kCiAgW1sgIiRyYXdfY21kIiA9PSBnaXRcICogXV0gfHwgcmV0dXJuCiAgR0lUX0NPTU1BTkRfVE9fTE9HPSIkcmF3X2NtZCIKfQoKIyBGdW5jdGlvbiB0byBsb2cgdGhlIGNvbW1hbmQgb25seSBpZiBpdCB3YXMgc3VjY2Vzc2Z1bApsb2dfc3VjY2Vzc2Z1bF9naXRfY29tbWFuZCgpIHsKICAjIENoZWNrIGlmIHdlIGhhdmUgYSBnaXQgY29tbWFuZCB0byBsb2cgYW5kIGlmIHRoZSBwcmV2aW91cyBjb21tYW5kIHdhcyBzdWNjZXNzZnVsCiAgaWYgW1sgLW4gIiRHSVRfQ09NTUFORF9UT19MT0ciICYmICQ/IC1lcSAwIF1dOyB0aGVuCiAgICBHSVRfVU5ET19JTlRFUk5BTF9IT09LPTEgY29tbWFuZCBnaXQtdW5kbyAtLWhvb2s9IiRHSVRfQ09NTUFORF9UT19MT0ciCiAgZmkKICAjIENsZWFyIHRoZSBzdG9yZWQgY29tbWFuZAogIEdJVF9DT01NQU5EX1RPX0xPRz0iIgp9CgojIHRyYXAgZG9lcyB0aGUgYWN0dWFsIGhvb2tpbmc6IG1ha2luZyBhbiBleHRyYSBnaXQtdW5kbyBjYWxsIGZvciBldmVyeSBnaXQgY29tbWFuZC4KdHJhcCAnc3RvcmVfZ2l0X2NvbW1hbmQgIiRCQVNIX0NPTU1BTkQiJyBERUJVRwoKIyBTZXQgdXAgUFJPTVBUX0NPTU1BTkQgdG8gbG9nIHN1Y2Nlc3NmdWwgY29tbWFuZHMgYWZ0ZXIgZXhlY3V0aW9uCmlmIFtbIC16ICIkUFJPTVBUX0NPTU1BTkQiIF1dOyB0aGVuCiAgUFJPTVBUX0NPTU1BTkQ9ImxvZ19zdWNjZXNzZnVsX2dpdF9jb21tYW5kIgplbHNlCiAgUFJPTVBUX0NPTU1BTkQ9IiRQUk9NUFRfQ09NTUFORDsgbG9nX3N1Y2Nlc3NmdWxfZ2l0X2NvbW1hbmQiCmZp'
EMBEDDED_BASH_TEST_HOOK='IyBWYXJpYWJsZSB0byBzdG9yZSB0aGUgZ2l0IGNvbW1hbmQgdGVtcG9yYXJpbHkKR0lUX0NPTU1BTkRfVE9fTE9HPSIiCgojIEZ1bmN0aW9uIHRvIHN0b3JlIHRoZSBnaXQgY29tbWFuZCB0ZW1wb3JhcmlseQpzdG9yZV9naXRfY29tbWFuZCgpIHsKICBsb2NhbCByYXdfY21kPSIkMSIKICBsb2NhbCBoZWFkPSR7cmF3X2NtZCUlICp9CiAgbG9jYWwgcmVzdD0ke3Jhd19jbWQjIiRoZWFkIn0KCiAgIyBDaGVjayBpZiB0aGUgY29tbWFuZCBpcyBhbiBhbGlhcyBhbmQgZXhwYW5kIGl0CiAgaWYgYWxpYXMgIiRoZWFkIiAmPi9kZXYvbnVsbDsgdGhlbgogICAgbG9jYWwgZGVmPSQoYWxpYXMgIiRoZWFkIikKICAgICMgRXh0cmFjdCB0aGUgZXhwYW5zaW9uIGZyb20gYWxpYXMgb3V0cHV0IChmb3JtYXQ6IGFsaWFzIG5hbWU9J2V4cGFuc2lvbicpCiAgICBsb2NhbCBleHBhbnNpb249JHtkZWYjKlwnfQogICAgZXhwYW5zaW9uPSR7ZXhwYW5zaW9uJVwnfQogICAgcmF3X2NtZD0iJHtleHBhbnNpb259JHtyZXN0fSIKICBmaQoKICAjIE9ubHkgc3RvcmUgaWYgaXQncyBhIGdpdCBjb21tYW5kCiAgW1sgIiRyYXdfY21kIiA9PSBnaXRcICogXV0gfHwgcmV0dXJuCiAgR0lUX0NPTU1BTkRfVE9fTE9HPSIkcmF3X2NtZCIKfQoKIyBGdW5jdGlvbiB0byBsb2cgdGhlIGNvbW1hbmQgb25seSBpZiBpdCB3YXMgc3VjY2Vzc2Z1bApsb2dfc3VjY2Vzc2Z1bF9naXRfY29tbWFuZCgpIHsKICAjIENoZWNrIGlmIHdlIGhhdmUgYSBnaXQgY29tbWFuZCB0byBsb2cgYW5kIGlmIHRoZSBwcmV2aW91cyBjb21tYW5kIHdhcyBzdWNjZXNzZnVsCiAgaWYgW1sgLW4gIiRHSVRfQ09NTUFORF9UT19MT0ciICYmICQ/IC1lcSAwIF1dOyB0aGVuCiAgICBHSVRfVU5ET19JTlRFUk5BTF9IT09LPTEgY29tbWFuZCBnaXQtdW5kbyAtLWhvb2s9IiRHSVRfQ09NTUFORF9UT19MT0ciCiAgZmkKICAjIENsZWFyIHRoZSBzdG9yZWQgY29tbWFuZAogIEdJVF9DT01NQU5EX1RPX0xPRz0iIgp9CgoKIyBUZXN0IG1vZGU6IHByb3ZpZGUgYSBtYW51YWwgd2F5IHRvIGNhcHR1cmUgY29tbWFuZHMKIyBUaGlzIGlzIG9ubHkgdXNlZCBmb3IgaW50ZWdyYXRpb24tdGVzdC5iYXRzLiAKZ2l0KCkgewogICAgY29tbWFuZCBnaXQgIiRAIgogICAgbG9jYWwgZXhpdF9jb2RlPSQ/CiAgICBpZiBbWyAkZXhpdF9jb2RlIC1lcSAwIF1dOyB0aGVuCiAgICAgICAgR0lUX1VORE9fSU5URVJOQUxfSE9PSz0xIGNvbW1hbmQgZ2l0LXVuZG8gLS1ob29rPSJnaXQgJCoiCiAgICBmaQogICAgcmV0dXJuICRleGl0X2NvZGUKfQoKCiMgU2V0IHVwIFBST01QVF9DT01NQU5EIHRvIGxvZyBzdWNjZXNzZnVsIGNvbW1hbmRzIGFmdGVyIGV4ZWN1dGlvbgppZiBbWyAteiAiJFBST01QVF9DT01NQU5EIiBdXTsgdGhlbgogIFBST01QVF9DT01NQU5EPSJsb2dfc3VjY2Vzc2Z1bF9naXRfY29tbWFuZCIKZWxzZQogIFBST01QVF9DT01NQU5EPSIkUFJPTVBUX0NPTU1BTkQ7IGxvZ19zdWNjZXNzZnVsX2dpdF9jb21tYW5kIgpmaQo='
EMBEDDED_ZSH_HOOK='IyBGdW5jdGlvbiB0byBzdG9yZSB0aGUgZ2l0IGNvbW1hbmQgdGVtcG9yYXJpbHkKc3RvcmVfZ2l0X2NvbW1hbmQoKSB7CiAgbG9jYWwgcmF3X2NtZD0iJDEiCiAgbG9jYWwgaGVhZD0ke3Jhd19jbWQlJSAqfQogIGxvY2FsIHJlc3Q9JHtyYXdfY21kIyIkaGVhZCJ9CiAgaWYgYWxpYXMgIiRoZWFkIiAmPi9kZXYvbnVsbDsgdGhlbgogICAgbG9jYWwgZGVmPSQoYWxpYXMgIiRoZWFkIikKICAgIGxvY2FsIGV4cGFuc2lvbj0ke2RlZiMqXCd9CiAgICBleHBhbnNpb249JHtleHBhbnNpb24lXCd9CiAgICByYXdfY21kPSIke2V4cGFuc2lvbn0ke3Jlc3R9IgogIGZpCiAgW1sgIiRyYXdfY21kIiA9PSBnaXRcICogXV0gfHwgcmV0dXJuCiAgR0lUX0NPTU1BTkRfVE9fTE9HPSIkcmF3X2NtZCIKfQoKIyBGdW5jdGlvbiB0byBsb2cgdGhlIGNvbW1hbmQgb25seSBpZiBpdCB3YXMgc3VjY2Vzc2Z1bApsb2dfc3VjY2Vzc2Z1bF9naXRfY29tbWFuZCgpIHsKICAjIENoZWNrIGlmIHdlIGhhdmUgYSBnaXQgY29tbWFuZCB0byBsb2cgYW5kIGlmIHRoZSBwcmV2aW91cyBjb21tYW5kIHdhcyBzdWNjZXNzZnVsCiAgaWYgW1sgLW4gIiRHSVRfQ09NTUFORF9UT19MT0ciICYmICQ/IC1lcSAwIF1dOyB0aGVuCiAgICBHSVRfVU5ET19JTlRFUk5BTF9IT09LPTEgY29tbWFuZCBnaXQtdW5kbyAtLWhvb2s9IiRHSVRfQ09NTUFORF9UT19MT0ciCiAgZmkKICAjIENsZWFyIHRoZSBzdG9yZWQgY29tbWFuZAogIEdJVF9DT01NQU5EX1RPX0xPRz0iIgp9CgphdXRvbG9hZCAtVSBhZGQtenNoLWhvb2sKYWRkLXpzaC1ob29rIHByZWV4ZWMgc3RvcmVfZ2l0X2NvbW1hbmQKYWRkLXpzaC1ob29rIHByZWNtZCBsb2dfc3VjY2Vzc2Z1bF9naXRfY29tbWFuZAo='
# ── End of embedded hook files ──────────────────────────────────────────────

set -e

# ── Inlined content from common.sh ──────────────────────────────────────────
Expand Down Expand Up @@ -140,6 +147,17 @@ version_compare() {
}
# ── End of inlined content ──────────────────────────────────────────────────

# Function to write an embedded hook file
write_embedded_hook() {
local target_file="$1"
local embedded_var="$2"

# Decode the base64 embedded content and write it to the target file
echo "${!embedded_var}" | base64 -d > "$target_file" 2>/dev/null || return 1
chmod 644 "$target_file" 2>/dev/null || return 1
return 0
}

install_shell_hook() {
local shell_type="$1"
local is_noop=true
Expand All @@ -153,14 +171,12 @@ install_shell_hook() {

case "$shell_type" in
"zsh")
local hook_file="git-undo-hook.zsh"
local rc_file="$HOME/.zshrc"
local source_line="source ~/.config/git-undo/$hook_file"
local source_line="source ~/.config/git-undo/git-undo-hook.zsh"

# Copy the hook file and set permissions
# Write the embedded hook file
if [ ! -f "$ZSH_HOOK" ]; then
cp "scripts/$hook_file" "$ZSH_HOOK" 2>/dev/null || return 1
chmod 644 "$ZSH_HOOK" 2>/dev/null || return 1
write_embedded_hook "$ZSH_HOOK" "EMBEDDED_ZSH_HOOK" || return 1
is_noop=false
fi

Expand All @@ -172,18 +188,17 @@ install_shell_hook() {
;;

"bash")
local hook_file="git-undo-hook.bash"
local source_line="source ~/.config/git-undo/$hook_file"
local source_line="source ~/.config/git-undo/git-undo-hook.bash"

# Use test hook in test environments (e.g., integration tests)
# Determine which embedded hook to use
local embedded_var="EMBEDDED_BASH_HOOK"
if [[ "${GIT_UNDO_TEST_MODE:-}" == "true" ]]; then
hook_file="git-undo-hook.test.bash"
embedded_var="EMBEDDED_BASH_TEST_HOOK"
fi

# Copy the hook file and set permissions
# Write the embedded hook file
if [ ! -f "$BASH_HOOK" ]; then
cp "scripts/$hook_file" "$BASH_HOOK" 2>/dev/null || return 1
chmod 644 "$BASH_HOOK" 2>/dev/null || return 1
write_embedded_hook "$BASH_HOOK" "$embedded_var" || return 1
is_noop=false
fi

Expand Down
3 changes: 1 addition & 2 deletions internal/app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ import (
type GitHelper interface {
GetCurrentGitRef() (string, error)
GetRepoGitDir() (string, error)
ValidateGitRepo() error

GitRun(subCmd string, args ...string) error
GitOutput(subCmd string, args ...string) (string, error)
Expand Down Expand Up @@ -138,7 +137,7 @@ func (a *App) Run(args []string) error {
}

// Ensure we're inside a Git repository for other commands
if err := a.git.ValidateGitRepo(); err != nil {
if _, err := a.git.GetRepoGitDir(); err != nil {
return err
}

Expand Down
33 changes: 18 additions & 15 deletions internal/githelpers/githelper.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,12 @@ func NewGitHelper(repoDirArg ...string) *H {

// execGitOutput executes a git command and returns its output as string.
func (h *H) execGitOutput(subCmd string, args ...string) (string, error) {
gitArgs := append([]string{subCmd}, args...)
cmd := exec.Command("git", gitArgs...)
if h.repoDir == invalidRepoDir {
return "", errors.New("not a valid git repository")
}

gitArgs := append([]string{subCmd}, args...)
cmd := exec.Command("git", gitArgs...)
cmd.Dir = h.repoDir

output, err := cmd.Output()
Expand All @@ -52,17 +52,27 @@ func (h *H) execGitOutput(subCmd string, args ...string) (string, error) {

// execGitRun executes a git command without output (via Run).
func (h *H) execGitRun(subCmd string, args ...string) error {
gitArgs := append([]string{subCmd}, args...)
cmd := exec.Command("git", gitArgs...)
if h.repoDir == invalidRepoDir {
return errors.New("not a valid git repository")
}

gitArgs := append([]string{subCmd}, args...)
cmd := exec.Command("git", gitArgs...)
cmd.Dir = h.repoDir

return cmd.Run()
}

// validateGitRepo checks if the current directory is inside a git repository.
func (h *H) validateGitRepo() (string, error) {
gitDir, err := h.execGitOutput("rev-parse", "--git-dir")
if err != nil {
return "", errors.New("not in a git repository")
}

return gitDir, nil
}

// GetCurrentGitRef returns the current ref (branch, tag, commit hash) in the repository.
func (h *H) GetCurrentGitRef() (string, error) {
// Try to get branch name first
Expand All @@ -86,9 +96,9 @@ func (h *H) GetCurrentGitRef() (string, error) {
// GetRepoGitDir returns the path to the .git directory of current repository.
func (h *H) GetRepoGitDir() (string, error) {
// Get the git directory (usually .git, but could be elsewhere in worktrees)
gitDir, err := h.execGitOutput("rev-parse", "--git-dir")
gitDir, err := h.validateGitRepo()
if err != nil {
return "", fmt.Errorf("failed to get git directory: %w", err)
return "", err
}

// If gitDir is not an absolute path, make it absolute relative to the repo root
Expand All @@ -104,19 +114,12 @@ func (h *H) GetRepoGitDir() (string, error) {
return gitDir, nil
}

// ValidateGitRepo checks if the current directory is inside a git repository.
func (h *H) ValidateGitRepo() error {
if err := h.execGitRun("rev-parse", "--git-dir"); err != nil {
return errors.New("not in a git repository")
}

return nil
}

// GitRun executes a git command without output (via Run).
func (h *H) GitRun(subCmd string, args ...string) error {
return h.execGitRun(subCmd, args...)
}

// GitOutput executes a git command and returns its output as string.
func (h *H) GitOutput(subCmd string, args ...string) (string, error) {
return h.execGitOutput(subCmd, args...)
}
21 changes: 21 additions & 0 deletions scripts/build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ set -e
SCRIPT_DIR="$(dirname "$0")"
COLORS_FILE="$SCRIPT_DIR/colors.sh"
COMMON_FILE="$SCRIPT_DIR/common.sh"
BASH_HOOK_FILE="$SCRIPT_DIR/git-undo-hook.bash"
BASH_TEST_HOOK_FILE="$SCRIPT_DIR/git-undo-hook.test.bash"
ZSH_HOOK_FILE="$SCRIPT_DIR/git-undo-hook.zsh"
SRC_INSTALL="$SCRIPT_DIR/install.src.sh"
SRC_UNINSTALL="$SCRIPT_DIR/uninstall.src.sh"
SRC_UPDATE="$SCRIPT_DIR/update.src.sh"
Expand All @@ -14,6 +17,13 @@ OUT_UPDATE="$SCRIPT_DIR/../update.sh"

echo "Building standalone scripts..."

# Function to encode a file as a base64 string for embedding
encode_hook_file() {
local file="$1"
local var_name="$2"
echo "${var_name}='$(base64 < "$file" | tr -d '\n')'"
}

# Function to build a standalone script
build_script() {
local src_file="$1"
Expand All @@ -29,6 +39,17 @@ build_script() {
# DO NOT EDIT - modify scripts/*.src.sh instead and run 'make buildscripts'
EOF

# If building install.sh, add embedded hook files
if [[ "$script_name" == "install.sh" ]]; then
echo "" >> "$out_file"
echo "# ── Embedded hook files ── that's a base64 of scripts/git-undo-hook.bash ────" >> "$out_file"
encode_hook_file "$BASH_HOOK_FILE" "EMBEDDED_BASH_HOOK" >> "$out_file"
encode_hook_file "$BASH_TEST_HOOK_FILE" "EMBEDDED_BASH_TEST_HOOK" >> "$out_file"
encode_hook_file "$ZSH_HOOK_FILE" "EMBEDDED_ZSH_HOOK" >> "$out_file"
echo "# ── End of embedded hook files ──────────────────────────────────────────────" >> "$out_file"
echo "" >> "$out_file"
fi

# Process the source file line by line
while IFS= read -r line; do
# Skip the shebang in source file
Expand Down
32 changes: 20 additions & 12 deletions scripts/install.src.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,17 @@ set -e

source "$(dirname "$0")/common.sh"

# Function to write an embedded hook file
write_embedded_hook() {
local target_file="$1"
local embedded_var="$2"

# Decode the base64 embedded content and write it to the target file
echo "${!embedded_var}" | base64 -d > "$target_file" 2>/dev/null || return 1
chmod 644 "$target_file" 2>/dev/null || return 1
return 0
}

install_shell_hook() {
local shell_type="$1"
local is_noop=true
Expand All @@ -16,14 +27,12 @@ install_shell_hook() {

case "$shell_type" in
"zsh")
local hook_file="git-undo-hook.zsh"
local rc_file="$HOME/.zshrc"
local source_line="source ~/.config/git-undo/$hook_file"
local source_line="source ~/.config/git-undo/git-undo-hook.zsh"

# Copy the hook file and set permissions
# Write the embedded hook file
if [ ! -f "$ZSH_HOOK" ]; then
cp "scripts/$hook_file" "$ZSH_HOOK" 2>/dev/null || return 1
chmod 644 "$ZSH_HOOK" 2>/dev/null || return 1
write_embedded_hook "$ZSH_HOOK" "EMBEDDED_ZSH_HOOK" || return 1
is_noop=false
fi

Expand All @@ -35,18 +44,17 @@ install_shell_hook() {
;;

"bash")
local hook_file="git-undo-hook.bash"
local source_line="source ~/.config/git-undo/$hook_file"
local source_line="source ~/.config/git-undo/git-undo-hook.bash"

# Use test hook in test environments (e.g., integration tests)
# Determine which embedded hook to use
local embedded_var="EMBEDDED_BASH_HOOK"
if [[ "${GIT_UNDO_TEST_MODE:-}" == "true" ]]; then
hook_file="git-undo-hook.test.bash"
embedded_var="EMBEDDED_BASH_TEST_HOOK"
fi

# Copy the hook file and set permissions
# Write the embedded hook file
if [ ! -f "$BASH_HOOK" ]; then
cp "scripts/$hook_file" "$BASH_HOOK" 2>/dev/null || return 1
chmod 644 "$BASH_HOOK" 2>/dev/null || return 1
write_embedded_hook "$BASH_HOOK" "$embedded_var" || return 1
is_noop=false
fi

Expand Down
Loading