From 5adf7cae243362677dca1072dc535f8d798f6cad Mon Sep 17 00:00:00 2001 From: Eugene Date: Sun, 25 May 2025 01:03:33 +0300 Subject: [PATCH 1/2] Minor refactor for githelpers --- internal/app/app.go | 3 +-- internal/githelpers/githelper.go | 33 +++++++++++++++++--------------- 2 files changed, 19 insertions(+), 17 deletions(-) diff --git a/internal/app/app.go b/internal/app/app.go index 6674b20..781b4ab 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -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) @@ -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 } diff --git a/internal/githelpers/githelper.go b/internal/githelpers/githelper.go index 172eab7..95afcd0 100644 --- a/internal/githelpers/githelper.go +++ b/internal/githelpers/githelper.go @@ -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() @@ -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 @@ -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 @@ -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...) } From ded64b3612f2ee4bd59c8302587700751339b763 Mon Sep 17 00:00:00 2001 From: Eugene Date: Sun, 25 May 2025 11:35:59 +0300 Subject: [PATCH 2/2] fix installation script --- install.sh | 39 +++++++++++++++++++++++++++------------ scripts/build.sh | 21 +++++++++++++++++++++ scripts/install.src.sh | 32 ++++++++++++++++++++------------ 3 files changed, 68 insertions(+), 24 deletions(-) diff --git a/install.sh b/install.sh index 0288c85..a05bd56 100755 --- a/install.sh +++ b/install.sh @@ -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 ────────────────────────────────────────── @@ -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 @@ -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 @@ -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 diff --git a/scripts/build.sh b/scripts/build.sh index 7d3fbf7..0deb053 100755 --- a/scripts/build.sh +++ b/scripts/build.sh @@ -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" @@ -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" @@ -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 diff --git a/scripts/install.src.sh b/scripts/install.src.sh index 425b83c..7d9bbc0 100755 --- a/scripts/install.src.sh +++ b/scripts/install.src.sh @@ -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 @@ -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 @@ -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