Class: Zwischen::Hooks

Inherits:
Object
  • Object
show all
Defined in:
lib/zwischen/hooks.rb

Constant Summary collapse

HOOK_MARKER =
"Zwischen pre-push hook"
APPEND_BEGIN =

Delimiters around the block we append to a pre-existing foreign hook, so uninstall can strip exactly our lines and leave the rest intact.

"# >>> #{HOOK_MARKER} >>>"
APPEND_END =
"# <<< #{HOOK_MARKER} <<<"

Class Method Summary collapse

Class Method Details

.append_to_existing(path) ⇒ Object



77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
# File 'lib/zwischen/hooks.rb', line 77

def self.append_to_existing(path)
  existing = File.read(path)
  return true if existing.include?(HOOK_MARKER) # already appended

  block = <<~BLOCK

    #{APPEND_BEGIN}
    # appended by 'zwischen init' - your original hook above still runs
    if [ "$ZWISCHEN_SKIP" != "1" ]; then
      zwischen scan --pre-push || exit $?
    fi
    #{APPEND_END}
  BLOCK

  File.write(path, existing.chomp + "\n" + block)
  File.chmod(0o755, path)

  true
end

.hook_path(project_root = Dir.pwd) ⇒ Object

Resolve the hooks directory through git itself so the hook lands where git will actually execute it — this respects core.hooksPath (husky, pre-commit framework, etc.) and linked worktrees, where .git is a file.



13
14
15
16
17
18
19
20
21
22
23
24
# File 'lib/zwischen/hooks.rb', line 13

def self.hook_path(project_root = Dir.pwd)
  stdout, _stderr, status = Open3.capture3(
    "git", "rev-parse", "--git-path", "hooks", chdir: project_root
  )
  hooks_dir = status.success? ? stdout.strip : nil
  hooks_dir = File.join(".git", "hooks") if hooks_dir.nil? || hooks_dir.empty?
  hooks_dir = File.expand_path(hooks_dir, project_root)

  File.join(hooks_dir, "pre-push")
rescue StandardError
  File.join(project_root, ".git", "hooks", "pre-push")
end

.install(project_root = Dir.pwd) ⇒ Object



42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
# File 'lib/zwischen/hooks.rb', line 42

def self.install(project_root = Dir.pwd)
  path = hook_path(project_root)
  hooks_dir = File.dirname(path)

  # Ensure hooks directory exists
  FileUtils.mkdir_p(hooks_dir) unless File.directory?(hooks_dir)

  if File.exist?(path)
    # Already present (standalone or appended) — never overwrite, an
    # appended hook also contains the user's own commands.
    return true if zwischen_hook?(path)

    # A foreign hook (husky shim, hand-written script, ...) keeps
    # working: we append our check instead of replacing the user's.
    return append_to_existing(path)
  end

  hook_content = <<~HOOK
    #!/usr/bin/env bash
    # #{HOOK_MARKER} - installed by 'zwischen init'

    if [ "$ZWISCHEN_SKIP" = "1" ]; then
      exit 0
    fi

    zwischen scan --pre-push
    exit $?
  HOOK

  File.write(path, hook_content)
  File.chmod(0o755, path)

  true
end

.installed?(project_root = Dir.pwd) ⇒ Boolean

Returns:

  • (Boolean)


32
33
34
35
# File 'lib/zwischen/hooks.rb', line 32

def self.installed?(project_root = Dir.pwd)
  path = hook_path(project_root)
  zwischen_hook?(path)
end

.uninstall(project_root = Dir.pwd) ⇒ Object



97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
# File 'lib/zwischen/hooks.rb', line 97

def self.uninstall(project_root = Dir.pwd)
  path = hook_path(project_root)
  return false unless File.exist?(path)
  return false unless zwischen_hook?(path)

  content = File.read(path)
  if content.include?(APPEND_BEGIN)
    # We were appended to someone else's hook: strip only our block.
    stripped = content.gsub(/\n?#{Regexp.escape(APPEND_BEGIN)}.*?#{Regexp.escape(APPEND_END)}\n?/m, "\n")
    File.write(path, stripped)
  else
    # The whole file is ours.
    File.delete(path)
  end
  true
end

.zwischen_hook?(hook_path) ⇒ Boolean

Returns:

  • (Boolean)


26
27
28
29
30
# File 'lib/zwischen/hooks.rb', line 26

def self.zwischen_hook?(hook_path)
  return false unless File.exist?(hook_path)

  File.read(hook_path).include?(HOOK_MARKER)
end